Skip to content

Commit 00389a4

Browse files
fix: safe randomUUID helper for non-secure browser contexts (#1593)
* fix(db): use safe randomUUID helper for non-secure browser contexts (#1541) * fix(db-sqlite-persistence-core): use safe randomUUID helper (#1541) * fix(browser-db-sqlite-persistence): use safe randomUUID helper (#1541) * fix(electron-db-sqlite-persistence): use safe randomUUID helper (#1541) * fix(offline-transactions): use safe randomUUID helper (#1541) * ci: apply automated fixes * refactor: rename randomUUID helper to safeRandomUUID and add crypto-undefined test --------- Co-authored-by: Kevin De Porre <kevin-dp@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 4d1abde commit 00389a4

16 files changed

Lines changed: 193 additions & 42 deletions

File tree

.changeset/safe-random-uuid.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@tanstack/db': patch
3+
'@tanstack/browser-db-sqlite-persistence': patch
4+
'@tanstack/offline-transactions': patch
5+
'@tanstack/db-sqlite-persistence-core': patch
6+
'@tanstack/electron-db-sqlite-persistence': patch
7+
---
8+
9+
Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541.

packages/browser-db-sqlite-persistence/src/browser-coordinator.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { safeRandomUUID } from '@tanstack/db-sqlite-persistence-core'
12
import type {
23
ApplyLocalMutationsResponse,
34
PersistedCollectionCoordinator,
@@ -118,7 +119,7 @@ export type BrowserCollectionCoordinatorOptions = {
118119
// ---------------------------------------------------------------------------
119120

120121
export class BrowserCollectionCoordinator implements PersistedCollectionCoordinator {
121-
private readonly nodeId = crypto.randomUUID()
122+
private readonly nodeId = safeRandomUUID()
122123
private readonly dbName: string
123124
private adapter: AdapterWithPullSince | null
124125
private readonly channel: BroadcastChannel
@@ -205,7 +206,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina
205206
error?: string
206207
}>(collectionId, {
207208
type: `rpc:ensureRemoteSubset:req`,
208-
rpcId: crypto.randomUUID(),
209+
rpcId: safeRandomUUID(),
209210
options,
210211
})
211212

@@ -233,7 +234,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina
233234
error?: string
234235
}>(collectionId, {
235236
type: `rpc:ensurePersistedIndex:req`,
236-
rpcId: crypto.randomUUID(),
237+
rpcId: safeRandomUUID(),
237238
signature,
238239
spec,
239240
})
@@ -252,16 +253,16 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina
252253
if (this.isLeader(collectionId)) {
253254
return this.handleApplyLocalMutations(collectionId, {
254255
type: `rpc:applyLocalMutations:req`,
255-
rpcId: crypto.randomUUID(),
256-
envelopeId: crypto.randomUUID(),
256+
rpcId: safeRandomUUID(),
257+
envelopeId: safeRandomUUID(),
257258
mutations,
258259
})
259260
}
260261

261262
return this.sendRPC<ApplyLocalMutationsResponse>(collectionId, {
262263
type: `rpc:applyLocalMutations:req`,
263-
rpcId: crypto.randomUUID(),
264-
envelopeId: crypto.randomUUID(),
264+
rpcId: safeRandomUUID(),
265+
envelopeId: safeRandomUUID(),
265266
mutations,
266267
})
267268
}
@@ -273,14 +274,14 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina
273274
if (this.isLeader(collectionId)) {
274275
return this.handlePullSince(collectionId, {
275276
type: `rpc:pullSince:req`,
276-
rpcId: crypto.randomUUID(),
277+
rpcId: safeRandomUUID(),
277278
fromRowVersion,
278279
})
279280
}
280281

281282
return this.sendRPC<PullSinceResponse>(collectionId, {
282283
type: `rpc:pullSince:req`,
283-
rpcId: crypto.randomUUID(),
284+
rpcId: safeRandomUUID(),
284285
fromRowVersion,
285286
})
286287
}
@@ -663,7 +664,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina
663664

664665
// Build and apply the persisted transaction
665666
const tx = {
666-
txId: crypto.randomUUID(),
667+
txId: safeRandomUUID(),
667668
term,
668669
seq,
669670
rowVersion,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export * from './persisted'
22
export * from './errors'
33
export * from './sqlite-core-adapter'
4+
// Re-export for use in non-secure browser contexts (see #1541)
5+
export { safeRandomUUID } from '@tanstack/db'

packages/db-sqlite-persistence-core/src/persisted.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { compileSingleRowExpression, toBooleanPredicate } from '@tanstack/db'
1+
import {
2+
compileSingleRowExpression,
3+
safeRandomUUID,
4+
toBooleanPredicate,
5+
} from '@tanstack/db'
26
import {
37
InvalidPersistedCollectionConfigError,
48
InvalidPersistedCollectionCoordinatorError,
@@ -440,7 +444,7 @@ type SyncControlFns<T extends object, TKey extends string | number> = {
440444
export class SingleProcessCoordinator implements PersistedCollectionCoordinator {
441445
private readonly nodeId: string
442446

443-
constructor(nodeId: string = crypto.randomUUID()) {
447+
constructor(nodeId: string = safeRandomUUID()) {
444448
this.nodeId = nodeId
445449
}
446450

@@ -467,7 +471,7 @@ export class SingleProcessCoordinator implements PersistedCollectionCoordinator
467471
public pullSince(): Promise<PullSinceResponse> {
468472
return Promise.resolve({
469473
type: `rpc:pullSince:res`,
470-
rpcId: crypto.randomUUID(),
474+
rpcId: safeRandomUUID(),
471475
ok: true,
472476
latestTerm: 1,
473477
latestSeq: 0,
@@ -1387,7 +1391,7 @@ class PersistedCollectionRuntime<
13871391
this.createTxCommittedPayload({
13881392
term: streamPosition.term,
13891393
seq: streamPosition.seq,
1390-
txId: crypto.randomUUID(),
1394+
txId: safeRandomUUID(),
13911395
latestRowVersion: streamPosition.rowVersion,
13921396
changedRows: [],
13931397
deletedKeys: [],
@@ -1427,7 +1431,7 @@ class PersistedCollectionRuntime<
14271431
streamPosition: { term: number; seq: number; rowVersion: number },
14281432
): PersistedTx {
14291433
return {
1430-
txId: crypto.randomUUID(),
1434+
txId: safeRandomUUID(),
14311435
term: streamPosition.term,
14321436
seq: streamPosition.seq,
14331437
rowVersion: streamPosition.rowVersion,
@@ -1471,7 +1475,7 @@ class PersistedCollectionRuntime<
14711475
streamPosition: { term: number; seq: number; rowVersion: number },
14721476
): PersistedTx {
14731477
return {
1474-
txId: crypto.randomUUID(),
1478+
txId: safeRandomUUID(),
14751479
term: streamPosition.term,
14761480
seq: streamPosition.seq,
14771481
rowVersion: streamPosition.rowVersion,
@@ -2607,7 +2611,7 @@ export function persistedCollectionOptions<
26072611

26082612
const { schemaVersion, ...syncOptions } = options
26092613
const collectionId =
2610-
syncOptions.id ?? `persisted-collection:${crypto.randomUUID()}`
2614+
syncOptions.id ?? `persisted-collection:${safeRandomUUID()}`
26112615
const persistence = resolvePersistenceForCollection(
26122616
syncOptions.persistence,
26132617
{
@@ -2635,7 +2639,7 @@ export function persistedCollectionOptions<
26352639

26362640
const { schemaVersion, ...localOnlyOptions } = options
26372641
const collectionId =
2638-
localOnlyOptions.id ?? `persisted-collection:${crypto.randomUUID()}`
2642+
localOnlyOptions.id ?? `persisted-collection:${safeRandomUUID()}`
26392643
const persistence = resolvePersistenceForCollection(
26402644
localOnlyOptions.persistence,
26412645
{

packages/db/src/collection/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { safeRandomUUID } from '../utils/uuid'
12
import {
23
CollectionConfigurationError,
34
CollectionRequiresConfigError,
@@ -329,7 +330,7 @@ export class CollectionImpl<
329330
if (config.id) {
330331
this.id = config.id
331332
} else {
332-
this.id = crypto.randomUUID()
333+
this.id = safeRandomUUID()
333334
}
334335

335336
// Set default values for optional config properties

packages/db/src/collection/mutations.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { withArrayChangeTracking, withChangeTracking } from '../proxy'
2+
import { safeRandomUUID } from '../utils/uuid'
23
import { createTransaction, getActiveTransaction } from '../transactions'
34
import {
45
DeleteKeyNotFoundError,
@@ -193,7 +194,7 @@ export class CollectionMutationsManager<
193194
const globalKey = this.generateGlobalKey(key, item)
194195

195196
const mutation: PendingMutation<TOutput, `insert`> = {
196-
mutationId: crypto.randomUUID(),
197+
mutationId: safeRandomUUID(),
197198
original: {},
198199
modified: validatedData,
199200
// Pick the values from validatedData based on what's passed in - this is for cases
@@ -366,7 +367,7 @@ export class CollectionMutationsManager<
366367
const globalKey = this.generateGlobalKey(modifiedItemId, modifiedItem)
367368

368369
return {
369-
mutationId: crypto.randomUUID(),
370+
mutationId: safeRandomUUID(),
370371
original: originalItem,
371372
modified: modifiedItem,
372373
// Pick the values from modifiedItem based on what's passed in - this is for cases
@@ -497,7 +498,7 @@ export class CollectionMutationsManager<
497498
`delete`,
498499
CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>
499500
> = {
500-
mutationId: crypto.randomUUID(),
501+
mutationId: safeRandomUUID(),
501502
original: this.state.get(key)!,
502503
modified: this.state.get(key)!,
503504
changes: this.state.get(key)!,

packages/db/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ export {
8080
type EffectQueryInput,
8181
} from './query/effect.js'
8282

83+
// UUID helper (safe in non-secure browser contexts, see #1541)
84+
export { safeRandomUUID } from './utils/uuid.js'
85+
8386
// Re-export some stuff explicitly to ensure the type & value is exported
8487
export type { Collection } from './collection/index.js'
8588
export { IR }

packages/db/src/local-only.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { safeRandomUUID } from './utils/uuid'
12
import type {
23
BaseCollectionConfig,
34
CollectionConfig,
@@ -182,7 +183,7 @@ export function localOnlyCollectionOptions<
182183
const { initialData, onInsert, onUpdate, onDelete, id, ...restConfig } =
183184
config
184185

185-
const collectionId = id ?? crypto.randomUUID()
186+
const collectionId = id ?? safeRandomUUID()
186187

187188
// Create the sync configuration with transaction confirmation capability
188189
const syncResult = createLocalOnlySync<T, TKey>(initialData)

packages/db/src/local-storage.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { safeRandomUUID } from './utils/uuid'
12
import {
23
InvalidStorageDataFormatError,
34
InvalidStorageObjectFormatError,
@@ -149,7 +150,7 @@ function validateJsonSerializable(
149150
* @returns A unique identifier string for tracking data versions
150151
*/
151152
function generateUuid(): string {
152-
return crypto.randomUUID()
153+
return safeRandomUUID()
153154
}
154155

155156
/**

packages/db/src/transactions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createDeferred } from './deferred'
2+
import { safeRandomUUID } from './utils/uuid'
23
import './duplicate-instance-check'
34
import {
45
MissingMutationFunctionError,
@@ -224,7 +225,7 @@ class Transaction<T extends object = Record<string, unknown>> {
224225
if (typeof config.mutationFn === `undefined`) {
225226
throw new MissingMutationFunctionError()
226227
}
227-
this.id = config.id ?? crypto.randomUUID()
228+
this.id = config.id ?? safeRandomUUID()
228229
this.mutationFn = config.mutationFn
229230
this.state = `pending`
230231
this.mutations = []

0 commit comments

Comments
 (0)