diff --git a/packages/firestore/src/platform/platform.ts b/packages/firestore/src/platform/platform.ts index 2263fed1d6b..5536fb5ce22 100644 --- a/packages/firestore/src/platform/platform.ts +++ b/packages/firestore/src/platform/platform.ts @@ -43,6 +43,12 @@ export interface Platform { /** Converts a binary string to a Base64 encoded string. */ btoa(raw: string): string; + /** + * Generates `nBytes` of random bytes. If `nBytes` is negative, an empty array + * will be returned. + */ + randomBytes(nBytes: number): Uint8Array; + /** The Platform's 'window' implementation or null if not available. */ readonly window: Window | null; diff --git a/packages/firestore/src/platform_browser/browser_platform.ts b/packages/firestore/src/platform_browser/browser_platform.ts index c047def3773..333548a92ba 100644 --- a/packages/firestore/src/platform_browser/browser_platform.ts +++ b/packages/firestore/src/platform_browser/browser_platform.ts @@ -67,4 +67,14 @@ export class BrowserPlatform implements Platform { btoa(raw: string): string { return btoa(raw); } + + randomBytes(nBytes: number): Uint8Array { + if (nBytes <= 0) { + return new Uint8Array(); + } + + const v = new Uint8Array(nBytes); + crypto.getRandomValues(v); + return v; + } } diff --git a/packages/firestore/src/platform_node/node_platform.ts b/packages/firestore/src/platform_node/node_platform.ts index 8937f3fbe4d..c42cd665dd3 100644 --- a/packages/firestore/src/platform_node/node_platform.ts +++ b/packages/firestore/src/platform_node/node_platform.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { randomBytes } from 'crypto'; import * as util from 'util'; import { DatabaseId, DatabaseInfo } from '../core/database_info'; @@ -74,4 +75,12 @@ export class NodePlatform implements Platform { btoa(raw: string): string { return new Buffer(raw, 'binary').toString('base64'); } + + randomBytes(nBytes: number): Uint8Array { + if (nBytes <= 0) { + return new Uint8Array(); + } + + return randomBytes(nBytes); + } } diff --git a/packages/firestore/src/util/misc.ts b/packages/firestore/src/util/misc.ts index 6e9cf71da78..ab80ceb53fa 100644 --- a/packages/firestore/src/util/misc.ts +++ b/packages/firestore/src/util/misc.ts @@ -16,6 +16,7 @@ */ import { assert } from './assert'; +import { PlatformSupport } from '../platform/platform'; export type EventHandler = (value: E) => void; export interface Indexable { @@ -28,8 +29,17 @@ export class AutoId { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let autoId = ''; - for (let i = 0; i < 20; i++) { - autoId += chars.charAt(Math.floor(Math.random() * chars.length)); + while (autoId.length < 20) { + const bytes = PlatformSupport.getPlatform().randomBytes(40); + bytes.forEach(b => { + // Length of `chars` is 62. We only take bytes between 0 and 62*4-1 + // (both inclusive). The value is then evenly mapped to indices of `char` + // via a modulo operation. + const maxValue = 62 * 4 - 1; + if (autoId.length < 20 && b <= maxValue) { + autoId += chars.charAt(b % 62); + } + }); } assert(autoId.length === 20, 'Invalid auto ID: ' + autoId); return autoId; diff --git a/packages/firestore/test/unit/generate_spec_json.js b/packages/firestore/test/unit/generate_spec_json.js index e267f77d894..90d3b9f39a9 100644 --- a/packages/firestore/test/unit/generate_spec_json.js +++ b/packages/firestore/test/unit/generate_spec_json.js @@ -65,7 +65,7 @@ function main(args) { var testName = specName.replace(/^specs\//, ''); var filename = testName.replace(/[^A-Za-z\d]/g, '_') + '.json'; var outputFile = outputPath + '/' + filename; - console.log("Generating " + outputFile); + console.log('Generating ' + outputFile); writeToJSON(testFiles[i], outputFile); } diff --git a/packages/firestore/test/unit/specs/spec_test_runner.ts b/packages/firestore/test/unit/specs/spec_test_runner.ts index 29c7df3a7c6..65fdfd1ce6c 100644 --- a/packages/firestore/test/unit/specs/spec_test_runner.ts +++ b/packages/firestore/test/unit/specs/spec_test_runner.ts @@ -1151,7 +1151,9 @@ abstract class TestRunner { expect(actualTarget.query).to.deep.equal(expectedTarget.query); expect(actualTarget.targetId).to.equal(expectedTarget.targetId); expect(actualTarget.readTime).to.equal(expectedTarget.readTime); - expect(actualTarget.resumeToken || '').to.equal(expectedTarget.resumeToken || ''); + expect(actualTarget.resumeToken || '').to.equal( + expectedTarget.resumeToken || '' + ); delete actualTargets[targetId]; }); expect(obj.size(actualTargets)).to.equal( diff --git a/packages/firestore/test/util/test_platform.ts b/packages/firestore/test/util/test_platform.ts index d9048db5dd4..5cf549f3322 100644 --- a/packages/firestore/test/util/test_platform.ts +++ b/packages/firestore/test/util/test_platform.ts @@ -262,10 +262,14 @@ export class TestPlatform implements Platform { btoa(raw: string): string { return this.basePlatform.btoa(raw); } + + randomBytes(nBytes: number): Uint8Array { + return this.basePlatform.randomBytes(nBytes); + } } /** Returns true if we are running under Node. */ -export function isNode() : boolean { +export function isNode(): boolean { return ( typeof process !== 'undefined' && process.title !== undefined &&