@keemakr/ui-sdk 1.2.0 → 1.3.0-poc-20260911235111

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/dist/index.d.ts CHANGED
@@ -4,3 +4,5 @@ export { childNodesOf, KNOWN_CONTAINERS, KNOWN_NODE_TYPES } from './nodes.js';
4
4
  export type { ContainerChild, ContainerNodeType, KnownNodeType } from './nodes.js';
5
5
  export { defineScreen, defineSurface } from './define.js';
6
6
  export type { ScreenDefinition, ScreenParams, LoadHandler, ActionHandler } from './define.js';
7
+ export { createWidgetClient } from './widget.js';
8
+ export type { WidgetClient, WidgetClientOptions, WidgetFetch, WidgetResponse, WidgetStorage, } from './widget.js';
package/dist/index.js CHANGED
@@ -9,3 +9,4 @@
9
9
  export { UI_SCHEMA_VERSION, pressSchema, buttonSchema, cellSchema, nodeSchema, screenTreeSchema, actionResultSchema, screenLoadRequestSchema, screenLoadResponseSchema, screenActionRequestSchema, interfaceFamilySchema, surfaceDefinitionSchema, surfaceSchema, screenRegistrationSchema, uiManifestSchema, } from './schema.js';
10
10
  export { childNodesOf, KNOWN_CONTAINERS, KNOWN_NODE_TYPES } from './nodes.js';
11
11
  export { defineScreen, defineSurface } from './define.js';
12
+ export { createWidgetClient } from './widget.js';
@@ -0,0 +1,36 @@
1
+ export interface WidgetStorage {
2
+ getItem(key: string): string | null;
3
+ setItem(key: string, value: string): void;
4
+ removeItem(key: string): void;
5
+ }
6
+ export interface WidgetResponse {
7
+ ok: boolean;
8
+ status: number;
9
+ json(): Promise<unknown>;
10
+ }
11
+ export type WidgetFetch = (url: string, init: {
12
+ method: 'GET' | 'POST';
13
+ headers?: Record<string, string>;
14
+ body?: unknown;
15
+ }) => Promise<WidgetResponse>;
16
+ export interface WidgetClientOptions {
17
+ coreUrl: string;
18
+ widgetId: string;
19
+ routeKey?: string;
20
+ storage?: WidgetStorage;
21
+ fetch?: WidgetFetch;
22
+ }
23
+ export interface WidgetClient {
24
+ bootstrap(): Promise<void>;
25
+ getMessages(): Promise<unknown>;
26
+ sendMessage(input: {
27
+ text: string;
28
+ idempotencyKey?: string;
29
+ attachmentIds?: string[];
30
+ }): Promise<unknown>;
31
+ assertHandle(kind: 'email' | 'phone', value: string): Promise<unknown>;
32
+ verifyHandle(challengeId: string, code: string): Promise<unknown>;
33
+ uploadAttachment(bytes: Uint8Array, mediaType: string): Promise<unknown>;
34
+ forget(): void;
35
+ }
36
+ export declare function createWidgetClient(options: WidgetClientOptions): WidgetClient;
package/dist/widget.js ADDED
@@ -0,0 +1,90 @@
1
+ function browserStorage() {
2
+ const storage = globalThis.localStorage;
3
+ if (!storage)
4
+ throw new Error('widget storage is unavailable');
5
+ return storage;
6
+ }
7
+ function browserFetch() {
8
+ const value = globalThis.fetch;
9
+ if (typeof value !== 'function')
10
+ throw new Error('widget fetch is unavailable');
11
+ return value.bind(globalThis);
12
+ }
13
+ function randomId() {
14
+ const crypto = globalThis.crypto;
15
+ if (!crypto?.randomUUID)
16
+ throw new Error('crypto.randomUUID is unavailable');
17
+ return crypto.randomUUID();
18
+ }
19
+ export function createWidgetClient(options) {
20
+ const coreUrl = options.coreUrl.replace(/\/$/, '');
21
+ const routeKey = options.routeKey ?? 'default';
22
+ const storage = options.storage ?? browserStorage();
23
+ const request = options.fetch ?? browserFetch();
24
+ const storageKey = `kee:widget:${options.widgetId}:${routeKey}`;
25
+ const query = new URLSearchParams({
26
+ widget_id: options.widgetId,
27
+ route_key: routeKey,
28
+ }).toString();
29
+ async function decode(response) {
30
+ const body = await response.json();
31
+ if (!response.ok)
32
+ throw new Error(`widget request failed (${response.status})`);
33
+ return body;
34
+ }
35
+ async function bootstrap(retry = true) {
36
+ const visitorToken = storage.getItem(storageKey);
37
+ const response = await request(`${coreUrl}/api/channels/widget/bootstrap?${query}`, {
38
+ method: 'POST',
39
+ headers: { 'content-type': 'application/json' },
40
+ body: JSON.stringify({
41
+ widget_id: options.widgetId,
42
+ route_key: routeKey,
43
+ ...(visitorToken ? { visitor_token: visitorToken } : {}),
44
+ }),
45
+ });
46
+ if (!response.ok && response.status === 401 && visitorToken && retry) {
47
+ storage.removeItem(storageKey);
48
+ return bootstrap(false);
49
+ }
50
+ const body = (await decode(response));
51
+ if (typeof body.visitor_token !== 'string')
52
+ throw new Error('widget bootstrap token is missing');
53
+ storage.setItem(storageKey, body.visitor_token);
54
+ }
55
+ async function authorizedRequest(path, init, retry = true) {
56
+ if (!storage.getItem(storageKey))
57
+ await bootstrap();
58
+ const token = storage.getItem(storageKey);
59
+ const response = await request(`${coreUrl}/api/channels/widget/${path}?${query}`, {
60
+ ...init,
61
+ headers: { authorization: `Bearer ${token}`, ...init.headers },
62
+ });
63
+ if (!response.ok && response.status === 401 && retry) {
64
+ storage.removeItem(storageKey);
65
+ await bootstrap(false);
66
+ return authorizedRequest(path, init, false);
67
+ }
68
+ return decode(response);
69
+ }
70
+ async function post(path, body, headers) {
71
+ return authorizedRequest(path, { method: 'POST', headers, body });
72
+ }
73
+ async function get(path) {
74
+ return authorizedRequest(path, { method: 'GET' });
75
+ }
76
+ return {
77
+ bootstrap,
78
+ getMessages: () => get('messages'),
79
+ sendMessage: ({ text, idempotencyKey = randomId(), attachmentIds = [] }) => post('messages', JSON.stringify({ text, idempotency_key: idempotencyKey, attachment_ids: attachmentIds }), { 'content-type': 'application/json' }),
80
+ assertHandle: (kind, value) => post('handles', JSON.stringify({ kind, value }), { 'content-type': 'application/json' }),
81
+ verifyHandle: (challengeId, code) => post('handles/verify', JSON.stringify({ challenge_id: challengeId, code }), {
82
+ 'content-type': 'application/json',
83
+ }),
84
+ uploadAttachment: (bytes, mediaType) => post('attachments', bytes, {
85
+ 'content-type': 'application/octet-stream',
86
+ 'x-kee-media-type': mediaType,
87
+ }),
88
+ forget: () => storage.removeItem(storageKey),
89
+ };
90
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keemakr/ui-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.3.0-poc-20260911235111",
4
4
  "description": "Product screens for keemakr marketplace modules: a versioned, server-driven UI tree schema plus defineScreen() handlers. Module code never ships to the browser — keemakr-core validates the tree and renders it with platform components.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,6 +16,11 @@
16
16
  "types": "./dist/build.d.ts",
17
17
  "import": "./dist/build.js",
18
18
  "default": "./dist/build.js"
19
+ },
20
+ "./widget": {
21
+ "types": "./dist/widget.d.ts",
22
+ "import": "./dist/widget.js",
23
+ "default": "./dist/widget.js"
19
24
  }
20
25
  },
21
26
  "bin": {