@oddington/sdk 0.1.16 → 0.1.18
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 +31 -7
- package/dist/browser.js +299 -131
- package/dist/index.js +1 -1
- package/dist/types/packages/sdk/src/auth-types.d.ts +2 -0
- package/dist/types/packages/sdk/src/public.d.ts +2 -2
- package/dist/types/shared/inbox-member-contract.d.ts +8 -3
- package/dist/types/shared/inbox-member-types.d.ts +25 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,16 @@ same key and content returns the original result; changed content conflicts.
|
|
|
12
12
|
Existing Inbox-retained files remain separate from shared Files, with their
|
|
13
13
|
original identities, bytes and audience. The SDK exposes no raw-credential API.
|
|
14
14
|
|
|
15
|
+
Focused apps use `inbox.questions.list()` for `AnswerableQuestion[]`, with relevance
|
|
16
|
+
bound by the trusted host to the canonical routines in the app declaration.
|
|
17
|
+
The same list, answer and Record-search methods work in hosted and local apps.
|
|
18
|
+
`InboxItem` remains the complete first-party Inbox value. Record refs are placement
|
|
19
|
+
hints only: match them against independently authorized Data, and omit ambiguous
|
|
20
|
+
detail placement. Keep drafts on failed or uncertain acceptance, prevent duplicate
|
|
21
|
+
interaction while submitting, and dismiss a Question only after acknowledged
|
|
22
|
+
acceptance. Refresh after a conflict; later effects remain Inbox work. These
|
|
23
|
+
interaction duties belong to the consuming app's interface.
|
|
24
|
+
|
|
15
25
|
```sh
|
|
16
26
|
npm install @oddington/sdk
|
|
17
27
|
```
|
|
@@ -95,7 +105,7 @@ try {
|
|
|
95
105
|
|
|
96
106
|
os.init({ auth: browserAuth }); // Trusted workspace host startup only.
|
|
97
107
|
// Later: await browserAuth.signOut(); // Ends only this browser's session.
|
|
98
|
-
// On
|
|
108
|
+
// On document teardown (refresh, navigation or close):
|
|
99
109
|
unsubscribe();
|
|
100
110
|
browserAuth.dispose();
|
|
101
111
|
```
|
|
@@ -113,6 +123,12 @@ provider, configuration, membership list or access/refresh credential. Consumers
|
|
|
113
123
|
never construct bearer headers. Multiple handles share provider coordination on
|
|
114
124
|
the same origin; each owns its subscription/disposal independently.
|
|
115
125
|
|
|
126
|
+
`dispose()` releases that handle's subscriptions, timers, installed SDK context
|
|
127
|
+
and pending in-memory work. It does not sign out. A disposed handle cannot be
|
|
128
|
+
reused; a refreshed or reopened document creates a fresh handle and calls
|
|
129
|
+
`os.init({ auth: browserAuth })` again. Use `signOut()` to end the browser session
|
|
130
|
+
and invalidate its installed capabilities intentionally.
|
|
131
|
+
|
|
116
132
|
The hosted runtime routes that exact capability path directly to OS while the
|
|
117
133
|
app keeps its independently deployed interface and other paths. Operators must
|
|
118
134
|
activate and verify the OS route before releasing a consumer that depends on
|
|
@@ -162,7 +178,7 @@ approves a fresh request. Results are validated consent labels, safe status or a
|
|
|
162
178
|
completed `returnUrl`; Auth owns navigation. Decisions require prior inspection
|
|
163
179
|
and remain bound to the current request and account. The reserved auth origin
|
|
164
180
|
cannot obtain workspace capabilities even if a caller invokes `os.init` there.
|
|
165
|
-
Existing Inbox/customer apps and local
|
|
181
|
+
Existing Inbox/customer apps and local app hosting are separate migrations.
|
|
166
182
|
|
|
167
183
|
The same `os.init({ auth })` call also consumes authenticated bootstrap state on
|
|
168
184
|
an OS-launched IPv4 loopback origin. It checks `/_os/app/context` on that exact
|
|
@@ -222,8 +238,15 @@ cancellation requests do not acknowledge cancellation. Handles expose no IDs,
|
|
|
222
238
|
cursors, raw output or process controls. Local sign-out, expiry, context
|
|
223
239
|
replacement, origin changes and unconfirmed credential changes invalidate pending work.
|
|
224
240
|
|
|
225
|
-
The exact-origin browser credential survives reload and tab
|
|
226
|
-
storage.
|
|
241
|
+
The exact-origin browser credential survives document disposal, reload and tab
|
|
242
|
+
closure in local storage. Disposal rejects the outgoing document's pending
|
|
243
|
+
operations and disconnects its Run handles without cancelling the host-owned
|
|
244
|
+
child. Fresh initialization at the same live Local Host origin can call the
|
|
245
|
+
declared operation's `recover()` to continue that same owned Run without
|
|
246
|
+
launching it again. Old handles remain invalid. Recovery is limited to the same
|
|
247
|
+
authorized logical session, live Host and operation; it is not durable history.
|
|
248
|
+
|
|
249
|
+
Each active initialization proactively renews before expiry, normally
|
|
227
250
|
sixty seconds early with a shorter lead for shorter lifetimes. Only the exact
|
|
228
251
|
Local Host silently refreshes the secure CLI login and confirms unchanged
|
|
229
252
|
complete access. Successful renewal rotates only the bounded browser credential;
|
|
@@ -233,9 +256,10 @@ retry proactively without starting or delaying Data reads. They
|
|
|
233
256
|
confirm the stored winner against the same Host and logical session before
|
|
234
257
|
silently adopting it. Rotation preserves admitted Data/results and owned runs;
|
|
235
258
|
old credentials cannot admit new requests and failed writes, launches and
|
|
236
|
-
cancellations are never replayed.
|
|
237
|
-
renewal, including pending response bodies.
|
|
238
|
-
|
|
259
|
+
cancellations are never replayed. Disposal, sign-out, replacement and expiry
|
|
260
|
+
cancel renewal, including pending response bodies. Unlike disposal, sign-out,
|
|
261
|
+
expiry, confirmed revocation and host stop end local access. Host expiry and
|
|
262
|
+
current membership remain authoritative; stored state cannot revive a stopped host. Recovery
|
|
239
263
|
covers only the operation's current logical session and bounded terminal grace,
|
|
240
264
|
without relaunching or durable history.
|
|
241
265
|
Supplied File inputs currently reject `execution_unavailable` before launch;
|
package/dist/browser.js
CHANGED
|
@@ -1782,9 +1782,14 @@ var InboxOperationError = class extends Error {
|
|
|
1782
1782
|
}
|
|
1783
1783
|
status;
|
|
1784
1784
|
reason;
|
|
1785
|
-
}, INBOX_RESULT_BYTES = 8 * 1024 * 1024, INBOX_INPUT_BYTES = 8192, INBOX_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
1785
|
+
}, INBOX_RESULT_BYTES = 8 * 1024 * 1024, INBOX_INPUT_BYTES = 8192, INBOX_LOCAL_INPUT_BYTES = 256 * 1024, INBOX_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1786
|
+
function isAppQuestionOperation(operation2) {
|
|
1787
|
+
return operation2 === "questions.list" || operation2 === "questions.answer" || operation2 === "questions.searchRecords";
|
|
1788
|
+
}
|
|
1789
|
+
var operations = {
|
|
1786
1790
|
current: [],
|
|
1787
1791
|
"items.list": [],
|
|
1792
|
+
"questions.list": [],
|
|
1788
1793
|
"items.read": ["itemId"],
|
|
1789
1794
|
"items.see": ["itemId"],
|
|
1790
1795
|
"items.markDigestRead": ["itemId"],
|
|
@@ -1937,7 +1942,7 @@ function parseInboxRequest(value) {
|
|
|
1937
1942
|
let envelope = object3(value);
|
|
1938
1943
|
assert(Object.keys(envelope).sort().join(",") === "input,operation"), assert(typeof envelope.operation == "string" && Object.hasOwn(operations, envelope.operation));
|
|
1939
1944
|
let operation2 = envelope.operation, input = object3(envelope.input), required = operations[operation2];
|
|
1940
|
-
assert(required.every((key) => Object.hasOwn(input, key)) && Object.keys(input).every((key) => required.includes(key) || operation2 === "questions.searchRecords" && key === "selected" || operation2 === "items.list" && key === "beforeNumber"));
|
|
1945
|
+
assert(required.every((key) => Object.hasOwn(input, key)) && Object.keys(input).every((key) => required.includes(key) || operation2 === "questions.searchRecords" && key === "selected" || (operation2 === "items.list" || operation2 === "questions.list") && key === "beforeNumber"));
|
|
1941
1946
|
for (let key of ["itemId", "personId", "fileId"]) key in input && assert(id2(input[key]));
|
|
1942
1947
|
return "revision" in input && assert(integer(input.revision)), "beforeNumber" in input && assert(integer(input.beforeNumber)), "questionId" in input && assert(text3(input.questionId) && input.questionId === input.questionId.trim()), "title" in input && assert(text3(input.title)), "lifecycle" in input && assert(input.lifecycle === "open" || input.lifecycle === "closed"), "query" in input && assert(text3(input.query, !0) && input.query.length <= 256), "selected" in input && recordReference(input.selected), "response" in input && response(input.response), assert(new TextEncoder().encode(JSON.stringify(envelope)).byteLength <= INBOX_INPUT_BYTES), { operation: operation2, input };
|
|
1943
1948
|
} catch {
|
|
@@ -1990,12 +1995,32 @@ function parseInboxItem(value) {
|
|
|
1990
1995
|
assert(id2(work.id) && text3(work.questionId) && text3(work.reason) && optionalDate(work.settledAt));
|
|
1991
1996
|
}), item;
|
|
1992
1997
|
}
|
|
1998
|
+
function parseAnswerableQuestion(value) {
|
|
1999
|
+
let entry = object3(json2(value));
|
|
2000
|
+
keys3(entry, ["item", "origin", "question", "recordRefs"]);
|
|
2001
|
+
let item = object3(entry.item);
|
|
2002
|
+
keys3(item, ["id", "number", "revision", "title", "href"]), assert(id2(item.id) && integer(item.number) && integer(item.revision) && text3(item.title) && text3(item.href));
|
|
2003
|
+
let link = new URL(item.href), slug = link.hostname.slice(0, -13);
|
|
2004
|
+
assert(link.hostname.endsWith(".oddington.ai") && slug.length <= 63 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug) && !["auth", "www", "api", "os"].includes(slug) && item.href === `https://${slug}.oddington.ai/inbox/${item.id}`);
|
|
2005
|
+
let origin = object3(entry.origin);
|
|
2006
|
+
keys3(origin, ["routine"]), assert(text3(origin.routine) && origin.routine === origin.routine.trim());
|
|
2007
|
+
let question2 = object3(entry.question);
|
|
2008
|
+
keys3(question2, ["id", "prompt", "instructions", "response"]), assert(text3(question2.id) && question2.id === question2.id.trim() && text3(question2.prompt) && (question2.instructions === null || text3(question2.instructions, !0))), responseDefinition(question2.response);
|
|
2009
|
+
let refs = array2(entry.recordRefs);
|
|
2010
|
+
return assert(refs.every((ref) => text3(ref) && ref === ref.trim()) && new Set(refs).size === refs.length), entry;
|
|
2011
|
+
}
|
|
1993
2012
|
function parseInboxAcceptance(value) {
|
|
1994
2013
|
let accepted = object3(value);
|
|
1995
2014
|
return assert(id2(accepted.responseId) && date(accepted.acceptedAt)), "executionId" in accepted && assert(id2(accepted.executionId)), "acceptanceOrder" in accepted && assert(integer(accepted.acceptanceOrder)), "label" in accepted && assert(text3(accepted.label)), accepted;
|
|
1996
2015
|
}
|
|
1997
2016
|
function parseInboxResult(operation2, value) {
|
|
1998
2017
|
if (operation2 === "current") return parseInboxCurrent(value);
|
|
2018
|
+
if (operation2 === "questions.list") {
|
|
2019
|
+
let page = object3(value);
|
|
2020
|
+
keys3(page, ["questions", "nextBeforeNumber"]);
|
|
2021
|
+
let questions = array2(page.questions).map(parseAnswerableQuestion);
|
|
2022
|
+
return assert(page.nextBeforeNumber === null || integer(page.nextBeforeNumber)), { questions, nextBeforeNumber: page.nextBeforeNumber };
|
|
2023
|
+
}
|
|
1999
2024
|
if (operation2 === "items.read") return parseInboxItem(value);
|
|
2000
2025
|
if (operation2 === "items.list") {
|
|
2001
2026
|
let page = object3(value), items = array2(page.items).map(parseInboxItem);
|
|
@@ -2063,128 +2088,61 @@ async function limitedBody(request, limit, timeoutMs) {
|
|
|
2063
2088
|
}
|
|
2064
2089
|
}
|
|
2065
2090
|
|
|
2066
|
-
var
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2091
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2092
|
+
function parseInboxHostContext(value) {
|
|
2093
|
+
if (!value || typeof value != "object" || Array.isArray(value)) throw new Error("Inbox context unavailable");
|
|
2094
|
+
let v = value;
|
|
2095
|
+
if (v.kind !== "inbox" && v.kind !== "focused" || Object.keys(v).sort().join(",") !== (v.kind === "inbox" ? "generation,kind,memberId,workspaceId" : "appId,generation,kind,memberId,workspaceId") || typeof v.workspaceId != "string" || !UUID.test(v.workspaceId) || typeof v.memberId != "string" || !UUID.test(v.memberId) || typeof v.generation != "string" || !UUID.test(v.generation) || v.kind === "focused" && (typeof v.appId != "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(v.appId)))
|
|
2096
|
+
throw new Error("Inbox context unavailable");
|
|
2097
|
+
let base = { workspaceId: v.workspaceId, memberId: v.memberId, generation: v.generation };
|
|
2098
|
+
return Object.freeze(v.kind === "inbox" ? { ...base, kind: "inbox" } : { ...base, kind: "focused", appId: v.appId });
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
var InboxContextError = class extends Error {
|
|
2102
|
+
constructor(status) {
|
|
2103
|
+
super("Inbox context unavailable");
|
|
2104
|
+
this.status = status;
|
|
2073
2105
|
}
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2106
|
+
status;
|
|
2107
|
+
}, contexts = /* @__PURE__ */ new WeakMap();
|
|
2108
|
+
function resolveHostedInboxContext(host) {
|
|
2109
|
+
if (!host.binding || !host.memberId) throw new Error("Inbox context unavailable");
|
|
2110
|
+
let resolve = contexts.get(host.binding);
|
|
2111
|
+
return resolve || (resolve = hostedInboxContext(), contexts.set(host.binding, resolve)), resolve({ ...host, memberId: host.memberId });
|
|
2112
|
+
}
|
|
2113
|
+
function hostedInboxContext() {
|
|
2114
|
+
let installed2, revision = 0, issued = 0, settled = 0;
|
|
2115
|
+
return async (host) => {
|
|
2116
|
+
let started = ++issued, check = () => {
|
|
2117
|
+
if (!host.isCurrent() || host.expiresAt <= Date.now()) throw new Error("Inbox context unavailable");
|
|
2118
|
+
};
|
|
2119
|
+
check();
|
|
2120
|
+
let response2 = await fetch(`${host.endpoint}/api/inbox/context`, {
|
|
2121
|
+
method: "GET",
|
|
2122
|
+
headers: { authorization: `Bearer ${host.accessToken}` },
|
|
2123
|
+
credentials: "omit",
|
|
2084
2124
|
redirect: "error",
|
|
2085
2125
|
cache: "no-store",
|
|
2086
|
-
signal: AbortSignal.timeout(
|
|
2126
|
+
signal: AbortSignal.timeout(15e3)
|
|
2087
2127
|
});
|
|
2088
2128
|
try {
|
|
2089
|
-
check();
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
})
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
[400, 401, 403, 409].includes(response2.status) ? response2.status : 503,
|
|
2098
|
-
response2.status === 400 ? "invalid_input" : response2.status === 401 ? "authority_unavailable" : response2.status === 403 ? "access_denied" : response2.status === 409 ? "conflict" : "unavailable"
|
|
2099
|
-
);
|
|
2100
|
-
if (response2.status !== 200)
|
|
2101
|
-
throw response2.body?.cancel().catch(() => {
|
|
2102
|
-
}), unavailable2();
|
|
2103
|
-
if (operation2 === "files.read") {
|
|
2104
|
-
if (!response2.body) throw unavailable2();
|
|
2105
|
-
let source = response2.body, reader, size = 0, cancel = async (reason) => {
|
|
2106
|
-
try {
|
|
2107
|
-
await (reader ? reader.cancel(reason) : source.cancel(reason));
|
|
2108
|
-
} finally {
|
|
2109
|
-
reader?.releaseLock();
|
|
2110
|
-
}
|
|
2111
|
-
}, body2 = new ReadableStream({
|
|
2112
|
-
async pull(controller) {
|
|
2113
|
-
try {
|
|
2114
|
-
check(), reader ??= source.getReader();
|
|
2115
|
-
let next = await reader.read();
|
|
2116
|
-
if (check(), next.done) {
|
|
2117
|
-
reader.releaseLock(), controller.close();
|
|
2118
|
-
return;
|
|
2119
|
-
}
|
|
2120
|
-
if (size += next.value.byteLength, size > 25 * 1024 * 1024) throw unavailable2();
|
|
2121
|
-
controller.enqueue(next.value);
|
|
2122
|
-
} catch (error) {
|
|
2123
|
-
controller.error(error), await cancel(error).catch(() => {
|
|
2124
|
-
});
|
|
2125
|
-
}
|
|
2126
|
-
},
|
|
2127
|
-
cancel
|
|
2128
|
-
}, { highWaterMark: 0 });
|
|
2129
|
-
return new Response(body2, { status: response2.status, headers: response2.headers });
|
|
2130
|
-
}
|
|
2131
|
-
let bytes = await limitedBody(response2, INBOX_RESULT_BYTES);
|
|
2132
|
-
if (check(), !bytes) throw unavailable2();
|
|
2133
|
-
let value = JSON.parse(new TextDecoder("utf-8", { fatal: !0 }).decode(bytes)), parsed = parseInboxResult(operation2, value);
|
|
2134
|
-
if (check(), operation2 === "current" && parsed.workspace.slug !== context2.workspace) throw unavailable2();
|
|
2135
|
-
if ("itemId" in envelope.input) {
|
|
2136
|
-
let item = operation2 === "items.read" ? parsed : operation2 === "items.rename" || operation2 === "items.setLifecycle" || operation2 === "items.addRecipient" ? parsed.item : void 0;
|
|
2137
|
-
if (item && (item.id !== envelope.input.itemId || "revision" in envelope.input && item.revision !== envelope.input.revision) || operation2 === "items.addRecipient" && parsed.addedPersonId !== envelope.input.personId || operation2 === "questions.answer" && typeof envelope.input.response.selection == "object" && !parsed.label?.trim()) throw unavailable2();
|
|
2138
|
-
}
|
|
2139
|
-
return parsed;
|
|
2140
|
-
} catch (error) {
|
|
2141
|
-
throw check(), error instanceof InboxOperationError ? error : unavailable2();
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
var inbox = {
|
|
2145
|
-
current: async () => await send2("current", {}),
|
|
2146
|
-
items: {
|
|
2147
|
-
async list() {
|
|
2148
|
-
let items = [], beforeNumber, initial;
|
|
2149
|
-
try {
|
|
2150
|
-
initial = await resolveAppContext();
|
|
2151
|
-
} catch {
|
|
2152
|
-
throw stale();
|
|
2129
|
+
if (check(), (response2.status === 401 || response2.status === 403) && (installed2 = void 0, revision++, settled = Math.max(settled, started)), !response2.ok || response2.status !== 200) throw new InboxContextError(response2.status);
|
|
2130
|
+
let bytes = await limitedBody(response2, 4096);
|
|
2131
|
+
if (check(), !bytes) throw new Error("Inbox context unavailable");
|
|
2132
|
+
let context2 = parseInboxHostContext(JSON.parse(new TextDecoder("utf-8", { fatal: !0 }).decode(bytes)));
|
|
2133
|
+
if (context2.memberId !== host.memberId) throw new Error("Inbox context unavailable");
|
|
2134
|
+
if (JSON.stringify(installed2) !== JSON.stringify(context2)) {
|
|
2135
|
+
if (started < settled) throw new Error("Inbox context unavailable");
|
|
2136
|
+
installed2 = context2, revision++;
|
|
2153
2137
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
};
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
beforeNumber = page.nextBeforeNumber;
|
|
2163
|
-
} while (!0);
|
|
2164
|
-
},
|
|
2165
|
-
read: async (itemId) => await send2("items.read", { itemId }),
|
|
2166
|
-
see: async (itemId) => {
|
|
2167
|
-
await send2("items.see", { itemId });
|
|
2168
|
-
},
|
|
2169
|
-
markDigestRead: async (itemId) => await send2("items.markDigestRead", { itemId }),
|
|
2170
|
-
rename: async (input) => await send2("items.rename", input),
|
|
2171
|
-
setLifecycle: async (input) => await send2("items.setLifecycle", input),
|
|
2172
|
-
listRecipientCandidates: async (itemId) => await send2("items.listRecipientCandidates", { itemId }),
|
|
2173
|
-
addRecipient: async (input) => await send2("items.addRecipient", input)
|
|
2174
|
-
},
|
|
2175
|
-
questions: {
|
|
2176
|
-
answer: async (input) => await send2("questions.answer", input),
|
|
2177
|
-
searchRecords: async (input) => await send2("questions.searchRecords", input)
|
|
2178
|
-
},
|
|
2179
|
-
files: { read: async (input) => await send2("files.read", input) }
|
|
2180
|
-
};
|
|
2181
|
-
|
|
2182
|
-
var sources = /* @__PURE__ */ new WeakMap();
|
|
2183
|
-
function registerBrowserAuth(handle2, source) {
|
|
2184
|
-
sources.set(handle2, source);
|
|
2185
|
-
}
|
|
2186
|
-
function browserAuthSource(handle2) {
|
|
2187
|
-
return sources.get(handle2);
|
|
2138
|
+
settled = Math.max(settled, started);
|
|
2139
|
+
let current2 = revision;
|
|
2140
|
+
return Object.freeze({ context: context2, isCurrent: () => host.isCurrent() && host.expiresAt > Date.now() && current2 === revision });
|
|
2141
|
+
} finally {
|
|
2142
|
+
response2.body?.cancel().catch(() => {
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
};
|
|
2188
2146
|
}
|
|
2189
2147
|
|
|
2190
2148
|
var sessionKey = "_os.app.session", responseChecks = /* @__PURE__ */ new WeakMap(), AppResponseReadError = class extends Error {
|
|
@@ -2426,10 +2384,17 @@ function appContext(origin, active2) {
|
|
|
2426
2384
|
throw error instanceof DataOperationError ? error : !current2() || controller.signal.aborted ? new DataOperationError("context_unavailable") : new DataOperationError("unavailable", !0);
|
|
2427
2385
|
}
|
|
2428
2386
|
}
|
|
2387
|
+
async function requestInbox(operation2, input) {
|
|
2388
|
+
if (!["questions.list", "questions.answer", "questions.searchRecords"].includes(operation2)) throw new Error("Inbox operation unavailable");
|
|
2389
|
+
return requestChecked(`${origin}/_os/app/inbox`, { method: "POST", body: JSON.stringify({ operation: operation2, input }) }, !0);
|
|
2390
|
+
}
|
|
2429
2391
|
async function requestRoutine(key, suffix, init2 = {}) {
|
|
2392
|
+
return requestChecked(`${origin}/_os/app/routines/${encodeURIComponent(key)}/${suffix}`, init2);
|
|
2393
|
+
}
|
|
2394
|
+
async function requestChecked(url, init2, inbox2 = !1) {
|
|
2430
2395
|
if (!current2())
|
|
2431
2396
|
throw invalidate(), new Error("Local app context unavailable.");
|
|
2432
|
-
let
|
|
2397
|
+
let admitted = credential, response2 = await fetch(url, {
|
|
2433
2398
|
...init2,
|
|
2434
2399
|
credentials: "omit",
|
|
2435
2400
|
redirect: "error",
|
|
@@ -2441,7 +2406,7 @@ function appContext(origin, active2) {
|
|
|
2441
2406
|
for (await synchronize(); alive() && stored() !== credential; ) await synchronize();
|
|
2442
2407
|
if (!current2() || response2.redirected || response2.url && response2.url !== url)
|
|
2443
2408
|
throw invalidate(), new Error("Local app context unavailable.");
|
|
2444
|
-
return (response2.status === 401 || response2.status === 403) && admitted === credential && invalidate(), responseChecks.set(response2, async (value) => {
|
|
2409
|
+
return (response2.status === 401 || !inbox2 && response2.status === 403) && admitted === credential && invalidate(), responseChecks.set(response2, async (value) => {
|
|
2445
2410
|
for (await synchronize(); alive() && stored() !== credential; ) await synchronize();
|
|
2446
2411
|
admitted === credential && value && typeof value == "object" && "error" in value && value.error === "authority_unavailable" && invalidate();
|
|
2447
2412
|
}), response2;
|
|
@@ -2461,6 +2426,7 @@ function appContext(origin, active2) {
|
|
|
2461
2426
|
},
|
|
2462
2427
|
requestRoutine,
|
|
2463
2428
|
requestData,
|
|
2429
|
+
requestInbox,
|
|
2464
2430
|
requestAvailability: (key) => requestRoutine(key, "availability", { method: "GET" })
|
|
2465
2431
|
};
|
|
2466
2432
|
}
|
|
@@ -2503,6 +2469,193 @@ function validBinding(value) {
|
|
|
2503
2469
|
return typeof binding.appId == "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(binding.appId) && typeof binding.workspace == "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(binding.workspace) && typeof binding.memberId == "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(binding.memberId) && typeof binding.sessionId == "string" && /^[A-Za-z0-9_-]{43}$/.test(binding.sessionId) && typeof binding.expiresAt == "number" && Number.isFinite(binding.expiresAt);
|
|
2504
2470
|
}
|
|
2505
2471
|
|
|
2472
|
+
var unavailable2 = () => new InboxOperationError(503, "unavailable"), stale = () => new InboxOperationError(401, "authority_unavailable");
|
|
2473
|
+
function hostFailure(error, context2) {
|
|
2474
|
+
return !context2.isCurrent() || context2.expiresAt <= Date.now() || error instanceof InboxContextError && error.status === 401 ? stale() : error instanceof InboxContextError && error.status === 403 ? new InboxOperationError(403, "access_denied") : unavailable2();
|
|
2475
|
+
}
|
|
2476
|
+
async function send2(operation2, input) {
|
|
2477
|
+
let envelope = parseInboxRequest({ operation: operation2, input }), context2;
|
|
2478
|
+
try {
|
|
2479
|
+
context2 = await resolveAppContext();
|
|
2480
|
+
} catch {
|
|
2481
|
+
throw stale();
|
|
2482
|
+
}
|
|
2483
|
+
if (context2.kind === "local") {
|
|
2484
|
+
if (!isAppQuestionOperation(operation2)) throw new InboxOperationError(403, "access_denied");
|
|
2485
|
+
let check2 = () => {
|
|
2486
|
+
if (!context2.isCurrent() || context2.signal.aborted || context2.expiresAt <= Date.now()) throw stale();
|
|
2487
|
+
};
|
|
2488
|
+
try {
|
|
2489
|
+
let response2 = await context2.requestInbox(operation2, envelope.input), value = await appResponse(response2, INBOX_RESULT_BYTES, context2.signal);
|
|
2490
|
+
if (check2(), !response2.ok) throw new InboxOperationError(
|
|
2491
|
+
[400, 401, 403, 409].includes(response2.status) ? response2.status : 503,
|
|
2492
|
+
response2.status === 400 ? "invalid_input" : response2.status === 401 ? "authority_unavailable" : response2.status === 403 ? "access_denied" : response2.status === 409 ? "conflict" : "unavailable"
|
|
2493
|
+
);
|
|
2494
|
+
if (response2.status !== 200) throw unavailable2();
|
|
2495
|
+
let parsed = parseInboxResult(operation2, value);
|
|
2496
|
+
if (check2(), operation2 === "questions.answer" && typeof envelope.input.response.selection == "object" && !parsed.label?.trim()) throw unavailable2();
|
|
2497
|
+
return parsed;
|
|
2498
|
+
} catch (error) {
|
|
2499
|
+
throw check2(), error instanceof InboxOperationError ? error : unavailable2();
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
if (context2.endpoint !== `https://${context2.workspace}.oddington.ai` || ["auth", "www", "api", "os"].includes(context2.workspace)) throw stale();
|
|
2503
|
+
let host;
|
|
2504
|
+
try {
|
|
2505
|
+
host = await resolveHostedInboxContext(context2);
|
|
2506
|
+
} catch (error) {
|
|
2507
|
+
throw hostFailure(error, context2);
|
|
2508
|
+
}
|
|
2509
|
+
if (!host) throw unavailable2();
|
|
2510
|
+
let check = () => {
|
|
2511
|
+
if (!context2.isCurrent() || !host.isCurrent() || !Number.isFinite(context2.expiresAt) || context2.expiresAt <= Date.now()) throw stale();
|
|
2512
|
+
};
|
|
2513
|
+
if (check(), host.context.kind === "focused" && !isAppQuestionOperation(operation2))
|
|
2514
|
+
throw new InboxOperationError(403, "access_denied");
|
|
2515
|
+
try {
|
|
2516
|
+
let response2 = await fetch(`${context2.endpoint}/api/inbox`, {
|
|
2517
|
+
method: "POST",
|
|
2518
|
+
headers: { authorization: `Bearer ${context2.accessToken}`, "content-type": "application/json" },
|
|
2519
|
+
body: JSON.stringify(envelope),
|
|
2520
|
+
redirect: "error",
|
|
2521
|
+
cache: "no-store",
|
|
2522
|
+
signal: AbortSignal.timeout(3e4)
|
|
2523
|
+
});
|
|
2524
|
+
try {
|
|
2525
|
+
check();
|
|
2526
|
+
} catch (error) {
|
|
2527
|
+
throw response2.body?.cancel().catch(() => {
|
|
2528
|
+
}), error;
|
|
2529
|
+
}
|
|
2530
|
+
if (!response2.ok)
|
|
2531
|
+
throw response2.body?.cancel().catch(() => {
|
|
2532
|
+
}), new InboxOperationError(
|
|
2533
|
+
[400, 401, 403, 409].includes(response2.status) ? response2.status : 503,
|
|
2534
|
+
response2.status === 400 ? "invalid_input" : response2.status === 401 ? "authority_unavailable" : response2.status === 403 ? "access_denied" : response2.status === 409 ? "conflict" : "unavailable"
|
|
2535
|
+
);
|
|
2536
|
+
if (response2.status !== 200)
|
|
2537
|
+
throw response2.body?.cancel().catch(() => {
|
|
2538
|
+
}), unavailable2();
|
|
2539
|
+
if (operation2 === "files.read") {
|
|
2540
|
+
if (!response2.body) throw unavailable2();
|
|
2541
|
+
let source = response2.body, reader, size = 0, cancel = async (reason) => {
|
|
2542
|
+
try {
|
|
2543
|
+
await (reader ? reader.cancel(reason) : source.cancel(reason));
|
|
2544
|
+
} finally {
|
|
2545
|
+
reader?.releaseLock();
|
|
2546
|
+
}
|
|
2547
|
+
}, body2 = new ReadableStream({
|
|
2548
|
+
async pull(controller) {
|
|
2549
|
+
try {
|
|
2550
|
+
check(), reader ??= source.getReader();
|
|
2551
|
+
let next = await reader.read();
|
|
2552
|
+
if (check(), next.done) {
|
|
2553
|
+
reader.releaseLock(), controller.close();
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
if (size += next.value.byteLength, size > 25 * 1024 * 1024) throw unavailable2();
|
|
2557
|
+
controller.enqueue(next.value);
|
|
2558
|
+
} catch (error) {
|
|
2559
|
+
controller.error(error), await cancel(error).catch(() => {
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
},
|
|
2563
|
+
cancel
|
|
2564
|
+
}, { highWaterMark: 0 });
|
|
2565
|
+
return new Response(body2, { status: response2.status, headers: response2.headers });
|
|
2566
|
+
}
|
|
2567
|
+
let bytes = await limitedBody(response2, INBOX_RESULT_BYTES);
|
|
2568
|
+
if (check(), !bytes) throw unavailable2();
|
|
2569
|
+
let value = JSON.parse(new TextDecoder("utf-8", { fatal: !0 }).decode(bytes)), parsed = parseInboxResult(operation2, value);
|
|
2570
|
+
if (check(), operation2 === "current" && parsed.workspace.slug !== context2.workspace) throw unavailable2();
|
|
2571
|
+
if ("itemId" in envelope.input) {
|
|
2572
|
+
let item = operation2 === "items.read" ? parsed : operation2 === "items.rename" || operation2 === "items.setLifecycle" || operation2 === "items.addRecipient" ? parsed.item : void 0;
|
|
2573
|
+
if (item && (item.id !== envelope.input.itemId || "revision" in envelope.input && item.revision !== envelope.input.revision) || operation2 === "items.addRecipient" && parsed.addedPersonId !== envelope.input.personId || operation2 === "questions.answer" && typeof envelope.input.response.selection == "object" && !parsed.label?.trim()) throw unavailable2();
|
|
2574
|
+
}
|
|
2575
|
+
return parsed;
|
|
2576
|
+
} catch (error) {
|
|
2577
|
+
throw check(), error instanceof InboxOperationError ? error : unavailable2();
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
var inbox = {
|
|
2581
|
+
current: async () => await send2("current", {}),
|
|
2582
|
+
items: {
|
|
2583
|
+
async list() {
|
|
2584
|
+
let items = [], beforeNumber, initial;
|
|
2585
|
+
try {
|
|
2586
|
+
initial = await resolveAppContext();
|
|
2587
|
+
} catch {
|
|
2588
|
+
throw stale();
|
|
2589
|
+
}
|
|
2590
|
+
let host;
|
|
2591
|
+
try {
|
|
2592
|
+
host = initial.kind !== "local" && await resolveHostedInboxContext(initial);
|
|
2593
|
+
} catch (error) {
|
|
2594
|
+
throw hostFailure(error, initial);
|
|
2595
|
+
}
|
|
2596
|
+
if (initial.kind === "local") throw new InboxOperationError(403, "access_denied");
|
|
2597
|
+
if (!host) throw unavailable2();
|
|
2598
|
+
let check = () => {
|
|
2599
|
+
if (!initial.isCurrent() || !host.isCurrent() || initial.expiresAt <= Date.now()) throw stale();
|
|
2600
|
+
};
|
|
2601
|
+
do {
|
|
2602
|
+
check();
|
|
2603
|
+
let page = await send2("items.list", beforeNumber === void 0 ? {} : { beforeNumber });
|
|
2604
|
+
if (check(), beforeNumber !== void 0 && (page.items.some((item) => item.number >= beforeNumber) || page.nextBeforeNumber !== null && page.nextBeforeNumber >= beforeNumber)) throw unavailable2();
|
|
2605
|
+
if (items.push(...page.items), page.nextBeforeNumber === null) return items;
|
|
2606
|
+
beforeNumber = page.nextBeforeNumber;
|
|
2607
|
+
} while (!0);
|
|
2608
|
+
},
|
|
2609
|
+
read: async (itemId) => await send2("items.read", { itemId }),
|
|
2610
|
+
see: async (itemId) => {
|
|
2611
|
+
await send2("items.see", { itemId });
|
|
2612
|
+
},
|
|
2613
|
+
markDigestRead: async (itemId) => await send2("items.markDigestRead", { itemId }),
|
|
2614
|
+
rename: async (input) => await send2("items.rename", input),
|
|
2615
|
+
setLifecycle: async (input) => await send2("items.setLifecycle", input),
|
|
2616
|
+
listRecipientCandidates: async (itemId) => await send2("items.listRecipientCandidates", { itemId }),
|
|
2617
|
+
addRecipient: async (input) => await send2("items.addRecipient", input)
|
|
2618
|
+
},
|
|
2619
|
+
questions: {
|
|
2620
|
+
async list() {
|
|
2621
|
+
let questions = [], beforeNumber, initial;
|
|
2622
|
+
try {
|
|
2623
|
+
initial = await resolveAppContext();
|
|
2624
|
+
} catch {
|
|
2625
|
+
throw stale();
|
|
2626
|
+
}
|
|
2627
|
+
let host;
|
|
2628
|
+
try {
|
|
2629
|
+
host = initial.kind !== "local" && await resolveHostedInboxContext(initial);
|
|
2630
|
+
} catch (error) {
|
|
2631
|
+
throw hostFailure(error, initial);
|
|
2632
|
+
}
|
|
2633
|
+
if (initial.kind !== "local" && !host) throw unavailable2();
|
|
2634
|
+
let check = () => {
|
|
2635
|
+
if (!initial.isCurrent() || host && !host.isCurrent() || initial.expiresAt <= Date.now()) throw stale();
|
|
2636
|
+
};
|
|
2637
|
+
do {
|
|
2638
|
+
check();
|
|
2639
|
+
let page = await send2("questions.list", beforeNumber === void 0 ? {} : { beforeNumber });
|
|
2640
|
+
if (check(), page.questions.some((question2) => question2.item.href !== `https://${initial.workspace}.oddington.ai/inbox/${question2.item.id}`) || beforeNumber !== void 0 && (page.questions.some((question2) => question2.item.number >= beforeNumber) || page.nextBeforeNumber !== null && page.nextBeforeNumber >= beforeNumber)) throw unavailable2();
|
|
2641
|
+
if (questions.push(...page.questions), page.nextBeforeNumber === null) return questions;
|
|
2642
|
+
beforeNumber = page.nextBeforeNumber;
|
|
2643
|
+
} while (!0);
|
|
2644
|
+
},
|
|
2645
|
+
answer: async (input) => await send2("questions.answer", input),
|
|
2646
|
+
searchRecords: async (input) => await send2("questions.searchRecords", input)
|
|
2647
|
+
},
|
|
2648
|
+
files: { read: async (input) => await send2("files.read", input) }
|
|
2649
|
+
};
|
|
2650
|
+
|
|
2651
|
+
var sources = /* @__PURE__ */ new WeakMap();
|
|
2652
|
+
function registerBrowserAuth(handle2, source) {
|
|
2653
|
+
sources.set(handle2, source);
|
|
2654
|
+
}
|
|
2655
|
+
function browserAuthSource(handle2) {
|
|
2656
|
+
return sources.get(handle2);
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2506
2659
|
var installedDispose;
|
|
2507
2660
|
function init({ auth: input }) {
|
|
2508
2661
|
installedDispose?.();
|
|
@@ -2521,10 +2674,7 @@ function init({ auth: input }) {
|
|
|
2521
2674
|
url.protocol === "https:" && !url.port && !url.username && !url.password && !url.search && !url.hash && url.pathname === "/" && /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(slug) && slug !== "auth" && (endpoint = url.origin, workspace = slug);
|
|
2522
2675
|
} catch {
|
|
2523
2676
|
}
|
|
2524
|
-
let active2 = !0, revision = 0, person, token, local = localOrigin ? appContext(localOrigin, () => active2) : void 0,
|
|
2525
|
-
let next = session?.user.id ?? null;
|
|
2526
|
-
(event2 === "SIGNED_OUT" || person !== void 0 && next !== person || registered && person !== void 0 && event2 === "SIGNED_IN" && token !== session?.access_token) && (revision++, local?.invalidate()), person = next, token = session?.access_token;
|
|
2527
|
-
}), uninstall = installMemberContext(async () => {
|
|
2677
|
+
let active2 = !0, revision = 0, person, token, binding = {}, local = localOrigin ? appContext(localOrigin, () => active2) : void 0, subscription, stopDisposal, uninstall = installMemberContext(async () => {
|
|
2528
2678
|
if (local) return local.resolve();
|
|
2529
2679
|
if (!active2 || !endpoint || !workspace || globalThis.location.origin !== endpoint) throw new Error("Hosted Data context unavailable.");
|
|
2530
2680
|
let started = revision, { data: { session }, error } = await auth2.getSession();
|
|
@@ -2533,17 +2683,24 @@ function init({ auth: input }) {
|
|
|
2533
2683
|
let expiresAt = (session.expires_at ?? 0) * 1e3;
|
|
2534
2684
|
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || !session.user.id)
|
|
2535
2685
|
throw new Error("Hosted Data context unavailable.");
|
|
2536
|
-
|
|
2686
|
+
person = session.user.id;
|
|
2687
|
+
let isCurrent = () => active2 && globalThis.location.origin === endpoint && revision === started && person === session.user.id;
|
|
2688
|
+
return {
|
|
2537
2689
|
workspace,
|
|
2538
2690
|
endpoint,
|
|
2539
2691
|
accessToken: session.access_token,
|
|
2540
2692
|
expiresAt,
|
|
2541
|
-
isCurrent
|
|
2693
|
+
isCurrent,
|
|
2694
|
+
memberId: session.user.id,
|
|
2695
|
+
binding
|
|
2542
2696
|
};
|
|
2543
2697
|
}), dispose = () => {
|
|
2544
|
-
active2 && (active2 = !1, local?.dispose(), revision++, subscription
|
|
2698
|
+
active2 && (active2 = !1, local?.dispose(), revision++, subscription?.unsubscribe(), stopDisposal?.(), uninstall(), installedDispose === dispose && (installedDispose = void 0));
|
|
2545
2699
|
};
|
|
2546
|
-
installedDispose = dispose,
|
|
2700
|
+
installedDispose = dispose, stopDisposal = registered?.onDispose(dispose), active2 && (subscription = auth2.onAuthStateChange((event2, session) => {
|
|
2701
|
+
let next = session?.user.id ?? null;
|
|
2702
|
+
(event2 === "SIGNED_OUT" || person !== void 0 && next !== person || registered && person !== void 0 && event2 === "SIGNED_IN" && token !== session?.access_token) && (revision++, local?.invalidate()), person = next, token = session?.access_token;
|
|
2703
|
+
}).data.subscription, local?.start());
|
|
2547
2704
|
}
|
|
2548
2705
|
var os = { init };
|
|
2549
2706
|
|
|
@@ -2678,7 +2835,7 @@ function createBrowserAuth(runtime) {
|
|
|
2678
2835
|
c || (c = createCoordinator(origin), coordinators.set(origin, c));
|
|
2679
2836
|
let coordinator = c;
|
|
2680
2837
|
coordinator.refs++;
|
|
2681
|
-
let active2 = !0, ownedAction, authorizationId, authorizationGeneration = 0, inspected, observers = /* @__PURE__ */ new Set(), sessionObservers = /* @__PURE__ */ new Set(), lastState = coordinator.state, observation = (state) => {
|
|
2838
|
+
let active2 = !0, ownedAction, authorizationId, authorizationGeneration = 0, inspected, observers = /* @__PURE__ */ new Set(), sessionObservers = /* @__PURE__ */ new Set(), disposalObservers = /* @__PURE__ */ new Set(), lastState = coordinator.state, observation = (state) => {
|
|
2682
2839
|
lastState = state;
|
|
2683
2840
|
for (let listener of observers)
|
|
2684
2841
|
try {
|
|
@@ -2787,13 +2944,24 @@ function createBrowserAuth(runtime) {
|
|
|
2787
2944
|
dispose() {
|
|
2788
2945
|
if (active2) {
|
|
2789
2946
|
active2 = !1, authorizationGeneration++, inspected = void 0, ownedAction && coordinator.action === ownedAction && coordinator.revision++, observers.clear(), coordinator.listeners.delete(observation);
|
|
2790
|
-
for (let listener of
|
|
2791
|
-
|
|
2947
|
+
for (let listener of disposalObservers)
|
|
2948
|
+
try {
|
|
2949
|
+
listener();
|
|
2950
|
+
} catch {
|
|
2951
|
+
}
|
|
2952
|
+
disposalObservers.clear();
|
|
2953
|
+
for (let listener of sessionObservers) coordinator.sessions.delete(listener);
|
|
2792
2954
|
sessionObservers.clear(), --coordinator.refs === 0 && (coordinator.active = !1, coordinator.revision++, coordinator.timer && clearTimeout(coordinator.timer), coordinator.unsubscribe?.(), coordinator.provider?.dispose(), coordinator.provider = void 0, coordinator.session = null, coordinator.listeners.clear(), coordinator.sessions.clear(), coordinators.delete(origin));
|
|
2793
2955
|
}
|
|
2794
2956
|
}
|
|
2795
2957
|
});
|
|
2796
2958
|
return registerBrowserAuth(handle2, {
|
|
2959
|
+
onDispose(callback) {
|
|
2960
|
+
return active2 ? (disposalObservers.add(callback), () => {
|
|
2961
|
+
disposalObservers.delete(callback);
|
|
2962
|
+
}) : (callback(), () => {
|
|
2963
|
+
});
|
|
2964
|
+
},
|
|
2797
2965
|
async getSession() {
|
|
2798
2966
|
assertActive(), await coordinator.ready, assertActive();
|
|
2799
2967
|
let revision = coordinator.revision;
|