deepspace 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/dist/cli.js +2912 -1984
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +31 -4
- package/dist/index.js +511 -366
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +29 -108
- package/dist/server.js +126 -589
- package/dist/server.js.map +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +29 -108
- package/dist/worker.js +126 -589
- package/dist/worker.js.map +1 -1
- package/features/admin-page/src/AdminPage.tsx +3 -5
- package/features/canvas/src/CanvasEditorPage.tsx +0 -2
- package/features/canvas/src/components/CanvasView.tsx +1 -1
- package/features/docs/src/LibrarySidebar.tsx +7 -1
- package/features/docs/src/use-yjs-room-with-awareness.ts +4 -0
- package/features/file-attachments/src/AttachmentsPage.tsx +7 -1
- package/features/file-attachments/src/useFilePreview.ts +7 -5
- package/features/file-manager/src/FileManagerPage.tsx +2 -3
- package/features/integration-test/feature.json +10 -3
- package/features/integration-test/src/IntegrationTestPage.tsx +15 -12
- package/features/landing/src/LandingPage.tsx +1 -1
- package/features/landing/src/sections/FeaturesGridSection.tsx +0 -92
- package/features/messaging/src/components/chat/MessageItem.tsx +5 -1
- package/features/messaging/src/hooks/useChatChannel.ts +5 -1
- package/features/presence-test/feature.json +2 -1
- package/features/sidebar/src/AppSidebar.tsx +1 -1
- package/features/tasks/src/TasksPage.tsx +1 -1
- package/features/testing/feature.json +2 -1
- package/features/testing/src/TestPage.tsx +8 -8
- package/package.json +4 -4
- package/scripts/add-feature.cjs +277 -272
package/dist/server.js
CHANGED
|
@@ -475,7 +475,8 @@ function buildCronContext(env, ownerUserId, roomId = "default") {
|
|
|
475
475
|
if (opts?.where) params.where = opts.where;
|
|
476
476
|
if (opts?.limit) params.limit = opts.limit;
|
|
477
477
|
const data = await executeTool3("records.query", params);
|
|
478
|
-
|
|
478
|
+
const records2 = data?.records;
|
|
479
|
+
return records2 ?? [];
|
|
479
480
|
},
|
|
480
481
|
async create(collection, data) {
|
|
481
482
|
return executeTool3("records.create", { collection, data });
|
|
@@ -632,7 +633,8 @@ async function composioTools(env, options) {
|
|
|
632
633
|
if (!list.ok || !list.success) {
|
|
633
634
|
throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
|
|
634
635
|
}
|
|
635
|
-
const
|
|
636
|
+
const listItems = list.data?.items;
|
|
637
|
+
const items = Array.isArray(listItems) ? listItems : [];
|
|
636
638
|
const tools = {};
|
|
637
639
|
for (const item of items) {
|
|
638
640
|
if (!item?.slug) continue;
|
|
@@ -1333,7 +1335,7 @@ var BaseRoom = class {
|
|
|
1333
1335
|
console.error(`[${this.constructor.name}] Message error:`, e);
|
|
1334
1336
|
}
|
|
1335
1337
|
}
|
|
1336
|
-
async webSocketClose(ws,
|
|
1338
|
+
async webSocketClose(ws, _code, _reason) {
|
|
1337
1339
|
const attachment = ws.deserializeAttachment();
|
|
1338
1340
|
if (attachment) {
|
|
1339
1341
|
try {
|
|
@@ -1358,12 +1360,12 @@ var BaseRoom = class {
|
|
|
1358
1360
|
* Return an augmented attachment to serialize on the WebSocket,
|
|
1359
1361
|
* or void to use the default attachment.
|
|
1360
1362
|
*/
|
|
1361
|
-
onConnect(
|
|
1363
|
+
onConnect(_ws, _user) {
|
|
1362
1364
|
}
|
|
1363
1365
|
/**
|
|
1364
1366
|
* Called when a WebSocket disconnects.
|
|
1365
1367
|
*/
|
|
1366
|
-
onDisconnect(
|
|
1368
|
+
onDisconnect(_ws, _user) {
|
|
1367
1369
|
}
|
|
1368
1370
|
// ==========================================================================
|
|
1369
1371
|
// Utilities
|
|
@@ -1611,7 +1613,7 @@ function coerceValue(value, storage, interpretation) {
|
|
|
1611
1613
|
if (m) return parseFloat(m[1]) / 100;
|
|
1612
1614
|
}
|
|
1613
1615
|
if (interpretation.kind === "currency") {
|
|
1614
|
-
const cleaned2 = s.replace(/[^0-9
|
|
1616
|
+
const cleaned2 = s.replace(/[^0-9.-]/g, "");
|
|
1615
1617
|
const n2 = parseFloat(cleaned2);
|
|
1616
1618
|
if (!isNaN(n2)) return n2;
|
|
1617
1619
|
return s;
|
|
@@ -1801,9 +1803,6 @@ var SchemaRegistry = class {
|
|
|
1801
1803
|
has(name) {
|
|
1802
1804
|
return this.trusted.has(name);
|
|
1803
1805
|
}
|
|
1804
|
-
hasTrusted(name) {
|
|
1805
|
-
return this.trusted.has(name);
|
|
1806
|
-
}
|
|
1807
1806
|
all() {
|
|
1808
1807
|
return Array.from(this.trusted.values());
|
|
1809
1808
|
}
|
|
@@ -2118,7 +2117,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
2118
2117
|
const columns = getResolvedColumns(schema);
|
|
2119
2118
|
const existing = getRecord(ctx.sql, collection, recordId, schema);
|
|
2120
2119
|
const isUpdate = existing !== null;
|
|
2121
|
-
|
|
2120
|
+
const mergedData = isUpdate ? { ...existing.data, ...data } : { ...data };
|
|
2122
2121
|
for (const col of columns) {
|
|
2123
2122
|
if (col.readonly) continue;
|
|
2124
2123
|
if (!isUpdate) {
|
|
@@ -2173,7 +2172,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
2173
2172
|
const permCtx = ctx.getPermissionContext();
|
|
2174
2173
|
if (isUpdate) {
|
|
2175
2174
|
if (!canUpdate(schema, userRole, { ...existing, recordId }, userId, permCtx)) {
|
|
2176
|
-
return {
|
|
2175
|
+
return {
|
|
2176
|
+
success: false,
|
|
2177
|
+
error: `UPDATE DENIED: role=${userRole}, collection=${collection}`
|
|
2178
|
+
};
|
|
2177
2179
|
}
|
|
2178
2180
|
const fieldError = checkFieldPermissions(schema, userRole, finalData, existing.data);
|
|
2179
2181
|
if (fieldError) {
|
|
@@ -2181,7 +2183,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
2181
2183
|
}
|
|
2182
2184
|
} else {
|
|
2183
2185
|
if (!canCreate(schema, userRole)) {
|
|
2184
|
-
return {
|
|
2186
|
+
return {
|
|
2187
|
+
success: false,
|
|
2188
|
+
error: `CREATE DENIED: role=${userRole}, collection=${collection}`
|
|
2189
|
+
};
|
|
2185
2190
|
}
|
|
2186
2191
|
}
|
|
2187
2192
|
}
|
|
@@ -2203,7 +2208,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
2203
2208
|
);
|
|
2204
2209
|
if (existing2.toArray().length > 0) {
|
|
2205
2210
|
const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? "null"}`).join(", ");
|
|
2206
|
-
return {
|
|
2211
|
+
return {
|
|
2212
|
+
success: false,
|
|
2213
|
+
error: `Duplicate: a record with ${fields} already exists in ${collection}`
|
|
2214
|
+
};
|
|
2207
2215
|
}
|
|
2208
2216
|
}
|
|
2209
2217
|
if (isUpdate) {
|
|
@@ -2217,13 +2225,16 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
2217
2225
|
ctx.sql.exec(`UPDATE "${tbl}" SET ${setClauses.join(", ")} WHERE _row_id = ?`, ...params);
|
|
2218
2226
|
} else {
|
|
2219
2227
|
const colIds = Object.keys(colValues);
|
|
2220
|
-
const allCols = [
|
|
2228
|
+
const allCols = [
|
|
2229
|
+
"_row_id",
|
|
2230
|
+
"_created_by",
|
|
2231
|
+
"_created_at",
|
|
2232
|
+
"_updated_at",
|
|
2233
|
+
...colIds.map((c) => `"${c}"`)
|
|
2234
|
+
];
|
|
2221
2235
|
const placeholders = allCols.map(() => "?").join(", ");
|
|
2222
2236
|
const params = [recordId, userId, now, now, ...colIds.map((c) => colValues[c])];
|
|
2223
|
-
ctx.sql.exec(
|
|
2224
|
-
`INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`,
|
|
2225
|
-
...params
|
|
2226
|
-
);
|
|
2237
|
+
ctx.sql.exec(`INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`, ...params);
|
|
2227
2238
|
}
|
|
2228
2239
|
const saved = getRecord(ctx.sql, collection, recordId, schema);
|
|
2229
2240
|
const record = {
|
|
@@ -3456,9 +3467,21 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3456
3467
|
ownerUserId;
|
|
3457
3468
|
/** True until the first fetch() completes — detects hibernation wake-up */
|
|
3458
3469
|
freshConstruct = true;
|
|
3470
|
+
/**
|
|
3471
|
+
* Per-connection `[DO Perf]` timing logs are noisy on every hot path, so
|
|
3472
|
+
* they're gated behind a `DEEPSPACE_DO_PERF` env binding (set it to any
|
|
3473
|
+
* truthy value on the worker to opt in). Off by default.
|
|
3474
|
+
*/
|
|
3475
|
+
get perfLogEnabled() {
|
|
3476
|
+
return !!this.env?.DEEPSPACE_DO_PERF;
|
|
3477
|
+
}
|
|
3459
3478
|
constructor(state, env, schemas = [], config = {}) {
|
|
3460
3479
|
super(state, env);
|
|
3461
|
-
this.schemaRegistry = new SchemaRegistry([
|
|
3480
|
+
this.schemaRegistry = new SchemaRegistry([
|
|
3481
|
+
...SYSTEM_COLLECTION_SCHEMAS,
|
|
3482
|
+
BASE_USERS_SCHEMA,
|
|
3483
|
+
...schemas
|
|
3484
|
+
]);
|
|
3462
3485
|
this.ownerUserId = config.ownerUserId ?? null;
|
|
3463
3486
|
const wsCount = this.state.getWebSockets().length;
|
|
3464
3487
|
if (wsCount > 0) {
|
|
@@ -3542,10 +3565,7 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3542
3565
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3543
3566
|
for (const row of oldUsers) {
|
|
3544
3567
|
const r = row;
|
|
3545
|
-
const existing = this.sql.exec(
|
|
3546
|
-
`SELECT 1 FROM c_users WHERE _row_id = ?`,
|
|
3547
|
-
r.user_id
|
|
3548
|
-
).toArray();
|
|
3568
|
+
const existing = this.sql.exec(`SELECT 1 FROM c_users WHERE _row_id = ?`, r.user_id).toArray();
|
|
3549
3569
|
if (existing.length > 0) continue;
|
|
3550
3570
|
this.sql.exec(
|
|
3551
3571
|
`INSERT INTO c_users (_row_id, _created_by, _created_at, _updated_at, col_email, col_name, col_imageurl, col_role, col_createdat, col_lastseenat) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -3579,7 +3599,6 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3579
3599
|
}
|
|
3580
3600
|
let migrated = 0;
|
|
3581
3601
|
let skipped = 0;
|
|
3582
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3583
3602
|
for (const row of rows) {
|
|
3584
3603
|
const r = row;
|
|
3585
3604
|
const schema = this.schemaRegistry.get(r.collection);
|
|
@@ -3588,17 +3607,11 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3588
3607
|
continue;
|
|
3589
3608
|
}
|
|
3590
3609
|
const tbl = collectionTableName(r.collection);
|
|
3591
|
-
const tblExists = this.sql.exec(
|
|
3592
|
-
`SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
|
|
3593
|
-
tbl
|
|
3594
|
-
).toArray().length > 0;
|
|
3610
|
+
const tblExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, tbl).toArray().length > 0;
|
|
3595
3611
|
if (!tblExists) {
|
|
3596
3612
|
this.ensureCollectionTable(schema);
|
|
3597
3613
|
}
|
|
3598
|
-
const existing = this.sql.exec(
|
|
3599
|
-
`SELECT 1 FROM "${tbl}" WHERE _row_id = ?`,
|
|
3600
|
-
r.record_id
|
|
3601
|
-
).toArray();
|
|
3614
|
+
const existing = this.sql.exec(`SELECT 1 FROM "${tbl}" WHERE _row_id = ?`, r.record_id).toArray();
|
|
3602
3615
|
if (existing.length > 0) {
|
|
3603
3616
|
skipped++;
|
|
3604
3617
|
continue;
|
|
@@ -3613,9 +3626,21 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3613
3626
|
const columns = schema.columns.map(resolveColumn);
|
|
3614
3627
|
const colValues = dataToColumnValues(data, columns);
|
|
3615
3628
|
const colIds = Object.keys(colValues);
|
|
3616
|
-
const allCols = [
|
|
3629
|
+
const allCols = [
|
|
3630
|
+
"_row_id",
|
|
3631
|
+
"_created_by",
|
|
3632
|
+
"_created_at",
|
|
3633
|
+
"_updated_at",
|
|
3634
|
+
...colIds.map((c) => `"${c}"`)
|
|
3635
|
+
];
|
|
3617
3636
|
const placeholders = allCols.map(() => "?").join(", ");
|
|
3618
|
-
const params = [
|
|
3637
|
+
const params = [
|
|
3638
|
+
r.record_id,
|
|
3639
|
+
r.created_by,
|
|
3640
|
+
r.created_at,
|
|
3641
|
+
r.updated_at,
|
|
3642
|
+
...colIds.map((c) => colValues[c])
|
|
3643
|
+
];
|
|
3619
3644
|
this.sql.exec(
|
|
3620
3645
|
`INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`,
|
|
3621
3646
|
...params
|
|
@@ -3623,7 +3648,9 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3623
3648
|
migrated++;
|
|
3624
3649
|
}
|
|
3625
3650
|
if (migrated > 0 || skipped > 0) {
|
|
3626
|
-
console.log(
|
|
3651
|
+
console.log(
|
|
3652
|
+
`[RecordRoom] Migrated ${migrated} records from document-mode, ${skipped} skipped`
|
|
3653
|
+
);
|
|
3627
3654
|
}
|
|
3628
3655
|
this.sql.exec(`DROP TABLE IF EXISTS records`);
|
|
3629
3656
|
} catch (error) {
|
|
@@ -3646,7 +3673,12 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3646
3673
|
// ============================================================================
|
|
3647
3674
|
async onConnect(ws, user) {
|
|
3648
3675
|
await this.ensureInitialized();
|
|
3649
|
-
const timing = this._fetchTiming ?? {
|
|
3676
|
+
const timing = this._fetchTiming ?? {
|
|
3677
|
+
fetchStart: Date.now(),
|
|
3678
|
+
isColdStart: false,
|
|
3679
|
+
needsInit: false,
|
|
3680
|
+
initMs: 0
|
|
3681
|
+
};
|
|
3650
3682
|
const userId = user.userId;
|
|
3651
3683
|
const userName = user.userName;
|
|
3652
3684
|
const userEmail = user.userEmail;
|
|
@@ -3657,7 +3689,16 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3657
3689
|
const isOwner2 = this.ownerUserId != null && userId === this.ownerUserId;
|
|
3658
3690
|
const regStart = Date.now();
|
|
3659
3691
|
const defaultRole = this.schemaRegistry.get("users")?.defaultRole ?? "member";
|
|
3660
|
-
const registeredUser = await registerUser(
|
|
3692
|
+
const registeredUser = await registerUser(
|
|
3693
|
+
this.sql,
|
|
3694
|
+
userId,
|
|
3695
|
+
userName,
|
|
3696
|
+
userEmail,
|
|
3697
|
+
userImageUrl,
|
|
3698
|
+
isOwner2,
|
|
3699
|
+
defaultRole,
|
|
3700
|
+
this.schemaRegistry
|
|
3701
|
+
);
|
|
3661
3702
|
const regMs = Date.now() - regStart;
|
|
3662
3703
|
attachment = {
|
|
3663
3704
|
userId: registeredUser.id,
|
|
@@ -3671,7 +3712,10 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3671
3712
|
};
|
|
3672
3713
|
this.send(ws, { type: MSG.USER_INFO, payload: registeredUser });
|
|
3673
3714
|
const totalMs = Date.now() - timing.fetchStart;
|
|
3674
|
-
|
|
3715
|
+
if (this.perfLogEnabled)
|
|
3716
|
+
console.log(
|
|
3717
|
+
`[DO Perf] cold=${timing.isColdStart} | init: ${timing.initMs}ms reg: ${regMs}ms | total: ${totalMs}ms`
|
|
3718
|
+
);
|
|
3675
3719
|
} else {
|
|
3676
3720
|
attachment = {
|
|
3677
3721
|
userId,
|
|
@@ -3682,63 +3726,37 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3682
3726
|
yjsSubscriptions: [],
|
|
3683
3727
|
yjsClientId: this.nextYjsClientId++
|
|
3684
3728
|
};
|
|
3685
|
-
this.send(ws, {
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3729
|
+
this.send(ws, {
|
|
3730
|
+
type: MSG.USER_INFO,
|
|
3731
|
+
payload: {
|
|
3732
|
+
id: userId,
|
|
3733
|
+
name: "Anonymous",
|
|
3734
|
+
email: "",
|
|
3735
|
+
role: ROLE_ANONYMOUS,
|
|
3736
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3737
|
+
lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3738
|
+
}
|
|
3739
|
+
});
|
|
3693
3740
|
const totalMs = Date.now() - timing.fetchStart;
|
|
3694
|
-
|
|
3741
|
+
if (this.perfLogEnabled)
|
|
3742
|
+
console.log(`[DO Perf] cold=${timing.isColdStart} anon | total: ${totalMs}ms`);
|
|
3695
3743
|
}
|
|
3696
3744
|
this._fetchTiming = null;
|
|
3697
3745
|
return attachment;
|
|
3698
3746
|
}
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
await this.handleRecordMessage(ws, attachment, msg);
|
|
3708
|
-
} catch (e) {
|
|
3709
|
-
const errMsg = e instanceof Error ? e.message : String(e);
|
|
3710
|
-
console.error(`[RecordRoom] Message handler error: ${errMsg}`);
|
|
3711
|
-
this.send(ws, { type: MSG.ERROR, payload: { error: `Invalid message: ${errMsg}` } });
|
|
3712
|
-
}
|
|
3713
|
-
}
|
|
3714
|
-
async onBinaryMessage(ws, user, data) {
|
|
3715
|
-
await this.ensureInitialized();
|
|
3716
|
-
const attachment = ws.deserializeAttachment();
|
|
3717
|
-
if (!attachment) return;
|
|
3718
|
-
try {
|
|
3719
|
-
await handleYjsBinaryMessage(
|
|
3720
|
-
this.createYjsContext(),
|
|
3721
|
-
ws,
|
|
3722
|
-
attachment,
|
|
3723
|
-
new Uint8Array(data)
|
|
3724
|
-
);
|
|
3725
|
-
} catch (e) {
|
|
3726
|
-
console.error("[RecordRoom] Yjs binary message error:", e);
|
|
3727
|
-
}
|
|
3728
|
-
}
|
|
3729
|
-
onDisconnect(ws, user) {
|
|
3730
|
-
console.log(`[RecordRoom] webSocketClose`);
|
|
3731
|
-
try {
|
|
3732
|
-
const attachment = ws.deserializeAttachment();
|
|
3733
|
-
if (attachment) {
|
|
3734
|
-
broadcastAwarenessRemoval(this.createYjsContext(), attachment);
|
|
3735
|
-
}
|
|
3736
|
-
} catch {
|
|
3737
|
-
}
|
|
3747
|
+
/**
|
|
3748
|
+
* Required to satisfy BaseRoom's abstract contract, but never invoked:
|
|
3749
|
+
* RecordRoom overrides `webSocketMessage`/`webSocketClose` directly (below),
|
|
3750
|
+
* so the BaseRoom dispatch path that would call this never runs. All real
|
|
3751
|
+
* message handling lives in `handleRecordMessage`.
|
|
3752
|
+
*/
|
|
3753
|
+
onMessage() {
|
|
3754
|
+
throw new Error("RecordRoom.onMessage is unreachable; use webSocketMessage");
|
|
3738
3755
|
}
|
|
3739
3756
|
// ============================================================================
|
|
3740
|
-
// Hibernation API
|
|
3741
|
-
//
|
|
3757
|
+
// Hibernation API — RecordRoom overrides these directly (rather than using
|
|
3758
|
+
// BaseRoom's onMessage/onBinaryMessage/onDisconnect hooks) so it can run
|
|
3759
|
+
// ensureInitialized() before every message after a hibernation wake-up.
|
|
3742
3760
|
// ============================================================================
|
|
3743
3761
|
async webSocketMessage(ws, message) {
|
|
3744
3762
|
await this.ensureInitialized();
|
|
@@ -3809,7 +3827,12 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3809
3827
|
handleUserList(userCtx, ws, attachment);
|
|
3810
3828
|
break;
|
|
3811
3829
|
case MSG.USER_UPDATE:
|
|
3812
|
-
handleUserUpdate(
|
|
3830
|
+
handleUserUpdate(
|
|
3831
|
+
recordCtx,
|
|
3832
|
+
ws,
|
|
3833
|
+
attachment,
|
|
3834
|
+
payload
|
|
3835
|
+
);
|
|
3813
3836
|
break;
|
|
3814
3837
|
case MSG.SET_ROLE:
|
|
3815
3838
|
await handleSetRole(userCtx, ws, attachment, payload);
|
|
@@ -3975,19 +3998,19 @@ var YjsRoom = class extends BaseRoom {
|
|
|
3975
3998
|
this.sendAwarenessSnapshot(ws);
|
|
3976
3999
|
return attachment;
|
|
3977
4000
|
}
|
|
3978
|
-
onMessage(
|
|
4001
|
+
onMessage(_ws, _user, _message) {
|
|
3979
4002
|
}
|
|
3980
4003
|
onBinaryMessage(ws, user, data) {
|
|
3981
|
-
const
|
|
3982
|
-
const decoder = createDecoder(
|
|
4004
|
+
const bytes = new Uint8Array(data);
|
|
4005
|
+
const decoder = createDecoder(bytes);
|
|
3983
4006
|
const messageType = readVarUint(decoder);
|
|
3984
4007
|
if (messageType === MSG_SYNC) {
|
|
3985
|
-
this.handleSync(ws, decoder,
|
|
4008
|
+
this.handleSync(ws, decoder, bytes);
|
|
3986
4009
|
} else if (messageType === MSG_AWARENESS) {
|
|
3987
|
-
this.handleAwareness(ws, decoder,
|
|
4010
|
+
this.handleAwareness(ws, decoder, bytes);
|
|
3988
4011
|
}
|
|
3989
4012
|
}
|
|
3990
|
-
onDisconnect(ws,
|
|
4013
|
+
onDisconnect(ws, _user) {
|
|
3991
4014
|
const attachment = ws.deserializeAttachment();
|
|
3992
4015
|
if (attachment?.awarenessClientId == null) return;
|
|
3993
4016
|
const clientId = attachment.awarenessClientId;
|
|
@@ -4348,16 +4371,16 @@ var GameRoom = class extends BaseRoom {
|
|
|
4348
4371
|
return this.currentTick;
|
|
4349
4372
|
}
|
|
4350
4373
|
/** Called when a player connects */
|
|
4351
|
-
onPlayerJoin(
|
|
4374
|
+
onPlayerJoin(_player) {
|
|
4352
4375
|
}
|
|
4353
4376
|
/** Called when a player disconnects */
|
|
4354
|
-
onPlayerLeave(
|
|
4377
|
+
onPlayerLeave(_player) {
|
|
4355
4378
|
}
|
|
4356
4379
|
/** Called when the game starts */
|
|
4357
4380
|
onGameStart() {
|
|
4358
4381
|
}
|
|
4359
4382
|
/** Called when the game ends */
|
|
4360
|
-
onGameEnd(
|
|
4383
|
+
onGameEnd(_finalState) {
|
|
4361
4384
|
}
|
|
4362
4385
|
/**
|
|
4363
4386
|
* Called once when the DO first hydrates persisted state from storage.
|
|
@@ -5189,319 +5212,6 @@ function cronMatches(parsed, timezone, date) {
|
|
|
5189
5212
|
return parsed.minute.has(minute) && parsed.hour.has(hour) && parsed.dayOfMonth.has(dayOfMonth) && parsed.month.has(month) && parsed.dayOfWeek.has(dayOfWeek);
|
|
5190
5213
|
}
|
|
5191
5214
|
|
|
5192
|
-
// src/server/rooms/secrets-room.ts
|
|
5193
|
-
var INTERNAL_HEADER = "x-deepspace-internal";
|
|
5194
|
-
var INTERNAL_VALUE = "app-secrets";
|
|
5195
|
-
var VERSION = "app-secret-v1";
|
|
5196
|
-
var NAME_INFO = "deepspace app secret name v1";
|
|
5197
|
-
var VALUE_INFO = "deepspace app secret value v1";
|
|
5198
|
-
var HMAC_INFO = "deepspace app secret lookup v1";
|
|
5199
|
-
var STORE_ID = "default";
|
|
5200
|
-
var SecretsRoom = class extends BaseRoom {
|
|
5201
|
-
initialized = false;
|
|
5202
|
-
onMessage(_ws, _user, _message) {
|
|
5203
|
-
}
|
|
5204
|
-
async fetch(request) {
|
|
5205
|
-
this.ensureInitialized();
|
|
5206
|
-
return super.fetch(request);
|
|
5207
|
-
}
|
|
5208
|
-
async onRequest(request) {
|
|
5209
|
-
if (request.headers.get(INTERNAL_HEADER) !== INTERNAL_VALUE) {
|
|
5210
|
-
return json({ error: "Not found" }, 404);
|
|
5211
|
-
}
|
|
5212
|
-
const url = new URL(request.url);
|
|
5213
|
-
try {
|
|
5214
|
-
if (request.method === "GET" && url.pathname.endsWith("/list")) {
|
|
5215
|
-
return json({ version: this.storeVersion(), secrets: await this.list() });
|
|
5216
|
-
}
|
|
5217
|
-
if (request.method === "GET" && url.pathname.endsWith("/pull")) {
|
|
5218
|
-
return json({ version: this.storeVersion(), secrets: await this.pull() });
|
|
5219
|
-
}
|
|
5220
|
-
if (request.method === "GET" && url.pathname.endsWith("/version")) {
|
|
5221
|
-
return json({ version: this.storeVersion() });
|
|
5222
|
-
}
|
|
5223
|
-
if (request.method === "PATCH" && url.pathname.endsWith("/patch")) {
|
|
5224
|
-
const body = await request.json();
|
|
5225
|
-
const result = await this.patch(body);
|
|
5226
|
-
return json(result);
|
|
5227
|
-
}
|
|
5228
|
-
if (request.method === "PUT" && url.pathname.endsWith("/replace")) {
|
|
5229
|
-
const body = await request.json();
|
|
5230
|
-
const result = await this.replace(body);
|
|
5231
|
-
return json(result);
|
|
5232
|
-
}
|
|
5233
|
-
} catch (err) {
|
|
5234
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5235
|
-
const status = message.includes("conflict") ? 409 : 500;
|
|
5236
|
-
return json({ error: message }, status);
|
|
5237
|
-
}
|
|
5238
|
-
return json({ error: "Not found" }, 404);
|
|
5239
|
-
}
|
|
5240
|
-
async list() {
|
|
5241
|
-
const rows = this.sql.exec(
|
|
5242
|
-
`SELECT name_encrypted, version, updated_by, created_at, updated_at
|
|
5243
|
-
FROM app_secrets_store ORDER BY updated_at DESC`
|
|
5244
|
-
).toArray();
|
|
5245
|
-
const out = [];
|
|
5246
|
-
for (const row of rows) {
|
|
5247
|
-
out.push({
|
|
5248
|
-
name: await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO),
|
|
5249
|
-
version: row.version,
|
|
5250
|
-
createdAt: row.created_at,
|
|
5251
|
-
updatedAt: row.updated_at,
|
|
5252
|
-
updatedBy: row.updated_by
|
|
5253
|
-
});
|
|
5254
|
-
}
|
|
5255
|
-
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
5256
|
-
}
|
|
5257
|
-
async pull() {
|
|
5258
|
-
const rows = this.sql.exec(
|
|
5259
|
-
`SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
|
|
5260
|
-
FROM app_secrets_store`
|
|
5261
|
-
).toArray();
|
|
5262
|
-
const out = {};
|
|
5263
|
-
for (const row of rows) {
|
|
5264
|
-
const name = await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO);
|
|
5265
|
-
out[name] = await decryptText(this.requireKey(), row.value_encrypted, VALUE_INFO);
|
|
5266
|
-
}
|
|
5267
|
-
return out;
|
|
5268
|
-
}
|
|
5269
|
-
async patch(body) {
|
|
5270
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5271
|
-
const actor = body.updatedBy ?? null;
|
|
5272
|
-
const key = this.requireKey();
|
|
5273
|
-
const setEntries = Object.entries(body.set ?? {});
|
|
5274
|
-
const deleteNames = body.delete ?? [];
|
|
5275
|
-
for (const name of [...setEntries.map(([name2]) => name2), ...deleteNames]) {
|
|
5276
|
-
validateSecretName(name);
|
|
5277
|
-
}
|
|
5278
|
-
const writes = [];
|
|
5279
|
-
for (const [name, value] of setEntries) {
|
|
5280
|
-
const nameHash = await hashName(key, name);
|
|
5281
|
-
const existing = this.sql.exec(
|
|
5282
|
-
`SELECT version, created_at FROM app_secrets_store WHERE name_hash = ?`,
|
|
5283
|
-
nameHash
|
|
5284
|
-
).toArray()[0];
|
|
5285
|
-
writes.push({
|
|
5286
|
-
nameHash,
|
|
5287
|
-
nameEncrypted: await encryptText(key, name, NAME_INFO),
|
|
5288
|
-
valueEncrypted: await encryptText(key, value, VALUE_INFO),
|
|
5289
|
-
version: (existing?.version ?? 0) + 1,
|
|
5290
|
-
createdAt: existing?.created_at ?? now
|
|
5291
|
-
});
|
|
5292
|
-
}
|
|
5293
|
-
const deletes = [];
|
|
5294
|
-
for (const name of deleteNames) {
|
|
5295
|
-
deletes.push(await hashName(key, name));
|
|
5296
|
-
}
|
|
5297
|
-
this.transaction(() => {
|
|
5298
|
-
for (const write of writes) {
|
|
5299
|
-
this.sql.exec(
|
|
5300
|
-
`INSERT OR REPLACE INTO app_secrets_store
|
|
5301
|
-
(name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
|
|
5302
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
5303
|
-
write.nameHash,
|
|
5304
|
-
write.nameEncrypted,
|
|
5305
|
-
write.valueEncrypted,
|
|
5306
|
-
write.version,
|
|
5307
|
-
actor,
|
|
5308
|
-
write.createdAt,
|
|
5309
|
-
now
|
|
5310
|
-
);
|
|
5311
|
-
}
|
|
5312
|
-
for (const nameHash of deletes) {
|
|
5313
|
-
this.sql.exec(`DELETE FROM app_secrets_store WHERE name_hash = ?`, nameHash);
|
|
5314
|
-
}
|
|
5315
|
-
this.bumpStoreVersion(now);
|
|
5316
|
-
});
|
|
5317
|
-
return { version: this.storeVersion(), secrets: await this.list() };
|
|
5318
|
-
}
|
|
5319
|
-
async replace(body) {
|
|
5320
|
-
const next = body.secrets ?? {};
|
|
5321
|
-
for (const name of Object.keys(next)) validateSecretName(name);
|
|
5322
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5323
|
-
const actor = body.updatedBy ?? null;
|
|
5324
|
-
const key = this.requireKey();
|
|
5325
|
-
const writes = [];
|
|
5326
|
-
for (const [name, value] of Object.entries(next)) {
|
|
5327
|
-
writes.push({
|
|
5328
|
-
nameHash: await hashName(key, name),
|
|
5329
|
-
nameEncrypted: await encryptText(key, name, NAME_INFO),
|
|
5330
|
-
valueEncrypted: await encryptText(key, value, VALUE_INFO)
|
|
5331
|
-
});
|
|
5332
|
-
}
|
|
5333
|
-
this.transaction(() => {
|
|
5334
|
-
this.sql.exec(`DELETE FROM app_secrets_store`);
|
|
5335
|
-
for (const write of writes) {
|
|
5336
|
-
this.sql.exec(
|
|
5337
|
-
`INSERT INTO app_secrets_store
|
|
5338
|
-
(name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
|
|
5339
|
-
VALUES (?, ?, ?, 1, ?, ?, ?)`,
|
|
5340
|
-
write.nameHash,
|
|
5341
|
-
write.nameEncrypted,
|
|
5342
|
-
write.valueEncrypted,
|
|
5343
|
-
actor,
|
|
5344
|
-
now,
|
|
5345
|
-
now
|
|
5346
|
-
);
|
|
5347
|
-
}
|
|
5348
|
-
this.bumpStoreVersion(now);
|
|
5349
|
-
});
|
|
5350
|
-
return { version: this.storeVersion(), secrets: await this.list() };
|
|
5351
|
-
}
|
|
5352
|
-
transaction(run) {
|
|
5353
|
-
this.state.storage.transactionSync(run);
|
|
5354
|
-
}
|
|
5355
|
-
migrateLegacyProductionSecrets() {
|
|
5356
|
-
const legacyExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secrets'`).toArray().length > 0;
|
|
5357
|
-
if (!legacyExists) return;
|
|
5358
|
-
const currentCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
|
|
5359
|
-
if ((currentCount?.count ?? 0) > 0) return;
|
|
5360
|
-
this.sql.exec(`
|
|
5361
|
-
INSERT OR IGNORE INTO app_secrets_store
|
|
5362
|
-
(name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
|
|
5363
|
-
SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
|
|
5364
|
-
FROM app_secrets
|
|
5365
|
-
WHERE env = 'production'
|
|
5366
|
-
`);
|
|
5367
|
-
const legacyVersionExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secret_env_versions'`).toArray().length > 0;
|
|
5368
|
-
const legacyVersion = legacyVersionExists ? this.sql.exec(`SELECT version, updated_at FROM app_secret_env_versions WHERE env = 'production'`).toArray()[0] : void 0;
|
|
5369
|
-
if (legacyVersion) {
|
|
5370
|
-
this.sql.exec(
|
|
5371
|
-
`INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
|
|
5372
|
-
STORE_ID,
|
|
5373
|
-
legacyVersion.version,
|
|
5374
|
-
legacyVersion.updated_at
|
|
5375
|
-
);
|
|
5376
|
-
}
|
|
5377
|
-
const migratedCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
|
|
5378
|
-
if (!legacyVersion && (migratedCount?.count ?? 0) > 0) {
|
|
5379
|
-
this.sql.exec(
|
|
5380
|
-
`INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
|
|
5381
|
-
STORE_ID,
|
|
5382
|
-
1,
|
|
5383
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
5384
|
-
);
|
|
5385
|
-
}
|
|
5386
|
-
}
|
|
5387
|
-
ensureInitialized() {
|
|
5388
|
-
if (this.initialized) return;
|
|
5389
|
-
this.initialized = true;
|
|
5390
|
-
this.sql.exec(`
|
|
5391
|
-
CREATE TABLE IF NOT EXISTS app_secrets_store (
|
|
5392
|
-
name_hash TEXT NOT NULL,
|
|
5393
|
-
name_encrypted TEXT NOT NULL,
|
|
5394
|
-
value_encrypted TEXT NOT NULL,
|
|
5395
|
-
version INTEGER NOT NULL,
|
|
5396
|
-
updated_by TEXT,
|
|
5397
|
-
created_at TEXT NOT NULL,
|
|
5398
|
-
updated_at TEXT NOT NULL,
|
|
5399
|
-
PRIMARY KEY (name_hash)
|
|
5400
|
-
)
|
|
5401
|
-
`);
|
|
5402
|
-
this.sql.exec(`
|
|
5403
|
-
CREATE TABLE IF NOT EXISTS app_secret_store_versions (
|
|
5404
|
-
id TEXT PRIMARY KEY,
|
|
5405
|
-
version INTEGER NOT NULL,
|
|
5406
|
-
updated_at TEXT NOT NULL
|
|
5407
|
-
)
|
|
5408
|
-
`);
|
|
5409
|
-
this.migrateLegacyProductionSecrets();
|
|
5410
|
-
}
|
|
5411
|
-
requireKey() {
|
|
5412
|
-
if (!this.env.APP_SECRETS_KEY) {
|
|
5413
|
-
throw new Error("APP_SECRETS_KEY binding is missing");
|
|
5414
|
-
}
|
|
5415
|
-
return this.env.APP_SECRETS_KEY;
|
|
5416
|
-
}
|
|
5417
|
-
storeVersion() {
|
|
5418
|
-
const row = this.sql.exec(
|
|
5419
|
-
`SELECT version FROM app_secret_store_versions WHERE id = ?`,
|
|
5420
|
-
STORE_ID
|
|
5421
|
-
).toArray()[0];
|
|
5422
|
-
return row?.version ?? 0;
|
|
5423
|
-
}
|
|
5424
|
-
bumpStoreVersion(now) {
|
|
5425
|
-
const next = this.storeVersion() + 1;
|
|
5426
|
-
this.sql.exec(
|
|
5427
|
-
`INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
|
|
5428
|
-
STORE_ID,
|
|
5429
|
-
next,
|
|
5430
|
-
now
|
|
5431
|
-
);
|
|
5432
|
-
}
|
|
5433
|
-
};
|
|
5434
|
-
function validateSecretName(name) {
|
|
5435
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
5436
|
-
throw new Error(`Invalid secret name "${name}"`);
|
|
5437
|
-
}
|
|
5438
|
-
}
|
|
5439
|
-
function json(body, status = 200) {
|
|
5440
|
-
return new Response(JSON.stringify(body), {
|
|
5441
|
-
status,
|
|
5442
|
-
headers: { "Content-Type": "application/json" }
|
|
5443
|
-
});
|
|
5444
|
-
}
|
|
5445
|
-
async function encryptText(root, plaintext, info) {
|
|
5446
|
-
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
5447
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
5448
|
-
const key = await aesKey(root, salt, info);
|
|
5449
|
-
const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, new TextEncoder().encode(plaintext));
|
|
5450
|
-
return [VERSION, b64(salt), b64(iv), b64(new Uint8Array(ciphertext))].join(":");
|
|
5451
|
-
}
|
|
5452
|
-
async function decryptText(root, encoded, info) {
|
|
5453
|
-
const [version, saltB64, ivB64, bodyB64] = encoded.split(":");
|
|
5454
|
-
if (version !== VERSION || !saltB64 || !ivB64 || !bodyB64) {
|
|
5455
|
-
throw new Error("Corrupt app secret ciphertext");
|
|
5456
|
-
}
|
|
5457
|
-
const key = await aesKey(root, unb64(saltB64), info);
|
|
5458
|
-
const plaintext = await crypto.subtle.decrypt(
|
|
5459
|
-
{ name: "AES-GCM", iv: bytes(unb64(ivB64)) },
|
|
5460
|
-
key,
|
|
5461
|
-
bytes(unb64(bodyB64))
|
|
5462
|
-
);
|
|
5463
|
-
return new TextDecoder().decode(plaintext);
|
|
5464
|
-
}
|
|
5465
|
-
async function hashName(root, name) {
|
|
5466
|
-
const key = await hmacKey(root, HMAC_INFO);
|
|
5467
|
-
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`production:${name}`));
|
|
5468
|
-
return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
5469
|
-
}
|
|
5470
|
-
async function aesKey(root, salt, info) {
|
|
5471
|
-
const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
|
|
5472
|
-
return crypto.subtle.deriveKey(
|
|
5473
|
-
{ name: "HKDF", hash: "SHA-256", salt: bytes(salt), info: new TextEncoder().encode(info) },
|
|
5474
|
-
base,
|
|
5475
|
-
{ name: "AES-GCM", length: 256 },
|
|
5476
|
-
false,
|
|
5477
|
-
["encrypt", "decrypt"]
|
|
5478
|
-
);
|
|
5479
|
-
}
|
|
5480
|
-
async function hmacKey(root, info) {
|
|
5481
|
-
const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
|
|
5482
|
-
return crypto.subtle.deriveKey(
|
|
5483
|
-
{ name: "HKDF", hash: "SHA-256", salt: bytes(new Uint8Array(16)), info: new TextEncoder().encode(info) },
|
|
5484
|
-
base,
|
|
5485
|
-
{ name: "HMAC", hash: "SHA-256", length: 256 },
|
|
5486
|
-
false,
|
|
5487
|
-
["sign"]
|
|
5488
|
-
);
|
|
5489
|
-
}
|
|
5490
|
-
function b64(bytes2) {
|
|
5491
|
-
let s = "";
|
|
5492
|
-
for (const b of bytes2) s += String.fromCharCode(b);
|
|
5493
|
-
return btoa(s);
|
|
5494
|
-
}
|
|
5495
|
-
function unb64(value) {
|
|
5496
|
-
const s = atob(value);
|
|
5497
|
-
const out = new Uint8Array(s.length);
|
|
5498
|
-
for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);
|
|
5499
|
-
return out;
|
|
5500
|
-
}
|
|
5501
|
-
function bytes(input) {
|
|
5502
|
-
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
5503
|
-
}
|
|
5504
|
-
|
|
5505
5215
|
// src/server/rooms/job-room.ts
|
|
5506
5216
|
var RUNNING_STALE_MS = 16 * 60 * 1e3;
|
|
5507
5217
|
var DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -6119,7 +5829,6 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
|
6119
5829
|
"AUTH_WORKER_URL",
|
|
6120
5830
|
"APP_IDENTITY_TOKEN",
|
|
6121
5831
|
"APP_OWNER_JWT",
|
|
6122
|
-
"APP_SECRETS_KEY",
|
|
6123
5832
|
"USAGE_EVENTS"
|
|
6124
5833
|
]);
|
|
6125
5834
|
function validateBindingManifest(manifest) {
|
|
@@ -6956,8 +6665,8 @@ import { importSPKI, jwtVerify } from "jose";
|
|
|
6956
6665
|
|
|
6957
6666
|
// src/server/auth/utils.ts
|
|
6958
6667
|
var BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/;
|
|
6959
|
-
function bufferToHex(
|
|
6960
|
-
const view =
|
|
6668
|
+
function bufferToHex(bytes) {
|
|
6669
|
+
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
6961
6670
|
let hex = "";
|
|
6962
6671
|
for (const byte of view) {
|
|
6963
6672
|
hex += byte.toString(16).padStart(2, "0");
|
|
@@ -6980,9 +6689,9 @@ function decodeJwtPayload(token) {
|
|
|
6980
6689
|
if (!BASE64_URL_REGEX.test(payloadSegment)) return void 0;
|
|
6981
6690
|
try {
|
|
6982
6691
|
const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payloadSegment.length / 4) * 4, "=");
|
|
6983
|
-
const
|
|
6984
|
-
const
|
|
6985
|
-
const payload = JSON.parse(
|
|
6692
|
+
const bytes = base64ToUint8Array(normalized);
|
|
6693
|
+
const json = new TextDecoder().decode(bytes);
|
|
6694
|
+
const payload = JSON.parse(json);
|
|
6986
6695
|
return {
|
|
6987
6696
|
iss: payload.iss ?? null,
|
|
6988
6697
|
aud: payload.aud ?? null,
|
|
@@ -7206,176 +6915,6 @@ async function generateAppleClientSecret(apple) {
|
|
|
7206
6915
|
return token;
|
|
7207
6916
|
}
|
|
7208
6917
|
|
|
7209
|
-
// src/server/secrets.ts
|
|
7210
|
-
var DEFAULT_REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
7211
|
-
function createSecrets(env, options = {}) {
|
|
7212
|
-
const refreshIntervalMs = options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
|
|
7213
|
-
const now = options.now ?? (() => Date.now());
|
|
7214
|
-
const cache = /* @__PURE__ */ new Map();
|
|
7215
|
-
let loadedAll = null;
|
|
7216
|
-
let loadedVersion = 0;
|
|
7217
|
-
let lastCheckedAt = 0;
|
|
7218
|
-
function stub() {
|
|
7219
|
-
if (!env.SECRETS_ROOMS) throw new Error("SECRETS_ROOMS binding is missing");
|
|
7220
|
-
if (!env.APP_SECRETS_KEY) throw new Error("APP_SECRETS_KEY binding is missing");
|
|
7221
|
-
const id = env.SECRETS_ROOMS.idFromName("app-secrets");
|
|
7222
|
-
return env.SECRETS_ROOMS.get(id);
|
|
7223
|
-
}
|
|
7224
|
-
async function fetchJson(path) {
|
|
7225
|
-
const res = await stub().fetch(`https://app-secrets/${path}`, {
|
|
7226
|
-
headers: { "x-deepspace-internal": "app-secrets" }
|
|
7227
|
-
});
|
|
7228
|
-
const body = await res.json().catch(() => ({}));
|
|
7229
|
-
if (!res.ok) throw new Error(body.error ?? `App secrets unavailable (${res.status})`);
|
|
7230
|
-
return body;
|
|
7231
|
-
}
|
|
7232
|
-
async function pullAll() {
|
|
7233
|
-
const body = await fetchJson("pull");
|
|
7234
|
-
const version = typeof body.version === "number" ? body.version : 0;
|
|
7235
|
-
cache.clear();
|
|
7236
|
-
for (const [name, value] of Object.entries(body.secrets ?? {})) {
|
|
7237
|
-
cache.set(name, value);
|
|
7238
|
-
}
|
|
7239
|
-
loadedVersion = version;
|
|
7240
|
-
lastCheckedAt = now();
|
|
7241
|
-
return version;
|
|
7242
|
-
}
|
|
7243
|
-
async function loadAll() {
|
|
7244
|
-
await pullAll();
|
|
7245
|
-
}
|
|
7246
|
-
async function ensureLoaded() {
|
|
7247
|
-
if (!loadedAll) {
|
|
7248
|
-
loadedAll = loadAll().catch((err) => {
|
|
7249
|
-
loadedAll = null;
|
|
7250
|
-
throw err;
|
|
7251
|
-
});
|
|
7252
|
-
}
|
|
7253
|
-
await loadedAll;
|
|
7254
|
-
}
|
|
7255
|
-
async function maybeCheckForUpdates() {
|
|
7256
|
-
if (!loadedAll) return;
|
|
7257
|
-
if (refreshIntervalMs < 0) return;
|
|
7258
|
-
if (now() - lastCheckedAt < refreshIntervalMs) return;
|
|
7259
|
-
try {
|
|
7260
|
-
await client.checkForUpdates();
|
|
7261
|
-
} catch {
|
|
7262
|
-
}
|
|
7263
|
-
}
|
|
7264
|
-
function localDevValue(name) {
|
|
7265
|
-
const value = env[name];
|
|
7266
|
-
return env.APP_SECRETS_LOCAL_FALLBACK === "true" && typeof value === "string" ? value : void 0;
|
|
7267
|
-
}
|
|
7268
|
-
const client = {
|
|
7269
|
-
async get(name) {
|
|
7270
|
-
const local = localDevValue(name);
|
|
7271
|
-
if (local !== void 0) return local;
|
|
7272
|
-
if (!cache.has(name)) await ensureLoaded();
|
|
7273
|
-
else await maybeCheckForUpdates();
|
|
7274
|
-
return cache.get(name);
|
|
7275
|
-
},
|
|
7276
|
-
async require(name) {
|
|
7277
|
-
const value = await this.get(name);
|
|
7278
|
-
if (value === void 0) throw new Error(`Missing app secret "${name}"`);
|
|
7279
|
-
return value;
|
|
7280
|
-
},
|
|
7281
|
-
async getMany(names) {
|
|
7282
|
-
const out = {};
|
|
7283
|
-
for (const name of names) out[name] = await this.get(name);
|
|
7284
|
-
return out;
|
|
7285
|
-
},
|
|
7286
|
-
async preload(names) {
|
|
7287
|
-
if (env.APP_SECRETS_LOCAL_FALLBACK === "true") {
|
|
7288
|
-
const missing = [];
|
|
7289
|
-
for (const name of names) {
|
|
7290
|
-
const local = localDevValue(name);
|
|
7291
|
-
if (local !== void 0) cache.set(name, local);
|
|
7292
|
-
else missing.push(name);
|
|
7293
|
-
}
|
|
7294
|
-
if (missing.length === 0) {
|
|
7295
|
-
await maybeCheckForUpdates();
|
|
7296
|
-
return;
|
|
7297
|
-
}
|
|
7298
|
-
}
|
|
7299
|
-
await ensureLoaded();
|
|
7300
|
-
await maybeCheckForUpdates();
|
|
7301
|
-
},
|
|
7302
|
-
async refresh(refreshOptions = {}) {
|
|
7303
|
-
if (refreshOptions.force || !loadedAll) {
|
|
7304
|
-
const previous = loadedVersion;
|
|
7305
|
-
const version = await pullAll();
|
|
7306
|
-
loadedAll = Promise.resolve();
|
|
7307
|
-
return { changed: version !== previous, version };
|
|
7308
|
-
}
|
|
7309
|
-
return this.checkForUpdates();
|
|
7310
|
-
},
|
|
7311
|
-
async checkForUpdates() {
|
|
7312
|
-
await ensureLoaded();
|
|
7313
|
-
const body = await fetchJson("version");
|
|
7314
|
-
const version = typeof body.version === "number" ? body.version : 0;
|
|
7315
|
-
lastCheckedAt = now();
|
|
7316
|
-
if (version === loadedVersion) return { changed: false, version };
|
|
7317
|
-
const pulledVersion = await pullAll();
|
|
7318
|
-
return { changed: true, version: pulledVersion };
|
|
7319
|
-
},
|
|
7320
|
-
invalidate() {
|
|
7321
|
-
cache.clear();
|
|
7322
|
-
loadedAll = null;
|
|
7323
|
-
loadedVersion = 0;
|
|
7324
|
-
lastCheckedAt = 0;
|
|
7325
|
-
},
|
|
7326
|
-
async withFreshSecrets(operation, retryOptions = {}) {
|
|
7327
|
-
let first;
|
|
7328
|
-
try {
|
|
7329
|
-
first = await operation({ secrets: client, attempt: 1, refreshed: false });
|
|
7330
|
-
} catch (err) {
|
|
7331
|
-
if (!shouldRefreshOnError(err)) throw err;
|
|
7332
|
-
const refresh = await refreshAfterFailure(err);
|
|
7333
|
-
if (!refresh.changed && !retryOptions.retryWhenUnchanged) throw err;
|
|
7334
|
-
return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
|
|
7335
|
-
}
|
|
7336
|
-
if (first instanceof Response && shouldRefreshOnStatus(first.status)) {
|
|
7337
|
-
const refresh = await refreshAfterFailure(first);
|
|
7338
|
-
if (refresh.changed || retryOptions.retryWhenUnchanged) {
|
|
7339
|
-
return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
|
|
7340
|
-
}
|
|
7341
|
-
}
|
|
7342
|
-
return first;
|
|
7343
|
-
}
|
|
7344
|
-
};
|
|
7345
|
-
async function refreshAfterFailure(original) {
|
|
7346
|
-
try {
|
|
7347
|
-
return await client.refresh({ force: true });
|
|
7348
|
-
} catch (err) {
|
|
7349
|
-
const originalMessage = describeFailure(original);
|
|
7350
|
-
const refreshMessage = err instanceof Error ? err.message : String(err);
|
|
7351
|
-
throw new Error(`Secret refresh failed after API failure (${originalMessage}): ${refreshMessage}`);
|
|
7352
|
-
}
|
|
7353
|
-
}
|
|
7354
|
-
return client;
|
|
7355
|
-
}
|
|
7356
|
-
function shouldRefreshOnError(error) {
|
|
7357
|
-
const status = statusFromError(error);
|
|
7358
|
-
return status !== void 0 && shouldRefreshOnStatus(status);
|
|
7359
|
-
}
|
|
7360
|
-
function shouldRefreshOnStatus(status) {
|
|
7361
|
-
return status === 401 || status === 403;
|
|
7362
|
-
}
|
|
7363
|
-
function statusFromError(error) {
|
|
7364
|
-
if (!error || typeof error !== "object") return void 0;
|
|
7365
|
-
const record = error;
|
|
7366
|
-
if (typeof record.status === "number") return record.status;
|
|
7367
|
-
if (typeof record.statusCode === "number") return record.statusCode;
|
|
7368
|
-
if (typeof record.response?.status === "number") return record.response.status;
|
|
7369
|
-
return void 0;
|
|
7370
|
-
}
|
|
7371
|
-
function describeFailure(value) {
|
|
7372
|
-
if (value instanceof Response) return `HTTP ${value.status}`;
|
|
7373
|
-
if (value instanceof Error) return value.message;
|
|
7374
|
-
const status = statusFromError(value);
|
|
7375
|
-
if (status) return `HTTP ${status}`;
|
|
7376
|
-
return String(value);
|
|
7377
|
-
}
|
|
7378
|
-
|
|
7379
6918
|
// src/shared/subscription.ts
|
|
7380
6919
|
var ENTITLED_STATUSES = /* @__PURE__ */ new Set([
|
|
7381
6920
|
"active",
|
|
@@ -7557,7 +7096,6 @@ export {
|
|
|
7557
7096
|
SYSTEM_COLLECTION_SCHEMAS,
|
|
7558
7097
|
SYSTEM_MANAGED_COLUMNS,
|
|
7559
7098
|
SchemaRegistry,
|
|
7560
|
-
SecretsRoom,
|
|
7561
7099
|
SubscriptionAuthError,
|
|
7562
7100
|
SubscriptionRequiredError,
|
|
7563
7101
|
USERS_COLUMNS,
|
|
@@ -7594,7 +7132,6 @@ export {
|
|
|
7594
7132
|
createDeepSpaceAI,
|
|
7595
7133
|
createDeepSpaceAuth,
|
|
7596
7134
|
createScopedR2Handler,
|
|
7597
|
-
createSecrets,
|
|
7598
7135
|
dataToColumnValues,
|
|
7599
7136
|
decodeJwtPayload,
|
|
7600
7137
|
deleteChatCascade,
|