@neta-art/cohub 12.5.0 → 12.6.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 +17 -10
- package/dist/board/model/media-playback.d.ts +1 -0
- package/dist/board/replica/index.d.ts +3 -2
- package/dist/board/replica/index.js +15 -4
- package/dist/board/replica/indexeddb.js +14 -85
- package/dist/board/stage/assets.d.ts +3 -2
- package/dist/board/stage/assets.js +53 -21
- package/dist/board/stage/scene.js +11 -4
- package/dist/board/stage/stage.js +1 -1
- package/dist/chunks/app-realtime.js +1 -1
- package/dist/chunks/environment.d.ts +9 -7
- package/dist/chunks/environment.js +4 -3
- package/dist/chunks/http-error.js +28 -5
- package/dist/chunks/http.d.ts +76 -15
- package/dist/chunks/http.js +674 -37
- package/dist/chunks/websocket.d.ts +9 -1
- package/dist/http.d.ts +2 -2
- package/dist/http.js +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.js +6 -4
- package/dist/indexeddb.js +79 -0
- package/dist/protocol/dist/board.d.ts +19 -1
- package/dist/protocol/dist/index.d.ts +2 -2
- package/dist/voice-input.js +1 -1
- package/dist/websocket.js +37 -5
- package/package.json +1 -1
package/dist/chunks/http.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { S as getRealtimeSpaceRoom, c as HttpError, l as HttpTransport, s as isHttpErrorCode, x as getRealtimeBoardRoom, z as isUuid } from "./http-error.js";
|
|
2
|
-
import {
|
|
2
|
+
import { s as resolveApiBaseUrl, u as resolveExecutionToken } from "./environment.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region ../protocol/dist/board-constants.js
|
|
5
5
|
const BOARD_ARROW_STROKE_SIZE = 2.5;
|
|
@@ -1447,7 +1447,7 @@ var CronJobsApi = class {
|
|
|
1447
1447
|
//#region src/apis/generations.ts
|
|
1448
1448
|
const DEFAULT_INTERVAL_MS = 1500;
|
|
1449
1449
|
const DEFAULT_TIMEOUT_MS = 18e5;
|
|
1450
|
-
function sleep$
|
|
1450
|
+
function sleep$2(ms, signal) {
|
|
1451
1451
|
if (signal?.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("Generation wait aborted"));
|
|
1452
1452
|
return new Promise((resolve, reject) => {
|
|
1453
1453
|
const cleanup = () => signal?.removeEventListener("abort", onAbort);
|
|
@@ -1512,7 +1512,7 @@ var GenerationsApi = class {
|
|
|
1512
1512
|
}
|
|
1513
1513
|
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
1514
1514
|
if (remainingMs <= 0) throw new Error(`Generation task timed out after ${timeoutMs}ms`);
|
|
1515
|
-
await sleep$
|
|
1515
|
+
await sleep$2(Math.min(intervalMs, remainingMs), options.signal);
|
|
1516
1516
|
}
|
|
1517
1517
|
}
|
|
1518
1518
|
async createAndWait(request, options) {
|
|
@@ -1895,7 +1895,7 @@ var BoardRealtimeClient = class {
|
|
|
1895
1895
|
if (!this.websocketClient) throw new Error("realtime transport is not configured for this client");
|
|
1896
1896
|
ensureRealtimeConnected(this.websocketClient);
|
|
1897
1897
|
const room = getRealtimeBoardRoom(this.boardId);
|
|
1898
|
-
const releaseRoom = this.websocketClient.retainRooms([
|
|
1898
|
+
const releaseRoom = this.websocketClient.retainRooms([room]);
|
|
1899
1899
|
const unsubscribe = this.websocketClient.on("event", (event) => {
|
|
1900
1900
|
if (event.spaceId !== this.spaceId) return;
|
|
1901
1901
|
if (event.type === "board.changed" && event.payload.boardId === this.boardId) {
|
|
@@ -1917,12 +1917,14 @@ var BoardRealtimeClient = class {
|
|
|
1917
1917
|
}
|
|
1918
1918
|
});
|
|
1919
1919
|
const resync = handlers.resync;
|
|
1920
|
-
const stopResync = resync ? this.websocketClient.on("subscribed", ({ rooms }) => {
|
|
1920
|
+
const stopResync = resync ? [this.websocketClient.on("subscribed", ({ rooms }) => {
|
|
1921
1921
|
if (rooms.includes(room)) resync();
|
|
1922
|
-
})
|
|
1922
|
+
}), this.websocketClient.on("subscribeError", ({ rejected }) => {
|
|
1923
|
+
if (rejected.some((entry) => entry.room === room && entry.code !== "UNAVAILABLE")) resync();
|
|
1924
|
+
})] : [];
|
|
1923
1925
|
return () => {
|
|
1924
1926
|
unsubscribe();
|
|
1925
|
-
stopResync
|
|
1927
|
+
for (const stop of stopResync) stop();
|
|
1926
1928
|
releaseRoom();
|
|
1927
1929
|
};
|
|
1928
1930
|
}
|
|
@@ -1951,7 +1953,7 @@ var BoardClient = class {
|
|
|
1951
1953
|
/** Read the Board, or part of it. Every value is complete, defaults included. */
|
|
1952
1954
|
get(input = {}, customFetch) {
|
|
1953
1955
|
const params = new URLSearchParams();
|
|
1954
|
-
if (input.only
|
|
1956
|
+
if (input.only) params.set("only", input.only.join(","));
|
|
1955
1957
|
if (input.rect) params.set("rect", [
|
|
1956
1958
|
input.rect.x,
|
|
1957
1959
|
input.rect.y,
|
|
@@ -2088,6 +2090,19 @@ var SpaceBoardsApi = class {
|
|
|
2088
2090
|
boardId
|
|
2089
2091
|
});
|
|
2090
2092
|
}
|
|
2093
|
+
assetSource() {
|
|
2094
|
+
return {
|
|
2095
|
+
resolveFileUrl: (path, options) => this.files.resolveUrl(path, {
|
|
2096
|
+
purpose: "preview",
|
|
2097
|
+
fresh: options?.fresh
|
|
2098
|
+
}),
|
|
2099
|
+
resolvePlaybackUrl: (path, options) => this.files.resolveUrl(path, {
|
|
2100
|
+
purpose: "playback",
|
|
2101
|
+
fresh: options?.fresh
|
|
2102
|
+
}),
|
|
2103
|
+
subscribe: (listener) => this.files.onUrlChange(listener)
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2091
2106
|
async open(target, customFetch) {
|
|
2092
2107
|
return this.byId(isBoardPath(target) ? await this.resolveId(target, customFetch) : target);
|
|
2093
2108
|
}
|
|
@@ -3000,7 +3015,7 @@ function getPatchMessageIdentity(event) {
|
|
|
3000
3015
|
messageOrdinal: typeof event.payload.messageOrdinal === "number" ? event.payload.messageOrdinal : null
|
|
3001
3016
|
};
|
|
3002
3017
|
}
|
|
3003
|
-
async function withTimeout(promise, timeoutMs) {
|
|
3018
|
+
async function withTimeout$1(promise, timeoutMs) {
|
|
3004
3019
|
let timeout = null;
|
|
3005
3020
|
try {
|
|
3006
3021
|
return await Promise.race([promise, new Promise((_, reject) => {
|
|
@@ -3169,7 +3184,7 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
|
|
|
3169
3184
|
async loadSnapshot() {
|
|
3170
3185
|
if (!this.fetchStreamSnapshot) return null;
|
|
3171
3186
|
try {
|
|
3172
|
-
return (await withTimeout(this.fetchStreamSnapshot(), SNAPSHOT_RECOVERY_TIMEOUT_MS)).snapshot;
|
|
3187
|
+
return (await withTimeout$1(this.fetchStreamSnapshot(), SNAPSHOT_RECOVERY_TIMEOUT_MS)).snapshot;
|
|
3173
3188
|
} catch (error) {
|
|
3174
3189
|
console.warn("[SessionGenerationStreamClient] Failed to recover stream snapshot:", error);
|
|
3175
3190
|
return null;
|
|
@@ -3543,6 +3558,631 @@ function buildSpaceInvitePath(input) {
|
|
|
3543
3558
|
return `${buildSpacePath(input)}/join/${inviteCode}`;
|
|
3544
3559
|
}
|
|
3545
3560
|
//#endregion
|
|
3561
|
+
//#region src/apis/space-file-reads.ts
|
|
3562
|
+
const BATCH_SIZE = 32;
|
|
3563
|
+
const BATCH_CONCURRENCY = 2;
|
|
3564
|
+
const SINGLE_CONCURRENCY = 4;
|
|
3565
|
+
const BACKGROUND_DELAY_MS = 1500;
|
|
3566
|
+
const READ_TIMEOUT_MS = 3e4;
|
|
3567
|
+
function createLimiter(limit) {
|
|
3568
|
+
let active = 0;
|
|
3569
|
+
const lanes = [[], []];
|
|
3570
|
+
function pump() {
|
|
3571
|
+
while (active < limit) {
|
|
3572
|
+
const job = lanes[0].shift() ?? lanes[1].shift();
|
|
3573
|
+
if (!job) return;
|
|
3574
|
+
active += 1;
|
|
3575
|
+
job();
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
return (run, background = false) => new Promise((resolve, reject) => {
|
|
3579
|
+
lanes[background ? 1 : 0].push(() => {
|
|
3580
|
+
run().then(resolve, reject).finally(() => {
|
|
3581
|
+
active -= 1;
|
|
3582
|
+
pump();
|
|
3583
|
+
});
|
|
3584
|
+
});
|
|
3585
|
+
pump();
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3588
|
+
const fileReadPath = (spaceId, path) => `/api/spaces/${spaceId}/fs/file?${new URLSearchParams({ path })}`;
|
|
3589
|
+
function createSpaceFileReads(transport) {
|
|
3590
|
+
const queues = [/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()];
|
|
3591
|
+
const timers = [null, null];
|
|
3592
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
3593
|
+
const batches = createLimiter(BATCH_CONCURRENCY);
|
|
3594
|
+
const singles = createLimiter(SINGLE_CONCURRENCY);
|
|
3595
|
+
const readOne = (spaceId, path, init = {}) => withTimeout((signal) => transport.requestRevalidated(fileReadPath(spaceId, path), {
|
|
3596
|
+
...init,
|
|
3597
|
+
signal
|
|
3598
|
+
}));
|
|
3599
|
+
function schedule(lane) {
|
|
3600
|
+
timers[lane] ??= setTimeout(() => {
|
|
3601
|
+
timers[lane] = null;
|
|
3602
|
+
flush(lane);
|
|
3603
|
+
}, lane ? BACKGROUND_DELAY_MS : 0);
|
|
3604
|
+
}
|
|
3605
|
+
function settle(pending, outcome) {
|
|
3606
|
+
if (inFlight.get(pending.key) === pending) inFlight.delete(pending.key);
|
|
3607
|
+
if ("file" in outcome) pending.resolve(outcome.file);
|
|
3608
|
+
else pending.reject(outcome.error);
|
|
3609
|
+
}
|
|
3610
|
+
function flush(lane) {
|
|
3611
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3612
|
+
for (const pending of queues[lane].values()) {
|
|
3613
|
+
inFlight.set(pending.key, pending);
|
|
3614
|
+
const id = `${pending.token}\u0000${pending.spaceId}`;
|
|
3615
|
+
const group = groups.get(id);
|
|
3616
|
+
if (group) group.push(pending);
|
|
3617
|
+
else groups.set(id, [pending]);
|
|
3618
|
+
}
|
|
3619
|
+
queues[lane].clear();
|
|
3620
|
+
for (const group of groups.values()) {
|
|
3621
|
+
if (group.length === 1) {
|
|
3622
|
+
readSingle(group[0], lane === 1);
|
|
3623
|
+
continue;
|
|
3624
|
+
}
|
|
3625
|
+
for (let index = 0; index < group.length; index += BATCH_SIZE) batches(() => readBatch(group.slice(index, index + BATCH_SIZE)), lane === 1);
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
async function readSingle(pending, background = false) {
|
|
3629
|
+
const { spaceId, path, token } = pending;
|
|
3630
|
+
try {
|
|
3631
|
+
settle(pending, { file: await singles(() => readOne(spaceId, path, { accessToken: token }), background) });
|
|
3632
|
+
} catch (error) {
|
|
3633
|
+
settle(pending, { error });
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
async function readBatch(chunk) {
|
|
3637
|
+
const { spaceId, token } = chunk[0];
|
|
3638
|
+
let response;
|
|
3639
|
+
try {
|
|
3640
|
+
response = await withTimeout((signal) => transport.request(`/api/spaces/${spaceId}/fs/files`, {
|
|
3641
|
+
method: "POST",
|
|
3642
|
+
headers: { "Content-Type": "application/json" },
|
|
3643
|
+
body: JSON.stringify({ paths: chunk.map((pending) => pending.path) }),
|
|
3644
|
+
accessToken: token,
|
|
3645
|
+
signal
|
|
3646
|
+
}));
|
|
3647
|
+
} catch {
|
|
3648
|
+
await Promise.all(chunk.map((pending) => readSingle(pending)));
|
|
3649
|
+
return;
|
|
3650
|
+
}
|
|
3651
|
+
const byPath = new Map(chunk.map((pending) => [pending.path, pending]));
|
|
3652
|
+
const take = (path) => {
|
|
3653
|
+
const pending = byPath.get(path);
|
|
3654
|
+
byPath.delete(path);
|
|
3655
|
+
return pending;
|
|
3656
|
+
};
|
|
3657
|
+
for (const file of [...response.files, ...response.preparing ?? []]) {
|
|
3658
|
+
const pending = take(file.path);
|
|
3659
|
+
if (pending) settle(pending, { file });
|
|
3660
|
+
}
|
|
3661
|
+
for (const { path, status, code, message } of response.errors) {
|
|
3662
|
+
const pending = take(path);
|
|
3663
|
+
if (pending) settle(pending, { error: new HttpError(message, status, {
|
|
3664
|
+
code,
|
|
3665
|
+
message
|
|
3666
|
+
}) });
|
|
3667
|
+
}
|
|
3668
|
+
await Promise.all([...byPath.values()].map((pending) => readSingle(pending)));
|
|
3669
|
+
}
|
|
3670
|
+
function share(token, spaceId, path, background) {
|
|
3671
|
+
const key = `${spaceId}\u0000${path}\u0000${token}`;
|
|
3672
|
+
let pending = inFlight.get(key) ?? queues[0].get(key);
|
|
3673
|
+
const deferred = queues[1].get(key);
|
|
3674
|
+
if (!pending && deferred && !background) {
|
|
3675
|
+
queues[1].delete(key);
|
|
3676
|
+
queues[0].set(key, deferred);
|
|
3677
|
+
schedule(0);
|
|
3678
|
+
}
|
|
3679
|
+
pending ??= deferred;
|
|
3680
|
+
if (!pending) {
|
|
3681
|
+
let resolve;
|
|
3682
|
+
let reject;
|
|
3683
|
+
const promise = new Promise((done, fail) => {
|
|
3684
|
+
resolve = done;
|
|
3685
|
+
reject = fail;
|
|
3686
|
+
});
|
|
3687
|
+
const lane = background ? 1 : 0;
|
|
3688
|
+
pending = {
|
|
3689
|
+
key,
|
|
3690
|
+
token,
|
|
3691
|
+
spaceId,
|
|
3692
|
+
path,
|
|
3693
|
+
promise,
|
|
3694
|
+
resolve,
|
|
3695
|
+
reject
|
|
3696
|
+
};
|
|
3697
|
+
queues[lane].set(key, pending);
|
|
3698
|
+
schedule(lane);
|
|
3699
|
+
}
|
|
3700
|
+
return pending.promise;
|
|
3701
|
+
}
|
|
3702
|
+
return {
|
|
3703
|
+
async read(spaceId, path, options = {}) {
|
|
3704
|
+
const own = options.token === void 0;
|
|
3705
|
+
const token = own ? await transport.credential() : options.token ?? null;
|
|
3706
|
+
try {
|
|
3707
|
+
return await abortable(share(token, spaceId, path, options.background === true), options.signal);
|
|
3708
|
+
} catch (error) {
|
|
3709
|
+
if (!own || !isUnauthorized(error)) throw error;
|
|
3710
|
+
return abortable(readOne(spaceId, path), options.signal);
|
|
3711
|
+
}
|
|
3712
|
+
},
|
|
3713
|
+
invalidate(spaceId, paths) {
|
|
3714
|
+
const stale = new Set([...paths].map((path) => `${spaceId}\u0000${path}`));
|
|
3715
|
+
for (const key of inFlight.keys()) if (stale.has(key.slice(0, key.lastIndexOf("\0")))) inFlight.delete(key);
|
|
3716
|
+
}
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3719
|
+
const isUnauthorized = (error) => error instanceof HttpError && error.status === 401;
|
|
3720
|
+
async function withTimeout(run) {
|
|
3721
|
+
const controller = new AbortController();
|
|
3722
|
+
const timer = setTimeout(() => controller.abort(new DOMException("Space file read timed out", "TimeoutError")), READ_TIMEOUT_MS);
|
|
3723
|
+
try {
|
|
3724
|
+
return await run(controller.signal);
|
|
3725
|
+
} finally {
|
|
3726
|
+
clearTimeout(timer);
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
function abortable(promise, signal) {
|
|
3730
|
+
if (!signal) return promise;
|
|
3731
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
3732
|
+
return new Promise((resolve, reject) => {
|
|
3733
|
+
const abort = () => reject(signal.reason);
|
|
3734
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
3735
|
+
promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
3736
|
+
});
|
|
3737
|
+
}
|
|
3738
|
+
//#endregion
|
|
3739
|
+
//#region src/indexeddb.ts
|
|
3740
|
+
const OPEN_TIMEOUT_MS = 3e3;
|
|
3741
|
+
const connections = /* @__PURE__ */ new Map();
|
|
3742
|
+
function open(name, version, upgrade) {
|
|
3743
|
+
const existing = connections.get(name);
|
|
3744
|
+
if (existing) return existing;
|
|
3745
|
+
const opening = new Promise((resolve, reject) => {
|
|
3746
|
+
const factory = globalThis.indexedDB;
|
|
3747
|
+
if (!factory) {
|
|
3748
|
+
reject(/* @__PURE__ */ new Error("IndexedDB is unavailable."));
|
|
3749
|
+
return;
|
|
3750
|
+
}
|
|
3751
|
+
let late = false;
|
|
3752
|
+
const timer = setTimeout(() => {
|
|
3753
|
+
late = true;
|
|
3754
|
+
reject(/* @__PURE__ */ new Error("IndexedDB did not open in time."));
|
|
3755
|
+
}, OPEN_TIMEOUT_MS);
|
|
3756
|
+
const request = factory.open(name, version);
|
|
3757
|
+
request.onupgradeneeded = () => upgrade(request.result);
|
|
3758
|
+
request.onsuccess = () => {
|
|
3759
|
+
clearTimeout(timer);
|
|
3760
|
+
const db = request.result;
|
|
3761
|
+
if (late) {
|
|
3762
|
+
db.close();
|
|
3763
|
+
return;
|
|
3764
|
+
}
|
|
3765
|
+
const forget = () => {
|
|
3766
|
+
if (connections.get(name) === opening) connections.delete(name);
|
|
3767
|
+
};
|
|
3768
|
+
db.onversionchange = () => {
|
|
3769
|
+
db.close();
|
|
3770
|
+
forget();
|
|
3771
|
+
};
|
|
3772
|
+
db.onclose = forget;
|
|
3773
|
+
resolve(db);
|
|
3774
|
+
};
|
|
3775
|
+
request.onerror = () => {
|
|
3776
|
+
clearTimeout(timer);
|
|
3777
|
+
reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB failed to open."));
|
|
3778
|
+
};
|
|
3779
|
+
});
|
|
3780
|
+
connections.set(name, opening);
|
|
3781
|
+
opening.catch(() => {
|
|
3782
|
+
if (connections.get(name) === opening) connections.delete(name);
|
|
3783
|
+
});
|
|
3784
|
+
return opening;
|
|
3785
|
+
}
|
|
3786
|
+
function indexedDb(name, version, upgrade) {
|
|
3787
|
+
async function transact(stores, mode, work, retry = true) {
|
|
3788
|
+
const db = await open(name, version, upgrade);
|
|
3789
|
+
let tx;
|
|
3790
|
+
try {
|
|
3791
|
+
tx = db.transaction(stores, mode);
|
|
3792
|
+
} catch (error) {
|
|
3793
|
+
if (!retry) throw error;
|
|
3794
|
+
db.close();
|
|
3795
|
+
connections.delete(name);
|
|
3796
|
+
return transact(stores, mode, work, false);
|
|
3797
|
+
}
|
|
3798
|
+
return new Promise((resolve, reject) => {
|
|
3799
|
+
const result = work(tx);
|
|
3800
|
+
tx.oncomplete = () => resolve(result());
|
|
3801
|
+
tx.onerror = () => reject(tx.error ?? /* @__PURE__ */ new Error("IndexedDB transaction failed."));
|
|
3802
|
+
tx.onabort = () => reject(tx.error ?? /* @__PURE__ */ new Error("IndexedDB transaction aborted."));
|
|
3803
|
+
});
|
|
3804
|
+
}
|
|
3805
|
+
return { transact: (stores, mode, work) => transact(stores, mode, work) };
|
|
3806
|
+
}
|
|
3807
|
+
function deleteByIndex(store, index, key) {
|
|
3808
|
+
const request = store.index(index).openKeyCursor(IDBKeyRange.only(key));
|
|
3809
|
+
request.onsuccess = () => {
|
|
3810
|
+
const cursor = request.result;
|
|
3811
|
+
if (!cursor) return;
|
|
3812
|
+
store.delete(cursor.primaryKey);
|
|
3813
|
+
cursor.continue();
|
|
3814
|
+
};
|
|
3815
|
+
}
|
|
3816
|
+
//#endregion
|
|
3817
|
+
//#region src/apis/space-file-url-store.ts
|
|
3818
|
+
const DATABASE = "cohub-file-urls";
|
|
3819
|
+
const STORE = "urls";
|
|
3820
|
+
const MAX_ENTRIES = 2e4;
|
|
3821
|
+
const TRIM_TO = 18e3;
|
|
3822
|
+
const TRIM_EVERY_WRITES = 500;
|
|
3823
|
+
const database = indexedDb(DATABASE, 1, (db) => {
|
|
3824
|
+
const store = db.createObjectStore(STORE, { keyPath: "key" });
|
|
3825
|
+
store.createIndex("scope", "scope");
|
|
3826
|
+
store.createIndex("verifiedAt", "verifiedAt");
|
|
3827
|
+
});
|
|
3828
|
+
const recordKey = (scope, spaceId, path) => `${scope}\u0000${spaceId}\u0000${path}`;
|
|
3829
|
+
let writes = 0;
|
|
3830
|
+
const fileUrlStore = {
|
|
3831
|
+
read(scope, spaceId, paths) {
|
|
3832
|
+
return database.transact([STORE], "readonly", (tx) => {
|
|
3833
|
+
const store = tx.objectStore(STORE);
|
|
3834
|
+
const requests = paths.map((path) => [path, store.get(recordKey(scope, spaceId, path))]);
|
|
3835
|
+
return () => {
|
|
3836
|
+
const found = /* @__PURE__ */ new Map();
|
|
3837
|
+
for (const [path, request] of requests) {
|
|
3838
|
+
const record = request.result;
|
|
3839
|
+
if (record) found.set(path, {
|
|
3840
|
+
url: record.url,
|
|
3841
|
+
verifiedAt: record.verifiedAt
|
|
3842
|
+
});
|
|
3843
|
+
}
|
|
3844
|
+
return found;
|
|
3845
|
+
};
|
|
3846
|
+
});
|
|
3847
|
+
},
|
|
3848
|
+
async write(scope, spaceId, changes) {
|
|
3849
|
+
await database.transact([STORE], "readwrite", (tx) => {
|
|
3850
|
+
const store = tx.objectStore(STORE);
|
|
3851
|
+
for (const [path, { url, verifiedAt }] of changes.put) store.put({
|
|
3852
|
+
key: recordKey(scope, spaceId, path),
|
|
3853
|
+
scope,
|
|
3854
|
+
url,
|
|
3855
|
+
verifiedAt
|
|
3856
|
+
});
|
|
3857
|
+
for (const path of changes.remove) store.delete(recordKey(scope, spaceId, path));
|
|
3858
|
+
return () => void 0;
|
|
3859
|
+
});
|
|
3860
|
+
writes += changes.put.size;
|
|
3861
|
+
if (writes < TRIM_EVERY_WRITES) return;
|
|
3862
|
+
writes = 0;
|
|
3863
|
+
await trim();
|
|
3864
|
+
}
|
|
3865
|
+
};
|
|
3866
|
+
function trim() {
|
|
3867
|
+
return database.transact([STORE], "readwrite", (tx) => {
|
|
3868
|
+
const store = tx.objectStore(STORE);
|
|
3869
|
+
const count = store.count();
|
|
3870
|
+
count.onsuccess = () => {
|
|
3871
|
+
let excess = count.result > MAX_ENTRIES ? count.result - TRIM_TO : 0;
|
|
3872
|
+
if (excess === 0) return;
|
|
3873
|
+
const cursor = store.index("verifiedAt").openKeyCursor();
|
|
3874
|
+
cursor.onsuccess = () => {
|
|
3875
|
+
const current = cursor.result;
|
|
3876
|
+
if (!current || excess-- <= 0) return;
|
|
3877
|
+
store.delete(current.primaryKey);
|
|
3878
|
+
current.continue();
|
|
3879
|
+
};
|
|
3880
|
+
};
|
|
3881
|
+
return () => void 0;
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3884
|
+
async function clearFileUrlCache(options = {}) {
|
|
3885
|
+
if (!globalThis.indexedDB) return;
|
|
3886
|
+
await database.transact([STORE], "readwrite", (tx) => {
|
|
3887
|
+
const store = tx.objectStore(STORE);
|
|
3888
|
+
if (options.scope === void 0) store.clear();
|
|
3889
|
+
else deleteByIndex(store, "scope", options.scope);
|
|
3890
|
+
return () => void 0;
|
|
3891
|
+
});
|
|
3892
|
+
}
|
|
3893
|
+
//#endregion
|
|
3894
|
+
//#region src/apis/space-file-urls.ts
|
|
3895
|
+
const MEMORY_LIMIT = 4096;
|
|
3896
|
+
const INLINE_MEMORY_MAX_CHARS = 262144;
|
|
3897
|
+
const PREPARING_CONCURRENCY = 4;
|
|
3898
|
+
const PREPARING_TIMEOUT_MS = 15e3;
|
|
3899
|
+
/** Well inside the fs CDN manifest's 6.5 days; CDN URLs carry the file version and are not signed. */
|
|
3900
|
+
const STORED_MAX_AGE_MS = 2592e5;
|
|
3901
|
+
const UNWATCHED_TRUST_MS = 6e4;
|
|
3902
|
+
function inlineFileDataUrl(file) {
|
|
3903
|
+
const mimeType = file.mimeType ?? "application/octet-stream";
|
|
3904
|
+
return file.encoding === "base64" ? `data:${mimeType};base64,${file.content}` : `data:${mimeType};charset=utf-8,${encodeURIComponent(file.content)}`;
|
|
3905
|
+
}
|
|
3906
|
+
function resolvedOf(file) {
|
|
3907
|
+
if (file.delivery === "url" && file.url) return {
|
|
3908
|
+
url: file.url,
|
|
3909
|
+
inline: false
|
|
3910
|
+
};
|
|
3911
|
+
return {
|
|
3912
|
+
url: inlineFileDataUrl(file),
|
|
3913
|
+
inline: true
|
|
3914
|
+
};
|
|
3915
|
+
}
|
|
3916
|
+
const isGone = (error) => error instanceof HttpError && (error.status === 404 || error.status === 410);
|
|
3917
|
+
const sleep$1 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3918
|
+
function remember(map, key, value) {
|
|
3919
|
+
map.delete(key);
|
|
3920
|
+
map.set(key, value);
|
|
3921
|
+
if (map.size > MEMORY_LIMIT) map.delete(map.keys().next().value);
|
|
3922
|
+
}
|
|
3923
|
+
function createSpaceFileUrls(options) {
|
|
3924
|
+
const { reads, cache } = options;
|
|
3925
|
+
const now = options.now ?? Date.now;
|
|
3926
|
+
const memory = /* @__PURE__ */ new Map();
|
|
3927
|
+
const invalidatedAt = /* @__PURE__ */ new Map();
|
|
3928
|
+
const epochs = /* @__PURE__ */ new Map();
|
|
3929
|
+
const serverReads = /* @__PURE__ */ new Map();
|
|
3930
|
+
const localReads = /* @__PURE__ */ new Map();
|
|
3931
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3932
|
+
const preparing = createLimiter(PREPARING_CONCURRENCY);
|
|
3933
|
+
let localBatch = null;
|
|
3934
|
+
let writes = null;
|
|
3935
|
+
let lastScope = null;
|
|
3936
|
+
const keyOf = (spaceId, path) => `${spaceId}\u0000${path}`;
|
|
3937
|
+
const epochOf = (spaceId) => epochs.get(spaceId) ?? 0;
|
|
3938
|
+
const persists = (scope) => cache && scope !== null && Boolean(globalThis.indexedDB);
|
|
3939
|
+
function persist(scope, spaceId, path, url) {
|
|
3940
|
+
if (!writes) {
|
|
3941
|
+
const batch = /* @__PURE__ */ new Map();
|
|
3942
|
+
writes = batch;
|
|
3943
|
+
setTimeout(() => {
|
|
3944
|
+
writes = null;
|
|
3945
|
+
for (const [id, { put, remove }] of batch) {
|
|
3946
|
+
const [batchScope, batchSpace] = id.split("\0");
|
|
3947
|
+
fileUrlStore.write(batchScope, batchSpace, {
|
|
3948
|
+
put,
|
|
3949
|
+
remove
|
|
3950
|
+
}).catch(() => void 0);
|
|
3951
|
+
}
|
|
3952
|
+
}, 0);
|
|
3953
|
+
}
|
|
3954
|
+
const id = `${scope}\u0000${spaceId}`;
|
|
3955
|
+
const pending = writes.get(id) ?? {
|
|
3956
|
+
put: /* @__PURE__ */ new Map(),
|
|
3957
|
+
remove: /* @__PURE__ */ new Set()
|
|
3958
|
+
};
|
|
3959
|
+
writes.set(id, pending);
|
|
3960
|
+
if (url) {
|
|
3961
|
+
pending.remove.delete(path);
|
|
3962
|
+
pending.put.set(path, {
|
|
3963
|
+
url,
|
|
3964
|
+
verifiedAt: now()
|
|
3965
|
+
});
|
|
3966
|
+
} else {
|
|
3967
|
+
pending.put.delete(path);
|
|
3968
|
+
pending.remove.add(path);
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
async function load(token, spaceId, path, background) {
|
|
3972
|
+
const first = await reads.read(spaceId, path, {
|
|
3973
|
+
background,
|
|
3974
|
+
token
|
|
3975
|
+
});
|
|
3976
|
+
if ("content" in first) return resolvedOf(first);
|
|
3977
|
+
return preparing(async () => {
|
|
3978
|
+
const deadline = now() + PREPARING_TIMEOUT_MS;
|
|
3979
|
+
let file = first;
|
|
3980
|
+
while (!("content" in file)) {
|
|
3981
|
+
const wait = Math.min(Math.max(250, Math.min(file.retryAfterMs, 2e3)), deadline - now());
|
|
3982
|
+
if (wait <= 0) return null;
|
|
3983
|
+
await sleep$1(wait);
|
|
3984
|
+
file = await reads.read(spaceId, path, { token });
|
|
3985
|
+
}
|
|
3986
|
+
return resolvedOf(file);
|
|
3987
|
+
}, background);
|
|
3988
|
+
}
|
|
3989
|
+
function server(token, scope, spaceId, path, background) {
|
|
3990
|
+
const key = keyOf(spaceId, path);
|
|
3991
|
+
const readKey = `${key}\u0000${token}`;
|
|
3992
|
+
const running = serverReads.get(readKey);
|
|
3993
|
+
if (running) {
|
|
3994
|
+
if (!background) reads.read(spaceId, path, { token }).catch(() => void 0);
|
|
3995
|
+
return running;
|
|
3996
|
+
}
|
|
3997
|
+
const epoch = epochOf(spaceId);
|
|
3998
|
+
const read = load(token, spaceId, path, background).then((resolved) => {
|
|
3999
|
+
const current = serverReads.get(readKey) === read;
|
|
4000
|
+
if (current) serverReads.delete(readKey);
|
|
4001
|
+
if (!resolved) return null;
|
|
4002
|
+
const entry = {
|
|
4003
|
+
...resolved,
|
|
4004
|
+
scope,
|
|
4005
|
+
epoch,
|
|
4006
|
+
verifiedAt: now()
|
|
4007
|
+
};
|
|
4008
|
+
if (current && cache) adopt(key, spaceId, path, entry);
|
|
4009
|
+
return entry;
|
|
4010
|
+
}, (error) => {
|
|
4011
|
+
const current = serverReads.get(readKey) === read;
|
|
4012
|
+
if (current) serverReads.delete(readKey);
|
|
4013
|
+
if (current && cache && isGone(error)) {
|
|
4014
|
+
memory.delete(key);
|
|
4015
|
+
if (persists(scope)) persist(scope, spaceId, path, null);
|
|
4016
|
+
}
|
|
4017
|
+
throw error;
|
|
4018
|
+
});
|
|
4019
|
+
serverReads.set(readKey, read);
|
|
4020
|
+
return read;
|
|
4021
|
+
}
|
|
4022
|
+
function adopt(key, spaceId, path, entry) {
|
|
4023
|
+
const previous = memory.get(key);
|
|
4024
|
+
if (!entry.inline || entry.url.length <= INLINE_MEMORY_MAX_CHARS) remember(memory, key, entry);
|
|
4025
|
+
if (!entry.inline && persists(entry.scope)) persist(entry.scope, spaceId, path, entry.url);
|
|
4026
|
+
if (previous && previous.url !== entry.url) emit(spaceId, path);
|
|
4027
|
+
}
|
|
4028
|
+
function revalidate(token, scope, spaceId, path) {
|
|
4029
|
+
server(token, scope, spaceId, path, true).catch(() => void 0);
|
|
4030
|
+
}
|
|
4031
|
+
function readLocal(token, scope, spaceId, path) {
|
|
4032
|
+
const key = keyOf(spaceId, path);
|
|
4033
|
+
const running = localReads.get(key);
|
|
4034
|
+
if (running) return running;
|
|
4035
|
+
let resolve;
|
|
4036
|
+
const read = new Promise((done) => {
|
|
4037
|
+
resolve = done;
|
|
4038
|
+
});
|
|
4039
|
+
const forget = () => {
|
|
4040
|
+
if (localReads.get(key) === read) localReads.delete(key);
|
|
4041
|
+
};
|
|
4042
|
+
read.then(forget, forget);
|
|
4043
|
+
localReads.set(key, read);
|
|
4044
|
+
if (!localBatch) {
|
|
4045
|
+
const batch = [];
|
|
4046
|
+
localBatch = batch;
|
|
4047
|
+
queueMicrotask(() => {
|
|
4048
|
+
localBatch = null;
|
|
4049
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4050
|
+
for (const item of batch) {
|
|
4051
|
+
const id = `${item.scope}\u0000${item.spaceId}`;
|
|
4052
|
+
const group = groups.get(id);
|
|
4053
|
+
if (group) group.push(item);
|
|
4054
|
+
else groups.set(id, [item]);
|
|
4055
|
+
}
|
|
4056
|
+
for (const group of groups.values()) readStored(group);
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
localBatch.push({
|
|
4060
|
+
key,
|
|
4061
|
+
token,
|
|
4062
|
+
scope,
|
|
4063
|
+
spaceId,
|
|
4064
|
+
path,
|
|
4065
|
+
resolve
|
|
4066
|
+
});
|
|
4067
|
+
return read;
|
|
4068
|
+
}
|
|
4069
|
+
async function readStored(group) {
|
|
4070
|
+
const { scope, spaceId } = group[0];
|
|
4071
|
+
const records = await fileUrlStore.read(scope, spaceId, group.map((item) => item.path)).catch(() => /* @__PURE__ */ new Map());
|
|
4072
|
+
for (const { key, token, path, resolve } of group) {
|
|
4073
|
+
const record = records.get(path);
|
|
4074
|
+
if (!(record && now() - record.verifiedAt <= STORED_MAX_AGE_MS && (invalidatedAt.get(key) ?? -1) < record.verifiedAt)) {
|
|
4075
|
+
resolve(server(token, scope, spaceId, path, false));
|
|
4076
|
+
continue;
|
|
4077
|
+
}
|
|
4078
|
+
const entry = {
|
|
4079
|
+
url: record.url,
|
|
4080
|
+
inline: false,
|
|
4081
|
+
scope,
|
|
4082
|
+
epoch: -1,
|
|
4083
|
+
verifiedAt: record.verifiedAt
|
|
4084
|
+
};
|
|
4085
|
+
if (!memory.has(key)) remember(memory, key, entry);
|
|
4086
|
+
revalidate(token, scope, spaceId, path);
|
|
4087
|
+
resolve(Promise.resolve(entry));
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
function emit(spaceId, path) {
|
|
4091
|
+
for (const listener of listeners) listener(spaceId, path);
|
|
4092
|
+
}
|
|
4093
|
+
function invalidate(spaceId, paths) {
|
|
4094
|
+
const stale = [...new Set(paths)];
|
|
4095
|
+
if (stale.length === 0) return;
|
|
4096
|
+
reads.invalidate(spaceId, stale);
|
|
4097
|
+
const at = now();
|
|
4098
|
+
const buffered = lastScope === null ? void 0 : writes?.get(`${lastScope}\u0000${spaceId}`);
|
|
4099
|
+
const keys = /* @__PURE__ */ new Set();
|
|
4100
|
+
for (const path of stale) {
|
|
4101
|
+
const key = keyOf(spaceId, path);
|
|
4102
|
+
keys.add(key);
|
|
4103
|
+
memory.delete(key);
|
|
4104
|
+
localReads.delete(key);
|
|
4105
|
+
remember(invalidatedAt, key, at);
|
|
4106
|
+
buffered?.put.delete(path);
|
|
4107
|
+
}
|
|
4108
|
+
for (const readKey of serverReads.keys()) if (keys.has(readKey.slice(0, readKey.lastIndexOf("\0")))) serverReads.delete(readKey);
|
|
4109
|
+
if (persists(lastScope)) fileUrlStore.write(lastScope, spaceId, {
|
|
4110
|
+
put: /* @__PURE__ */ new Map(),
|
|
4111
|
+
remove: stale
|
|
4112
|
+
}).catch(() => void 0);
|
|
4113
|
+
}
|
|
4114
|
+
return {
|
|
4115
|
+
async resolve(spaceId, path, request = {}) {
|
|
4116
|
+
const purpose = request.purpose ?? "preview";
|
|
4117
|
+
const token = await options.credential();
|
|
4118
|
+
const scope = cache ? await options.scope().catch(() => null) : null;
|
|
4119
|
+
if (scope !== null) lastScope = scope;
|
|
4120
|
+
const key = keyOf(spaceId, path);
|
|
4121
|
+
const hit = cache && !request.fresh ? memory.get(key) : void 0;
|
|
4122
|
+
let entry;
|
|
4123
|
+
if (hit && hit.scope === scope) {
|
|
4124
|
+
remember(memory, key, hit);
|
|
4125
|
+
const unwatched = !options.watched(spaceId) && now() - hit.verifiedAt > UNWATCHED_TRUST_MS;
|
|
4126
|
+
if (hit.epoch !== epochOf(spaceId) || unwatched) revalidate(token, scope, spaceId, path);
|
|
4127
|
+
entry = hit;
|
|
4128
|
+
} else entry = await settle((persists(scope) && !request.fresh ? readLocal(token, scope, spaceId, path) : server(token, scope, spaceId, path, false)).catch(async (error) => {
|
|
4129
|
+
if (!isUnauthorized(error)) throw error;
|
|
4130
|
+
await reads.read(spaceId, path, { signal: request.signal });
|
|
4131
|
+
request.signal?.throwIfAborted();
|
|
4132
|
+
return server(await options.credential(), scope, spaceId, path, false);
|
|
4133
|
+
}), request);
|
|
4134
|
+
return !entry || purpose === "playback" && entry.inline ? null : entry.url;
|
|
4135
|
+
},
|
|
4136
|
+
applyFsChange(spaceId, payload) {
|
|
4137
|
+
if (payload.resync) epochs.set(spaceId, epochOf(spaceId) + 1);
|
|
4138
|
+
const paths = /* @__PURE__ */ new Set();
|
|
4139
|
+
for (const change of payload.changes ?? []) {
|
|
4140
|
+
if (change.path) paths.add(change.path);
|
|
4141
|
+
if (change.oldPath) paths.add(change.oldPath);
|
|
4142
|
+
}
|
|
4143
|
+
invalidate(spaceId, paths);
|
|
4144
|
+
for (const path of paths) emit(spaceId, path);
|
|
4145
|
+
},
|
|
4146
|
+
subscribe(listener) {
|
|
4147
|
+
listeners.add(listener);
|
|
4148
|
+
return () => {
|
|
4149
|
+
listeners.delete(listener);
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
4152
|
+
};
|
|
4153
|
+
}
|
|
4154
|
+
function createSpaceFileServices(transport, websocket) {
|
|
4155
|
+
const reads = createSpaceFileReads(transport);
|
|
4156
|
+
const urls = createSpaceFileUrls({
|
|
4157
|
+
reads,
|
|
4158
|
+
credential: () => transport.credential(),
|
|
4159
|
+
scope: () => transport.cacheScope(),
|
|
4160
|
+
watched: (spaceId) => websocket?.isRoomLive(getRealtimeSpaceRoom(spaceId)) ?? false,
|
|
4161
|
+
cache: websocket !== null && typeof window !== "undefined"
|
|
4162
|
+
});
|
|
4163
|
+
websocket?.on("event", (event) => {
|
|
4164
|
+
if (event.type === "space.fs.changed" && event.spaceId) urls.applyFsChange(event.spaceId, event.payload);
|
|
4165
|
+
});
|
|
4166
|
+
return {
|
|
4167
|
+
reads,
|
|
4168
|
+
urls
|
|
4169
|
+
};
|
|
4170
|
+
}
|
|
4171
|
+
function settle(read, { timeoutMs = PREPARING_TIMEOUT_MS, signal }) {
|
|
4172
|
+
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
4173
|
+
return new Promise((resolve, reject) => {
|
|
4174
|
+
const timer = setTimeout(() => finish(() => resolve(null)), Math.max(0, timeoutMs));
|
|
4175
|
+
const abort = () => finish(() => reject(signal?.reason));
|
|
4176
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
4177
|
+
function finish(done) {
|
|
4178
|
+
clearTimeout(timer);
|
|
4179
|
+
signal?.removeEventListener("abort", abort);
|
|
4180
|
+
done();
|
|
4181
|
+
}
|
|
4182
|
+
read.then((value) => finish(() => resolve(value)), (error) => finish(() => reject(error)));
|
|
4183
|
+
});
|
|
4184
|
+
}
|
|
4185
|
+
//#endregion
|
|
3546
4186
|
//#region src/apis/spaces.ts
|
|
3547
4187
|
async function fetchTurnObject(url, fetchImpl = globalThis.fetch, signal) {
|
|
3548
4188
|
const response = await fetchImpl(url, { signal });
|
|
@@ -3671,10 +4311,6 @@ var SpacesApi = class {
|
|
|
3671
4311
|
});
|
|
3672
4312
|
}
|
|
3673
4313
|
};
|
|
3674
|
-
function inlineFileDataUrl(file) {
|
|
3675
|
-
const mimeType = file.mimeType ?? "application/octet-stream";
|
|
3676
|
-
return file.encoding === "base64" ? `data:${mimeType};base64,${file.content}` : `data:${mimeType};charset=utf-8,${encodeURIComponent(file.content)}`;
|
|
3677
|
-
}
|
|
3678
4314
|
function spaceFileAbortReason(signal) {
|
|
3679
4315
|
return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
3680
4316
|
}
|
|
@@ -3748,22 +4384,34 @@ var SpacePublicFilesApi = class {
|
|
|
3748
4384
|
var SpaceFilesApi = class {
|
|
3749
4385
|
transport;
|
|
3750
4386
|
spaceId;
|
|
3751
|
-
|
|
4387
|
+
services;
|
|
4388
|
+
constructor(transport, spaceId, services) {
|
|
3752
4389
|
this.transport = transport;
|
|
3753
4390
|
this.spaceId = spaceId;
|
|
4391
|
+
this.services = services;
|
|
3754
4392
|
}
|
|
3755
4393
|
list(path = "", options) {
|
|
3756
4394
|
return listDirectory(this.transport, `/api/spaces/${this.spaceId}/fs/tree`, path, options);
|
|
3757
4395
|
}
|
|
3758
4396
|
read(path, customFetch, signal) {
|
|
3759
|
-
|
|
3760
|
-
return this.transport.requestRevalidated(`/api/spaces/${this.spaceId}/fs/file?${params.toString()}`, {
|
|
4397
|
+
if (customFetch) return this.transport.requestRevalidated(fileReadPath(this.spaceId, path), {
|
|
3761
4398
|
fetch: customFetch,
|
|
3762
4399
|
signal
|
|
3763
4400
|
});
|
|
4401
|
+
return this.services.reads.read(this.spaceId, path, { signal });
|
|
3764
4402
|
}
|
|
3765
4403
|
/** Resolve a browser-ready file URL, waiting for CDN delivery when necessary. */
|
|
3766
|
-
|
|
4404
|
+
resolveUrl(path, options = {}) {
|
|
4405
|
+
const { fetch: customFetch, ...request } = options;
|
|
4406
|
+
if (customFetch) return this.resolveUrlThrough(path, customFetch, options);
|
|
4407
|
+
return this.services.urls.resolve(this.spaceId, path, request);
|
|
4408
|
+
}
|
|
4409
|
+
onUrlChange(listener) {
|
|
4410
|
+
return this.services.urls.subscribe((spaceId, path) => {
|
|
4411
|
+
if (spaceId === this.spaceId) listener(path);
|
|
4412
|
+
});
|
|
4413
|
+
}
|
|
4414
|
+
async resolveUrlThrough(path, customFetch, options) {
|
|
3767
4415
|
const purpose = options.purpose ?? "preview";
|
|
3768
4416
|
const requestedTimeoutMs = options.timeoutMs ?? 15e3;
|
|
3769
4417
|
const timeoutMs = Number.isFinite(requestedTimeoutMs) ? Math.max(0, requestedTimeoutMs) : 15e3;
|
|
@@ -3771,7 +4419,7 @@ var SpaceFilesApi = class {
|
|
|
3771
4419
|
const deadline = createSpaceFileDeadlineSignal(options.signal, timeoutMs);
|
|
3772
4420
|
try {
|
|
3773
4421
|
while (true) {
|
|
3774
|
-
const file = await this.read(path,
|
|
4422
|
+
const file = await this.read(path, customFetch, deadline.signal);
|
|
3775
4423
|
if (deadline.timedOut()) return null;
|
|
3776
4424
|
if ("content" in file) {
|
|
3777
4425
|
if (file.delivery === "url" && file.url) return file.url;
|
|
@@ -4049,14 +4697,9 @@ var SessionTurnsClient = class {
|
|
|
4049
4697
|
});
|
|
4050
4698
|
}
|
|
4051
4699
|
};
|
|
4052
|
-
/**
|
|
4053
|
-
* Routes a rejected subscription for `room` (for example a missing
|
|
4054
|
-
* `space.view` permission) to `onRejected`. Without this an unauthorized
|
|
4055
|
-
* subscriber would simply never receive events, with no way to tell.
|
|
4056
|
-
*/
|
|
4057
4700
|
const onRoomRejected = (websocketClient, room, onRejected) => websocketClient.on("event", (event) => {
|
|
4058
4701
|
if (event.type !== "system.subscribe.error") return;
|
|
4059
|
-
if (event.payload.rejected?.some((entry) => entry.room === room)) onRejected(event);
|
|
4702
|
+
if (event.payload.rejected?.some((entry) => entry.room === room && entry.code !== "UNAVAILABLE")) onRejected(event);
|
|
4060
4703
|
});
|
|
4061
4704
|
var SessionRealtimeClient = class {
|
|
4062
4705
|
websocketClient;
|
|
@@ -4314,14 +4957,6 @@ var SpaceEventsApi = class {
|
|
|
4314
4957
|
handler(event);
|
|
4315
4958
|
return;
|
|
4316
4959
|
}
|
|
4317
|
-
if (type === "board.changed" && event.type === "board.changed") {
|
|
4318
|
-
handler(event);
|
|
4319
|
-
return;
|
|
4320
|
-
}
|
|
4321
|
-
if (type === "board.playback.changed" && event.type === "board.playback.changed") {
|
|
4322
|
-
handler(event);
|
|
4323
|
-
return;
|
|
4324
|
-
}
|
|
4325
4960
|
if (type === "app.version.published" && event.type === "app.version.published") {
|
|
4326
4961
|
handler(event);
|
|
4327
4962
|
return;
|
|
@@ -4844,11 +5479,11 @@ var SpaceClient = class {
|
|
|
4844
5479
|
boards;
|
|
4845
5480
|
wallet;
|
|
4846
5481
|
commerce;
|
|
4847
|
-
constructor(id, transport, websocketClient) {
|
|
5482
|
+
constructor(id, transport, websocketClient, fileServices) {
|
|
4848
5483
|
this.id = id;
|
|
4849
5484
|
this.transport = transport;
|
|
4850
5485
|
this.websocketClient = websocketClient;
|
|
4851
|
-
this.files = new SpaceFilesApi(transport, id);
|
|
5486
|
+
this.files = new SpaceFilesApi(transport, id, fileServices);
|
|
4852
5487
|
this.publicFiles = new SpacePublicFilesApi(transport, id);
|
|
4853
5488
|
this.sessions = new SpaceSessionsApi(transport, id, websocketClient);
|
|
4854
5489
|
this.turns = new SpaceTurnsApi(transport, id);
|
|
@@ -5812,6 +6447,7 @@ var CohubHttpClient = class {
|
|
|
5812
6447
|
return this.desktop;
|
|
5813
6448
|
}
|
|
5814
6449
|
transport;
|
|
6450
|
+
fileServices;
|
|
5815
6451
|
constructor(options = {}) {
|
|
5816
6452
|
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
5817
6453
|
const executionToken = resolveExecutionToken();
|
|
@@ -5820,6 +6456,7 @@ var CohubHttpClient = class {
|
|
|
5820
6456
|
getAccessToken: () => executionToken
|
|
5821
6457
|
};
|
|
5822
6458
|
this.transport = new HttpTransport(resolvedOptions);
|
|
6459
|
+
this.fileServices = createSpaceFileServices(this.transport, null);
|
|
5823
6460
|
this.spaces = new SpacesApi(this.transport);
|
|
5824
6461
|
this.channels = new ChannelsApi(this.transport);
|
|
5825
6462
|
this.user = new UserApi(this.transport, apiBaseUrl, options.setStoredAuthToken, options.clearStoredAuthToken);
|
|
@@ -5842,9 +6479,9 @@ var CohubHttpClient = class {
|
|
|
5842
6479
|
this.desktop = new DesktopCommandsApi(this.transport);
|
|
5843
6480
|
}
|
|
5844
6481
|
space(spaceId) {
|
|
5845
|
-
return new SpaceClient(spaceId, this.transport, null);
|
|
6482
|
+
return new SpaceClient(spaceId, this.transport, null, this.fileServices);
|
|
5846
6483
|
}
|
|
5847
6484
|
};
|
|
5848
6485
|
const createHttpClient = (options) => new CohubHttpClient(options);
|
|
5849
6486
|
//#endregion
|
|
5850
|
-
export {
|
|
6487
|
+
export { defaultDesktopCommandTimeoutMs as $, readRtcTransportStats as A, ModelsApi as B, createSessionGenerationStreamClient as C, BoardVec2Schema as Ct, SpaceDisplaysApi as D, normalizeBoardRemoteUrl as Dt, createSessionPatchReducer as E, parseBoardItem as Et, ReferencesApi as F, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS as G, CronJobsApi as H, SearchApi as I, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES as J, DESKTOP_COMMAND_MAX_TIMEOUT_MS as K, PublicAssetsApi as L, BoardClient as M, ensureRealtimeConnected as N, DisplayConnection as O, BOARD_ARROW_STROKE_SIZE as Ot, SessionAccessApi as P, DESKTOP_COMMAND_VERSION as Q, SkillsApi as R, SessionGenerationStreamClient as S, BoardTrackSchema as St, SessionPatchReducer as T, isBuiltinItemType as Tt, ChannelsApi as U, GenerationsApi as V, DESKTOP_COMMAND_ACCEPT_TIMEOUT_MS as W, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS as X, DESKTOP_COMMAND_PENDING_TTL_SECONDS as Y, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS as Z, createSpaceFileServices as _, BoardAnimationHeaderSchema as _t, WorkCommerceApi as a, NAVIGATION_ERROR_MESSAGE_MAX_LENGTH as at, buildSpaceInvitePath as b, BoardPatchSchema as bt, AppsApi as c, DISPLAY_INPUT_MAX_TEXT as ct, UserApi as d, splitDisplayText as dt, isDesktopCallMethod as et, TasksApi as f, BOARD_EXTENSION as ft, SpacesApi as g, BOARD_ITEM_TYPES as gt, SpaceWebhooksApi as h, BOARD_COLOR_TOKENS as ht, AppCommerceApi as i, resolveOpenSurface as it, waitForIceGathering as j, connectDisplay as k, boardDefaultSize as kt, ReferralsApi as l, DISPLAY_SYSTEM_ACTIONS as lt, SpacePublicFilesApi as m, BOARD_CAMERA_TARGET as mt, createHttpClient as n, parseDesktopCommand as nt, DesktopCommandsApi as o, DISPLAY_ELEMENT_REF_PATTERN as ot, SpaceClient as p, BOARD_MIME_TYPE as pt, DESKTOP_COMMAND_OPEN_TIMEOUT_MS as q, RoomsApi as r, parseDesktopCommandId as rt, UiCommandsApi as s, DISPLAY_INPUT_MAX_SCHEDULE_MS as st, CohubHttpClient as t, isTerminalDesktopCommandStatus as tt, UsersApi as u, normalizeDisplayPoint as ut, clearFileUrlCache as v, BoardAnimationSchema as vt, parseAssistantMessageCommit as w, boardItemSchema as wt, buildSpacePath as x, BoardSettingsSchema as xt, PublicInviteApi as y, BoardColorSchema as yt, PromptsApi as z };
|