@goflowstate/widget-sdk 0.1.1 → 0.2.1
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 +43 -0
- package/dist/ai.test.d.ts +2 -0
- package/dist/ai.test.d.ts.map +1 -0
- package/dist/canvas-widget-sdk.js +285 -4
- package/dist/canvas-widget-sdk.js.map +3 -3
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +445 -5
- package/dist/index.mjs.map +3 -3
- package/dist/ingest.d.ts +22 -0
- package/dist/ingest.d.ts.map +1 -0
- package/dist/ingest.test.d.ts +2 -0
- package/dist/ingest.test.d.ts.map +1 -0
- package/dist/sdk.d.ts +24 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.namespace.test.d.ts +2 -0
- package/dist/sdk.namespace.test.d.ts.map +1 -0
- package/dist/session.d.ts +139 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.test.d.ts +2 -0
- package/dist/session.test.d.ts.map +1 -0
- package/dist/theme.d.ts +30 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.test.d.ts +2 -0
- package/dist/theme.test.d.ts.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,10 @@ export type { CanvasWidgetSDK, WidgetDisplayState, WidgetEvent, SettingsAPI } fr
|
|
|
3
3
|
export { createServiceClient } from './service';
|
|
4
4
|
export type { WidgetManifestServiceBlock, WidgetManifestServiceScope, WidgetServiceClient, DbRecord, DbCollection, DbAPI, DbListOptions, } from './types';
|
|
5
5
|
export type { CreateServiceClientOptions } from './service';
|
|
6
|
+
export { initSession, createMockSession, readSessionLaunchParams } from './session';
|
|
7
|
+
export type { SessionClient, SessionStateView, SessionMember, SessionPhase, SessionGateView, SessionFlowView, SessionPollOptions, SessionLaunchParams, MockSessionOptions, } from './session';
|
|
8
|
+
export { getInitialTheme, onThemeChange, applyWidgetTheme, initWidgetTheme } from './theme';
|
|
9
|
+
export type { WidgetTheme } from './theme';
|
|
10
|
+
export { onArtifactIngested, INGEST_MESSAGE_TYPE } from './ingest';
|
|
11
|
+
export type { IngestedArtifact } from './ingest';
|
|
6
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AACrC,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,YAAY,EACV,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,EACnB,QAAQ,EACR,YAAY,EACZ,KAAK,EACL,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AACrC,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,YAAY,EACV,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,EACnB,QAAQ,EACR,YAAY,EACZ,KAAK,EACL,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACpF,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC5F,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACnE,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// @goflowstate/widget-sdk v0.
|
|
2
|
-
// https://github.com/
|
|
1
|
+
// @goflowstate/widget-sdk v0.2.1
|
|
2
|
+
// https://github.com/goflowstate/goflowstate-sdk
|
|
3
3
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -39,10 +39,292 @@ function createServiceClient(opts) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// src/session.ts
|
|
43
|
+
function readSessionLaunchParams() {
|
|
44
|
+
if (typeof window === "undefined") return null;
|
|
45
|
+
const q = new URLSearchParams(window.location.search);
|
|
46
|
+
const slug = q.get("slug") ?? q.get("tripId") ?? "";
|
|
47
|
+
if (!slug) return null;
|
|
48
|
+
const mobile = q.get("mobile");
|
|
49
|
+
return {
|
|
50
|
+
slug,
|
|
51
|
+
ticket: q.get("ticket"),
|
|
52
|
+
apiBase: (q.get("api") ?? "http://localhost:13002").replace(/\/$/, ""),
|
|
53
|
+
webBase: (q.get("web") ?? "http://localhost:14321").replace(/\/$/, ""),
|
|
54
|
+
mobileBase: mobile ? mobile.replace(/\/$/, "") : null
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
var SESSION_INIT_TIMEOUT_MS = 1e4;
|
|
58
|
+
var DEFAULT_POLL_INTERVAL_MS = 4e3;
|
|
59
|
+
function awaitInitTicket(webBase) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
if (typeof window === "undefined" || window.parent === window) {
|
|
62
|
+
reject(new Error("no parent page to receive a ticket from"));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const webOrigin = new URL(webBase).origin;
|
|
66
|
+
const timer = setTimeout(() => {
|
|
67
|
+
window.removeEventListener("message", onMessage);
|
|
68
|
+
reject(new Error("timed out waiting for the session handoff"));
|
|
69
|
+
}, SESSION_INIT_TIMEOUT_MS);
|
|
70
|
+
const onMessage = (e) => {
|
|
71
|
+
if (e.origin !== webOrigin) return;
|
|
72
|
+
const data = e.data;
|
|
73
|
+
if (data?.type !== "flowstate:init" || typeof data.ticket !== "string") return;
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
window.removeEventListener("message", onMessage);
|
|
76
|
+
resolve(data.ticket);
|
|
77
|
+
};
|
|
78
|
+
window.addEventListener("message", onMessage);
|
|
79
|
+
window.parent.postMessage({ type: "flowstate:ready" }, webOrigin);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
var WIRE_PHASE = {
|
|
83
|
+
setup: "setup",
|
|
84
|
+
wishlist: "open",
|
|
85
|
+
sealed: "sealed",
|
|
86
|
+
plan: "resolved"
|
|
87
|
+
};
|
|
88
|
+
function toStateView(s) {
|
|
89
|
+
return {
|
|
90
|
+
slug: s.tripId,
|
|
91
|
+
phase: WIRE_PHASE[s.phase] ?? "open",
|
|
92
|
+
status: s.status,
|
|
93
|
+
setup: s.setup ?? null,
|
|
94
|
+
members: s.members,
|
|
95
|
+
sealedCount: s.sealedCount,
|
|
96
|
+
memberCount: s.memberCount,
|
|
97
|
+
allSealed: s.allSealed,
|
|
98
|
+
quorumMet: s.quorumMet ?? s.allSealed,
|
|
99
|
+
gate: s.gate ?? null,
|
|
100
|
+
flow: s.flow ?? null,
|
|
101
|
+
myContribution: s.myWishlist ?? null,
|
|
102
|
+
doc: s.plan ?? null,
|
|
103
|
+
artifactId: s.artifactId,
|
|
104
|
+
extension: s.extension ?? null
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
var parentOpener = (webBase) => {
|
|
108
|
+
return (url) => {
|
|
109
|
+
if (typeof window !== "undefined" && window.parent !== window) {
|
|
110
|
+
window.parent.postMessage({ type: "flowstate:navigate", url }, webBase);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (typeof window !== "undefined") window.open(url, "_blank", "noopener");
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
var HttpSessionClient = class {
|
|
117
|
+
constructor(params, opener) {
|
|
118
|
+
__publicField(this, "slug");
|
|
119
|
+
__publicField(this, "webBase");
|
|
120
|
+
__publicField(this, "mobileBase");
|
|
121
|
+
__publicField(this, "apiBase");
|
|
122
|
+
__publicField(this, "tokenPromise");
|
|
123
|
+
__publicField(this, "opener");
|
|
124
|
+
this.slug = params.slug;
|
|
125
|
+
this.webBase = params.webBase;
|
|
126
|
+
this.mobileBase = params.mobileBase ?? null;
|
|
127
|
+
this.apiBase = params.apiBase;
|
|
128
|
+
this.opener = opener ?? parentOpener(params.webBase);
|
|
129
|
+
this.tokenPromise = this.acquireTicket(params).then((t) => this.redeem(t));
|
|
130
|
+
}
|
|
131
|
+
async acquireTicket(params) {
|
|
132
|
+
if (params.ticket) return params.ticket;
|
|
133
|
+
return awaitInitTicket(params.webBase);
|
|
134
|
+
}
|
|
135
|
+
async redeem(ticket) {
|
|
136
|
+
const res = await fetch(`${this.apiBase}/api/auth/ticket/redeem`, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: { "content-type": "application/json" },
|
|
139
|
+
body: JSON.stringify({ ticket })
|
|
140
|
+
});
|
|
141
|
+
if (!res.ok) throw new Error(`ticket redeem failed (${res.status})`);
|
|
142
|
+
const json = await res.json();
|
|
143
|
+
return json.data.token;
|
|
144
|
+
}
|
|
145
|
+
async call(path, init = {}) {
|
|
146
|
+
const token = await this.tokenPromise;
|
|
147
|
+
const res = await fetch(`${this.apiBase}/api/w/${this.slug}${path}`, {
|
|
148
|
+
...init,
|
|
149
|
+
headers: {
|
|
150
|
+
authorization: `Bearer ${token}`,
|
|
151
|
+
...init.body ? { "content-type": "application/json" } : {},
|
|
152
|
+
...init.headers ?? {}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
if (!res.ok) {
|
|
156
|
+
const body = await res.json().catch(() => ({}));
|
|
157
|
+
throw new Error(body.message ?? `session service ${res.status}`);
|
|
158
|
+
}
|
|
159
|
+
const json = await res.json();
|
|
160
|
+
const data = json.data;
|
|
161
|
+
return toStateView(data.state ?? data);
|
|
162
|
+
}
|
|
163
|
+
join() {
|
|
164
|
+
return this.call("/enter", { method: "POST" });
|
|
165
|
+
}
|
|
166
|
+
state() {
|
|
167
|
+
return this.call("/state");
|
|
168
|
+
}
|
|
169
|
+
poll(onState, opts) {
|
|
170
|
+
const interval = opts?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
171
|
+
let stopped = false;
|
|
172
|
+
let timer = null;
|
|
173
|
+
const tick = async () => {
|
|
174
|
+
try {
|
|
175
|
+
const s = await this.state();
|
|
176
|
+
if (!stopped) onState(s);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
opts?.onError?.(err);
|
|
179
|
+
}
|
|
180
|
+
if (!stopped) timer = setTimeout(tick, interval);
|
|
181
|
+
};
|
|
182
|
+
void tick();
|
|
183
|
+
return () => {
|
|
184
|
+
stopped = true;
|
|
185
|
+
if (timer) clearTimeout(timer);
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
commitSetup(setup) {
|
|
189
|
+
return this.call("/setup", { method: "POST", body: JSON.stringify({ setup }) });
|
|
190
|
+
}
|
|
191
|
+
contribute(payload) {
|
|
192
|
+
return this.call("/contribute", { method: "POST", body: JSON.stringify({ payload }) });
|
|
193
|
+
}
|
|
194
|
+
close() {
|
|
195
|
+
return this.call("/close", { method: "POST" });
|
|
196
|
+
}
|
|
197
|
+
action(verb, body) {
|
|
198
|
+
return this.call(`/${verb.replace(/^\//, "")}`, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
body: JSON.stringify(body ?? {})
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async request(verb, body) {
|
|
204
|
+
const token = await this.tokenPromise;
|
|
205
|
+
const res = await fetch(`${this.apiBase}/api/w/${this.slug}/${verb.replace(/^\//, "")}`, {
|
|
206
|
+
method: "POST",
|
|
207
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
208
|
+
body: JSON.stringify(body ?? {})
|
|
209
|
+
});
|
|
210
|
+
if (!res.ok) {
|
|
211
|
+
const errBody = await res.json().catch(() => ({}));
|
|
212
|
+
throw new Error(errBody.message ?? `session service ${res.status}`);
|
|
213
|
+
}
|
|
214
|
+
const json = await res.json();
|
|
215
|
+
return json.data;
|
|
216
|
+
}
|
|
217
|
+
openExternal(url) {
|
|
218
|
+
this.opener(url);
|
|
219
|
+
}
|
|
220
|
+
revealUrl(artifactId) {
|
|
221
|
+
return `${this.webBase}/a/${artifactId}`;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
function createMockSession(opts = {}) {
|
|
225
|
+
const palette = ["#8b7ef8", "#f8a34d", "#4dc9a4", "#f26d8f", "#5aa9e6"];
|
|
226
|
+
const members = [
|
|
227
|
+
{ userId: "mock-me", name: opts.me ?? "You", color: palette[0], sealed: false, isMe: true, isHost: true },
|
|
228
|
+
...(opts.others ?? []).map((name, i) => ({
|
|
229
|
+
userId: `mock-${i + 1}`,
|
|
230
|
+
name,
|
|
231
|
+
color: palette[(i + 1) % palette.length],
|
|
232
|
+
sealed: false,
|
|
233
|
+
isMe: false,
|
|
234
|
+
isHost: false
|
|
235
|
+
}))
|
|
236
|
+
];
|
|
237
|
+
let setup = opts.setup ?? null;
|
|
238
|
+
let myContribution = null;
|
|
239
|
+
let doc = null;
|
|
240
|
+
let status = "open";
|
|
241
|
+
const view = () => {
|
|
242
|
+
const sealedCount = members.filter((m) => m.sealed).length;
|
|
243
|
+
const allSealed = members.length > 0 && sealedCount >= members.length;
|
|
244
|
+
const phase = status === "closed" && doc ? "resolved" : !setup ? "setup" : allSealed ? "sealed" : "open";
|
|
245
|
+
return {
|
|
246
|
+
slug: "mock-session",
|
|
247
|
+
phase,
|
|
248
|
+
status,
|
|
249
|
+
setup,
|
|
250
|
+
members: members.map((m) => ({ ...m })),
|
|
251
|
+
sealedCount,
|
|
252
|
+
memberCount: members.length,
|
|
253
|
+
allSealed,
|
|
254
|
+
quorumMet: allSealed,
|
|
255
|
+
gate: null,
|
|
256
|
+
flow: null,
|
|
257
|
+
myContribution,
|
|
258
|
+
doc,
|
|
259
|
+
artifactId: doc ? "mock-artifact" : null,
|
|
260
|
+
extension: null
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
return {
|
|
264
|
+
slug: "mock-session",
|
|
265
|
+
webBase: "http://localhost:14321",
|
|
266
|
+
async join() {
|
|
267
|
+
return view();
|
|
268
|
+
},
|
|
269
|
+
async state() {
|
|
270
|
+
return view();
|
|
271
|
+
},
|
|
272
|
+
poll(onState, pollOpts) {
|
|
273
|
+
const interval = pollOpts?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
274
|
+
const timer = setInterval(() => onState(view()), interval);
|
|
275
|
+
onState(view());
|
|
276
|
+
return () => clearInterval(timer);
|
|
277
|
+
},
|
|
278
|
+
async commitSetup(s) {
|
|
279
|
+
setup = s;
|
|
280
|
+
return view();
|
|
281
|
+
},
|
|
282
|
+
async contribute(payload) {
|
|
283
|
+
myContribution = payload;
|
|
284
|
+
const me = members.find((m) => m.isMe);
|
|
285
|
+
if (me) me.sealed = true;
|
|
286
|
+
return view();
|
|
287
|
+
},
|
|
288
|
+
async close() {
|
|
289
|
+
status = "closed";
|
|
290
|
+
doc = opts.synthesize ? opts.synthesize([myContribution]) : { mock: true };
|
|
291
|
+
return view();
|
|
292
|
+
},
|
|
293
|
+
async action() {
|
|
294
|
+
return view();
|
|
295
|
+
},
|
|
296
|
+
async request() {
|
|
297
|
+
return {};
|
|
298
|
+
},
|
|
299
|
+
openExternal(url) {
|
|
300
|
+
if (typeof window !== "undefined") window.open(url, "_blank", "noopener");
|
|
301
|
+
},
|
|
302
|
+
revealUrl(artifactId) {
|
|
303
|
+
return `http://localhost:14321/a/${artifactId}`;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function initSession() {
|
|
308
|
+
const params = readSessionLaunchParams();
|
|
309
|
+
if (!params) return null;
|
|
310
|
+
return new HttpSessionClient(params);
|
|
311
|
+
}
|
|
312
|
+
|
|
42
313
|
// src/sdk.ts
|
|
43
314
|
var PROTOCOL = "canvas-widget-v1";
|
|
44
315
|
var DEV_MODE_TOKEN = "dev-mode-skip";
|
|
45
316
|
var INIT_TIMEOUT_MS = 5e3;
|
|
317
|
+
function safeRandomUUID() {
|
|
318
|
+
const c = globalThis.crypto;
|
|
319
|
+
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
320
|
+
const b = new Uint8Array(16);
|
|
321
|
+
if (c && typeof c.getRandomValues === "function") c.getRandomValues(b);
|
|
322
|
+
else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);
|
|
323
|
+
b[6] = b[6] & 15 | 64;
|
|
324
|
+
b[8] = b[8] & 63 | 128;
|
|
325
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0"));
|
|
326
|
+
return `${h[0]}${h[1]}${h[2]}${h[3]}-${h[4]}${h[5]}-${h[6]}${h[7]}-${h[8]}${h[9]}-${h[10]}${h[11]}${h[12]}${h[13]}${h[14]}${h[15]}`;
|
|
327
|
+
}
|
|
46
328
|
var cachedKey = null;
|
|
47
329
|
async function getHmacKey(secret) {
|
|
48
330
|
if (cachedKey && cachedKey.secret === secret) return cachedKey.key;
|
|
@@ -69,6 +351,7 @@ var CanvasWidgetSDKImpl = class {
|
|
|
69
351
|
__publicField(this, "_state");
|
|
70
352
|
__publicField(this, "_hmacSecret");
|
|
71
353
|
__publicField(this, "_user");
|
|
354
|
+
__publicField(this, "_namespace");
|
|
72
355
|
__publicField(this, "_widgetApiUrl");
|
|
73
356
|
__publicField(this, "_widgetApiToken");
|
|
74
357
|
__publicField(this, "_installationId");
|
|
@@ -89,6 +372,7 @@ var CanvasWidgetSDKImpl = class {
|
|
|
89
372
|
this._state = init.state;
|
|
90
373
|
this._hmacSecret = init.hmacSecret;
|
|
91
374
|
this._user = init.user;
|
|
375
|
+
this._namespace = init.namespace ?? null;
|
|
92
376
|
this._widgetApiUrl = init.widgetApiUrl;
|
|
93
377
|
this._widgetApiToken = init.widgetApiToken ?? null;
|
|
94
378
|
this._installationId = init.installationId;
|
|
@@ -114,6 +398,11 @@ var CanvasWidgetSDKImpl = class {
|
|
|
114
398
|
get user() {
|
|
115
399
|
return this._user;
|
|
116
400
|
}
|
|
401
|
+
/** Namespace (office) context — id and owner-inclusive member count — or
|
|
402
|
+
* null on a personal canvas. What the `canvas.context` permission grants. */
|
|
403
|
+
get namespace() {
|
|
404
|
+
return this._namespace;
|
|
405
|
+
}
|
|
117
406
|
/** Subscribe to display state changes; returns an unsubscribe function. */
|
|
118
407
|
onStateChange(cb) {
|
|
119
408
|
this._stateListeners.push(cb);
|
|
@@ -238,6 +527,52 @@ var CanvasWidgetSDKImpl = class {
|
|
|
238
527
|
}
|
|
239
528
|
};
|
|
240
529
|
}
|
|
530
|
+
/** Platform LLM access (widget.* aliases only). Requests ride the same
|
|
531
|
+
* widget-gateway auth as db/storage; the platform meters every call and
|
|
532
|
+
* attributes it to this widget's publishing org. */
|
|
533
|
+
get ai() {
|
|
534
|
+
const complete = (opts) => this.widgetApiRequest("/widget-api/ai/complete", "POST", opts);
|
|
535
|
+
const streamRequest = (opts) => this.widgetApiStream(opts);
|
|
536
|
+
return {
|
|
537
|
+
complete,
|
|
538
|
+
// Async generator over the gateway's SSE frames. Ends on [DONE] or
|
|
539
|
+
// stream end; returning early (break) cancels the fetch body, which
|
|
540
|
+
// the gateway sees as a disconnect and aborts the upstream call, so
|
|
541
|
+
// an abandoned stream stops generating and billing.
|
|
542
|
+
async *stream(opts) {
|
|
543
|
+
const response = await streamRequest(opts);
|
|
544
|
+
const reader = response.body?.getReader();
|
|
545
|
+
if (!reader) throw new Error("ai.stream: response has no body");
|
|
546
|
+
const decoder = new TextDecoder();
|
|
547
|
+
let buffer = "";
|
|
548
|
+
try {
|
|
549
|
+
for (; ; ) {
|
|
550
|
+
const { done, value } = await reader.read();
|
|
551
|
+
if (done) break;
|
|
552
|
+
buffer += decoder.decode(value, { stream: true });
|
|
553
|
+
const frames = buffer.split("\n\n");
|
|
554
|
+
buffer = frames.pop() ?? "";
|
|
555
|
+
for (const frame of frames) {
|
|
556
|
+
const dataLine = frame.split("\n").find((l) => l.startsWith("data:"));
|
|
557
|
+
if (!dataLine) continue;
|
|
558
|
+
const payload = dataLine.slice(5).trim();
|
|
559
|
+
if (!payload || payload === "[DONE]") continue;
|
|
560
|
+
let parsed;
|
|
561
|
+
try {
|
|
562
|
+
parsed = JSON.parse(payload);
|
|
563
|
+
} catch {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
yield parsed;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
} finally {
|
|
570
|
+
await reader.cancel().catch(() => {
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
}
|
|
241
576
|
/** Widget configuration settings persisted by the canvas host. */
|
|
242
577
|
get settings() {
|
|
243
578
|
return {
|
|
@@ -263,6 +598,28 @@ var CanvasWidgetSDKImpl = class {
|
|
|
263
598
|
});
|
|
264
599
|
}
|
|
265
600
|
// ── Private ─────────────────────────────────────────────────
|
|
601
|
+
/** Raw streaming POST against the widget gateway — same auth as
|
|
602
|
+
* widgetApiRequest, but returns the Response for SSE consumption
|
|
603
|
+
* instead of parsing a JSON envelope. */
|
|
604
|
+
async widgetApiStream(body) {
|
|
605
|
+
if (!this._widgetApiUrl) {
|
|
606
|
+
throw new Error("Widget API Gateway URL not available");
|
|
607
|
+
}
|
|
608
|
+
const headers = { "Content-Type": "application/json" };
|
|
609
|
+
if (this._widgetApiToken) {
|
|
610
|
+
headers["Authorization"] = `Bearer ${this._widgetApiToken}`;
|
|
611
|
+
}
|
|
612
|
+
const response = await fetch(`${this._widgetApiUrl}/widget-api/ai/stream`, {
|
|
613
|
+
method: "POST",
|
|
614
|
+
headers,
|
|
615
|
+
body: JSON.stringify(body)
|
|
616
|
+
});
|
|
617
|
+
if (!response.ok) {
|
|
618
|
+
const err = await response.json().catch(() => ({}));
|
|
619
|
+
throw new Error(err.message ?? "ai.stream request failed");
|
|
620
|
+
}
|
|
621
|
+
return response;
|
|
622
|
+
}
|
|
266
623
|
async widgetApiRequest(path, method, body) {
|
|
267
624
|
if (!this._widgetApiUrl) {
|
|
268
625
|
throw new Error("Widget API Gateway URL not available");
|
|
@@ -377,7 +734,7 @@ var CanvasWidgetSDKImpl = class {
|
|
|
377
734
|
});
|
|
378
735
|
}
|
|
379
736
|
async buildMessage(type, payload) {
|
|
380
|
-
const nonce =
|
|
737
|
+
const nonce = safeRandomUUID();
|
|
381
738
|
const timestamp = Date.now();
|
|
382
739
|
let token;
|
|
383
740
|
if (this._hmacSecret) {
|
|
@@ -407,6 +764,13 @@ var CanvasWidgetSDKImpl = class {
|
|
|
407
764
|
}
|
|
408
765
|
};
|
|
409
766
|
var CanvasWidget = {
|
|
767
|
+
/**
|
|
768
|
+
* Initialize a group-session client for widgets hosted on the /w/[slug]
|
|
769
|
+
* session page (ticket handshake + session REST rails). Returns null when
|
|
770
|
+
* the launch context carries no session params — see `initSession` in
|
|
771
|
+
* ./session for details and `createMockSession` for standalone dev.
|
|
772
|
+
*/
|
|
773
|
+
initSession,
|
|
410
774
|
/**
|
|
411
775
|
* Initialize the SDK and establish communication with the canvas host.
|
|
412
776
|
*
|
|
@@ -436,7 +800,7 @@ var CanvasWidget = {
|
|
|
436
800
|
type: "READY",
|
|
437
801
|
widgetId: "",
|
|
438
802
|
// Not yet known — host identifies us by iframe source
|
|
439
|
-
nonce:
|
|
803
|
+
nonce: safeRandomUUID(),
|
|
440
804
|
timestamp: Date.now(),
|
|
441
805
|
token: DEV_MODE_TOKEN,
|
|
442
806
|
// READY is always allowed, token doesn't matter
|
|
@@ -446,8 +810,84 @@ var CanvasWidget = {
|
|
|
446
810
|
});
|
|
447
811
|
}
|
|
448
812
|
};
|
|
813
|
+
|
|
814
|
+
// src/theme.ts
|
|
815
|
+
var THEME_MESSAGE_TYPE = "flowstate:theme";
|
|
816
|
+
var DEFAULT_THEME = "glass";
|
|
817
|
+
function isWidgetTheme(value) {
|
|
818
|
+
return value === "light" || value === "dark" || value === "glass";
|
|
819
|
+
}
|
|
820
|
+
function getInitialTheme() {
|
|
821
|
+
if (typeof window === "undefined") return DEFAULT_THEME;
|
|
822
|
+
try {
|
|
823
|
+
const param = new URLSearchParams(window.location.search).get("theme");
|
|
824
|
+
return isWidgetTheme(param) ? param : DEFAULT_THEME;
|
|
825
|
+
} catch {
|
|
826
|
+
return DEFAULT_THEME;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
function onThemeChange(cb, opts) {
|
|
830
|
+
if (typeof window === "undefined") return () => {
|
|
831
|
+
};
|
|
832
|
+
const listener = (event) => {
|
|
833
|
+
if (event.source !== window.parent) return;
|
|
834
|
+
if (opts?.allowedOrigin && event.origin !== opts.allowedOrigin) return;
|
|
835
|
+
const data = event.data;
|
|
836
|
+
if (typeof data !== "object" || data === null) return;
|
|
837
|
+
const message = data;
|
|
838
|
+
if (message.type !== THEME_MESSAGE_TYPE) return;
|
|
839
|
+
if (!isWidgetTheme(message.theme)) return;
|
|
840
|
+
cb(message.theme);
|
|
841
|
+
};
|
|
842
|
+
window.addEventListener("message", listener);
|
|
843
|
+
return () => window.removeEventListener("message", listener);
|
|
844
|
+
}
|
|
845
|
+
function applyWidgetTheme(theme) {
|
|
846
|
+
if (typeof document === "undefined") return;
|
|
847
|
+
if (theme === "glass") {
|
|
848
|
+
delete document.documentElement.dataset.theme;
|
|
849
|
+
} else {
|
|
850
|
+
document.documentElement.dataset.theme = theme;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
function initWidgetTheme() {
|
|
854
|
+
applyWidgetTheme(getInitialTheme());
|
|
855
|
+
return onThemeChange(applyWidgetTheme);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/ingest.ts
|
|
859
|
+
var INGEST_MESSAGE_TYPE = "flowstate:ingest";
|
|
860
|
+
function onArtifactIngested(cb, opts) {
|
|
861
|
+
if (typeof window === "undefined") return () => {
|
|
862
|
+
};
|
|
863
|
+
const listener = (event) => {
|
|
864
|
+
if (event.source !== window.parent) return;
|
|
865
|
+
if (opts?.allowedOrigin && event.origin !== opts.allowedOrigin) return;
|
|
866
|
+
const data = event.data;
|
|
867
|
+
if (typeof data !== "object" || data === null) return;
|
|
868
|
+
const message = data;
|
|
869
|
+
if (message.type !== INGEST_MESSAGE_TYPE) return;
|
|
870
|
+
if (typeof message.artifactId !== "string" || !message.artifactId) return;
|
|
871
|
+
cb({
|
|
872
|
+
artifactId: message.artifactId,
|
|
873
|
+
kind: typeof message.kind === "string" ? message.kind : void 0,
|
|
874
|
+
payload: message.payload
|
|
875
|
+
});
|
|
876
|
+
};
|
|
877
|
+
window.addEventListener("message", listener);
|
|
878
|
+
return () => window.removeEventListener("message", listener);
|
|
879
|
+
}
|
|
449
880
|
export {
|
|
450
881
|
CanvasWidget,
|
|
451
|
-
|
|
882
|
+
INGEST_MESSAGE_TYPE,
|
|
883
|
+
applyWidgetTheme,
|
|
884
|
+
createMockSession,
|
|
885
|
+
createServiceClient,
|
|
886
|
+
getInitialTheme,
|
|
887
|
+
initSession,
|
|
888
|
+
initWidgetTheme,
|
|
889
|
+
onArtifactIngested,
|
|
890
|
+
onThemeChange,
|
|
891
|
+
readSessionLaunchParams
|
|
452
892
|
};
|
|
453
893
|
//# sourceMappingURL=index.mjs.map
|