deepspace 0.20.0 → 0.22.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 +28 -0
- package/dist/{chunk-A7UFIOWO.js → chunk-7TDBFEB7.js} +7 -2
- package/dist/chunk-7TDBFEB7.js.map +1 -0
- package/dist/cli.js +288 -120
- 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 +239 -100
- package/dist/index.js.map +1 -1
- package/dist/{public-CFAj1hL2.d.ts → public-DenQ1TsZ.d.ts} +36 -11
- package/dist/server.d.ts +32 -4
- package/dist/server.js +177 -43
- 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 +57 -5
- package/dist/worker.js +262 -62
- 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
|
|
@@ -3627,7 +3677,12 @@ 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
3688
|
const requestId = crypto.randomUUID();
|
|
@@ -3637,7 +3692,7 @@ async function uploadInParts(file, name, scopeParams, authHeaders, onProgress) {
|
|
|
3637
3692
|
mimeType: file.type || "application/octet-stream",
|
|
3638
3693
|
size: file.size
|
|
3639
3694
|
});
|
|
3640
|
-
const sendInit = () => fetch(`/api/files/multipart?${scopeParams}`, {
|
|
3695
|
+
const sendInit = () => fetch(`/api/files/multipart?${withUploadKey(scopeParams, key)}`, {
|
|
3641
3696
|
method: "POST",
|
|
3642
3697
|
headers: json,
|
|
3643
3698
|
body: initBody
|
|
@@ -3716,17 +3771,27 @@ function useR2Files(options) {
|
|
|
3716
3771
|
if (tooBig) return tooBig;
|
|
3717
3772
|
const fileName = name ?? (file instanceof File ? file.name : "upload");
|
|
3718
3773
|
if (file.size > UPLOAD_PART_BYTES) {
|
|
3719
|
-
return await uploadInParts(
|
|
3774
|
+
return await uploadInParts(
|
|
3775
|
+
file,
|
|
3776
|
+
fileName,
|
|
3777
|
+
options2?.key,
|
|
3778
|
+
scopeParams,
|
|
3779
|
+
authHeaders,
|
|
3780
|
+
options2?.onProgress
|
|
3781
|
+
);
|
|
3720
3782
|
}
|
|
3721
3783
|
const headers = await authHeaders();
|
|
3722
3784
|
const formData = new FormData();
|
|
3723
3785
|
formData.append("file", file);
|
|
3724
3786
|
if (name) formData.append("name", name);
|
|
3725
|
-
const response = await fetch(
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3787
|
+
const response = await fetch(
|
|
3788
|
+
`/api/files/upload?${withUploadKey(scopeParams, options2?.key)}`,
|
|
3789
|
+
{
|
|
3790
|
+
method: "POST",
|
|
3791
|
+
headers,
|
|
3792
|
+
body: formData
|
|
3793
|
+
}
|
|
3794
|
+
);
|
|
3730
3795
|
options2?.onProgress?.(file.size, file.size);
|
|
3731
3796
|
return await readUploadResult(response);
|
|
3732
3797
|
} catch (err) {
|
|
@@ -3738,17 +3803,20 @@ function useR2Files(options) {
|
|
|
3738
3803
|
[scopeParams, authHeaders]
|
|
3739
3804
|
);
|
|
3740
3805
|
const uploadBase64 = useCallback14(
|
|
3741
|
-
async (base64Data, name, mimeType) => {
|
|
3806
|
+
async (base64Data, name, mimeType, options2) => {
|
|
3742
3807
|
setIsUploading(true);
|
|
3743
3808
|
try {
|
|
3744
3809
|
const tooBig = oversize(Math.floor(base64Data.length * 3 / 4), MAX_BASE64_UPLOAD_BYTES);
|
|
3745
3810
|
if (tooBig) return tooBig;
|
|
3746
3811
|
const headers = await authHeaders();
|
|
3747
|
-
const response = await fetch(
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
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
|
+
);
|
|
3752
3820
|
return await readUploadResult(response);
|
|
3753
3821
|
} catch (err) {
|
|
3754
3822
|
return { success: false, error: err instanceof Error ? err.message : "Upload failed" };
|
|
@@ -3758,12 +3826,18 @@ function useR2Files(options) {
|
|
|
3758
3826
|
},
|
|
3759
3827
|
[scopeParams, authHeaders]
|
|
3760
3828
|
);
|
|
3829
|
+
const getUrl = useCallback14(
|
|
3830
|
+
(fileOrKey) => {
|
|
3831
|
+
const { key } = resolveFile(fileOrKey);
|
|
3832
|
+
return `/api/files/${encodeKeyPath(key)}?${scopeParams}`;
|
|
3833
|
+
},
|
|
3834
|
+
[scopeParams]
|
|
3835
|
+
);
|
|
3761
3836
|
const deleteFile = useCallback14(
|
|
3762
3837
|
async (fileOrKey) => {
|
|
3763
|
-
const { key } = resolveFile(fileOrKey);
|
|
3764
3838
|
try {
|
|
3765
3839
|
const headers = await authHeaders();
|
|
3766
|
-
const response = await fetch(
|
|
3840
|
+
const response = await fetch(getUrl(fileOrKey), {
|
|
3767
3841
|
method: "DELETE",
|
|
3768
3842
|
headers
|
|
3769
3843
|
});
|
|
@@ -3772,32 +3846,40 @@ function useR2Files(options) {
|
|
|
3772
3846
|
return { success: false, error: err instanceof Error ? err.message : "Delete failed" };
|
|
3773
3847
|
}
|
|
3774
3848
|
},
|
|
3775
|
-
[
|
|
3849
|
+
[getUrl, authHeaders]
|
|
3776
3850
|
);
|
|
3777
|
-
const
|
|
3778
|
-
async (prefix) => {
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
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()));
|
|
3788
3861
|
}
|
|
3862
|
+
const result = await response.json();
|
|
3863
|
+
return {
|
|
3864
|
+
files: result.files || [],
|
|
3865
|
+
cursor: result.cursor,
|
|
3866
|
+
truncated: result.truncated ?? false
|
|
3867
|
+
};
|
|
3789
3868
|
},
|
|
3790
3869
|
[scopeParams, authHeaders]
|
|
3791
3870
|
);
|
|
3871
|
+
const list = useCallback14(
|
|
3872
|
+
async (prefix, options2) => (await listPage(prefix, options2)).files,
|
|
3873
|
+
[listPage]
|
|
3874
|
+
);
|
|
3792
3875
|
const downloadFile = useCallback14(
|
|
3793
3876
|
async (fileOrKey, fileName) => {
|
|
3794
3877
|
const { key, name: infoName } = resolveFile(fileOrKey);
|
|
3795
3878
|
try {
|
|
3796
3879
|
const headers = await authHeaders();
|
|
3797
|
-
const response = await fetch(
|
|
3880
|
+
const response = await fetch(getUrl(key), { headers });
|
|
3798
3881
|
if (!response.ok) {
|
|
3799
|
-
|
|
3800
|
-
return { success: false, error: body?.error ?? `Download failed (${response.status})` };
|
|
3882
|
+
return { success: false, error: describeFilesFailure(response.status, await response.text()) };
|
|
3801
3883
|
}
|
|
3802
3884
|
const disposition = response.headers.get("Content-Disposition");
|
|
3803
3885
|
const dispositionName = disposition?.match(/filename="(.+?)"/)?.[1];
|
|
@@ -3817,29 +3899,30 @@ function useR2Files(options) {
|
|
|
3817
3899
|
return { success: false, error: err instanceof Error ? err.message : "Download failed" };
|
|
3818
3900
|
}
|
|
3819
3901
|
},
|
|
3820
|
-
[
|
|
3902
|
+
[getUrl, authHeaders]
|
|
3821
3903
|
);
|
|
3822
3904
|
const readFile = useCallback14(
|
|
3823
3905
|
async (fileOrKey) => {
|
|
3824
|
-
const { key } = resolveFile(fileOrKey);
|
|
3825
3906
|
const headers = await authHeaders();
|
|
3826
|
-
const response = await fetch(
|
|
3907
|
+
const response = await fetch(getUrl(fileOrKey), { headers });
|
|
3827
3908
|
if (!response.ok) {
|
|
3828
|
-
|
|
3829
|
-
throw new Error(`Failed to read file (${response.status}): ${body}`);
|
|
3909
|
+
throw new Error(describeFilesFailure(response.status, await response.text()));
|
|
3830
3910
|
}
|
|
3831
3911
|
return response;
|
|
3832
3912
|
},
|
|
3833
|
-
[
|
|
3913
|
+
[getUrl, authHeaders]
|
|
3834
3914
|
);
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3915
|
+
return {
|
|
3916
|
+
upload,
|
|
3917
|
+
uploadBase64,
|
|
3918
|
+
deleteFile,
|
|
3919
|
+
downloadFile,
|
|
3920
|
+
readFile,
|
|
3921
|
+
list,
|
|
3922
|
+
listPage,
|
|
3923
|
+
getUrl,
|
|
3924
|
+
isUploading
|
|
3925
|
+
};
|
|
3843
3926
|
}
|
|
3844
3927
|
|
|
3845
3928
|
// src/client/storage/file-attachment-utils.ts
|
|
@@ -5175,6 +5258,68 @@ function usePlatformWS(options) {
|
|
|
5175
5258
|
return { state, send };
|
|
5176
5259
|
}
|
|
5177
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
|
+
|
|
5178
5323
|
// src/client/integration.ts
|
|
5179
5324
|
var ENDPOINT_PREFIX = "/api/integrations";
|
|
5180
5325
|
var DEFAULT_TIMEOUT_MS2 = 12e4;
|
|
@@ -5196,7 +5341,7 @@ async function request(method, endpoint, body, options, query) {
|
|
|
5196
5341
|
const url = `${resolveUrl(endpoint)}${queryString ? `?${queryString}` : ""}`;
|
|
5197
5342
|
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
5198
5343
|
if (options?.signal?.aborted) {
|
|
5199
|
-
return { success: false, error: "Request cancelled" };
|
|
5344
|
+
return { success: false, error: "Request cancelled", status: 0 };
|
|
5200
5345
|
}
|
|
5201
5346
|
const headers = {
|
|
5202
5347
|
"Content-Type": "application/json",
|
|
@@ -5226,14 +5371,10 @@ async function request(method, endpoint, body, options, query) {
|
|
|
5226
5371
|
try {
|
|
5227
5372
|
payload = await res.json();
|
|
5228
5373
|
} catch {
|
|
5229
|
-
return { success: false, error: `Request failed (${res.status})
|
|
5374
|
+
return { success: false, error: `Request failed (${res.status})`, status: res.status };
|
|
5230
5375
|
}
|
|
5231
5376
|
if (!res.ok || payload.success === false) {
|
|
5232
|
-
return {
|
|
5233
|
-
success: false,
|
|
5234
|
-
error: payload.error ?? payload.message ?? `Request failed (${res.status})`,
|
|
5235
|
-
...payload.issues ? { issues: payload.issues } : {}
|
|
5236
|
-
};
|
|
5377
|
+
return { success: false, ...normalizeApiError(res.status, payload) };
|
|
5237
5378
|
}
|
|
5238
5379
|
if ("data" in payload) {
|
|
5239
5380
|
return { success: true, data: payload.data };
|
|
@@ -5245,12 +5386,14 @@ async function request(method, endpoint, body, options, query) {
|
|
|
5245
5386
|
if (err instanceof DOMException && err.name === "AbortError") {
|
|
5246
5387
|
return {
|
|
5247
5388
|
success: false,
|
|
5248
|
-
error: timedOut ? `Request timed out after ${timeoutMs}ms` : "Request cancelled"
|
|
5389
|
+
error: timedOut ? `Request timed out after ${timeoutMs}ms` : "Request cancelled",
|
|
5390
|
+
status: 0
|
|
5249
5391
|
};
|
|
5250
5392
|
}
|
|
5251
5393
|
return {
|
|
5252
5394
|
success: false,
|
|
5253
|
-
error: err instanceof Error ? err.message : "Request failed"
|
|
5395
|
+
error: err instanceof Error ? err.message : "Request failed",
|
|
5396
|
+
status: 0
|
|
5254
5397
|
};
|
|
5255
5398
|
} finally {
|
|
5256
5399
|
clearTimeout(timeout);
|
|
@@ -5934,17 +6077,7 @@ function usePagedResource(fetchPage, deps, options = {}) {
|
|
|
5934
6077
|
// src/client/subscriptions/useSubscription.ts
|
|
5935
6078
|
import { useCallback as useCallback27, useEffect as useEffect21, useState as useState18 } from "react";
|
|
5936
6079
|
|
|
5937
|
-
// src/
|
|
5938
|
-
var ENTITLED_STATUSES = /* @__PURE__ */ new Set([
|
|
5939
|
-
"active",
|
|
5940
|
-
"trialing"
|
|
5941
|
-
]);
|
|
5942
|
-
function isEntitled(tier, status) {
|
|
5943
|
-
if (tier === "free") return true;
|
|
5944
|
-
return ENTITLED_STATUSES.has(status);
|
|
5945
|
-
}
|
|
5946
|
-
|
|
5947
|
-
// src/client/subscriptions/useSubscription.ts
|
|
6080
|
+
// src/client/authed-json.ts
|
|
5948
6081
|
async function authedJson(path, init) {
|
|
5949
6082
|
const token = await getAuthToken();
|
|
5950
6083
|
const res = await fetch(path, {
|
|
@@ -5958,10 +6091,22 @@ async function authedJson(path, init) {
|
|
|
5958
6091
|
});
|
|
5959
6092
|
const data = await res.json().catch(() => ({}));
|
|
5960
6093
|
if (!res.ok) {
|
|
5961
|
-
throw new
|
|
6094
|
+
throw new PlatformApiError(normalizeApiError(res.status, data));
|
|
5962
6095
|
}
|
|
5963
6096
|
return data;
|
|
5964
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
|
|
5965
6110
|
function useSubscription() {
|
|
5966
6111
|
const [state, setState] = useState18({
|
|
5967
6112
|
tier: "free",
|
|
@@ -5974,6 +6119,7 @@ function useSubscription() {
|
|
|
5974
6119
|
});
|
|
5975
6120
|
const [isLoading, setLoading] = useState18(true);
|
|
5976
6121
|
const [error, setError] = useState18(null);
|
|
6122
|
+
const [errorCode, setErrorCode] = useState18(null);
|
|
5977
6123
|
const refresh = useCallback27(async () => {
|
|
5978
6124
|
setLoading(true);
|
|
5979
6125
|
try {
|
|
@@ -5988,8 +6134,10 @@ function useSubscription() {
|
|
|
5988
6134
|
plans: r.plans ?? []
|
|
5989
6135
|
});
|
|
5990
6136
|
setError(null);
|
|
6137
|
+
setErrorCode(null);
|
|
5991
6138
|
} catch (e) {
|
|
5992
6139
|
setError(e instanceof Error ? e.message : "Failed to load subscription");
|
|
6140
|
+
setErrorCode(apiErrorCode(e));
|
|
5993
6141
|
} finally {
|
|
5994
6142
|
setLoading(false);
|
|
5995
6143
|
}
|
|
@@ -6051,6 +6199,7 @@ function useSubscription() {
|
|
|
6051
6199
|
trialEndsAt: state.trialEndsAt,
|
|
6052
6200
|
isLoading,
|
|
6053
6201
|
error,
|
|
6202
|
+
errorCode,
|
|
6054
6203
|
hasTier,
|
|
6055
6204
|
isAtLeast,
|
|
6056
6205
|
plans: state.plans,
|
|
@@ -6102,37 +6251,23 @@ function PricingTable(props) {
|
|
|
6102
6251
|
|
|
6103
6252
|
// src/client/charges/useCheckout.ts
|
|
6104
6253
|
import { useCallback as useCallback28, useEffect as useEffect22, useMemo as useMemo17, useState as useState19 } from "react";
|
|
6105
|
-
async function authedJson2(path, init) {
|
|
6106
|
-
const token = await getAuthToken();
|
|
6107
|
-
const res = await fetch(path, {
|
|
6108
|
-
...init,
|
|
6109
|
-
credentials: "include",
|
|
6110
|
-
headers: {
|
|
6111
|
-
"Content-Type": "application/json",
|
|
6112
|
-
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
6113
|
-
...init?.headers
|
|
6114
|
-
}
|
|
6115
|
-
});
|
|
6116
|
-
const data = await res.json().catch(() => ({}));
|
|
6117
|
-
if (!res.ok) {
|
|
6118
|
-
throw new Error(data.error ?? `Request failed (${res.status})`);
|
|
6119
|
-
}
|
|
6120
|
-
return data;
|
|
6121
|
-
}
|
|
6122
6254
|
function useCheckout(opts) {
|
|
6123
6255
|
const [purchases, setPurchases] = useState19([]);
|
|
6124
6256
|
const [isLoading, setLoading] = useState19(true);
|
|
6125
6257
|
const [error, setError] = useState19(null);
|
|
6258
|
+
const [errorCode, setErrorCode] = useState19(null);
|
|
6126
6259
|
const refresh = useCallback28(async () => {
|
|
6127
6260
|
setLoading(true);
|
|
6128
6261
|
try {
|
|
6129
|
-
const r = await
|
|
6262
|
+
const r = await authedJson(
|
|
6130
6263
|
"/_deepspace/charges/me"
|
|
6131
6264
|
);
|
|
6132
6265
|
setPurchases(r.purchases ?? []);
|
|
6133
6266
|
setError(null);
|
|
6267
|
+
setErrorCode(null);
|
|
6134
6268
|
} catch (e) {
|
|
6135
6269
|
setError(e instanceof Error ? e.message : "Failed to load purchases");
|
|
6270
|
+
setErrorCode(apiErrorCode(e));
|
|
6136
6271
|
} finally {
|
|
6137
6272
|
setLoading(false);
|
|
6138
6273
|
}
|
|
@@ -6143,6 +6278,7 @@ function useCheckout(opts) {
|
|
|
6143
6278
|
const chargeOnce = useCallback28(
|
|
6144
6279
|
async (chargeOpts) => {
|
|
6145
6280
|
setError(null);
|
|
6281
|
+
setErrorCode(null);
|
|
6146
6282
|
const common = {
|
|
6147
6283
|
returnUrl: chargeOpts.returnUrl ?? window.location.href,
|
|
6148
6284
|
cancelUrl: chargeOpts.cancelUrl ?? window.location.href,
|
|
@@ -6155,7 +6291,7 @@ function useCheckout(opts) {
|
|
|
6155
6291
|
...chargeOpts.description ? { description: chargeOpts.description } : {}
|
|
6156
6292
|
};
|
|
6157
6293
|
try {
|
|
6158
|
-
const result = await
|
|
6294
|
+
const result = await authedJson(
|
|
6159
6295
|
"/_deepspace/charges/create",
|
|
6160
6296
|
{
|
|
6161
6297
|
method: "POST",
|
|
@@ -6169,6 +6305,7 @@ function useCheckout(opts) {
|
|
|
6169
6305
|
} catch (e) {
|
|
6170
6306
|
const msg = e instanceof Error ? e.message : "Failed to start checkout";
|
|
6171
6307
|
setError(msg);
|
|
6308
|
+
setErrorCode(apiErrorCode(e));
|
|
6172
6309
|
throw e;
|
|
6173
6310
|
}
|
|
6174
6311
|
},
|
|
@@ -6186,6 +6323,7 @@ function useCheckout(opts) {
|
|
|
6186
6323
|
chargeOnce,
|
|
6187
6324
|
isLoading,
|
|
6188
6325
|
error,
|
|
6326
|
+
errorCode,
|
|
6189
6327
|
purchases,
|
|
6190
6328
|
owned,
|
|
6191
6329
|
ownsProduct,
|
|
@@ -6362,6 +6500,7 @@ export {
|
|
|
6362
6500
|
MSG_SYNC_UPDATE,
|
|
6363
6501
|
MSG_YJS_AWARENESS,
|
|
6364
6502
|
MSG_YJS_SYNC,
|
|
6503
|
+
PlatformApiError,
|
|
6365
6504
|
PlatformContext,
|
|
6366
6505
|
PlatformProvider,
|
|
6367
6506
|
PricingTable,
|