@iota-uz/sdk 0.1.2 → 0.3.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.
@@ -0,0 +1,84 @@
1
+ /**
2
+ * TypeScript type definitions for IOTA SDK Applet Core
3
+ * Matches Go backend types from pkg/applet/types.go
4
+ */
5
+ interface InitialContext {
6
+ user: UserContext;
7
+ tenant: TenantContext;
8
+ locale: LocaleContext;
9
+ config: AppConfig;
10
+ route: RouteContext;
11
+ session: SessionContext;
12
+ error: ErrorContext | null;
13
+ extensions?: Record<string, unknown>;
14
+ }
15
+ interface UserContext {
16
+ id: number;
17
+ email: string;
18
+ firstName: string;
19
+ lastName: string;
20
+ permissions: string[];
21
+ }
22
+ interface TenantContext {
23
+ id: string;
24
+ name: string;
25
+ }
26
+ interface LocaleContext {
27
+ language: string;
28
+ translations: Record<string, string>;
29
+ }
30
+ interface AppConfig {
31
+ graphQLEndpoint?: string;
32
+ streamEndpoint?: string;
33
+ restEndpoint?: string;
34
+ basePath?: string;
35
+ assetsBasePath?: string;
36
+ rpcUIEndpoint?: string;
37
+ shellMode?: 'embedded' | 'standalone';
38
+ }
39
+ interface RouteContext {
40
+ path: string;
41
+ params: Record<string, string>;
42
+ query: Record<string, string>;
43
+ }
44
+ interface SessionContext {
45
+ expiresAt: number;
46
+ refreshURL: string;
47
+ csrfToken: string;
48
+ }
49
+ interface ErrorContext {
50
+ supportEmail?: string;
51
+ debugMode: boolean;
52
+ errorCodes?: Record<string, string>;
53
+ retryConfig?: RetryConfig;
54
+ }
55
+ interface RetryConfig {
56
+ maxAttempts: number;
57
+ backoffMs: number;
58
+ }
59
+ /**
60
+ * Hook return types
61
+ */
62
+ interface TranslationHook {
63
+ t: (key: string, params?: Record<string, unknown>) => string;
64
+ language: string;
65
+ }
66
+ interface PermissionsHook {
67
+ hasPermission: (permission: string) => boolean;
68
+ hasAnyPermission: (...permissions: string[]) => boolean;
69
+ permissions: string[];
70
+ }
71
+ interface SessionHook {
72
+ isExpiringSoon: boolean;
73
+ refreshSession: () => Promise<void>;
74
+ csrfToken: string;
75
+ expiresAt: number;
76
+ }
77
+ interface StreamingHook {
78
+ isStreaming: boolean;
79
+ processStream: <T>(generator: AsyncGenerator<T>, onChunk: (chunk: T) => void, signal?: AbortSignal) => Promise<void>;
80
+ cancel: () => void;
81
+ reset: () => void;
82
+ }
83
+
84
+ export type { AppConfig as A, InitialContext as I, LocaleContext as L, PermissionsHook as P, RouteContext as R, SessionHook as S, TranslationHook as T, UserContext as U, StreamingHook as a, SessionContext as b, TenantContext as c };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * TypeScript type definitions for IOTA SDK Applet Core
3
+ * Matches Go backend types from pkg/applet/types.go
4
+ */
5
+ interface InitialContext {
6
+ user: UserContext;
7
+ tenant: TenantContext;
8
+ locale: LocaleContext;
9
+ config: AppConfig;
10
+ route: RouteContext;
11
+ session: SessionContext;
12
+ error: ErrorContext | null;
13
+ extensions?: Record<string, unknown>;
14
+ }
15
+ interface UserContext {
16
+ id: number;
17
+ email: string;
18
+ firstName: string;
19
+ lastName: string;
20
+ permissions: string[];
21
+ }
22
+ interface TenantContext {
23
+ id: string;
24
+ name: string;
25
+ }
26
+ interface LocaleContext {
27
+ language: string;
28
+ translations: Record<string, string>;
29
+ }
30
+ interface AppConfig {
31
+ graphQLEndpoint?: string;
32
+ streamEndpoint?: string;
33
+ restEndpoint?: string;
34
+ basePath?: string;
35
+ assetsBasePath?: string;
36
+ rpcUIEndpoint?: string;
37
+ shellMode?: 'embedded' | 'standalone';
38
+ }
39
+ interface RouteContext {
40
+ path: string;
41
+ params: Record<string, string>;
42
+ query: Record<string, string>;
43
+ }
44
+ interface SessionContext {
45
+ expiresAt: number;
46
+ refreshURL: string;
47
+ csrfToken: string;
48
+ }
49
+ interface ErrorContext {
50
+ supportEmail?: string;
51
+ debugMode: boolean;
52
+ errorCodes?: Record<string, string>;
53
+ retryConfig?: RetryConfig;
54
+ }
55
+ interface RetryConfig {
56
+ maxAttempts: number;
57
+ backoffMs: number;
58
+ }
59
+ /**
60
+ * Hook return types
61
+ */
62
+ interface TranslationHook {
63
+ t: (key: string, params?: Record<string, unknown>) => string;
64
+ language: string;
65
+ }
66
+ interface PermissionsHook {
67
+ hasPermission: (permission: string) => boolean;
68
+ hasAnyPermission: (...permissions: string[]) => boolean;
69
+ permissions: string[];
70
+ }
71
+ interface SessionHook {
72
+ isExpiringSoon: boolean;
73
+ refreshSession: () => Promise<void>;
74
+ csrfToken: string;
75
+ expiresAt: number;
76
+ }
77
+ interface StreamingHook {
78
+ isStreaming: boolean;
79
+ processStream: <T>(generator: AsyncGenerator<T>, onChunk: (chunk: T) => void, signal?: AbortSignal) => Promise<void>;
80
+ cancel: () => void;
81
+ reset: () => void;
82
+ }
83
+
84
+ export type { AppConfig as A, InitialContext as I, LocaleContext as L, PermissionsHook as P, RouteContext as R, SessionHook as S, TranslationHook as T, UserContext as U, StreamingHook as a, SessionContext as b, TenantContext as c };
package/dist/index.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var react = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
+ var client = require('react-dom/client');
5
6
 
6
7
  // ui/src/applet-core/context/AppletContext.tsx
7
8
  var AppletContext = react.createContext(null);
@@ -175,10 +176,356 @@ function useStreaming() {
175
176
  };
176
177
  }
177
178
 
179
+ // ui/src/applet-core/hooks/useAppletRuntime.ts
180
+ function useAppletRuntime() {
181
+ const config = useConfig();
182
+ const basePath = config.basePath ?? "";
183
+ const normalizedBasePath = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
184
+ const assetsBasePath = config.assetsBasePath ?? `${normalizedBasePath || ""}/assets`;
185
+ const rpcEndpoint = config.rpcUIEndpoint;
186
+ const shellMode = config.shellMode;
187
+ return { basePath: normalizedBasePath, assetsBasePath, rpcEndpoint, shellMode };
188
+ }
189
+ function defineReactAppletElement(options) {
190
+ const tagName = options.tagName.toLowerCase();
191
+ const registry = getRegistry();
192
+ const existing = registry.get(tagName);
193
+ if (existing) {
194
+ existing.options = options;
195
+ for (const a of options.observedAttributes ?? []) existing.observed.add(a);
196
+ } else {
197
+ const observed = /* @__PURE__ */ new Set(["base-path", "shell-mode", "router-mode"]);
198
+ for (const a of options.observedAttributes ?? []) observed.add(a);
199
+ registry.set(tagName, { options, observed });
200
+ }
201
+ if (customElements.get(tagName)) {
202
+ if (typeof window !== "undefined") {
203
+ window.dispatchEvent(new CustomEvent("iota:applet-host-update", { detail: { tagName } }));
204
+ }
205
+ return;
206
+ }
207
+ function getEntry() {
208
+ const entry = getRegistry().get(tagName);
209
+ if (!entry) throw new Error(`[${tagName}] applet host registry entry missing`);
210
+ return entry;
211
+ }
212
+ class ReactAppletElement extends HTMLElement {
213
+ constructor() {
214
+ super(...arguments);
215
+ this.reactRoot = null;
216
+ this.container = null;
217
+ this.darkModeObserver = null;
218
+ this.styleEl = null;
219
+ this.updateListener = null;
220
+ }
221
+ static get observedAttributes() {
222
+ return Array.from(getEntry().observed);
223
+ }
224
+ connectedCallback() {
225
+ const shadowRoot = this.shadowRoot ?? this.attachShadow({ mode: "open" });
226
+ if (!this.container) {
227
+ this.container = document.createElement("div");
228
+ this.container.id = "react-root";
229
+ this.container.style.display = "flex";
230
+ this.container.style.flexDirection = "column";
231
+ this.container.style.flex = "1";
232
+ this.container.style.minHeight = "0";
233
+ this.container.style.height = "100%";
234
+ this.container.style.width = "100%";
235
+ }
236
+ const existingContainer = shadowRoot.querySelector("#react-root");
237
+ if (!existingContainer) {
238
+ if (this.styleEl) shadowRoot.appendChild(this.styleEl);
239
+ shadowRoot.appendChild(this.container);
240
+ } else if (existingContainer !== this.container) {
241
+ this.container = existingContainer;
242
+ }
243
+ this.syncFromRegistry();
244
+ this.updateListener ?? (this.updateListener = (e) => {
245
+ if (!(e instanceof CustomEvent)) return;
246
+ const detail = e.detail;
247
+ if (!detail || detail.tagName !== tagName) return;
248
+ this.syncFromRegistry();
249
+ this.renderReact();
250
+ });
251
+ window.addEventListener("iota:applet-host-update", this.updateListener);
252
+ this.renderReact();
253
+ }
254
+ disconnectedCallback() {
255
+ if (this.updateListener) {
256
+ window.removeEventListener("iota:applet-host-update", this.updateListener);
257
+ }
258
+ this.darkModeObserver?.disconnect();
259
+ this.darkModeObserver = null;
260
+ this.reactRoot?.unmount();
261
+ this.reactRoot = null;
262
+ }
263
+ attributeChangedCallback(_name, oldValue, newValue) {
264
+ if (oldValue === newValue) return;
265
+ if (this.container) this.renderReact();
266
+ }
267
+ getHostConfig() {
268
+ const attrs = {};
269
+ for (const { name, value } of Array.from(this.attributes)) {
270
+ attrs[name] = value;
271
+ }
272
+ const basePath = this.getAttribute("base-path") ?? "";
273
+ const shellMode = this.getAttribute("shell-mode") ?? void 0;
274
+ const routerMode = this.getAttribute("router-mode") ?? "url";
275
+ return { basePath, shellMode, routerMode, attrs };
276
+ }
277
+ renderReact() {
278
+ if (!this.container) return;
279
+ if (!this.reactRoot) {
280
+ this.reactRoot = client.createRoot(this.container);
281
+ }
282
+ try {
283
+ this.reactRoot.render(getEntry().options.render(this.getHostConfig()));
284
+ } catch (err) {
285
+ console.error(`[${tagName}] failed to mount React app:`, err);
286
+ }
287
+ }
288
+ syncFromRegistry() {
289
+ const entry = getEntry();
290
+ const styles = typeof entry.options.styles === "function" ? entry.options.styles() : entry.options.styles;
291
+ if (styles) {
292
+ this.styleEl ?? (this.styleEl = document.createElement("style"));
293
+ this.styleEl.textContent = styles;
294
+ if (this.shadowRoot && !this.shadowRoot.contains(this.styleEl)) {
295
+ this.shadowRoot.insertBefore(this.styleEl, this.shadowRoot.firstChild);
296
+ }
297
+ } else if (this.styleEl) {
298
+ this.styleEl.remove();
299
+ this.styleEl = null;
300
+ }
301
+ if (entry.options.observeDarkMode !== false) {
302
+ this.darkModeObserver ?? (this.darkModeObserver = this.syncDarkMode());
303
+ } else if (this.darkModeObserver) {
304
+ this.darkModeObserver.disconnect();
305
+ this.darkModeObserver = null;
306
+ }
307
+ }
308
+ syncDarkMode() {
309
+ const root = this.container;
310
+ if (!root) throw new Error("react root container not found");
311
+ const apply = () => {
312
+ if (document.documentElement.classList.contains("dark")) root.classList.add("dark");
313
+ else root.classList.remove("dark");
314
+ };
315
+ apply();
316
+ const observer = new MutationObserver(apply);
317
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
318
+ return observer;
319
+ }
320
+ }
321
+ customElements.define(tagName, ReactAppletElement);
322
+ }
323
+ function getRegistry() {
324
+ const anyGlobal = globalThis;
325
+ anyGlobal.__IOTA_REACT_APPLET_HOST_REGISTRY__ ?? (anyGlobal.__IOTA_REACT_APPLET_HOST_REGISTRY__ = /* @__PURE__ */ new Map());
326
+ return anyGlobal.__IOTA_REACT_APPLET_HOST_REGISTRY__;
327
+ }
328
+
329
+ // ui/src/applet-devtools/enabled.ts
330
+ function shouldEnableAppletDevtools() {
331
+ if (typeof window === "undefined") return false;
332
+ const url = new URL(window.location.href);
333
+ if (url.searchParams.get("appletDebug") === "1") return true;
334
+ try {
335
+ return window.localStorage.getItem("iotaAppletDevtools") === "1";
336
+ } catch {
337
+ return false;
338
+ }
339
+ }
340
+
341
+ // ui/src/applet-host/rpc.ts
342
+ var AppletRPCException = class extends Error {
343
+ constructor(args) {
344
+ super(args.message);
345
+ this.name = "AppletRPCException";
346
+ this.code = args.code;
347
+ this.details = args.details;
348
+ this.cause = args.cause;
349
+ }
350
+ };
351
+ function createAppletRPCClient(options) {
352
+ const fetcher = options.fetcher ?? fetch;
353
+ async function call(method, params) {
354
+ const req = { id: crypto.randomUUID(), method, params };
355
+ const startedAt = typeof performance !== "undefined" ? performance.now() : Date.now();
356
+ maybeDispatchRPCEvent({
357
+ id: req.id,
358
+ method: req.method,
359
+ status: "start"
360
+ });
361
+ try {
362
+ const resp = await fetcher(options.endpoint, {
363
+ method: "POST",
364
+ headers: { "Content-Type": "application/json" },
365
+ body: JSON.stringify(req)
366
+ });
367
+ if (!resp.ok) {
368
+ throw new AppletRPCException({
369
+ code: "http_error",
370
+ message: `HTTP ${resp.status}`,
371
+ details: { status: resp.status }
372
+ });
373
+ }
374
+ const json = await resp.json();
375
+ if (json.error) {
376
+ throw new AppletRPCException({
377
+ code: json.error.code,
378
+ message: json.error.message,
379
+ details: json.error.details
380
+ });
381
+ }
382
+ if (json.result === void 0) {
383
+ throw new AppletRPCException({
384
+ code: "invalid_response",
385
+ message: "Missing result in successful response"
386
+ });
387
+ }
388
+ maybeDispatchRPCEvent({
389
+ id: req.id,
390
+ method: req.method,
391
+ status: "success",
392
+ durationMs: elapsedMs(startedAt)
393
+ });
394
+ return json.result;
395
+ } catch (err) {
396
+ maybeDispatchRPCEvent({
397
+ id: req.id,
398
+ method: req.method,
399
+ status: "error",
400
+ durationMs: elapsedMs(startedAt),
401
+ error: err
402
+ });
403
+ throw err;
404
+ }
405
+ }
406
+ async function callTyped(method, params) {
407
+ return call(method, params);
408
+ }
409
+ return { call, callTyped };
410
+ }
411
+ function maybeDispatchRPCEvent(detail) {
412
+ if (typeof window === "undefined") return;
413
+ if (!shouldEnableAppletDevtools()) return;
414
+ window.dispatchEvent(new CustomEvent("iota:applet-rpc", { detail }));
415
+ }
416
+ function elapsedMs(startedAt) {
417
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
418
+ return Math.max(0, Math.round(now - startedAt));
419
+ }
420
+ function createAppletRouter(options) {
421
+ const basePath = options.basePath ?? "";
422
+ const Router = ({ children }) => {
423
+ if (options.mode === "memory") {
424
+ const MemoryRouter = options.MemoryRouter;
425
+ return /* @__PURE__ */ jsxRuntime.jsx(MemoryRouter, { children });
426
+ }
427
+ const BrowserRouter = options.BrowserRouter;
428
+ return /* @__PURE__ */ jsxRuntime.jsx(BrowserRouter, { basename: basePath, children });
429
+ };
430
+ return { Router };
431
+ }
432
+ function AppletDevtoolsOverlay() {
433
+ const ctx = useAppletContext();
434
+ const runtime = useAppletRuntime();
435
+ const [rpcEvents, setRPCEvents] = react.useState([]);
436
+ react.useEffect(() => {
437
+ const onEvent = (e) => {
438
+ if (!(e instanceof CustomEvent)) return;
439
+ const detail = e.detail;
440
+ if (!detail) return;
441
+ setRPCEvents((prev) => [detail, ...prev].slice(0, 50));
442
+ };
443
+ window.addEventListener("iota:applet-rpc", onEvent);
444
+ return () => window.removeEventListener("iota:applet-rpc", onEvent);
445
+ }, []);
446
+ const summary = react.useMemo(() => {
447
+ return {
448
+ basePath: runtime.basePath,
449
+ assetsBasePath: runtime.assetsBasePath,
450
+ rpcEndpoint: runtime.rpcEndpoint,
451
+ shellMode: runtime.shellMode,
452
+ route: ctx.route,
453
+ user: { id: ctx.user.id, email: ctx.user.email },
454
+ tenant: ctx.tenant
455
+ };
456
+ }, [ctx.route, ctx.tenant, ctx.user.email, ctx.user.id, runtime.assetsBasePath, runtime.basePath, runtime.rpcEndpoint, runtime.shellMode]);
457
+ return /* @__PURE__ */ jsxRuntime.jsxs(
458
+ "div",
459
+ {
460
+ style: {
461
+ position: "fixed",
462
+ right: 12,
463
+ bottom: 12,
464
+ width: 420,
465
+ maxHeight: "60vh",
466
+ overflow: "auto",
467
+ background: "rgba(17, 24, 39, 0.92)",
468
+ color: "#E5E7EB",
469
+ border: "1px solid rgba(255,255,255,0.12)",
470
+ borderRadius: 10,
471
+ padding: 12,
472
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
473
+ fontSize: 12,
474
+ zIndex: 2147483647
475
+ },
476
+ children: [
477
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }, children: [
478
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 700 }, children: "Applet Devtools" }),
479
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { opacity: 0.7 }, children: runtime.shellMode ?? "unknown" })
480
+ ] }),
481
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 10 }, children: [
482
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { opacity: 0.85, marginBottom: 4 }, children: "Context" }),
483
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { margin: 0, whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: JSON.stringify(summary, null, 2) })
484
+ ] }),
485
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
486
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { opacity: 0.85, marginBottom: 4 }, children: "RPC" }),
487
+ rpcEvents.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { opacity: 0.7 }, children: "No calls yet" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: rpcEvents.map((ev) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: 8, border: "1px solid rgba(255,255,255,0.08)", borderRadius: 8 }, children: [
488
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
489
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: ev.method }),
490
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { opacity: 0.8 }, children: [
491
+ ev.status,
492
+ typeof ev.durationMs === "number" ? ` (${ev.durationMs}ms)` : ""
493
+ ] })
494
+ ] }),
495
+ ev.status === "error" ? /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { margin: "6px 0 0", opacity: 0.8, whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: JSON.stringify(serializeError(ev.error) ?? {}, null, 2) }) : null
496
+ ] }, `${ev.id}:${ev.status}`)) })
497
+ ] })
498
+ ]
499
+ }
500
+ );
501
+ }
502
+ function serializeError(err) {
503
+ if (err instanceof Error) {
504
+ const anyErr = err;
505
+ const out = {
506
+ name: err.name,
507
+ message: err.message
508
+ };
509
+ if (err.stack) out.stack = err.stack;
510
+ if (typeof anyErr.code === "string") out.code = anyErr.code;
511
+ if (anyErr.details !== void 0) out.details = anyErr.details;
512
+ if (anyErr.cause !== void 0) out.cause = anyErr.cause;
513
+ return out;
514
+ }
515
+ return err;
516
+ }
517
+
518
+ exports.AppletDevtoolsOverlay = AppletDevtoolsOverlay;
178
519
  exports.AppletProvider = AppletProvider;
520
+ exports.AppletRPCException = AppletRPCException;
179
521
  exports.ConfigProvider = ConfigProvider;
522
+ exports.createAppletRPCClient = createAppletRPCClient;
523
+ exports.createAppletRouter = createAppletRouter;
524
+ exports.defineReactAppletElement = defineReactAppletElement;
525
+ exports.shouldEnableAppletDevtools = shouldEnableAppletDevtools;
180
526
  exports.useAppletContext = useAppletContext;
181
527
  exports.useAppletContextDirect = useAppletContext2;
528
+ exports.useAppletRuntime = useAppletRuntime;
182
529
  exports.useConfig = useConfig;
183
530
  exports.useConfigContext = useConfigContext;
184
531
  exports.usePermissions = usePermissions;