deepspace 0.19.5 → 0.21.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/CHANGELOG.md +24 -0
- package/dist/{chunk-A7UFIOWO.js → chunk-KDCDRHBA.js} +7 -2
- package/dist/chunk-KDCDRHBA.js.map +1 -0
- package/dist/cli.js +219 -94
- package/dist/cli.js.map +1 -1
- package/dist/documentation-client-core.d.ts +3 -2
- package/dist/documentation-client-core.js +1 -1
- package/dist/documentation-default-renderer.cjs +48 -48
- package/dist/documentation-react.d.ts +2 -1
- package/dist/documentation-react.js +1 -1
- package/dist/documentation-runtime.js +7 -7
- package/dist/documentation-server-core.d.ts +3 -2
- package/dist/documentation-server-core.js +1 -1
- package/dist/documentation.d.ts +40 -12
- package/dist/documentation.js +40 -7
- package/dist/documentation.js.map +1 -1
- package/dist/index.d.ts +225 -21
- package/dist/index.js +262 -112
- package/dist/index.js.map +1 -1
- package/dist/{public-CFAj1hL2.d.ts → public-DenQ1TsZ.d.ts} +36 -11
- package/dist/server.d.ts +46 -29
- package/dist/server.js +1041 -254
- package/dist/server.js.map +1 -1
- package/dist/testing-mcp.d.ts +69 -0
- package/dist/testing-mcp.js +99 -0
- package/dist/testing-mcp.js.map +1 -0
- package/dist/testing.d.ts +17 -20
- package/dist/testing.js +47 -25
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +73 -32
- package/dist/worker.js +1094 -244
- package/dist/worker.js.map +1 -1
- package/features/ai-chat/feature.json +2 -2
- package/features/ai-chat/src/AiChatPage.tsx +56 -21
- package/features/ai-chat/src/ChatPanel.tsx +33 -10
- package/features/ai-chat/src/__tests__/ChatPanel.lifecycle.test.tsx +156 -0
- package/features/cron/src/CronLogPage.tsx +107 -69
- package/features/file-attachments/feature.json +2 -1
- package/features/file-manager/feature.json +2 -1
- package/features/file-manager/src/FileManagerPage.tsx +227 -194
- package/features/integration-test/src/IntegrationTestPage.tsx +54 -34
- package/package.json +5 -1
- package/dist/chunk-A7UFIOWO.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -587,6 +587,9 @@ var MSG = {
|
|
|
587
587
|
CRON_PAUSE: "cron.pause",
|
|
588
588
|
CRON_RESUME: "cron.resume",
|
|
589
589
|
CRON_STATUS: "cron.status",
|
|
590
|
+
// Per-mutation receipt, sent only to the socket whose trigger/pause/
|
|
591
|
+
// resume carried a `requestId` — frames without one stay fire-and-forget.
|
|
592
|
+
CRON_ACK: "cron.ack",
|
|
590
593
|
// ---- JobRoom ---------------------------------------------------------
|
|
591
594
|
// Background-job queue with durable, alarm-driven execution. See
|
|
592
595
|
// server/rooms/job-room.ts. Clients enqueue and subscribe to updates;
|
|
@@ -643,9 +646,9 @@ var clientBuild = {
|
|
|
643
646
|
canvasUndo: () => ({ type: MSG.CANVAS_UNDO, payload: EMPTY }),
|
|
644
647
|
canvasRedo: () => ({ type: MSG.CANVAS_REDO, payload: EMPTY }),
|
|
645
648
|
// Cron
|
|
646
|
-
cronTrigger: (taskName) => ({ type: MSG.CRON_TRIGGER, payload: { taskName } }),
|
|
647
|
-
cronPause: (taskName) => ({ type: MSG.CRON_PAUSE, payload: { taskName } }),
|
|
648
|
-
cronResume: (taskName) => ({ type: MSG.CRON_RESUME, payload: { taskName } }),
|
|
649
|
+
cronTrigger: (taskName, requestId) => ({ type: MSG.CRON_TRIGGER, payload: { taskName, requestId } }),
|
|
650
|
+
cronPause: (taskName, requestId) => ({ type: MSG.CRON_PAUSE, payload: { taskName, requestId } }),
|
|
651
|
+
cronResume: (taskName, requestId) => ({ type: MSG.CRON_RESUME, payload: { taskName, requestId } }),
|
|
649
652
|
// Jobs
|
|
650
653
|
jobEnqueue: (requestId, type, payload, maxAttempts) => ({
|
|
651
654
|
type: MSG.JOB_ENQUEUE,
|
|
@@ -699,6 +702,14 @@ var serverBuild = {
|
|
|
699
702
|
cronTasks: (tasks) => ({ type: MSG.CRON_TASKS, payload: { tasks } }),
|
|
700
703
|
cronHistory: (history) => ({ type: MSG.CRON_HISTORY, payload: { history } }),
|
|
701
704
|
cronStatus: (tasks, recentHistory) => ({ type: MSG.CRON_STATUS, payload: { tasks, recentHistory } }),
|
|
705
|
+
cronAckSuccess: (requestId, taskName) => ({
|
|
706
|
+
type: MSG.CRON_ACK,
|
|
707
|
+
payload: { requestId, taskName, ok: true }
|
|
708
|
+
}),
|
|
709
|
+
cronAckFailure: (requestId, reason, error, taskName) => ({
|
|
710
|
+
type: MSG.CRON_ACK,
|
|
711
|
+
payload: { requestId, taskName, ok: false, reason, error }
|
|
712
|
+
}),
|
|
702
713
|
// Jobs
|
|
703
714
|
jobSnapshot: (jobs) => ({ type: MSG.JOB_UPDATE, payload: { kind: "snapshot", jobs } }),
|
|
704
715
|
jobEnqueued: (job, requestId) => ({
|
|
@@ -950,7 +961,10 @@ import { useState } from "react";
|
|
|
950
961
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
951
962
|
function AuthOverlay({
|
|
952
963
|
onClose,
|
|
953
|
-
providers = ["github", "google"]
|
|
964
|
+
providers = ["github", "google"],
|
|
965
|
+
title = "Sign in to DeepSpace",
|
|
966
|
+
description = "Sync your data across devices",
|
|
967
|
+
logo
|
|
954
968
|
} = {}) {
|
|
955
969
|
const { isLoaded, isSignedIn } = useAuth();
|
|
956
970
|
const [showEmailForm, setShowEmailForm] = useState(false);
|
|
@@ -996,7 +1010,7 @@ function AuthOverlay({
|
|
|
996
1010
|
onClick: onClose ? (e) => {
|
|
997
1011
|
if (e.target === e.currentTarget) onClose();
|
|
998
1012
|
} : void 0,
|
|
999
|
-
className: "fixed inset-0 z-[99999] flex items-center justify-center bg-
|
|
1013
|
+
className: "fixed inset-0 z-[99999] flex items-center justify-center bg-[var(--ds-auth-backdrop,#0000008c)] backdrop-blur-2xl",
|
|
1000
1014
|
style: {
|
|
1001
1015
|
opacity: 0,
|
|
1002
1016
|
animation: "ds-auth-overlay-in 0.4s ease forwards"
|
|
@@ -1020,8 +1034,9 @@ function AuthOverlay({
|
|
|
1020
1034
|
children: "\u2715"
|
|
1021
1035
|
}
|
|
1022
1036
|
),
|
|
1023
|
-
/* @__PURE__ */ jsx2("
|
|
1024
|
-
/* @__PURE__ */ jsx2("
|
|
1037
|
+
logo && /* @__PURE__ */ jsx2("div", { className: "mb-4 flex justify-center", children: logo }),
|
|
1038
|
+
/* @__PURE__ */ jsx2("h2", { className: "mb-2 text-[22px] font-bold leading-snug text-foreground", children: title }),
|
|
1039
|
+
/* @__PURE__ */ jsx2("p", { className: "mb-6 text-sm leading-snug text-muted-foreground", children: description }),
|
|
1025
1040
|
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2", children: [
|
|
1026
1041
|
providers.includes("github") && /* @__PURE__ */ jsx2(OAuthButton, { onClick: () => handleSocial("github"), disabled: loading, icon: /* @__PURE__ */ jsx2(GitHubIcon, {}), children: "Continue with GitHub" }),
|
|
1027
1042
|
providers.includes("google") && /* @__PURE__ */ jsx2(OAuthButton, { onClick: () => handleSocial("google"), disabled: loading, icon: /* @__PURE__ */ jsx2(GoogleIcon, {}), children: "Continue with Google" })
|
|
@@ -3221,11 +3236,17 @@ function useCronMonitor(roomId) {
|
|
|
3221
3236
|
const [history, setHistory] = useState9([]);
|
|
3222
3237
|
const [connected, setConnected] = useState9(false);
|
|
3223
3238
|
const [canWrite, setCanWrite] = useState9(false);
|
|
3239
|
+
const [lastError, setLastError] = useState9(null);
|
|
3224
3240
|
const wsRef = useRef10(null);
|
|
3241
|
+
const pendingRef = useRef10(/* @__PURE__ */ new Map());
|
|
3225
3242
|
useEffect12(() => {
|
|
3226
3243
|
let ws = null;
|
|
3227
3244
|
let reconnectTimer = null;
|
|
3228
3245
|
let alive = true;
|
|
3246
|
+
const drainPending = (result) => {
|
|
3247
|
+
for (const resolve of pendingRef.current.values()) resolve(result);
|
|
3248
|
+
pendingRef.current.clear();
|
|
3249
|
+
};
|
|
3229
3250
|
const connect = async () => {
|
|
3230
3251
|
if (!alive) return;
|
|
3231
3252
|
const token = await getAuthToken();
|
|
@@ -3250,6 +3271,17 @@ function useCronMonitor(roomId) {
|
|
|
3250
3271
|
[MSG.CRON_STATUS]: (p) => {
|
|
3251
3272
|
setTasks(p.tasks);
|
|
3252
3273
|
setHistory(p.recentHistory);
|
|
3274
|
+
},
|
|
3275
|
+
[MSG.CRON_ACK]: (p) => {
|
|
3276
|
+
const resolve = pendingRef.current.get(p.requestId);
|
|
3277
|
+
if (!resolve) return;
|
|
3278
|
+
pendingRef.current.delete(p.requestId);
|
|
3279
|
+
resolve(
|
|
3280
|
+
p.ok ? { ok: true, taskName: p.taskName, requestId: p.requestId } : { ok: false, reason: p.reason, error: p.error }
|
|
3281
|
+
);
|
|
3282
|
+
},
|
|
3283
|
+
[MSG.ERROR]: (p) => {
|
|
3284
|
+
setLastError(p.error);
|
|
3253
3285
|
}
|
|
3254
3286
|
});
|
|
3255
3287
|
};
|
|
@@ -3257,6 +3289,7 @@ function useCronMonitor(roomId) {
|
|
|
3257
3289
|
wsRef.current = null;
|
|
3258
3290
|
setConnected(false);
|
|
3259
3291
|
setCanWrite(false);
|
|
3292
|
+
drainPending({ ok: false, reason: "not_connected" });
|
|
3260
3293
|
if (alive) reconnectTimer = setTimeout(connect, 1e3);
|
|
3261
3294
|
};
|
|
3262
3295
|
ws.onerror = () => ws?.close();
|
|
@@ -3265,32 +3298,49 @@ function useCronMonitor(roomId) {
|
|
|
3265
3298
|
return () => {
|
|
3266
3299
|
alive = false;
|
|
3267
3300
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
3301
|
+
drainPending({ ok: false, reason: "not_connected" });
|
|
3268
3302
|
ws?.close();
|
|
3269
3303
|
wsRef.current = null;
|
|
3270
3304
|
};
|
|
3271
3305
|
}, [roomId]);
|
|
3272
3306
|
const sendWrite = useCallback11(
|
|
3273
|
-
(
|
|
3274
|
-
|
|
3307
|
+
(build) => {
|
|
3308
|
+
setLastError(null);
|
|
3309
|
+
if (!canWrite) return Promise.resolve({ ok: false, reason: "read_only" });
|
|
3275
3310
|
const ws = wsRef.current;
|
|
3276
|
-
if (!ws || ws.readyState !== WebSocket.OPEN)
|
|
3277
|
-
|
|
3311
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
3312
|
+
return Promise.resolve({ ok: false, reason: "not_connected" });
|
|
3313
|
+
}
|
|
3314
|
+
return new Promise((resolve) => {
|
|
3315
|
+
const requestId = crypto.randomUUID();
|
|
3316
|
+
pendingRef.current.set(requestId, resolve);
|
|
3317
|
+
try {
|
|
3318
|
+
ws.send(encode(build(requestId)));
|
|
3319
|
+
} catch (e) {
|
|
3320
|
+
pendingRef.current.delete(requestId);
|
|
3321
|
+
resolve({
|
|
3322
|
+
ok: false,
|
|
3323
|
+
reason: "not_connected",
|
|
3324
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
});
|
|
3278
3328
|
},
|
|
3279
3329
|
[canWrite]
|
|
3280
3330
|
);
|
|
3281
3331
|
const trigger = useCallback11(
|
|
3282
|
-
(taskName) => sendWrite(clientBuild.cronTrigger(taskName)),
|
|
3332
|
+
(taskName) => sendWrite((requestId) => clientBuild.cronTrigger(taskName, requestId)),
|
|
3283
3333
|
[sendWrite]
|
|
3284
3334
|
);
|
|
3285
3335
|
const pause = useCallback11(
|
|
3286
|
-
(taskName) => sendWrite(clientBuild.cronPause(taskName)),
|
|
3336
|
+
(taskName) => sendWrite((requestId) => clientBuild.cronPause(taskName, requestId)),
|
|
3287
3337
|
[sendWrite]
|
|
3288
3338
|
);
|
|
3289
3339
|
const resume = useCallback11(
|
|
3290
|
-
(taskName) => sendWrite(clientBuild.cronResume(taskName)),
|
|
3340
|
+
(taskName) => sendWrite((requestId) => clientBuild.cronResume(taskName, requestId)),
|
|
3291
3341
|
[sendWrite]
|
|
3292
3342
|
);
|
|
3293
|
-
return { tasks, history, connected, canWrite, trigger, pause, resume };
|
|
3343
|
+
return { tasks, history, connected, canWrite, lastError, trigger, pause, resume };
|
|
3294
3344
|
}
|
|
3295
3345
|
|
|
3296
3346
|
// src/client/storage/hooks/useJobs.ts
|
|
@@ -3548,13 +3598,13 @@ var MAX_UPLOAD_REQUEST_BYTES = 25 * 1024 * 1024;
|
|
|
3548
3598
|
var UPLOAD_PART_BYTES = 20 * 1024 * 1024;
|
|
3549
3599
|
var MAX_UPLOAD_PARTS = Math.ceil(MAX_APP_FILE_BYTES / UPLOAD_PART_BYTES);
|
|
3550
3600
|
var MAX_BASE64_UPLOAD_BYTES = Math.floor(MAX_UPLOAD_REQUEST_BYTES * 3 / 4);
|
|
3551
|
-
function planUploadParts(totalBytes
|
|
3601
|
+
function planUploadParts(totalBytes) {
|
|
3552
3602
|
const parts = [];
|
|
3553
|
-
for (let start = 0; start < totalBytes; start +=
|
|
3603
|
+
for (let start = 0; start < totalBytes; start += UPLOAD_PART_BYTES) {
|
|
3554
3604
|
parts.push({
|
|
3555
3605
|
partNumber: parts.length + 1,
|
|
3556
3606
|
start,
|
|
3557
|
-
end: Math.min(start +
|
|
3607
|
+
end: Math.min(start + UPLOAD_PART_BYTES, totalBytes)
|
|
3558
3608
|
});
|
|
3559
3609
|
}
|
|
3560
3610
|
return parts;
|
|
@@ -3627,24 +3677,43 @@ function resolveFile(fileOrKey) {
|
|
|
3627
3677
|
}
|
|
3628
3678
|
return { key: fileOrKey.key, name: fileOrKey.originalName };
|
|
3629
3679
|
}
|
|
3630
|
-
|
|
3680
|
+
function withUploadKey(params, key) {
|
|
3681
|
+
if (!key) return params;
|
|
3682
|
+
const pair = `key=${encodeURIComponent(key)}`;
|
|
3683
|
+
return params ? `${params}&${pair}` : pair;
|
|
3684
|
+
}
|
|
3685
|
+
async function uploadInParts(file, name, key, scopeParams, authHeaders, onProgress) {
|
|
3631
3686
|
const headers = await authHeaders();
|
|
3632
3687
|
const json = { ...headers, "Content-Type": "application/json" };
|
|
3633
|
-
const
|
|
3688
|
+
const requestId = crypto.randomUUID();
|
|
3689
|
+
const initBody = JSON.stringify({
|
|
3690
|
+
requestId,
|
|
3691
|
+
name,
|
|
3692
|
+
mimeType: file.type || "application/octet-stream",
|
|
3693
|
+
size: file.size
|
|
3694
|
+
});
|
|
3695
|
+
const sendInit = () => fetch(`/api/files/multipart?${withUploadKey(scopeParams, key)}`, {
|
|
3634
3696
|
method: "POST",
|
|
3635
3697
|
headers: json,
|
|
3636
|
-
body:
|
|
3698
|
+
body: initBody
|
|
3637
3699
|
});
|
|
3700
|
+
let initResponse = await sendInit().catch(() => null);
|
|
3701
|
+
if (!initResponse || initResponse.status === 429 || initResponse.status >= 500) {
|
|
3702
|
+
initResponse = await sendInit().catch(() => null);
|
|
3703
|
+
}
|
|
3704
|
+
if (!initResponse) {
|
|
3705
|
+
return { success: false, error: "The files service could not be reached." };
|
|
3706
|
+
}
|
|
3638
3707
|
if (!initResponse.ok) return await readUploadResult(initResponse);
|
|
3639
3708
|
const session = await initResponse.json();
|
|
3640
|
-
if (!
|
|
3709
|
+
if (!session.uploadId || !session.uploadKey || !session.reservationId || session.partSize !== UPLOAD_PART_BYTES) {
|
|
3641
3710
|
return {
|
|
3642
3711
|
success: false,
|
|
3643
|
-
error: `The files service
|
|
3712
|
+
error: `The files service returned an unusable part size or multipart session (${session.partSize}).`
|
|
3644
3713
|
};
|
|
3645
3714
|
}
|
|
3646
|
-
const query = `${scopeParams}&uploadKey=${encodeKeyPath(session.uploadKey)}&uploadId=${encodeURIComponent(session.uploadId)}`;
|
|
3647
|
-
const plan = planUploadParts(file.size
|
|
3715
|
+
const query = `${scopeParams}&uploadKey=${encodeKeyPath(session.uploadKey)}&uploadId=${encodeURIComponent(session.uploadId)}&reservationId=${encodeURIComponent(session.reservationId)}`;
|
|
3716
|
+
const plan = planUploadParts(file.size);
|
|
3648
3717
|
const parts = [];
|
|
3649
3718
|
let assembled = false;
|
|
3650
3719
|
try {
|
|
@@ -3702,17 +3771,27 @@ function useR2Files(options) {
|
|
|
3702
3771
|
if (tooBig) return tooBig;
|
|
3703
3772
|
const fileName = name ?? (file instanceof File ? file.name : "upload");
|
|
3704
3773
|
if (file.size > UPLOAD_PART_BYTES) {
|
|
3705
|
-
return await uploadInParts(
|
|
3774
|
+
return await uploadInParts(
|
|
3775
|
+
file,
|
|
3776
|
+
fileName,
|
|
3777
|
+
options2?.key,
|
|
3778
|
+
scopeParams,
|
|
3779
|
+
authHeaders,
|
|
3780
|
+
options2?.onProgress
|
|
3781
|
+
);
|
|
3706
3782
|
}
|
|
3707
3783
|
const headers = await authHeaders();
|
|
3708
3784
|
const formData = new FormData();
|
|
3709
3785
|
formData.append("file", file);
|
|
3710
3786
|
if (name) formData.append("name", name);
|
|
3711
|
-
const response = await fetch(
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3787
|
+
const response = await fetch(
|
|
3788
|
+
`/api/files/upload?${withUploadKey(scopeParams, options2?.key)}`,
|
|
3789
|
+
{
|
|
3790
|
+
method: "POST",
|
|
3791
|
+
headers,
|
|
3792
|
+
body: formData
|
|
3793
|
+
}
|
|
3794
|
+
);
|
|
3716
3795
|
options2?.onProgress?.(file.size, file.size);
|
|
3717
3796
|
return await readUploadResult(response);
|
|
3718
3797
|
} catch (err) {
|
|
@@ -3724,20 +3803,20 @@ function useR2Files(options) {
|
|
|
3724
3803
|
[scopeParams, authHeaders]
|
|
3725
3804
|
);
|
|
3726
3805
|
const uploadBase64 = useCallback14(
|
|
3727
|
-
async (base64Data, name, mimeType) => {
|
|
3806
|
+
async (base64Data, name, mimeType, options2) => {
|
|
3728
3807
|
setIsUploading(true);
|
|
3729
3808
|
try {
|
|
3730
|
-
const tooBig = oversize(
|
|
3731
|
-
Math.floor(base64Data.length * 3 / 4),
|
|
3732
|
-
MAX_BASE64_UPLOAD_BYTES
|
|
3733
|
-
);
|
|
3809
|
+
const tooBig = oversize(Math.floor(base64Data.length * 3 / 4), MAX_BASE64_UPLOAD_BYTES);
|
|
3734
3810
|
if (tooBig) return tooBig;
|
|
3735
3811
|
const headers = await authHeaders();
|
|
3736
|
-
const response = await fetch(
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3812
|
+
const response = await fetch(
|
|
3813
|
+
`/api/files/upload?${withUploadKey(scopeParams, options2?.key)}`,
|
|
3814
|
+
{
|
|
3815
|
+
method: "POST",
|
|
3816
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
3817
|
+
body: JSON.stringify({ data: base64Data, name, mimeType })
|
|
3818
|
+
}
|
|
3819
|
+
);
|
|
3741
3820
|
return await readUploadResult(response);
|
|
3742
3821
|
} catch (err) {
|
|
3743
3822
|
return { success: false, error: err instanceof Error ? err.message : "Upload failed" };
|
|
@@ -3747,12 +3826,18 @@ function useR2Files(options) {
|
|
|
3747
3826
|
},
|
|
3748
3827
|
[scopeParams, authHeaders]
|
|
3749
3828
|
);
|
|
3829
|
+
const getUrl = useCallback14(
|
|
3830
|
+
(fileOrKey) => {
|
|
3831
|
+
const { key } = resolveFile(fileOrKey);
|
|
3832
|
+
return `/api/files/${encodeKeyPath(key)}?${scopeParams}`;
|
|
3833
|
+
},
|
|
3834
|
+
[scopeParams]
|
|
3835
|
+
);
|
|
3750
3836
|
const deleteFile = useCallback14(
|
|
3751
3837
|
async (fileOrKey) => {
|
|
3752
|
-
const { key } = resolveFile(fileOrKey);
|
|
3753
3838
|
try {
|
|
3754
3839
|
const headers = await authHeaders();
|
|
3755
|
-
const response = await fetch(
|
|
3840
|
+
const response = await fetch(getUrl(fileOrKey), {
|
|
3756
3841
|
method: "DELETE",
|
|
3757
3842
|
headers
|
|
3758
3843
|
});
|
|
@@ -3761,32 +3846,40 @@ function useR2Files(options) {
|
|
|
3761
3846
|
return { success: false, error: err instanceof Error ? err.message : "Delete failed" };
|
|
3762
3847
|
}
|
|
3763
3848
|
},
|
|
3764
|
-
[
|
|
3849
|
+
[getUrl, authHeaders]
|
|
3765
3850
|
);
|
|
3766
|
-
const
|
|
3767
|
-
async (prefix) => {
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
return [];
|
|
3851
|
+
const listPage = useCallback14(
|
|
3852
|
+
async (prefix, options2) => {
|
|
3853
|
+
const headers = await authHeaders();
|
|
3854
|
+
const params = new URLSearchParams(scopeParams);
|
|
3855
|
+
if (prefix) params.set("prefix", prefix);
|
|
3856
|
+
if (options2?.limit !== void 0) params.set("limit", String(options2.limit));
|
|
3857
|
+
if (options2?.cursor) params.set("cursor", options2.cursor);
|
|
3858
|
+
const response = await fetch(`/api/files?${params.toString()}`, { headers });
|
|
3859
|
+
if (!response.ok) {
|
|
3860
|
+
throw new Error(describeFilesFailure(response.status, await response.text()));
|
|
3777
3861
|
}
|
|
3862
|
+
const result = await response.json();
|
|
3863
|
+
return {
|
|
3864
|
+
files: result.files || [],
|
|
3865
|
+
cursor: result.cursor,
|
|
3866
|
+
truncated: result.truncated ?? false
|
|
3867
|
+
};
|
|
3778
3868
|
},
|
|
3779
3869
|
[scopeParams, authHeaders]
|
|
3780
3870
|
);
|
|
3871
|
+
const list = useCallback14(
|
|
3872
|
+
async (prefix, options2) => (await listPage(prefix, options2)).files,
|
|
3873
|
+
[listPage]
|
|
3874
|
+
);
|
|
3781
3875
|
const downloadFile = useCallback14(
|
|
3782
3876
|
async (fileOrKey, fileName) => {
|
|
3783
3877
|
const { key, name: infoName } = resolveFile(fileOrKey);
|
|
3784
3878
|
try {
|
|
3785
3879
|
const headers = await authHeaders();
|
|
3786
|
-
const response = await fetch(
|
|
3880
|
+
const response = await fetch(getUrl(key), { headers });
|
|
3787
3881
|
if (!response.ok) {
|
|
3788
|
-
|
|
3789
|
-
return { success: false, error: body?.error ?? `Download failed (${response.status})` };
|
|
3882
|
+
return { success: false, error: describeFilesFailure(response.status, await response.text()) };
|
|
3790
3883
|
}
|
|
3791
3884
|
const disposition = response.headers.get("Content-Disposition");
|
|
3792
3885
|
const dispositionName = disposition?.match(/filename="(.+?)"/)?.[1];
|
|
@@ -3806,29 +3899,30 @@ function useR2Files(options) {
|
|
|
3806
3899
|
return { success: false, error: err instanceof Error ? err.message : "Download failed" };
|
|
3807
3900
|
}
|
|
3808
3901
|
},
|
|
3809
|
-
[
|
|
3902
|
+
[getUrl, authHeaders]
|
|
3810
3903
|
);
|
|
3811
3904
|
const readFile = useCallback14(
|
|
3812
3905
|
async (fileOrKey) => {
|
|
3813
|
-
const { key } = resolveFile(fileOrKey);
|
|
3814
3906
|
const headers = await authHeaders();
|
|
3815
|
-
const response = await fetch(
|
|
3907
|
+
const response = await fetch(getUrl(fileOrKey), { headers });
|
|
3816
3908
|
if (!response.ok) {
|
|
3817
|
-
|
|
3818
|
-
throw new Error(`Failed to read file (${response.status}): ${body}`);
|
|
3909
|
+
throw new Error(describeFilesFailure(response.status, await response.text()));
|
|
3819
3910
|
}
|
|
3820
3911
|
return response;
|
|
3821
3912
|
},
|
|
3822
|
-
[
|
|
3913
|
+
[getUrl, authHeaders]
|
|
3823
3914
|
);
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3915
|
+
return {
|
|
3916
|
+
upload,
|
|
3917
|
+
uploadBase64,
|
|
3918
|
+
deleteFile,
|
|
3919
|
+
downloadFile,
|
|
3920
|
+
readFile,
|
|
3921
|
+
list,
|
|
3922
|
+
listPage,
|
|
3923
|
+
getUrl,
|
|
3924
|
+
isUploading
|
|
3925
|
+
};
|
|
3832
3926
|
}
|
|
3833
3927
|
|
|
3834
3928
|
// src/client/storage/file-attachment-utils.ts
|
|
@@ -5164,6 +5258,68 @@ function usePlatformWS(options) {
|
|
|
5164
5258
|
return { state, send };
|
|
5165
5259
|
}
|
|
5166
5260
|
|
|
5261
|
+
// src/shared/api-error.ts
|
|
5262
|
+
var SLUG_RE = /^[a-z0-9]+(?:[_.-][a-z0-9]+)*$/;
|
|
5263
|
+
var HUMAN_MESSAGES = {
|
|
5264
|
+
owner_connect_not_ready: "This app isn't ready to accept payments yet \u2014 the app owner needs to finish payment setup.",
|
|
5265
|
+
invalid_json: "The request body was not valid JSON.",
|
|
5266
|
+
invalid_body: "The request body is invalid.",
|
|
5267
|
+
invalid_product_id: "Invalid product id.",
|
|
5268
|
+
duplicate_product_id: "Duplicate product id.",
|
|
5269
|
+
unknown_product: "Unknown product.",
|
|
5270
|
+
too_many_products: "Too many products declared.",
|
|
5271
|
+
invalid_amount: "Invalid amount.",
|
|
5272
|
+
invalid_name: "Invalid product name.",
|
|
5273
|
+
invalid_description: "Invalid product description.",
|
|
5274
|
+
unsupported_currency: "Unsupported currency.",
|
|
5275
|
+
unknown_app: "Unknown app.",
|
|
5276
|
+
not_app_owner: "Only the app owner (or a collaborator) can do this.",
|
|
5277
|
+
invalid_return_url: "Invalid return URL.",
|
|
5278
|
+
invalid_cancel_url: "Invalid cancel URL.",
|
|
5279
|
+
unknown_plan: "Unknown subscription plan.",
|
|
5280
|
+
unknown_price: "No price is configured for that billing interval.",
|
|
5281
|
+
no_subscription: "No subscription found for this account."
|
|
5282
|
+
};
|
|
5283
|
+
function normalizeApiError(status, body) {
|
|
5284
|
+
const payload = body !== null && typeof body === "object" ? body : {};
|
|
5285
|
+
const rawError = typeof payload.error === "string" ? payload.error : void 0;
|
|
5286
|
+
const message = typeof payload.message === "string" ? payload.message : void 0;
|
|
5287
|
+
const humanError = rawError !== void 0 && !SLUG_RE.test(rawError) ? rawError : void 0;
|
|
5288
|
+
const code = rawError !== void 0 && SLUG_RE.test(rawError) ? rawError : typeof payload.code === "string" && SLUG_RE.test(payload.code) ? payload.code : void 0;
|
|
5289
|
+
const details = {};
|
|
5290
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
5291
|
+
if (key === "success" || key === "error" || key === "message" || key === "issues" || key === "code")
|
|
5292
|
+
continue;
|
|
5293
|
+
details[key] = value;
|
|
5294
|
+
}
|
|
5295
|
+
return {
|
|
5296
|
+
error: message ?? humanError ?? (code !== void 0 ? HUMAN_MESSAGES[code] : void 0) ?? rawError ?? `Request failed (${status})`,
|
|
5297
|
+
...code !== void 0 ? { code } : {},
|
|
5298
|
+
status,
|
|
5299
|
+
...Object.keys(details).length > 0 ? { details } : {},
|
|
5300
|
+
...Array.isArray(payload.issues) ? { issues: payload.issues } : {}
|
|
5301
|
+
};
|
|
5302
|
+
}
|
|
5303
|
+
var PlatformApiError = class extends Error {
|
|
5304
|
+
/** The machine slug (e.g. 'owner_connect_not_ready'), when the server sent one. */
|
|
5305
|
+
code;
|
|
5306
|
+
/** HTTP status of the response; 0 when no HTTP response arrived. */
|
|
5307
|
+
status;
|
|
5308
|
+
/** Structured fields the server sent alongside the error. */
|
|
5309
|
+
details;
|
|
5310
|
+
/** Zod issues accompanying a `validation_failed` response. */
|
|
5311
|
+
issues;
|
|
5312
|
+
constructor(normalized) {
|
|
5313
|
+
super(normalized.error);
|
|
5314
|
+
this.name = "PlatformApiError";
|
|
5315
|
+
this.code = normalized.code;
|
|
5316
|
+
this.status = normalized.status;
|
|
5317
|
+
this.details = normalized.details;
|
|
5318
|
+
this.issues = normalized.issues;
|
|
5319
|
+
}
|
|
5320
|
+
};
|
|
5321
|
+
var apiErrorCode = (e) => e instanceof PlatformApiError ? e.code ?? null : null;
|
|
5322
|
+
|
|
5167
5323
|
// src/client/integration.ts
|
|
5168
5324
|
var ENDPOINT_PREFIX = "/api/integrations";
|
|
5169
5325
|
var DEFAULT_TIMEOUT_MS2 = 12e4;
|
|
@@ -5185,7 +5341,7 @@ async function request(method, endpoint, body, options, query) {
|
|
|
5185
5341
|
const url = `${resolveUrl(endpoint)}${queryString ? `?${queryString}` : ""}`;
|
|
5186
5342
|
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
5187
5343
|
if (options?.signal?.aborted) {
|
|
5188
|
-
return { success: false, error: "Request cancelled" };
|
|
5344
|
+
return { success: false, error: "Request cancelled", status: 0 };
|
|
5189
5345
|
}
|
|
5190
5346
|
const headers = {
|
|
5191
5347
|
"Content-Type": "application/json",
|
|
@@ -5215,14 +5371,10 @@ async function request(method, endpoint, body, options, query) {
|
|
|
5215
5371
|
try {
|
|
5216
5372
|
payload = await res.json();
|
|
5217
5373
|
} catch {
|
|
5218
|
-
return { success: false, error: `Request failed (${res.status})
|
|
5374
|
+
return { success: false, error: `Request failed (${res.status})`, status: res.status };
|
|
5219
5375
|
}
|
|
5220
5376
|
if (!res.ok || payload.success === false) {
|
|
5221
|
-
return {
|
|
5222
|
-
success: false,
|
|
5223
|
-
error: payload.error ?? payload.message ?? `Request failed (${res.status})`,
|
|
5224
|
-
...payload.issues ? { issues: payload.issues } : {}
|
|
5225
|
-
};
|
|
5377
|
+
return { success: false, ...normalizeApiError(res.status, payload) };
|
|
5226
5378
|
}
|
|
5227
5379
|
if ("data" in payload) {
|
|
5228
5380
|
return { success: true, data: payload.data };
|
|
@@ -5234,12 +5386,14 @@ async function request(method, endpoint, body, options, query) {
|
|
|
5234
5386
|
if (err instanceof DOMException && err.name === "AbortError") {
|
|
5235
5387
|
return {
|
|
5236
5388
|
success: false,
|
|
5237
|
-
error: timedOut ? `Request timed out after ${timeoutMs}ms` : "Request cancelled"
|
|
5389
|
+
error: timedOut ? `Request timed out after ${timeoutMs}ms` : "Request cancelled",
|
|
5390
|
+
status: 0
|
|
5238
5391
|
};
|
|
5239
5392
|
}
|
|
5240
5393
|
return {
|
|
5241
5394
|
success: false,
|
|
5242
|
-
error: err instanceof Error ? err.message : "Request failed"
|
|
5395
|
+
error: err instanceof Error ? err.message : "Request failed",
|
|
5396
|
+
status: 0
|
|
5243
5397
|
};
|
|
5244
5398
|
} finally {
|
|
5245
5399
|
clearTimeout(timeout);
|
|
@@ -5923,17 +6077,7 @@ function usePagedResource(fetchPage, deps, options = {}) {
|
|
|
5923
6077
|
// src/client/subscriptions/useSubscription.ts
|
|
5924
6078
|
import { useCallback as useCallback27, useEffect as useEffect21, useState as useState18 } from "react";
|
|
5925
6079
|
|
|
5926
|
-
// src/
|
|
5927
|
-
var ENTITLED_STATUSES = /* @__PURE__ */ new Set([
|
|
5928
|
-
"active",
|
|
5929
|
-
"trialing"
|
|
5930
|
-
]);
|
|
5931
|
-
function isEntitled(tier, status) {
|
|
5932
|
-
if (tier === "free") return true;
|
|
5933
|
-
return ENTITLED_STATUSES.has(status);
|
|
5934
|
-
}
|
|
5935
|
-
|
|
5936
|
-
// src/client/subscriptions/useSubscription.ts
|
|
6080
|
+
// src/client/authed-json.ts
|
|
5937
6081
|
async function authedJson(path, init) {
|
|
5938
6082
|
const token = await getAuthToken();
|
|
5939
6083
|
const res = await fetch(path, {
|
|
@@ -5947,10 +6091,22 @@ async function authedJson(path, init) {
|
|
|
5947
6091
|
});
|
|
5948
6092
|
const data = await res.json().catch(() => ({}));
|
|
5949
6093
|
if (!res.ok) {
|
|
5950
|
-
throw new
|
|
6094
|
+
throw new PlatformApiError(normalizeApiError(res.status, data));
|
|
5951
6095
|
}
|
|
5952
6096
|
return data;
|
|
5953
6097
|
}
|
|
6098
|
+
|
|
6099
|
+
// src/shared/subscription.ts
|
|
6100
|
+
var ENTITLED_STATUSES = /* @__PURE__ */ new Set([
|
|
6101
|
+
"active",
|
|
6102
|
+
"trialing"
|
|
6103
|
+
]);
|
|
6104
|
+
function isEntitled(tier, status) {
|
|
6105
|
+
if (tier === "free") return true;
|
|
6106
|
+
return ENTITLED_STATUSES.has(status);
|
|
6107
|
+
}
|
|
6108
|
+
|
|
6109
|
+
// src/client/subscriptions/useSubscription.ts
|
|
5954
6110
|
function useSubscription() {
|
|
5955
6111
|
const [state, setState] = useState18({
|
|
5956
6112
|
tier: "free",
|
|
@@ -5963,6 +6119,7 @@ function useSubscription() {
|
|
|
5963
6119
|
});
|
|
5964
6120
|
const [isLoading, setLoading] = useState18(true);
|
|
5965
6121
|
const [error, setError] = useState18(null);
|
|
6122
|
+
const [errorCode, setErrorCode] = useState18(null);
|
|
5966
6123
|
const refresh = useCallback27(async () => {
|
|
5967
6124
|
setLoading(true);
|
|
5968
6125
|
try {
|
|
@@ -5977,8 +6134,10 @@ function useSubscription() {
|
|
|
5977
6134
|
plans: r.plans ?? []
|
|
5978
6135
|
});
|
|
5979
6136
|
setError(null);
|
|
6137
|
+
setErrorCode(null);
|
|
5980
6138
|
} catch (e) {
|
|
5981
6139
|
setError(e instanceof Error ? e.message : "Failed to load subscription");
|
|
6140
|
+
setErrorCode(apiErrorCode(e));
|
|
5982
6141
|
} finally {
|
|
5983
6142
|
setLoading(false);
|
|
5984
6143
|
}
|
|
@@ -6040,6 +6199,7 @@ function useSubscription() {
|
|
|
6040
6199
|
trialEndsAt: state.trialEndsAt,
|
|
6041
6200
|
isLoading,
|
|
6042
6201
|
error,
|
|
6202
|
+
errorCode,
|
|
6043
6203
|
hasTier,
|
|
6044
6204
|
isAtLeast,
|
|
6045
6205
|
plans: state.plans,
|
|
@@ -6091,37 +6251,23 @@ function PricingTable(props) {
|
|
|
6091
6251
|
|
|
6092
6252
|
// src/client/charges/useCheckout.ts
|
|
6093
6253
|
import { useCallback as useCallback28, useEffect as useEffect22, useMemo as useMemo17, useState as useState19 } from "react";
|
|
6094
|
-
async function authedJson2(path, init) {
|
|
6095
|
-
const token = await getAuthToken();
|
|
6096
|
-
const res = await fetch(path, {
|
|
6097
|
-
...init,
|
|
6098
|
-
credentials: "include",
|
|
6099
|
-
headers: {
|
|
6100
|
-
"Content-Type": "application/json",
|
|
6101
|
-
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
6102
|
-
...init?.headers
|
|
6103
|
-
}
|
|
6104
|
-
});
|
|
6105
|
-
const data = await res.json().catch(() => ({}));
|
|
6106
|
-
if (!res.ok) {
|
|
6107
|
-
throw new Error(data.error ?? `Request failed (${res.status})`);
|
|
6108
|
-
}
|
|
6109
|
-
return data;
|
|
6110
|
-
}
|
|
6111
6254
|
function useCheckout(opts) {
|
|
6112
6255
|
const [purchases, setPurchases] = useState19([]);
|
|
6113
6256
|
const [isLoading, setLoading] = useState19(true);
|
|
6114
6257
|
const [error, setError] = useState19(null);
|
|
6258
|
+
const [errorCode, setErrorCode] = useState19(null);
|
|
6115
6259
|
const refresh = useCallback28(async () => {
|
|
6116
6260
|
setLoading(true);
|
|
6117
6261
|
try {
|
|
6118
|
-
const r = await
|
|
6262
|
+
const r = await authedJson(
|
|
6119
6263
|
"/_deepspace/charges/me"
|
|
6120
6264
|
);
|
|
6121
6265
|
setPurchases(r.purchases ?? []);
|
|
6122
6266
|
setError(null);
|
|
6267
|
+
setErrorCode(null);
|
|
6123
6268
|
} catch (e) {
|
|
6124
6269
|
setError(e instanceof Error ? e.message : "Failed to load purchases");
|
|
6270
|
+
setErrorCode(apiErrorCode(e));
|
|
6125
6271
|
} finally {
|
|
6126
6272
|
setLoading(false);
|
|
6127
6273
|
}
|
|
@@ -6132,6 +6278,7 @@ function useCheckout(opts) {
|
|
|
6132
6278
|
const chargeOnce = useCallback28(
|
|
6133
6279
|
async (chargeOpts) => {
|
|
6134
6280
|
setError(null);
|
|
6281
|
+
setErrorCode(null);
|
|
6135
6282
|
const common = {
|
|
6136
6283
|
returnUrl: chargeOpts.returnUrl ?? window.location.href,
|
|
6137
6284
|
cancelUrl: chargeOpts.cancelUrl ?? window.location.href,
|
|
@@ -6144,7 +6291,7 @@ function useCheckout(opts) {
|
|
|
6144
6291
|
...chargeOpts.description ? { description: chargeOpts.description } : {}
|
|
6145
6292
|
};
|
|
6146
6293
|
try {
|
|
6147
|
-
const result = await
|
|
6294
|
+
const result = await authedJson(
|
|
6148
6295
|
"/_deepspace/charges/create",
|
|
6149
6296
|
{
|
|
6150
6297
|
method: "POST",
|
|
@@ -6158,6 +6305,7 @@ function useCheckout(opts) {
|
|
|
6158
6305
|
} catch (e) {
|
|
6159
6306
|
const msg = e instanceof Error ? e.message : "Failed to start checkout";
|
|
6160
6307
|
setError(msg);
|
|
6308
|
+
setErrorCode(apiErrorCode(e));
|
|
6161
6309
|
throw e;
|
|
6162
6310
|
}
|
|
6163
6311
|
},
|
|
@@ -6175,6 +6323,7 @@ function useCheckout(opts) {
|
|
|
6175
6323
|
chargeOnce,
|
|
6176
6324
|
isLoading,
|
|
6177
6325
|
error,
|
|
6326
|
+
errorCode,
|
|
6178
6327
|
purchases,
|
|
6179
6328
|
owned,
|
|
6180
6329
|
ownsProduct,
|
|
@@ -6351,6 +6500,7 @@ export {
|
|
|
6351
6500
|
MSG_SYNC_UPDATE,
|
|
6352
6501
|
MSG_YJS_AWARENESS,
|
|
6353
6502
|
MSG_YJS_SYNC,
|
|
6503
|
+
PlatformApiError,
|
|
6354
6504
|
PlatformContext,
|
|
6355
6505
|
PlatformProvider,
|
|
6356
6506
|
PricingTable,
|