Skip to content

Commit 37c8390

Browse files
authored
Merge pull request #218 from zlxlabs/card/vta-upload-key-fallback-261010
上传幂等键兼容非安全上下文(纯 HTTP 直连)
2 parents 1dac573 + efb5df2 commit 37c8390

2 files changed

Lines changed: 33 additions & 3 deletions

File tree

‎src/web/static/js/app.js‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,15 +1256,28 @@ function encodeUploadMetadata(metadata) {
12561256
/**
12571257
* 生成 Idempotency-Key (epoch_ms-UUID)
12581258
*/
1259+
function uuidFromRandomValues(cryptoObj) {
1260+
if (!cryptoObj || typeof cryptoObj.getRandomValues !== 'function') {
1261+
throw new Error('no Web Crypto random source available in this environment');
1262+
}
1263+
const bytes = cryptoObj.getRandomValues(new Uint8Array(16));
1264+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
1265+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
1266+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
1267+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1268+
}
1269+
12591270
function generateUploadIdempotencyKey() {
12601271
const epoch = Date.now();
12611272
const cryptoObj = typeof crypto !== 'undefined'
12621273
? crypto
12631274
: (typeof globalThis !== 'undefined' && globalThis.crypto ? globalThis.crypto : null);
1264-
if (!cryptoObj || typeof cryptoObj.randomUUID !== 'function') {
1265-
throw new Error('crypto.randomUUID is not supported in this environment');
1275+
let uuid = null;
1276+
if (cryptoObj && typeof cryptoObj.randomUUID === 'function') {
1277+
uuid = cryptoObj.randomUUID();
1278+
} else {
1279+
uuid = uuidFromRandomValues(cryptoObj);
12661280
}
1267-
const uuid = cryptoObj.randomUUID();
12681281
return `${epoch}-${uuid}`;
12691282
}
12701283

‎src/web/tests/local-upload.test.js‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,23 @@ describe('local upload frontend logic', () => {
152152
expect(key).toMatch(/^\d+-[0-9a-fA-F-]+$/);
153153
});
154154

155+
it('still generates epoch_ms-UUID keys when crypto.randomUUID is unavailable (plain HTTP origin)', async () => {
156+
await installIndexPage();
157+
const { window } = dom;
158+
159+
const originalCrypto = window.crypto;
160+
Object.defineProperty(window, 'crypto', {
161+
configurable: true,
162+
value: { getRandomValues: (array) => originalCrypto.getRandomValues(array) },
163+
});
164+
try {
165+
const key = window.generateUploadIdempotencyKey();
166+
expect(key).toMatch(/^\d+-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
167+
} finally {
168+
Object.defineProperty(window, 'crypto', { configurable: true, value: originalCrypto });
169+
}
170+
});
171+
155172
it('switches between URL mode and Upload mode without breaking URL transcription', async () => {
156173
await installIndexPage();
157174
const { document } = dom.window;

0 commit comments

Comments
 (0)