Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace shortid with vendored nanoid #3855

Merged
merged 5 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ webpack.config.js
craco.config.js
packages/core/util/QuickLRU.js
packages/core/util/QuickLRU.d.ts
packages/core/util/nanoid.js
packages/core/util/nanoid.d.ts
**/umd_plugin.js
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@
"@types/react-dom": "^18.0.0",
"@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/set-value": "^2.0.0",
"@types/shortid": "^0.0.29",
"@types/string-template": "^1.0.2",
"@types/tmp": "^0.2.1",
"@typescript-eslint/parser": "^6.0.0",
Expand Down
3 changes: 1 addition & 2 deletions packages/app-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@
"@mui/icons-material": "^5.0.0",
"@mui/material": "^5.10.17",
"copy-to-clipboard": "^3.3.1",
"react-error-boundary": "^4.0.3",
"shortid": "^2.2.15"
"react-error-boundary": "^4.0.3"
},
"peerDependencies": {
"mobx": "^6.0.0",
Expand Down
1 change: 0 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
"rbush": "^3.0.1",
"react-error-boundary": "^4.0.3",
"serialize-error": "^8.0.0",
"shortid": "^2.2.13",
"svg-path-generator": "^1.1.0"
},
"peerDependencies": {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/rpc/WebWorkerRpcDriver.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Rpc from 'librpc-web-mod'
import shortid from 'shortid'
import { deserializeError } from 'serialize-error'

// locals
import { nanoid } from '../util/nanoid'
import BaseRpcDriver, { RpcDriverConstructorArgs } from './BaseRpcDriver'
import { PluginDefinition } from '../PluginLoader'
import { deserializeError } from 'serialize-error'

interface WebWorkerRpcDriverConstructorArgs extends RpcDriverConstructorArgs {
makeWorkerInstance: () => Worker
Expand All @@ -22,7 +22,7 @@ class WebWorkerHandle extends Rpc.Client {

async call(funcName: string, args: Record<string, unknown>, opts: Options) {
const { statusCallback, rpcDriverClassName } = opts
const channel = `message-${shortid.generate()}`
const channel = `message-${nanoid()}`
const listener = (message: string) => {
statusCallback?.(message)
}
Expand Down
91 changes: 91 additions & 0 deletions packages/core/util/nanoid.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Generate secure URL-friendly unique ID.
*
* By default, the ID will have 21 symbols to have a collision probability
* similar to UUID v4.
*
* ```js
* import { nanoid } from 'nanoid'
* model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL"
* ```
*
* @param size Size of the ID. The default size is 21.
* @returns A random string.
*/
export function nanoid(size?: number): string

/**
* Generate secure unique ID with custom alphabet.
*
* Alphabet must contain 256 symbols or less. Otherwise, the generator
* will not be secure.
*
* @param alphabet Alphabet used to generate the ID.
* @param defaultSize Size of the ID. The default size is 21.
* @returns A random string generator.
*
* ```js
* const { customAlphabet } = require('nanoid')
* const nanoid = customAlphabet('0123456789абвгдеё', 5)
* nanoid() //=> "8ё56а"
* ```
*/
export function customAlphabet(
alphabet: string,
defaultSize?: number,
): (size?: number) => string

/**
* Generate unique ID with custom random generator and alphabet.
*
* Alphabet must contain 256 symbols or less. Otherwise, the generator
* will not be secure.
*
* ```js
* import { customRandom } from 'nanoid/format'
*
* const nanoid = customRandom('abcdef', 5, size => {
* const random = []
* for (let i = 0; i < size; i++) {
* random.push(randomByte())
* }
* return random
* })
*
* nanoid() //=> "fbaef"
* ```
*
* @param alphabet Alphabet used to generate a random string.
* @param size Size of the random string.
* @param random A random bytes generator.
* @returns A random string generator.
*/
export function customRandom(
alphabet: string,
size: number,
random: (bytes: number) => Uint8Array,
): () => string

/**
* URL safe symbols.
*
* ```js
* import { urlAlphabet } from 'nanoid'
* const nanoid = customAlphabet(urlAlphabet, 10)
* nanoid() //=> "Uakgb_J5m9"
* ```
*/
export const urlAlphabet: string

/**
* Generate an array of random bytes collected from hardware noise.
*
* ```js
* import { customRandom, random } from 'nanoid'
* const nanoid = customRandom("abcdef", 5, random)
* ```
*
* @param bytes Size of the array.
* @returns An array of random bytes.
*/
export function random(bytes: number): Uint8Array
74 changes: 74 additions & 0 deletions packages/core/util/nanoid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// This file replaces `index.js` in bundlers like webpack or Rollup,
// according to `browser` config in `package.json`.

// This alphabet uses `A-Za-z0-9_-` symbols.
// The order of characters is optimized for better gzip and brotli compression.
// Same as in non-secure/index.js
export const urlAlphabet =
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'

export let random = bytes => crypto.getRandomValues(new Uint8Array(bytes))

export let customRandom = (alphabet, defaultSize, getRandom) => {
// First, a bitmask is necessary to generate the ID. The bitmask makes bytes
// values closer to the alphabet size. The bitmask calculates the closest
// `2^31 - 1` number, which exceeds the alphabet size.
// For example, the bitmask for the alphabet size 30 is 31 (00011111).
// `Math.clz32` is not used, because it is not available in browsers.
let mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1
// Though, the bitmask solution is not perfect since the bytes exceeding
// the alphabet size are refused. Therefore, to reliably generate the ID,
// the random bytes redundancy has to be satisfied.

// Note: every hardware random generator call is performance expensive,
// because the system call for entropy collection takes a lot of time.
// So, to avoid additional system calls, extra bytes are requested in advance.

// Next, a step determines how many random bytes to generate.
// The number of random bytes gets decided upon the ID size, mask,
// alphabet size, and magic number 1.6 (using 1.6 peaks at performance
// according to benchmarks).

// `-~f => Math.ceil(f)` if f is a float
// `-~i => i + 1` if i is an integer
let step = -~((1.6 * mask * defaultSize) / alphabet.length)

return (size = defaultSize) => {
let id = ''
while (true) {
let bytes = getRandom(step)
// A compact alternative for `for (var i = 0; i < step; i++)`.
let j = step
while (j--) {
// Adding `|| ''` refuses a random byte that exceeds the alphabet size.
id += alphabet[bytes[j] & mask] || ''
if (id.length === size) return id
}
}
}
}

export let customAlphabet = (alphabet, size = 21) =>
customRandom(alphabet, size, random)

export let nanoid = (size = 21) =>
crypto.getRandomValues(new Uint8Array(size)).reduce((id, byte) => {
// It is incorrect to use bytes exceeding the alphabet size.
// The following mask reduces the random byte in the 0-255 value
// range to the 0-63 value range. Therefore, adding hacks, such
// as empty string fallback or magic numbers, is unnecessary because
// the bitmask trims bytes down to the alphabet size.
byte &= 63
if (byte < 36) {
// `0-9a-z`
id += byte.toString(36)
} else if (byte < 62) {
// `A-Z`
id += (byte - 26).toString(36).toUpperCase()
} else if (byte > 62) {
id += '-'
} else {
id += '_'
}
return id
}, '')
5 changes: 3 additions & 2 deletions packages/core/util/types/mst.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import shortid from 'shortid'
import { types } from 'mobx-state-tree'
import propTypes from 'prop-types'
import { PropTypes as MxPropTypes } from 'mobx-react'

export const ElementId = types.optional(types.identifier, shortid.generate)
import { nanoid } from '../nanoid'

export const ElementId = types.optional(types.identifier, () => nanoid())

// PropTypes that are useful when working with instances of these in react components
export const PropTypes = {
Expand Down
3 changes: 1 addition & 2 deletions packages/embedded-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@
"@mui/icons-material": "^5.0.0",
"@mui/material": "^5.10.17",
"copy-to-clipboard": "^3.3.1",
"react-error-boundary": "^4.0.3",
"shortid": "^2.2.15"
"react-error-boundary": "^4.0.3"
},
"peerDependencies": {
"mobx": "^6.0.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/product-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@
},
"dependencies": {
"@babel/runtime": "^7.16.3",
"@jbrowse/core": "^2.6.3",
"@mui/icons-material": "^5.0.0",
"@mui/material": "^5.10.17",
"copy-to-clipboard": "^3.3.1",
"librpc-web-mod": "^1.0.0",
"react-error-boundary": "^4.0.3",
"serialize-error": "^8.0.0",
"shortid": "^2.2.15"
"serialize-error": "^8.0.0"
},
"peerDependencies": {
"mobx": "^6.0.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/product-core/src/Session/BaseSession.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import shortid from 'shortid'
import { nanoid } from '@jbrowse/core/util/nanoid'
import PluginManager from '@jbrowse/core/PluginManager'
import {
IAnyStateTreeNode,
Expand Down Expand Up @@ -27,7 +27,7 @@ export function BaseSessionModel<
/**
* #property
*/
id: types.optional(types.identifier, shortid()),
id: types.optional(types.identifier, nanoid()),
/**
* #property
*/
Expand Down
3 changes: 1 addition & 2 deletions packages/web-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@
"@mui/material": "^5.10.17",
"clone": "^2.0.0",
"copy-to-clipboard": "^3.3.1",
"react-error-boundary": "^4.0.3",
"shortid": "^2.2.15"
"react-error-boundary": "^4.0.3"
},
"peerDependencies": {
"mobx": "^6.0.0",
Expand Down
1 change: 0 additions & 1 deletion products/jbrowse-react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@
"prop-types": "^15.0.0",
"react-error-boundary": "^4.0.0",
"rxjs": "^7.0.0",
"shortid": "^2.2.15",
"tss-react": "^4.4.1",
"use-query-params": "^2.0.0",
"webpack": "^5.72.0"
Expand Down
1 change: 0 additions & 1 deletion products/jbrowse-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@
"react-error-boundary": "^4.0.3",
"requestidlecallback-polyfill": "^1.0.2",
"rxjs": "^7.0.0",
"shortid": "^2.2.15",
"tss-react": "^4.4.1",
"use-query-params": "^2.0.0",
"webpack": "^5.72.0"
Expand Down
10 changes: 5 additions & 5 deletions products/jbrowse-web/src/SessionLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PluginLoader, {
} from '@jbrowse/core/PluginLoader'
import PluginManager from '@jbrowse/core/PluginManager'
import { openLocation } from '@jbrowse/core/util/io'
import shortid from 'shortid'
import { nanoid } from '@jbrowse/core/util/nanoid'

// locals
import { readSessionFromDynamo } from './sessionSharing'
Expand Down Expand Up @@ -242,7 +242,7 @@ const SessionLoader = types
setTimeout(() => reject(), 1000)
},
)
return this.setSessionSnapshot({ ...result, id: shortid() })
return this.setSessionSnapshot({ ...result, id: nanoid() })
} catch (e) {
// the broadcast channels did not find the session in another tab
// clear session param, so just ignore
Expand All @@ -260,15 +260,15 @@ const SessionLoader = types
)

const session = JSON.parse(await fromUrlSafeB64(decryptedSession))
await this.setSessionSnapshot({ ...session, id: shortid() })
await this.setSessionSnapshot({ ...session, id: nanoid() })
},

async decodeEncodedUrlSession() {
const session = JSON.parse(
// @ts-expect-error
await fromUrlSafeB64(self.sessionQuery.replace('encoded-', '')),
)
await this.setSessionSnapshot({ ...session, id: shortid() })
await this.setSessionSnapshot({ ...session, id: nanoid() })
},

decodeSessionSpec() {
Expand Down Expand Up @@ -299,7 +299,7 @@ const SessionLoader = types
async decodeJsonUrlSession() {
// @ts-expect-error
const session = JSON.parse(self.sessionQuery.replace('json-', ''))
await this.setSessionSnapshot({ ...session.session, id: shortid() })
await this.setSessionSnapshot({ ...session.session, id: nanoid() })
},

async afterCreate() {
Expand Down
4 changes: 2 additions & 2 deletions products/jbrowse-web/src/components/ConfigWarningDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
DialogContentText,
} from '@mui/material'
import { Dialog } from '@jbrowse/core/ui'
import shortid from 'shortid'
import { nanoid } from '@jbrowse/core/util/nanoid'
import factoryReset from '../factoryReset'
import { SessionLoaderModel } from '../SessionLoader'

Expand Down Expand Up @@ -70,7 +70,7 @@ export default function ConfigTriaged({
onConfirm={async () => {
const session = JSON.parse(JSON.stringify(loader.sessionTriaged.snap))
await loader.fetchPlugins(session)
loader.setConfigSnapshot({ ...session, id: shortid() })
loader.setConfigSnapshot({ ...session, id: nanoid() })
handleClose()
}}
onCancel={async () => {
Expand Down
4 changes: 2 additions & 2 deletions products/jbrowse-web/src/components/SessionWarningDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
DialogContentText,
DialogActions,
} from '@mui/material'
import shortid from 'shortid'
import { nanoid } from '@jbrowse/core/util/nanoid'
import { SessionLoaderModel } from '../SessionLoader'

import WarningIcon from '@mui/icons-material/Warning'
Expand Down Expand Up @@ -68,7 +68,7 @@ export default function SessionTriaged({
const session = JSON.parse(JSON.stringify(loader.sessionTriaged.snap))

// second param true says we passed user confirmation
await loader.setSessionSnapshot({ ...session, id: shortid() }, true)
await loader.setSessionSnapshot({ ...session, id: nanoid() }, true)
handleClose()
}}
onCancel={() => {
Expand Down
Loading