Skip to content
Open
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
15 changes: 15 additions & 0 deletions src/mobile-web/tests/host-dialog-queue.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,18 @@ test('a live send becomes recoverable only when its acknowledgement fails',async
assert.equal(f.calls.find(call=>call.action==='submit').message.turnId,id);
assert.equal(f.executions,1);
});

test('a page without Web Crypto randomUUID still queues a send and an operation',async()=>{
const f=fixture();const q=f.create();
// The remote page of LAN mode is served over plain HTTP and older WebViews
// never expose `randomUUID`, while `getRandomValues` stays available.
const descriptor=Object.getOwnPropertyDescriptor(globalThis,'crypto');const original=globalThis.crypto;
Object.defineProperty(globalThis,'crypto',{configurable:true,value:{getRandomValues:original.getRandomValues.bind(original)}});
try {
await q.submit(message);await q.act({turnId:'queued'},'promote');
} finally {Object.defineProperty(globalThis,'crypto',descriptor);}
const uuid=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
assert.match(f.calls.find(call=>call.action==='submit').message.turnId,uuid);
assert.match(f.calls.find(call=>call.action==='promote').operationId,uuid);
assert.equal(f.executions,1);
});
11 changes: 10 additions & 1 deletion src/shared/dialog-queue/HostDialogQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,16 @@ export const queueStorage: QueueStorage = {
async remove(key) { await transaction('readwrite', store => store.delete(key)); },
};
export interface QueueView { snapshot: QueueSnapshot | null; error: string | null; pending: QueueOutboxRecord[] }
const newId = () => crypto.randomUUID();
const newId = (): string => {
// `crypto.randomUUID` exists only in secure contexts, so a page served over
// plain HTTP (LAN remote control) or an older WebView must still queue sends.
if (typeof crypto.randomUUID === 'function') return crypto.randomUUID();
const bytes = crypto.getRandomValues(new Uint8Array(16));
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
const hex = Array.from(bytes, value => value.toString(16).padStart(2, '0')).join('');
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
};
function messageIdentity(message: Omit<QueueMessage, 'turnId'>): string {
// Reopening the composer can allocate fresh image IDs. An ambiguous retry
// must send the stored payload, including its original IDs, unchanged.
Expand Down
Loading