@nextage/ent-embed-sdk 0.1.0 → 0.1.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.
@@ -1,56 +1,76 @@
1
1
  /*! @nextage/ent-embed-sdk — see https://www.npmjs.com/package/@nextage/ent-embed-sdk */
2
2
  'use strict';
3
3
 
4
+ const KNOWN_FRAME_TYPES = /* @__PURE__ */ new Set([
5
+ "ent:result",
6
+ "ent:cancelled",
7
+ "ent:terminal"
8
+ ]);
9
+ const DEFAULT_TERMINAL_CODE = "embed.errors.session.terminal";
4
10
  function isTrustedEntFrame(event, allowedOrigin, popup) {
5
- if (event.origin !== allowedOrigin) return false;
6
- if (popup && event.source !== popup) return false;
11
+ if (event.origin !== allowedOrigin)
12
+ return false;
13
+ if (popup && event.source !== popup)
14
+ return false;
7
15
  const data = event.data;
8
- if (!data || typeof data !== "object") return false;
16
+ if (!data || typeof data !== "object")
17
+ return false;
9
18
  const type = data.type;
10
- return type === "ent:result" || type === "ent:cancelled" || type === "ent:terminal";
19
+ return typeof type === "string" && KNOWN_FRAME_TYPES.has(type);
11
20
  }
12
21
  function frameToEvent(frame) {
13
22
  switch (frame.type) {
14
- case "ent:result": {
15
- const result = normalizeResult(frame.payload);
16
- if (!result) return null;
17
- return { type: "result", result, raw: frame };
18
- }
19
- case "ent:cancelled": {
20
- const reason = frame.payload && typeof frame.payload === "object" ? frame.payload.reason : void 0;
21
- return {
22
- type: "cancelled",
23
- ...typeof reason === "string" ? { reason } : {},
24
- raw: frame
25
- };
26
- }
27
- case "ent:terminal": {
28
- const p = frame.payload && typeof frame.payload === "object" ? frame.payload : {};
29
- const code = typeof p.code === "string" ? p.code : "embed.errors.session.terminal";
30
- const message = typeof p.message === "string" ? p.message : void 0;
31
- return {
32
- type: "terminal",
33
- code,
34
- ...message ? { message } : {},
35
- raw: frame
36
- };
37
- }
23
+ case "ent:result":
24
+ return mapResultFrame(frame);
25
+ case "ent:cancelled":
26
+ return mapCancelledFrame(frame);
27
+ case "ent:terminal":
28
+ return mapTerminalFrame(frame);
38
29
  default:
39
30
  return null;
40
31
  }
41
32
  }
33
+ function mapResultFrame(frame) {
34
+ const result = normalizeResult(frame.payload);
35
+ if (!result)
36
+ return null;
37
+ return { type: "result", result, raw: frame };
38
+ }
39
+ function mapCancelledFrame(frame) {
40
+ const reason = readStringField(frame.payload, "reason");
41
+ return {
42
+ type: "cancelled",
43
+ ...reason ? { reason } : {},
44
+ raw: frame
45
+ };
46
+ }
47
+ function mapTerminalFrame(frame) {
48
+ const code = readStringField(frame.payload, "code") ?? DEFAULT_TERMINAL_CODE;
49
+ const message = readStringField(frame.payload, "message");
50
+ return {
51
+ type: "terminal",
52
+ code,
53
+ ...message ? { message } : {},
54
+ raw: frame
55
+ };
56
+ }
57
+ function readStringField(payload, field) {
58
+ if (!payload || typeof payload !== "object")
59
+ return void 0;
60
+ const value = payload[field];
61
+ return typeof value === "string" ? value : void 0;
62
+ }
42
63
  function normalizeResult(payload) {
43
- if (!payload || typeof payload !== "object") return null;
64
+ if (!payload || typeof payload !== "object")
65
+ return null;
44
66
  const p = payload;
45
67
  const diagnoses = Array.isArray(p["diagnoses"]) ? p["diagnoses"] : [];
46
68
  const procedures = Array.isArray(p["procedures"]) ? p["procedures"] : [];
47
69
  const result = { diagnoses, procedures };
48
- if (p["drg"] && typeof p["drg"] === "object") {
70
+ if (p["drg"] && typeof p["drg"] === "object")
49
71
  result.drg = p["drg"];
50
- }
51
- if (p["meta"] && typeof p["meta"] === "object") {
72
+ if (p["meta"] && typeof p["meta"] === "object")
52
73
  result.meta = p["meta"];
53
- }
54
74
  return result;
55
75
  }
56
76
 
@@ -68,7 +88,7 @@ function withCenteredGeometry(win, base) {
68
88
  return `${base},left=${left},top=${top}`;
69
89
  }
70
90
  function parseFeature(features, key, fallback) {
71
- const m = features.match(new RegExp(`(?:^|,)${key}=(\\d+)`));
91
+ const m = new RegExp(String.raw`(?:^|,)${key}=(\d+)`).exec(features);
72
92
  return m ? Number(m[1]) : fallback;
73
93
  }
74
94
  function watchPopupClosed(win, popup, onClosed, intervalMs = 500) {
@@ -85,28 +105,44 @@ const DEFAULT_TIMEOUT_MS = 15 * 60 * 1e3;
85
105
  class EmbedClient {
86
106
  constructor() {
87
107
  }
108
+ /**
109
+ * Open the ENT embed popup and resolve when the session terminates.
110
+ *
111
+ * Lifecycle:
112
+ * 1. {@link validateOptions} — throw `TypeError` for bad input.
113
+ * 2. `window.open` — reject if the browser blocks the popup.
114
+ * 3. Install a `message` listener filtered by {@link isTrustedEntFrame}.
115
+ * 4. Poll `popup.closed` via {@link watchPopupClosed}.
116
+ * 5. Arm a timeout (default {@link DEFAULT_TIMEOUT_MS}).
117
+ * 6. The first of (`result` / `cancelled` / `terminal` / `closed`)
118
+ * wins, cleans up the other watchers, and resolves the Promise.
119
+ *
120
+ * @param options - See {@link EmbedOpenOptions}.
121
+ * @returns The terminal {@link EmbedEvent} for this session.
122
+ */
88
123
  static open(options) {
89
124
  validateOptions(options);
90
125
  const win = options.windowRef ?? globalThis.window;
91
- if (!win || typeof win.open !== "function") {
126
+ if (!win || typeof win.open !== "function")
92
127
  return Promise.reject(new Error("[ent-embed-sdk] No window available to open popup"));
93
- }
94
128
  const popup = openCenteredPopup(win, options.launchUrl, options.popupFeatures);
95
- if (!popup) {
129
+ if (!popup)
96
130
  return Promise.reject(
97
131
  new Error("[ent-embed-sdk] Popup blocked by browser. Call EmbedClient.open() from a user gesture.")
98
132
  );
99
- }
100
133
  return new Promise((resolve) => {
101
134
  let settled = false;
102
135
  let stopWatchClosed = null;
103
136
  let timeoutHandle = null;
104
137
  const settle = (event, closePopup) => {
105
- if (settled) return;
138
+ if (settled)
139
+ return;
106
140
  settled = true;
107
141
  win.removeEventListener("message", onMessage);
108
- if (stopWatchClosed) stopWatchClosed();
109
- if (timeoutHandle !== null) clearTimeout(timeoutHandle);
142
+ if (stopWatchClosed)
143
+ stopWatchClosed();
144
+ if (timeoutHandle !== null)
145
+ clearTimeout(timeoutHandle);
110
146
  safeNotify(options.onEvent, event);
111
147
  if (closePopup && popup && !popup.closed) {
112
148
  try {
@@ -117,9 +153,11 @@ class EmbedClient {
117
153
  resolve(event);
118
154
  };
119
155
  const onMessage = (event) => {
120
- if (!isTrustedEntFrame(event, options.allowedOrigin, popup)) return;
156
+ if (!isTrustedEntFrame(event, options.allowedOrigin, popup))
157
+ return;
121
158
  const mapped = frameToEvent(event.data);
122
- if (!mapped) return;
159
+ if (!mapped)
160
+ return;
123
161
  settle(
124
162
  mapped,
125
163
  /* closePopup */
@@ -148,24 +186,20 @@ class EmbedClient {
148
186
  }
149
187
  }
150
188
  function validateOptions(options) {
151
- if (!options || typeof options !== "object") {
189
+ if (!options || typeof options !== "object")
152
190
  throw new TypeError("[ent-embed-sdk] options object is required");
153
- }
154
- if (!options.launchUrl || typeof options.launchUrl !== "string") {
191
+ if (!options.launchUrl || typeof options.launchUrl !== "string")
155
192
  throw new TypeError("[ent-embed-sdk] options.launchUrl (string) is required");
156
- }
157
- if (!options.allowedOrigin || typeof options.allowedOrigin !== "string") {
193
+ if (!options.allowedOrigin || typeof options.allowedOrigin !== "string")
158
194
  throw new TypeError("[ent-embed-sdk] options.allowedOrigin (string) is required");
159
- }
160
- if (options.allowedOrigin === "*" || options.allowedOrigin.includes("*")) {
195
+ if (options.allowedOrigin === "*" || options.allowedOrigin.includes("*"))
161
196
  throw new TypeError("[ent-embed-sdk] options.allowedOrigin must be an exact origin (no wildcards)");
162
- }
163
- if (options.mode && options.mode !== "popup") {
197
+ if (options.mode && options.mode !== "popup")
164
198
  throw new TypeError(`[ent-embed-sdk] mode "${options.mode}" is not supported in this version`);
165
- }
166
199
  }
167
200
  function safeNotify(hook, event) {
168
- if (!hook) return;
201
+ if (!hook)
202
+ return;
169
203
  try {
170
204
  hook(event);
171
205
  } catch {
@@ -1 +1 @@
1
- {"version":3,"file":"ent-embed-sdk.cjs","sources":["../src/bridge.ts","../src/popup.ts","../src/index.ts"],"sourcesContent":["import type { EmbedEvent, EntCodingResult, EntPostMessageFrame } from './types.js';\n\n/**\n * Validate that a postMessage frame originates from the trusted ENT origin\n * and matches the documented shape. Implements the three OWASP checks:\n *\n * 1. `event.origin === allowedOrigin` (constant comparison, no regex).\n * 2. `event.source === popupWindow` (no other window can spoof it).\n * 3. `data` is a plain object with a recognised `type`.\n */\nexport function isTrustedEntFrame(\n event: MessageEvent,\n allowedOrigin: string,\n popup: Window | null,\n): event is MessageEvent<EntPostMessageFrame> {\n if (event.origin !== allowedOrigin) return false;\n if (popup && event.source !== popup) return false;\n const data = event.data;\n if (!data || typeof data !== 'object') return false;\n const type = (data as { type?: unknown }).type;\n return type === 'ent:result' || type === 'ent:cancelled' || type === 'ent:terminal';\n}\n\n/**\n * Map a trusted frame to the public `EmbedEvent` discriminated union.\n * Returns `null` if the payload is malformed for the given type.\n */\nexport function frameToEvent(frame: EntPostMessageFrame): EmbedEvent | null {\n switch (frame.type) {\n case 'ent:result': {\n const result = normalizeResult(frame.payload);\n if (!result) return null;\n return { type: 'result', result, raw: frame };\n }\n case 'ent:cancelled': {\n const reason =\n frame.payload && typeof frame.payload === 'object'\n ? (frame.payload as { reason?: unknown }).reason\n : undefined;\n return {\n type: 'cancelled',\n ...(typeof reason === 'string' ? { reason } : {}),\n raw: frame,\n };\n }\n case 'ent:terminal': {\n const p =\n frame.payload && typeof frame.payload === 'object'\n ? (frame.payload as { code?: unknown; message?: unknown })\n : {};\n const code = typeof p.code === 'string' ? p.code : 'embed.errors.session.terminal';\n const message = typeof p.message === 'string' ? p.message : undefined;\n return {\n type: 'terminal',\n code,\n ...(message ? { message } : {}),\n raw: frame,\n };\n }\n default:\n return null;\n }\n}\n\nfunction normalizeResult(payload: unknown): EntCodingResult | null {\n if (!payload || typeof payload !== 'object') return null;\n const p = payload as Record<string, unknown>;\n const diagnoses = Array.isArray(p['diagnoses']) ? (p['diagnoses'] as EntCodingResult['diagnoses']) : [];\n const procedures = Array.isArray(p['procedures'])\n ? (p['procedures'] as EntCodingResult['procedures'])\n : [];\n const result: EntCodingResult = { diagnoses, procedures };\n if (p['drg'] && typeof p['drg'] === 'object') {\n result.drg = p['drg'] as EntCodingResult['drg'];\n }\n if (p['meta'] && typeof p['meta'] === 'object') {\n result.meta = p['meta'] as Record<string, unknown>;\n }\n return result;\n}\n","/**\n * Cross-browser helpers to open a popup centered on the parent window and\n * detect when the user closes it without producing an event.\n */\n\nconst DEFAULT_FEATURES =\n 'popup=yes,noopener=no,noreferrer=no,width=1100,height=780,resizable=yes,scrollbars=yes';\n\nexport function openCenteredPopup(\n win: Window,\n url: string,\n features: string | undefined,\n): Window | null {\n const computed = features ?? withCenteredGeometry(win, DEFAULT_FEATURES);\n // `_blank` + named features is enough for modern browsers; we deliberately\n // do NOT pass `noopener` because we rely on the returned reference for\n // postMessage origin validation (`event.source === popup`).\n return win.open(url, '_blank', computed);\n}\n\nfunction withCenteredGeometry(win: Window, base: string): string {\n const screen = win.screen ?? ({} as Screen);\n const width = parseFeature(base, 'width', 1100);\n const height = parseFeature(base, 'height', 780);\n const left = Math.max(0, Math.round(((screen.availWidth ?? width) - width) / 2));\n const top = Math.max(0, Math.round(((screen.availHeight ?? height) - height) / 2));\n return `${base},left=${left},top=${top}`;\n}\n\nfunction parseFeature(features: string, key: string, fallback: number): number {\n const m = features.match(new RegExp(`(?:^|,)${key}=(\\\\d+)`));\n return m ? Number(m[1]) : fallback;\n}\n\n/**\n * Poll `popup.closed` at a low frequency. We use polling instead of\n * `beforeunload`/`unload` because cross-origin popups do not expose those\n * events to the opener. Returns a function that stops the poll.\n */\nexport function watchPopupClosed(\n win: Window,\n popup: Window,\n onClosed: () => void,\n intervalMs = 500,\n): () => void {\n const handle = win.setInterval(() => {\n if (popup.closed) {\n win.clearInterval(handle);\n onClosed();\n }\n }, intervalMs);\n return () => win.clearInterval(handle);\n}\n","import { frameToEvent, isTrustedEntFrame } from './bridge.js';\nimport { openCenteredPopup, watchPopupClosed } from './popup.js';\nimport type { EmbedEvent, EmbedOpenOptions } from './types.js';\n\nconst DEFAULT_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes\n\n/**\n * Public entry point of the SDK.\n *\n * Usage:\n * ```ts\n * const ev = await EmbedClient.open({ launchUrl, allowedOrigin });\n * if (ev.type === 'result') { ... }\n * ```\n *\n * The Promise resolves exactly once with a discriminated `EmbedEvent`.\n * It never rejects for protocol outcomes (cancel / terminal / closed):\n * it only rejects for *programming* errors (missing options, popup blocked).\n */\nexport class EmbedClient {\n private constructor() {\n /* static-only */\n }\n\n static open(options: EmbedOpenOptions): Promise<EmbedEvent> {\n validateOptions(options);\n const win = options.windowRef ?? globalThis.window;\n if (!win || typeof win.open !== 'function') {\n return Promise.reject(new Error('[ent-embed-sdk] No window available to open popup'));\n }\n\n const popup = openCenteredPopup(win, options.launchUrl, options.popupFeatures);\n if (!popup) {\n return Promise.reject(\n new Error('[ent-embed-sdk] Popup blocked by browser. Call EmbedClient.open() from a user gesture.'),\n );\n }\n\n return new Promise<EmbedEvent>((resolve) => {\n let settled = false;\n let stopWatchClosed: (() => void) | null = null;\n let timeoutHandle: ReturnType<typeof setTimeout> | null = null;\n\n const settle = (event: EmbedEvent, closePopup: boolean) => {\n if (settled) return;\n settled = true;\n win.removeEventListener('message', onMessage);\n if (stopWatchClosed) stopWatchClosed();\n if (timeoutHandle !== null) clearTimeout(timeoutHandle);\n safeNotify(options.onEvent, event);\n if (closePopup && popup && !popup.closed) {\n try {\n popup.close();\n } catch {\n /* cross-origin popups may refuse close() — ignored */\n }\n }\n resolve(event);\n };\n\n const onMessage = (event: MessageEvent) => {\n if (!isTrustedEntFrame(event, options.allowedOrigin, popup)) return;\n const mapped = frameToEvent(event.data);\n if (!mapped) return;\n // result/terminal/cancelled are all terminal for the SDK lifecycle.\n settle(mapped, /* closePopup */ true);\n };\n\n win.addEventListener('message', onMessage);\n\n stopWatchClosed = watchPopupClosed(win, popup, () => {\n settle({ type: 'closed', reason: 'user-closed' }, /* closePopup */ false);\n });\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n if (timeoutMs > 0 && Number.isFinite(timeoutMs)) {\n timeoutHandle = setTimeout(() => {\n settle({ type: 'closed', reason: 'timeout' }, /* closePopup */ true);\n }, timeoutMs);\n }\n });\n }\n}\n\nfunction validateOptions(options: EmbedOpenOptions): void {\n if (!options || typeof options !== 'object') {\n throw new TypeError('[ent-embed-sdk] options object is required');\n }\n if (!options.launchUrl || typeof options.launchUrl !== 'string') {\n throw new TypeError('[ent-embed-sdk] options.launchUrl (string) is required');\n }\n if (!options.allowedOrigin || typeof options.allowedOrigin !== 'string') {\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin (string) is required');\n }\n if (options.allowedOrigin === '*' || options.allowedOrigin.includes('*')) {\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin must be an exact origin (no wildcards)');\n }\n if (options.mode && options.mode !== 'popup') {\n throw new TypeError(`[ent-embed-sdk] mode \"${options.mode}\" is not supported in this version`);\n }\n}\n\nfunction safeNotify(hook: EmbedOpenOptions['onEvent'], event: EmbedEvent): void {\n if (!hook) return;\n try {\n hook(event);\n } catch {\n /* user hook errors must not break the SDK contract */\n }\n}\n\nexport type {\n EmbedEvent,\n EmbedOpenOptions,\n EmbedClosedReason,\n EmbedTerminalCode,\n EntCodingResult,\n EntDiagnosis,\n EntProcedure,\n EntDrg,\n EntPostMessageFrame,\n} from './types.js';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ent-embed-sdk.cjs","sources":["../src/bridge.ts","../src/popup.ts","../src/index.ts"],"sourcesContent":["import type { EmbedEvent, EntCodingResult, EntPostMessageFrame } from './types.js';\n\n/**\n * Set of `type` discriminants accepted from the ENT embed page.\n * Centralises the protocol surface so both the runtime guard and the\n * mapper agree on what is supported.\n */\nconst KNOWN_FRAME_TYPES = new Set<EntPostMessageFrame['type']>([\n 'ent:result',\n 'ent:cancelled',\n 'ent:terminal',\n]);\n\n/** Default i18n code emitted when the embed page does not supply one. */\nconst DEFAULT_TERMINAL_CODE = 'embed.errors.session.terminal';\n\n/**\n * Validate that a postMessage frame originates from the trusted ENT origin\n * and matches the documented shape.\n *\n * Implements the three OWASP checks required for a safe cross-origin bridge:\n *\n * 1. `event.origin === allowedOrigin` (constant comparison, no regex).\n * 2. `event.source === popupWindow` (no other window can spoof it).\n * 3. `data` is a plain object whose `type` is in {@link KNOWN_FRAME_TYPES}.\n *\n * @param event - The raw `MessageEvent` received by `window`.\n * @param allowedOrigin - Exact origin (scheme + host + port) trusted by the SDK.\n * @param popup - Window reference returned by `window.open`, or `null`\n * if not yet available; when present, must equal\n * `event.source`.\n * @returns `true` (with a type predicate) when the frame can be safely\n * forwarded to {@link frameToEvent}.\n */\nexport function isTrustedEntFrame(\n event : MessageEvent,\n allowedOrigin: string,\n popup : Window | null,\n): event is MessageEvent<EntPostMessageFrame> {\n\n if (event.origin !== allowedOrigin)\n return false;\n\n if (popup && event.source !== popup)\n return false;\n\n const data = event.data;\n\n if (!data || typeof data !== 'object')\n return false;\n\n const type = (data as { type?: unknown }).type;\n\n return typeof type === 'string'\n && KNOWN_FRAME_TYPES.has(type as EntPostMessageFrame['type']);\n}\n\n/**\n * Map a trusted frame to the public {@link EmbedEvent} discriminated union.\n *\n * Returns `null` when the payload is malformed for the given `type` (e.g. a\n * `ent:result` without a usable result object): the SDK ignores such frames\n * and keeps listening, leaving the popup open.\n *\n * @param frame - A frame already validated by {@link isTrustedEntFrame}.\n */\nexport function frameToEvent(frame: EntPostMessageFrame): EmbedEvent | null {\n\n switch (frame.type) {\n case 'ent:result' : return mapResultFrame(frame);\n case 'ent:cancelled': return mapCancelledFrame(frame);\n case 'ent:terminal' : return mapTerminalFrame(frame);\n default : return null;\n }\n}\n\n/**\n * Map a `ent:result` frame. Returns `null` when {@link normalizeResult}\n * rejects the payload.\n */\nfunction mapResultFrame(frame: EntPostMessageFrame): EmbedEvent | null {\n\n const result = normalizeResult(frame.payload);\n\n if (!result)\n return null;\n\n return { type: 'result', result, raw: frame };\n}\n\n/**\n * Map a `ent:cancelled` frame. The optional `reason` string is forwarded\n * verbatim when present; any other shape is silently dropped.\n */\nfunction mapCancelledFrame(frame: EntPostMessageFrame): EmbedEvent {\n\n const reason = readStringField(frame.payload, 'reason');\n\n return {\n type: 'cancelled',\n ...(reason ? { reason } : {}),\n raw : frame,\n };\n}\n\n/**\n * Map a `ent:terminal` frame. A missing or invalid `code` falls back to\n * {@link DEFAULT_TERMINAL_CODE} so the integrator always receives a stable\n * i18n key.\n */\nfunction mapTerminalFrame(frame: EntPostMessageFrame): EmbedEvent {\n\n const code = readStringField(frame.payload, 'code') ?? DEFAULT_TERMINAL_CODE;\n const message = readStringField(frame.payload, 'message');\n\n return {\n type: 'terminal',\n code,\n ...(message ? { message } : {}),\n raw: frame,\n };\n}\n\n/**\n * Read a string field from an unknown payload, returning `undefined` when\n * the payload is not an object or the field is missing/non-string.\n * Centralises the defensive type-narrowing used by all frame mappers.\n */\nfunction readStringField(payload: unknown, field: string): string | undefined {\n\n if (!payload || typeof payload !== 'object')\n return undefined;\n\n const value = (payload as Record<string, unknown>)[field];\n\n return typeof value === 'string' ? value : undefined;\n}\n\n/**\n * Normalise the `payload` of a `ent:result` frame into an {@link EntCodingResult}.\n *\n * - `diagnoses` and `procedures` default to empty arrays when missing.\n * - `drg` and `meta` are forwarded only when present and object-shaped.\n *\n * @returns The normalised result, or `null` if the payload is not an object.\n */\nfunction normalizeResult(payload: unknown): EntCodingResult | null {\n \n if (!payload || typeof payload !== 'object')\n return null;\n\n const p = payload as Record<string, unknown>;\n const diagnoses = Array.isArray(p['diagnoses']) ? (p['diagnoses'] as EntCodingResult['diagnoses']) : [];\n const procedures = Array.isArray(p['procedures'])\n ? (p['procedures'] as EntCodingResult['procedures'])\n : [];\n\n const result: EntCodingResult = { diagnoses, procedures };\n\n if (p['drg'] && typeof p['drg'] === 'object')\n result.drg = p['drg'] as EntCodingResult['drg'];\n\n if (p['meta'] && typeof p['meta'] === 'object')\n result.meta = p['meta'] as Record<string, unknown>;\n\n return result;\n}\n","/**\n * Cross-browser helpers to open a popup centered on the parent window and\n * detect when the user closes it without producing an event.\n *\n * The SDK keeps the popup reference alive so it can:\n * - validate `event.source === popup` when receiving postMessage frames;\n * - call `popup.close()` after a terminal protocol event;\n * - poll `popup.closed` to detect manual dismissal.\n */\n\n/**\n * Default `window.open` features used when the caller does not override\n * {@link openCenteredPopup}'s `features` argument.\n *\n * Notes:\n * - `noopener=no` / `noreferrer=no` are intentional: the SDK needs the\n * `Window` reference returned by `window.open` for origin validation.\n * - Width/height are passed through {@link withCenteredGeometry} so the\n * popup is centered on the current screen.\n */\nconst DEFAULT_FEATURES =\n 'popup=yes,noopener=no,noreferrer=no,width=1100,height=780,resizable=yes,scrollbars=yes';\n\n/**\n * Open a centered popup pointing at `url`.\n *\n * @param win - The opener window (usually `globalThis.window`).\n * @param url - The launch URL returned by the ENT backend.\n * @param features - Optional override for the `window.open` features string.\n * When omitted, {@link DEFAULT_FEATURES} is used and\n * centered via {@link withCenteredGeometry}.\n * @returns The popup `Window`, or `null` when the browser blocks it (e.g.\n * the call did not happen inside a user gesture).\n */\nexport function openCenteredPopup(\n win : Window,\n url : string,\n features: string | undefined,\n): Window | null {\n\n const computed = features ?? withCenteredGeometry(win, DEFAULT_FEATURES);\n // `_blank` + named features is enough for modern browsers; we deliberately\n // do NOT pass `noopener` because we rely on the returned reference for\n // postMessage origin validation (`event.source === popup`).\n return win.open(url, '_blank', computed);\n}\n\n/**\n * Append `left=`/`top=` to a features string so the popup is centered on the\n * available screen area. Falls back to the popup's intrinsic size when the\n * runtime does not expose `screen.availWidth`/`availHeight` (jsdom, SSR).\n */\nfunction withCenteredGeometry(win: Window, base: string): string {\n\n const screen = win.screen ?? ({} as Screen);\n const width = parseFeature(base, 'width', 1100);\n const height = parseFeature(base, 'height', 780);\n const left = Math.max(0, Math.round(((screen.availWidth ?? width) - width) / 2));\n const top = Math.max(0, Math.round(((screen.availHeight ?? height) - height) / 2));\n\n return `${base},left=${left},top=${top}`;\n}\n\n/**\n * Extract a numeric `window.open` feature value from a comma-separated\n * features string (e.g. `\"width=800,height=600\"`).\n *\n * @returns The parsed integer, or `fallback` when the key is missing or\n * malformed.\n */\nfunction parseFeature(features: string, key: string, fallback: number): number {\n\n const m = new RegExp(String.raw`(?:^|,)${key}=(\\d+)`).exec(features);\n return m ? Number(m[1]) : fallback;\n}\n\n/**\n * Poll `popup.closed` at a low frequency to detect manual dismissal of the\n * popup.\n *\n * We use polling instead of `beforeunload`/`unload` because cross-origin\n * popups do not expose those events to the opener.\n *\n * @param win - The opener window used to schedule the interval.\n * @param popup - The popup whose `closed` flag is observed.\n * @param onClosed - Invoked exactly once when the popup is detected closed.\n * @param intervalMs - Polling interval in milliseconds (default `500`).\n * @returns A function that stops the polling. Safe to call multiple times.\n */\nexport function watchPopupClosed(\n win : Window,\n popup : Window,\n onClosed: () => void,\n intervalMs = 500,\n): () => void {\n\n const handle = win.setInterval(() => {\n\n if (popup.closed) {\n win.clearInterval(handle);\n onClosed();\n }\n }, intervalMs);\n\n return () => win.clearInterval(handle);\n}\n","import { frameToEvent, isTrustedEntFrame } from './bridge.js';\nimport { openCenteredPopup, watchPopupClosed } from './popup.js';\nimport type { EmbedEvent, EmbedOpenOptions } from './types.js';\n\n/** Default popup lifetime: 15 minutes from `EmbedClient.open()` call. */\nconst DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;\n\n/**\n * Public entry point of the SDK.\n *\n * Usage:\n * ```ts\n * const ev = await EmbedClient.open({ launchUrl, allowedOrigin });\n * if (ev.type === 'result') { ... }\n * ```\n *\n * The Promise resolves **exactly once** with a discriminated\n * {@link EmbedEvent}. It never rejects for protocol outcomes\n * (`cancelled` / `terminal` / `closed`): it only rejects for\n * *programming* errors (missing options, popup blocked by the browser).\n */\nexport class EmbedClient {\n\n private constructor() {\n /* static-only */\n }\n\n /**\n * Open the ENT embed popup and resolve when the session terminates.\n *\n * Lifecycle:\n * 1. {@link validateOptions} — throw `TypeError` for bad input.\n * 2. `window.open` — reject if the browser blocks the popup.\n * 3. Install a `message` listener filtered by {@link isTrustedEntFrame}.\n * 4. Poll `popup.closed` via {@link watchPopupClosed}.\n * 5. Arm a timeout (default {@link DEFAULT_TIMEOUT_MS}).\n * 6. The first of (`result` / `cancelled` / `terminal` / `closed`)\n * wins, cleans up the other watchers, and resolves the Promise.\n *\n * @param options - See {@link EmbedOpenOptions}.\n * @returns The terminal {@link EmbedEvent} for this session.\n */\n static open(options: EmbedOpenOptions): Promise<EmbedEvent> {\n\n validateOptions(options);\n\n const win = options.windowRef ?? globalThis.window;\n\n if (!win || typeof win.open !== 'function')\n return Promise.reject(new Error('[ent-embed-sdk] No window available to open popup'));\n\n const popup = openCenteredPopup(win, options.launchUrl, options.popupFeatures);\n\n if (!popup)\n return Promise.reject(\n new Error('[ent-embed-sdk] Popup blocked by browser. Call EmbedClient.open() from a user gesture.'),\n );\n\n\n return new Promise<EmbedEvent>((resolve) => {\n\n let settled = false;\n let stopWatchClosed: (() => void) | null = null;\n let timeoutHandle : ReturnType<typeof setTimeout> | null = null;\n\n /**\n * Resolve the outer Promise exactly once. Subsequent calls are no-ops,\n * which lets the message listener, the `closed` poller and the timeout\n * all race safely.\n *\n * @param event - The event delivered to the integrator.\n * @param closePopup - Whether to call `popup.close()` after settling.\n * We skip the close when the user already closed\n * the popup manually (cross-origin `close()` can\n * throw in some browsers).\n */\n const settle = (event: EmbedEvent, closePopup: boolean) => {\n\n if (settled)\n return;\n\n settled = true;\n\n win.removeEventListener('message', onMessage);\n\n if (stopWatchClosed)\n stopWatchClosed();\n\n if (timeoutHandle !== null)\n clearTimeout(timeoutHandle);\n\n safeNotify(options.onEvent, event);\n\n if (closePopup && popup && !popup.closed) {\n\n try {\n popup.close();\n } catch {\n /* cross-origin popups may refuse close() — ignored */\n }\n }\n\n resolve(event);\n };\n\n /**\n * `message` event handler. Drops anything that is not a trusted ENT\n * frame, then maps the frame to an {@link EmbedEvent} and settles.\n */\n const onMessage = (event: MessageEvent) => {\n\n if (!isTrustedEntFrame(event, options.allowedOrigin, popup))\n return;\n\n const mapped = frameToEvent(event.data);\n\n if (!mapped)\n return;\n\n // result/terminal/cancelled are all terminal for the SDK lifecycle.\n settle(mapped, /* closePopup */ true);\n };\n\n win.addEventListener('message', onMessage);\n\n stopWatchClosed = watchPopupClosed(win, popup, () => {\n settle({ type: 'closed', reason: 'user-closed' }, /* closePopup */ false);\n });\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n if (timeoutMs > 0 && Number.isFinite(timeoutMs)) {\n\n timeoutHandle = setTimeout(() => {\n settle({ type: 'closed', reason: 'timeout' }, /* closePopup */ true);\n }, timeoutMs);\n }\n });\n }\n}\n\n/**\n * Throw a {@link TypeError} when {@link EmbedOpenOptions} are missing or\n * structurally invalid.\n *\n * In particular, `allowedOrigin` must be an **exact origin**: wildcards are\n * rejected to prevent accidental opening of the bridge to untrusted hosts.\n */\nfunction validateOptions(options: EmbedOpenOptions): void {\n\n if (!options || typeof options !== 'object')\n throw new TypeError('[ent-embed-sdk] options object is required');\n\n if (!options.launchUrl || typeof options.launchUrl !== 'string')\n throw new TypeError('[ent-embed-sdk] options.launchUrl (string) is required');\n\n if (!options.allowedOrigin || typeof options.allowedOrigin !== 'string')\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin (string) is required');\n\n if (options.allowedOrigin === '*' || options.allowedOrigin.includes('*'))\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin must be an exact origin (no wildcards)');\n\n if (options.mode && options.mode !== 'popup')\n throw new TypeError(`[ent-embed-sdk] mode \"${options.mode}\" is not supported in this version`);\n}\n\n/**\n * Invoke the optional `onEvent` observer in a fail-safe way: integrator\n * exceptions must never break the SDK contract (single Promise resolution).\n */\nfunction safeNotify(hook: EmbedOpenOptions['onEvent'], event: EmbedEvent): void {\n\n if (!hook)\n return;\n\n try {\n hook(event);\n } catch {\n /* user hook errors must not break the SDK contract */\n }\n}\n\nexport type {\n EmbedEvent,\n EmbedOpenOptions,\n EmbedClosedReason,\n EmbedTerminalCode,\n EntCodingResult,\n EntDiagnosis,\n EntProcedure,\n EntDrg,\n EntPostMessageFrame,\n} from './types.js';\n"],"names":[],"mappings":"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"}
@@ -1,54 +1,74 @@
1
1
  /*! @nextage/ent-embed-sdk — see https://www.npmjs.com/package/@nextage/ent-embed-sdk */
2
+ const KNOWN_FRAME_TYPES = /* @__PURE__ */ new Set([
3
+ "ent:result",
4
+ "ent:cancelled",
5
+ "ent:terminal"
6
+ ]);
7
+ const DEFAULT_TERMINAL_CODE = "embed.errors.session.terminal";
2
8
  function isTrustedEntFrame(event, allowedOrigin, popup) {
3
- if (event.origin !== allowedOrigin) return false;
4
- if (popup && event.source !== popup) return false;
9
+ if (event.origin !== allowedOrigin)
10
+ return false;
11
+ if (popup && event.source !== popup)
12
+ return false;
5
13
  const data = event.data;
6
- if (!data || typeof data !== "object") return false;
14
+ if (!data || typeof data !== "object")
15
+ return false;
7
16
  const type = data.type;
8
- return type === "ent:result" || type === "ent:cancelled" || type === "ent:terminal";
17
+ return typeof type === "string" && KNOWN_FRAME_TYPES.has(type);
9
18
  }
10
19
  function frameToEvent(frame) {
11
20
  switch (frame.type) {
12
- case "ent:result": {
13
- const result = normalizeResult(frame.payload);
14
- if (!result) return null;
15
- return { type: "result", result, raw: frame };
16
- }
17
- case "ent:cancelled": {
18
- const reason = frame.payload && typeof frame.payload === "object" ? frame.payload.reason : void 0;
19
- return {
20
- type: "cancelled",
21
- ...typeof reason === "string" ? { reason } : {},
22
- raw: frame
23
- };
24
- }
25
- case "ent:terminal": {
26
- const p = frame.payload && typeof frame.payload === "object" ? frame.payload : {};
27
- const code = typeof p.code === "string" ? p.code : "embed.errors.session.terminal";
28
- const message = typeof p.message === "string" ? p.message : void 0;
29
- return {
30
- type: "terminal",
31
- code,
32
- ...message ? { message } : {},
33
- raw: frame
34
- };
35
- }
21
+ case "ent:result":
22
+ return mapResultFrame(frame);
23
+ case "ent:cancelled":
24
+ return mapCancelledFrame(frame);
25
+ case "ent:terminal":
26
+ return mapTerminalFrame(frame);
36
27
  default:
37
28
  return null;
38
29
  }
39
30
  }
31
+ function mapResultFrame(frame) {
32
+ const result = normalizeResult(frame.payload);
33
+ if (!result)
34
+ return null;
35
+ return { type: "result", result, raw: frame };
36
+ }
37
+ function mapCancelledFrame(frame) {
38
+ const reason = readStringField(frame.payload, "reason");
39
+ return {
40
+ type: "cancelled",
41
+ ...reason ? { reason } : {},
42
+ raw: frame
43
+ };
44
+ }
45
+ function mapTerminalFrame(frame) {
46
+ const code = readStringField(frame.payload, "code") ?? DEFAULT_TERMINAL_CODE;
47
+ const message = readStringField(frame.payload, "message");
48
+ return {
49
+ type: "terminal",
50
+ code,
51
+ ...message ? { message } : {},
52
+ raw: frame
53
+ };
54
+ }
55
+ function readStringField(payload, field) {
56
+ if (!payload || typeof payload !== "object")
57
+ return void 0;
58
+ const value = payload[field];
59
+ return typeof value === "string" ? value : void 0;
60
+ }
40
61
  function normalizeResult(payload) {
41
- if (!payload || typeof payload !== "object") return null;
62
+ if (!payload || typeof payload !== "object")
63
+ return null;
42
64
  const p = payload;
43
65
  const diagnoses = Array.isArray(p["diagnoses"]) ? p["diagnoses"] : [];
44
66
  const procedures = Array.isArray(p["procedures"]) ? p["procedures"] : [];
45
67
  const result = { diagnoses, procedures };
46
- if (p["drg"] && typeof p["drg"] === "object") {
68
+ if (p["drg"] && typeof p["drg"] === "object")
47
69
  result.drg = p["drg"];
48
- }
49
- if (p["meta"] && typeof p["meta"] === "object") {
70
+ if (p["meta"] && typeof p["meta"] === "object")
50
71
  result.meta = p["meta"];
51
- }
52
72
  return result;
53
73
  }
54
74
 
@@ -66,7 +86,7 @@ function withCenteredGeometry(win, base) {
66
86
  return `${base},left=${left},top=${top}`;
67
87
  }
68
88
  function parseFeature(features, key, fallback) {
69
- const m = features.match(new RegExp(`(?:^|,)${key}=(\\d+)`));
89
+ const m = new RegExp(String.raw`(?:^|,)${key}=(\d+)`).exec(features);
70
90
  return m ? Number(m[1]) : fallback;
71
91
  }
72
92
  function watchPopupClosed(win, popup, onClosed, intervalMs = 500) {
@@ -83,28 +103,44 @@ const DEFAULT_TIMEOUT_MS = 15 * 60 * 1e3;
83
103
  class EmbedClient {
84
104
  constructor() {
85
105
  }
106
+ /**
107
+ * Open the ENT embed popup and resolve when the session terminates.
108
+ *
109
+ * Lifecycle:
110
+ * 1. {@link validateOptions} — throw `TypeError` for bad input.
111
+ * 2. `window.open` — reject if the browser blocks the popup.
112
+ * 3. Install a `message` listener filtered by {@link isTrustedEntFrame}.
113
+ * 4. Poll `popup.closed` via {@link watchPopupClosed}.
114
+ * 5. Arm a timeout (default {@link DEFAULT_TIMEOUT_MS}).
115
+ * 6. The first of (`result` / `cancelled` / `terminal` / `closed`)
116
+ * wins, cleans up the other watchers, and resolves the Promise.
117
+ *
118
+ * @param options - See {@link EmbedOpenOptions}.
119
+ * @returns The terminal {@link EmbedEvent} for this session.
120
+ */
86
121
  static open(options) {
87
122
  validateOptions(options);
88
123
  const win = options.windowRef ?? globalThis.window;
89
- if (!win || typeof win.open !== "function") {
124
+ if (!win || typeof win.open !== "function")
90
125
  return Promise.reject(new Error("[ent-embed-sdk] No window available to open popup"));
91
- }
92
126
  const popup = openCenteredPopup(win, options.launchUrl, options.popupFeatures);
93
- if (!popup) {
127
+ if (!popup)
94
128
  return Promise.reject(
95
129
  new Error("[ent-embed-sdk] Popup blocked by browser. Call EmbedClient.open() from a user gesture.")
96
130
  );
97
- }
98
131
  return new Promise((resolve) => {
99
132
  let settled = false;
100
133
  let stopWatchClosed = null;
101
134
  let timeoutHandle = null;
102
135
  const settle = (event, closePopup) => {
103
- if (settled) return;
136
+ if (settled)
137
+ return;
104
138
  settled = true;
105
139
  win.removeEventListener("message", onMessage);
106
- if (stopWatchClosed) stopWatchClosed();
107
- if (timeoutHandle !== null) clearTimeout(timeoutHandle);
140
+ if (stopWatchClosed)
141
+ stopWatchClosed();
142
+ if (timeoutHandle !== null)
143
+ clearTimeout(timeoutHandle);
108
144
  safeNotify(options.onEvent, event);
109
145
  if (closePopup && popup && !popup.closed) {
110
146
  try {
@@ -115,9 +151,11 @@ class EmbedClient {
115
151
  resolve(event);
116
152
  };
117
153
  const onMessage = (event) => {
118
- if (!isTrustedEntFrame(event, options.allowedOrigin, popup)) return;
154
+ if (!isTrustedEntFrame(event, options.allowedOrigin, popup))
155
+ return;
119
156
  const mapped = frameToEvent(event.data);
120
- if (!mapped) return;
157
+ if (!mapped)
158
+ return;
121
159
  settle(
122
160
  mapped,
123
161
  /* closePopup */
@@ -146,24 +184,20 @@ class EmbedClient {
146
184
  }
147
185
  }
148
186
  function validateOptions(options) {
149
- if (!options || typeof options !== "object") {
187
+ if (!options || typeof options !== "object")
150
188
  throw new TypeError("[ent-embed-sdk] options object is required");
151
- }
152
- if (!options.launchUrl || typeof options.launchUrl !== "string") {
189
+ if (!options.launchUrl || typeof options.launchUrl !== "string")
153
190
  throw new TypeError("[ent-embed-sdk] options.launchUrl (string) is required");
154
- }
155
- if (!options.allowedOrigin || typeof options.allowedOrigin !== "string") {
191
+ if (!options.allowedOrigin || typeof options.allowedOrigin !== "string")
156
192
  throw new TypeError("[ent-embed-sdk] options.allowedOrigin (string) is required");
157
- }
158
- if (options.allowedOrigin === "*" || options.allowedOrigin.includes("*")) {
193
+ if (options.allowedOrigin === "*" || options.allowedOrigin.includes("*"))
159
194
  throw new TypeError("[ent-embed-sdk] options.allowedOrigin must be an exact origin (no wildcards)");
160
- }
161
- if (options.mode && options.mode !== "popup") {
195
+ if (options.mode && options.mode !== "popup")
162
196
  throw new TypeError(`[ent-embed-sdk] mode "${options.mode}" is not supported in this version`);
163
- }
164
197
  }
165
198
  function safeNotify(hook, event) {
166
- if (!hook) return;
199
+ if (!hook)
200
+ return;
167
201
  try {
168
202
  hook(event);
169
203
  } catch {
@@ -1 +1 @@
1
- {"version":3,"file":"ent-embed-sdk.mjs","sources":["../src/bridge.ts","../src/popup.ts","../src/index.ts"],"sourcesContent":["import type { EmbedEvent, EntCodingResult, EntPostMessageFrame } from './types.js';\n\n/**\n * Validate that a postMessage frame originates from the trusted ENT origin\n * and matches the documented shape. Implements the three OWASP checks:\n *\n * 1. `event.origin === allowedOrigin` (constant comparison, no regex).\n * 2. `event.source === popupWindow` (no other window can spoof it).\n * 3. `data` is a plain object with a recognised `type`.\n */\nexport function isTrustedEntFrame(\n event: MessageEvent,\n allowedOrigin: string,\n popup: Window | null,\n): event is MessageEvent<EntPostMessageFrame> {\n if (event.origin !== allowedOrigin) return false;\n if (popup && event.source !== popup) return false;\n const data = event.data;\n if (!data || typeof data !== 'object') return false;\n const type = (data as { type?: unknown }).type;\n return type === 'ent:result' || type === 'ent:cancelled' || type === 'ent:terminal';\n}\n\n/**\n * Map a trusted frame to the public `EmbedEvent` discriminated union.\n * Returns `null` if the payload is malformed for the given type.\n */\nexport function frameToEvent(frame: EntPostMessageFrame): EmbedEvent | null {\n switch (frame.type) {\n case 'ent:result': {\n const result = normalizeResult(frame.payload);\n if (!result) return null;\n return { type: 'result', result, raw: frame };\n }\n case 'ent:cancelled': {\n const reason =\n frame.payload && typeof frame.payload === 'object'\n ? (frame.payload as { reason?: unknown }).reason\n : undefined;\n return {\n type: 'cancelled',\n ...(typeof reason === 'string' ? { reason } : {}),\n raw: frame,\n };\n }\n case 'ent:terminal': {\n const p =\n frame.payload && typeof frame.payload === 'object'\n ? (frame.payload as { code?: unknown; message?: unknown })\n : {};\n const code = typeof p.code === 'string' ? p.code : 'embed.errors.session.terminal';\n const message = typeof p.message === 'string' ? p.message : undefined;\n return {\n type: 'terminal',\n code,\n ...(message ? { message } : {}),\n raw: frame,\n };\n }\n default:\n return null;\n }\n}\n\nfunction normalizeResult(payload: unknown): EntCodingResult | null {\n if (!payload || typeof payload !== 'object') return null;\n const p = payload as Record<string, unknown>;\n const diagnoses = Array.isArray(p['diagnoses']) ? (p['diagnoses'] as EntCodingResult['diagnoses']) : [];\n const procedures = Array.isArray(p['procedures'])\n ? (p['procedures'] as EntCodingResult['procedures'])\n : [];\n const result: EntCodingResult = { diagnoses, procedures };\n if (p['drg'] && typeof p['drg'] === 'object') {\n result.drg = p['drg'] as EntCodingResult['drg'];\n }\n if (p['meta'] && typeof p['meta'] === 'object') {\n result.meta = p['meta'] as Record<string, unknown>;\n }\n return result;\n}\n","/**\n * Cross-browser helpers to open a popup centered on the parent window and\n * detect when the user closes it without producing an event.\n */\n\nconst DEFAULT_FEATURES =\n 'popup=yes,noopener=no,noreferrer=no,width=1100,height=780,resizable=yes,scrollbars=yes';\n\nexport function openCenteredPopup(\n win: Window,\n url: string,\n features: string | undefined,\n): Window | null {\n const computed = features ?? withCenteredGeometry(win, DEFAULT_FEATURES);\n // `_blank` + named features is enough for modern browsers; we deliberately\n // do NOT pass `noopener` because we rely on the returned reference for\n // postMessage origin validation (`event.source === popup`).\n return win.open(url, '_blank', computed);\n}\n\nfunction withCenteredGeometry(win: Window, base: string): string {\n const screen = win.screen ?? ({} as Screen);\n const width = parseFeature(base, 'width', 1100);\n const height = parseFeature(base, 'height', 780);\n const left = Math.max(0, Math.round(((screen.availWidth ?? width) - width) / 2));\n const top = Math.max(0, Math.round(((screen.availHeight ?? height) - height) / 2));\n return `${base},left=${left},top=${top}`;\n}\n\nfunction parseFeature(features: string, key: string, fallback: number): number {\n const m = features.match(new RegExp(`(?:^|,)${key}=(\\\\d+)`));\n return m ? Number(m[1]) : fallback;\n}\n\n/**\n * Poll `popup.closed` at a low frequency. We use polling instead of\n * `beforeunload`/`unload` because cross-origin popups do not expose those\n * events to the opener. Returns a function that stops the poll.\n */\nexport function watchPopupClosed(\n win: Window,\n popup: Window,\n onClosed: () => void,\n intervalMs = 500,\n): () => void {\n const handle = win.setInterval(() => {\n if (popup.closed) {\n win.clearInterval(handle);\n onClosed();\n }\n }, intervalMs);\n return () => win.clearInterval(handle);\n}\n","import { frameToEvent, isTrustedEntFrame } from './bridge.js';\nimport { openCenteredPopup, watchPopupClosed } from './popup.js';\nimport type { EmbedEvent, EmbedOpenOptions } from './types.js';\n\nconst DEFAULT_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes\n\n/**\n * Public entry point of the SDK.\n *\n * Usage:\n * ```ts\n * const ev = await EmbedClient.open({ launchUrl, allowedOrigin });\n * if (ev.type === 'result') { ... }\n * ```\n *\n * The Promise resolves exactly once with a discriminated `EmbedEvent`.\n * It never rejects for protocol outcomes (cancel / terminal / closed):\n * it only rejects for *programming* errors (missing options, popup blocked).\n */\nexport class EmbedClient {\n private constructor() {\n /* static-only */\n }\n\n static open(options: EmbedOpenOptions): Promise<EmbedEvent> {\n validateOptions(options);\n const win = options.windowRef ?? globalThis.window;\n if (!win || typeof win.open !== 'function') {\n return Promise.reject(new Error('[ent-embed-sdk] No window available to open popup'));\n }\n\n const popup = openCenteredPopup(win, options.launchUrl, options.popupFeatures);\n if (!popup) {\n return Promise.reject(\n new Error('[ent-embed-sdk] Popup blocked by browser. Call EmbedClient.open() from a user gesture.'),\n );\n }\n\n return new Promise<EmbedEvent>((resolve) => {\n let settled = false;\n let stopWatchClosed: (() => void) | null = null;\n let timeoutHandle: ReturnType<typeof setTimeout> | null = null;\n\n const settle = (event: EmbedEvent, closePopup: boolean) => {\n if (settled) return;\n settled = true;\n win.removeEventListener('message', onMessage);\n if (stopWatchClosed) stopWatchClosed();\n if (timeoutHandle !== null) clearTimeout(timeoutHandle);\n safeNotify(options.onEvent, event);\n if (closePopup && popup && !popup.closed) {\n try {\n popup.close();\n } catch {\n /* cross-origin popups may refuse close() — ignored */\n }\n }\n resolve(event);\n };\n\n const onMessage = (event: MessageEvent) => {\n if (!isTrustedEntFrame(event, options.allowedOrigin, popup)) return;\n const mapped = frameToEvent(event.data);\n if (!mapped) return;\n // result/terminal/cancelled are all terminal for the SDK lifecycle.\n settle(mapped, /* closePopup */ true);\n };\n\n win.addEventListener('message', onMessage);\n\n stopWatchClosed = watchPopupClosed(win, popup, () => {\n settle({ type: 'closed', reason: 'user-closed' }, /* closePopup */ false);\n });\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n if (timeoutMs > 0 && Number.isFinite(timeoutMs)) {\n timeoutHandle = setTimeout(() => {\n settle({ type: 'closed', reason: 'timeout' }, /* closePopup */ true);\n }, timeoutMs);\n }\n });\n }\n}\n\nfunction validateOptions(options: EmbedOpenOptions): void {\n if (!options || typeof options !== 'object') {\n throw new TypeError('[ent-embed-sdk] options object is required');\n }\n if (!options.launchUrl || typeof options.launchUrl !== 'string') {\n throw new TypeError('[ent-embed-sdk] options.launchUrl (string) is required');\n }\n if (!options.allowedOrigin || typeof options.allowedOrigin !== 'string') {\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin (string) is required');\n }\n if (options.allowedOrigin === '*' || options.allowedOrigin.includes('*')) {\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin must be an exact origin (no wildcards)');\n }\n if (options.mode && options.mode !== 'popup') {\n throw new TypeError(`[ent-embed-sdk] mode \"${options.mode}\" is not supported in this version`);\n }\n}\n\nfunction safeNotify(hook: EmbedOpenOptions['onEvent'], event: EmbedEvent): void {\n if (!hook) return;\n try {\n hook(event);\n } catch {\n /* user hook errors must not break the SDK contract */\n }\n}\n\nexport type {\n EmbedEvent,\n EmbedOpenOptions,\n EmbedClosedReason,\n EmbedTerminalCode,\n EntCodingResult,\n EntDiagnosis,\n EntProcedure,\n EntDrg,\n EntPostMessageFrame,\n} from './types.js';\n"],"names":[],"mappings":";AAUO,SAAS,iBAAA,CACd,KAAA,EACA,aAAA,EACA,KAAA,EAC4C;AAC5C,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,aAAA,EAAe,OAAO,KAAA;AAC3C,EAAA,IAAI,KAAA,IAAS,KAAA,CAAM,MAAA,KAAW,KAAA,EAAO,OAAO,KAAA;AAC5C,EAAA,MAAM,OAAO,KAAA,CAAM,IAAA;AACnB,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,UAAU,OAAO,KAAA;AAC9C,EAAA,MAAM,OAAQ,IAAA,CAA4B,IAAA;AAC1C,EAAA,OAAO,IAAA,KAAS,YAAA,IAAgB,IAAA,KAAS,eAAA,IAAmB,IAAA,KAAS,cAAA;AACvE;AAMO,SAAS,aAAa,KAAA,EAA+C;AAC1E,EAAA,QAAQ,MAAM,IAAA;AAAM,IAClB,KAAK,YAAA,EAAc;AACjB,MAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,KAAA,CAAM,OAAO,CAAA;AAC5C,MAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,MAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,KAAK,KAAA,EAAM;AAAA,IAC9C;AAAA,IACA,KAAK,eAAA,EAAiB;AACpB,MAAA,MAAM,MAAA,GACJ,MAAM,OAAA,IAAW,OAAO,MAAM,OAAA,KAAY,QAAA,GACrC,KAAA,CAAM,OAAA,CAAiC,MAAA,GACxC,MAAA;AACN,MAAA,OAAO;AAAA,QACL,IAAA,EAAM,WAAA;AAAA,QACN,GAAI,OAAO,MAAA,KAAW,WAAW,EAAE,MAAA,KAAW,EAAC;AAAA,QAC/C,GAAA,EAAK;AAAA,OACP;AAAA,IACF;AAAA,IACA,KAAK,cAAA,EAAgB;AACnB,MAAA,MAAM,CAAA,GACJ,MAAM,OAAA,IAAW,OAAO,MAAM,OAAA,KAAY,QAAA,GACrC,KAAA,CAAM,OAAA,GACP,EAAC;AACP,MAAA,MAAM,OAAO,OAAO,CAAA,CAAE,IAAA,KAAS,QAAA,GAAW,EAAE,IAAA,GAAO,+BAAA;AACnD,MAAA,MAAM,UAAU,OAAO,CAAA,CAAE,OAAA,KAAY,QAAA,GAAW,EAAE,OAAA,GAAU,MAAA;AAC5D,MAAA,OAAO;AAAA,QACL,IAAA,EAAM,UAAA;AAAA,QACN,IAAA;AAAA,QACA,GAAI,OAAA,GAAU,EAAE,OAAA,KAAY,EAAC;AAAA,QAC7B,GAAA,EAAK;AAAA,OACP;AAAA,IACF;AAAA,IACA;AACE,MAAA,OAAO,IAAA;AAAA;AAEb;AAEA,SAAS,gBAAgB,OAAA,EAA0C;AACjE,EAAA,IAAI,CAAC,OAAA,IAAW,OAAO,OAAA,KAAY,UAAU,OAAO,IAAA;AACpD,EAAA,MAAM,CAAA,GAAI,OAAA;AACV,EAAA,MAAM,SAAA,GAAY,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,WAAW,CAAC,CAAA,GAAK,CAAA,CAAE,WAAW,CAAA,GAAqC,EAAC;AACtG,EAAA,MAAM,UAAA,GAAa,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,YAAY,CAAC,CAAA,GAC3C,CAAA,CAAE,YAAY,CAAA,GACf,EAAC;AACL,EAAA,MAAM,MAAA,GAA0B,EAAE,SAAA,EAAW,UAAA,EAAW;AACxD,EAAA,IAAI,EAAE,KAAK,CAAA,IAAK,OAAO,CAAA,CAAE,KAAK,MAAM,QAAA,EAAU;AAC5C,IAAA,MAAA,CAAO,GAAA,GAAM,EAAE,KAAK,CAAA;AAAA,EACtB;AACA,EAAA,IAAI,EAAE,MAAM,CAAA,IAAK,OAAO,CAAA,CAAE,MAAM,MAAM,QAAA,EAAU;AAC9C,IAAA,MAAA,CAAO,IAAA,GAAO,EAAE,MAAM,CAAA;AAAA,EACxB;AACA,EAAA,OAAO,MAAA;AACT;;AC1EA,MAAM,gBAAA,GACJ,wFAAA;AAEK,SAAS,iBAAA,CACd,GAAA,EACA,GAAA,EACA,QAAA,EACe;AACf,EAAA,MAAM,QAAA,GAAW,QAAA,IAAY,oBAAA,CAAqB,GAAA,EAAK,gBAAgB,CAAA;AAIvE,EAAA,OAAO,GAAA,CAAI,IAAA,CAAK,GAAA,EAAK,QAAA,EAAU,QAAQ,CAAA;AACzC;AAEA,SAAS,oBAAA,CAAqB,KAAa,IAAA,EAAsB;AAC/D,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,MAAA,IAAW,EAAC;AAC/B,EAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,IAAA,EAAM,OAAA,EAAS,IAAI,CAAA;AAC9C,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,IAAA,EAAM,QAAA,EAAU,GAAG,CAAA;AAC/C,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAA,CAAA,CAAQ,MAAA,CAAO,UAAA,IAAc,KAAA,IAAS,KAAA,IAAS,CAAC,CAAC,CAAA;AAC/E,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAA,CAAA,CAAQ,MAAA,CAAO,WAAA,IAAe,MAAA,IAAU,MAAA,IAAU,CAAC,CAAC,CAAA;AACjF,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,MAAA,EAAS,IAAI,QAAQ,GAAG,CAAA,CAAA;AACxC;AAEA,SAAS,YAAA,CAAa,QAAA,EAAkB,GAAA,EAAa,QAAA,EAA0B;AAC7E,EAAA,MAAM,CAAA,GAAI,SAAS,KAAA,CAAM,IAAI,OAAO,CAAA,OAAA,EAAU,GAAG,SAAS,CAAC,CAAA;AAC3D,EAAA,OAAO,CAAA,GAAI,MAAA,CAAO,CAAA,CAAE,CAAC,CAAC,CAAA,GAAI,QAAA;AAC5B;AAOO,SAAS,gBAAA,CACd,GAAA,EACA,KAAA,EACA,QAAA,EACA,aAAa,GAAA,EACD;AACZ,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,WAAA,CAAY,MAAM;AACnC,IAAA,IAAI,MAAM,MAAA,EAAQ;AAChB,MAAA,GAAA,CAAI,cAAc,MAAM,CAAA;AACxB,MAAA,QAAA,EAAS;AAAA,IACX;AAAA,EACF,GAAG,UAAU,CAAA;AACb,EAAA,OAAO,MAAM,GAAA,CAAI,aAAA,CAAc,MAAM,CAAA;AACvC;;AChDA,MAAM,kBAAA,GAAqB,KAAK,EAAA,GAAK,GAAA;AAe9B,MAAM,WAAA,CAAY;AAAA,EACf,WAAA,GAAc;AAAA,EAEtB;AAAA,EAEA,OAAO,KAAK,OAAA,EAAgD;AAC1D,IAAA,eAAA,CAAgB,OAAO,CAAA;AACvB,IAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,SAAA,IAAa,UAAA,CAAW,MAAA;AAC5C,IAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,CAAI,SAAS,UAAA,EAAY;AAC1C,MAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,IAAI,KAAA,CAAM,mDAAmD,CAAC,CAAA;AAAA,IACtF;AAEA,IAAA,MAAM,QAAQ,iBAAA,CAAkB,GAAA,EAAK,OAAA,CAAQ,SAAA,EAAW,QAAQ,aAAa,CAAA;AAC7E,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,QACb,IAAI,MAAM,wFAAwF;AAAA,OACpG;AAAA,IACF;AAEA,IAAA,OAAO,IAAI,OAAA,CAAoB,CAAC,OAAA,KAAY;AAC1C,MAAA,IAAI,OAAA,GAAU,KAAA;AACd,MAAA,IAAI,eAAA,GAAuC,IAAA;AAC3C,MAAA,IAAI,aAAA,GAAsD,IAAA;AAE1D,MAAA,MAAM,MAAA,GAAS,CAAC,KAAA,EAAmB,UAAA,KAAwB;AACzD,QAAA,IAAI,OAAA,EAAS;AACb,QAAA,OAAA,GAAU,IAAA;AACV,QAAA,GAAA,CAAI,mBAAA,CAAoB,WAAW,SAAS,CAAA;AAC5C,QAAA,IAAI,iBAAiB,eAAA,EAAgB;AACrC,QAAA,IAAI,aAAA,KAAkB,IAAA,EAAM,YAAA,CAAa,aAAa,CAAA;AACtD,QAAA,UAAA,CAAW,OAAA,CAAQ,SAAS,KAAK,CAAA;AACjC,QAAA,IAAI,UAAA,IAAc,KAAA,IAAS,CAAC,KAAA,CAAM,MAAA,EAAQ;AACxC,UAAA,IAAI;AACF,YAAA,KAAA,CAAM,KAAA,EAAM;AAAA,UACd,CAAA,CAAA,MAAQ;AAAA,UAER;AAAA,QACF;AACA,QAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,MACf,CAAA;AAEA,MAAA,MAAM,SAAA,GAAY,CAAC,KAAA,KAAwB;AACzC,QAAA,IAAI,CAAC,iBAAA,CAAkB,KAAA,EAAO,OAAA,CAAQ,aAAA,EAAe,KAAK,CAAA,EAAG;AAC7D,QAAA,MAAM,MAAA,GAAS,YAAA,CAAa,KAAA,CAAM,IAAI,CAAA;AACtC,QAAA,IAAI,CAAC,MAAA,EAAQ;AAEb,QAAA,MAAA;AAAA,UAAO,MAAA;AAAA;AAAA,UAAyB;AAAA,SAAI;AAAA,MACtC,CAAA;AAEA,MAAA,GAAA,CAAI,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAEzC,MAAA,eAAA,GAAkB,gBAAA,CAAiB,GAAA,EAAK,KAAA,EAAO,MAAM;AACnD,QAAA,MAAA;AAAA,UAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,aAAA,EAAc;AAAA;AAAA,UAAoB;AAAA,SAAK;AAAA,MAC1E,CAAC,CAAA;AAED,MAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,MAAA,IAAI,SAAA,GAAY,CAAA,IAAK,MAAA,CAAO,QAAA,CAAS,SAAS,CAAA,EAAG;AAC/C,QAAA,aAAA,GAAgB,WAAW,MAAM;AAC/B,UAAA,MAAA;AAAA,YAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,SAAA,EAAU;AAAA;AAAA,YAAoB;AAAA,WAAI;AAAA,QACrE,GAAG,SAAS,CAAA;AAAA,MACd;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AACF;AAEA,SAAS,gBAAgB,OAAA,EAAiC;AACxD,EAAA,IAAI,CAAC,OAAA,IAAW,OAAO,OAAA,KAAY,QAAA,EAAU;AAC3C,IAAA,MAAM,IAAI,UAAU,4CAA4C,CAAA;AAAA,EAClE;AACA,EAAA,IAAI,CAAC,OAAA,CAAQ,SAAA,IAAa,OAAO,OAAA,CAAQ,cAAc,QAAA,EAAU;AAC/D,IAAA,MAAM,IAAI,UAAU,wDAAwD,CAAA;AAAA,EAC9E;AACA,EAAA,IAAI,CAAC,OAAA,CAAQ,aAAA,IAAiB,OAAO,OAAA,CAAQ,kBAAkB,QAAA,EAAU;AACvE,IAAA,MAAM,IAAI,UAAU,4DAA4D,CAAA;AAAA,EAClF;AACA,EAAA,IAAI,QAAQ,aAAA,KAAkB,GAAA,IAAO,QAAQ,aAAA,CAAc,QAAA,CAAS,GAAG,CAAA,EAAG;AACxE,IAAA,MAAM,IAAI,UAAU,8EAA8E,CAAA;AAAA,EACpG;AACA,EAAA,IAAI,OAAA,CAAQ,IAAA,IAAQ,OAAA,CAAQ,IAAA,KAAS,OAAA,EAAS;AAC5C,IAAA,MAAM,IAAI,SAAA,CAAU,CAAA,sBAAA,EAAyB,OAAA,CAAQ,IAAI,CAAA,kCAAA,CAAoC,CAAA;AAAA,EAC/F;AACF;AAEA,SAAS,UAAA,CAAW,MAAmC,KAAA,EAAyB;AAC9E,EAAA,IAAI,CAAC,IAAA,EAAM;AACX,EAAA,IAAI;AACF,IAAA,IAAA,CAAK,KAAK,CAAA;AAAA,EACZ,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;;;;"}
1
+ {"version":3,"file":"ent-embed-sdk.mjs","sources":["../src/bridge.ts","../src/popup.ts","../src/index.ts"],"sourcesContent":["import type { EmbedEvent, EntCodingResult, EntPostMessageFrame } from './types.js';\n\n/**\n * Set of `type` discriminants accepted from the ENT embed page.\n * Centralises the protocol surface so both the runtime guard and the\n * mapper agree on what is supported.\n */\nconst KNOWN_FRAME_TYPES = new Set<EntPostMessageFrame['type']>([\n 'ent:result',\n 'ent:cancelled',\n 'ent:terminal',\n]);\n\n/** Default i18n code emitted when the embed page does not supply one. */\nconst DEFAULT_TERMINAL_CODE = 'embed.errors.session.terminal';\n\n/**\n * Validate that a postMessage frame originates from the trusted ENT origin\n * and matches the documented shape.\n *\n * Implements the three OWASP checks required for a safe cross-origin bridge:\n *\n * 1. `event.origin === allowedOrigin` (constant comparison, no regex).\n * 2. `event.source === popupWindow` (no other window can spoof it).\n * 3. `data` is a plain object whose `type` is in {@link KNOWN_FRAME_TYPES}.\n *\n * @param event - The raw `MessageEvent` received by `window`.\n * @param allowedOrigin - Exact origin (scheme + host + port) trusted by the SDK.\n * @param popup - Window reference returned by `window.open`, or `null`\n * if not yet available; when present, must equal\n * `event.source`.\n * @returns `true` (with a type predicate) when the frame can be safely\n * forwarded to {@link frameToEvent}.\n */\nexport function isTrustedEntFrame(\n event : MessageEvent,\n allowedOrigin: string,\n popup : Window | null,\n): event is MessageEvent<EntPostMessageFrame> {\n\n if (event.origin !== allowedOrigin)\n return false;\n\n if (popup && event.source !== popup)\n return false;\n\n const data = event.data;\n\n if (!data || typeof data !== 'object')\n return false;\n\n const type = (data as { type?: unknown }).type;\n\n return typeof type === 'string'\n && KNOWN_FRAME_TYPES.has(type as EntPostMessageFrame['type']);\n}\n\n/**\n * Map a trusted frame to the public {@link EmbedEvent} discriminated union.\n *\n * Returns `null` when the payload is malformed for the given `type` (e.g. a\n * `ent:result` without a usable result object): the SDK ignores such frames\n * and keeps listening, leaving the popup open.\n *\n * @param frame - A frame already validated by {@link isTrustedEntFrame}.\n */\nexport function frameToEvent(frame: EntPostMessageFrame): EmbedEvent | null {\n\n switch (frame.type) {\n case 'ent:result' : return mapResultFrame(frame);\n case 'ent:cancelled': return mapCancelledFrame(frame);\n case 'ent:terminal' : return mapTerminalFrame(frame);\n default : return null;\n }\n}\n\n/**\n * Map a `ent:result` frame. Returns `null` when {@link normalizeResult}\n * rejects the payload.\n */\nfunction mapResultFrame(frame: EntPostMessageFrame): EmbedEvent | null {\n\n const result = normalizeResult(frame.payload);\n\n if (!result)\n return null;\n\n return { type: 'result', result, raw: frame };\n}\n\n/**\n * Map a `ent:cancelled` frame. The optional `reason` string is forwarded\n * verbatim when present; any other shape is silently dropped.\n */\nfunction mapCancelledFrame(frame: EntPostMessageFrame): EmbedEvent {\n\n const reason = readStringField(frame.payload, 'reason');\n\n return {\n type: 'cancelled',\n ...(reason ? { reason } : {}),\n raw : frame,\n };\n}\n\n/**\n * Map a `ent:terminal` frame. A missing or invalid `code` falls back to\n * {@link DEFAULT_TERMINAL_CODE} so the integrator always receives a stable\n * i18n key.\n */\nfunction mapTerminalFrame(frame: EntPostMessageFrame): EmbedEvent {\n\n const code = readStringField(frame.payload, 'code') ?? DEFAULT_TERMINAL_CODE;\n const message = readStringField(frame.payload, 'message');\n\n return {\n type: 'terminal',\n code,\n ...(message ? { message } : {}),\n raw: frame,\n };\n}\n\n/**\n * Read a string field from an unknown payload, returning `undefined` when\n * the payload is not an object or the field is missing/non-string.\n * Centralises the defensive type-narrowing used by all frame mappers.\n */\nfunction readStringField(payload: unknown, field: string): string | undefined {\n\n if (!payload || typeof payload !== 'object')\n return undefined;\n\n const value = (payload as Record<string, unknown>)[field];\n\n return typeof value === 'string' ? value : undefined;\n}\n\n/**\n * Normalise the `payload` of a `ent:result` frame into an {@link EntCodingResult}.\n *\n * - `diagnoses` and `procedures` default to empty arrays when missing.\n * - `drg` and `meta` are forwarded only when present and object-shaped.\n *\n * @returns The normalised result, or `null` if the payload is not an object.\n */\nfunction normalizeResult(payload: unknown): EntCodingResult | null {\n \n if (!payload || typeof payload !== 'object')\n return null;\n\n const p = payload as Record<string, unknown>;\n const diagnoses = Array.isArray(p['diagnoses']) ? (p['diagnoses'] as EntCodingResult['diagnoses']) : [];\n const procedures = Array.isArray(p['procedures'])\n ? (p['procedures'] as EntCodingResult['procedures'])\n : [];\n\n const result: EntCodingResult = { diagnoses, procedures };\n\n if (p['drg'] && typeof p['drg'] === 'object')\n result.drg = p['drg'] as EntCodingResult['drg'];\n\n if (p['meta'] && typeof p['meta'] === 'object')\n result.meta = p['meta'] as Record<string, unknown>;\n\n return result;\n}\n","/**\n * Cross-browser helpers to open a popup centered on the parent window and\n * detect when the user closes it without producing an event.\n *\n * The SDK keeps the popup reference alive so it can:\n * - validate `event.source === popup` when receiving postMessage frames;\n * - call `popup.close()` after a terminal protocol event;\n * - poll `popup.closed` to detect manual dismissal.\n */\n\n/**\n * Default `window.open` features used when the caller does not override\n * {@link openCenteredPopup}'s `features` argument.\n *\n * Notes:\n * - `noopener=no` / `noreferrer=no` are intentional: the SDK needs the\n * `Window` reference returned by `window.open` for origin validation.\n * - Width/height are passed through {@link withCenteredGeometry} so the\n * popup is centered on the current screen.\n */\nconst DEFAULT_FEATURES =\n 'popup=yes,noopener=no,noreferrer=no,width=1100,height=780,resizable=yes,scrollbars=yes';\n\n/**\n * Open a centered popup pointing at `url`.\n *\n * @param win - The opener window (usually `globalThis.window`).\n * @param url - The launch URL returned by the ENT backend.\n * @param features - Optional override for the `window.open` features string.\n * When omitted, {@link DEFAULT_FEATURES} is used and\n * centered via {@link withCenteredGeometry}.\n * @returns The popup `Window`, or `null` when the browser blocks it (e.g.\n * the call did not happen inside a user gesture).\n */\nexport function openCenteredPopup(\n win : Window,\n url : string,\n features: string | undefined,\n): Window | null {\n\n const computed = features ?? withCenteredGeometry(win, DEFAULT_FEATURES);\n // `_blank` + named features is enough for modern browsers; we deliberately\n // do NOT pass `noopener` because we rely on the returned reference for\n // postMessage origin validation (`event.source === popup`).\n return win.open(url, '_blank', computed);\n}\n\n/**\n * Append `left=`/`top=` to a features string so the popup is centered on the\n * available screen area. Falls back to the popup's intrinsic size when the\n * runtime does not expose `screen.availWidth`/`availHeight` (jsdom, SSR).\n */\nfunction withCenteredGeometry(win: Window, base: string): string {\n\n const screen = win.screen ?? ({} as Screen);\n const width = parseFeature(base, 'width', 1100);\n const height = parseFeature(base, 'height', 780);\n const left = Math.max(0, Math.round(((screen.availWidth ?? width) - width) / 2));\n const top = Math.max(0, Math.round(((screen.availHeight ?? height) - height) / 2));\n\n return `${base},left=${left},top=${top}`;\n}\n\n/**\n * Extract a numeric `window.open` feature value from a comma-separated\n * features string (e.g. `\"width=800,height=600\"`).\n *\n * @returns The parsed integer, or `fallback` when the key is missing or\n * malformed.\n */\nfunction parseFeature(features: string, key: string, fallback: number): number {\n\n const m = new RegExp(String.raw`(?:^|,)${key}=(\\d+)`).exec(features);\n return m ? Number(m[1]) : fallback;\n}\n\n/**\n * Poll `popup.closed` at a low frequency to detect manual dismissal of the\n * popup.\n *\n * We use polling instead of `beforeunload`/`unload` because cross-origin\n * popups do not expose those events to the opener.\n *\n * @param win - The opener window used to schedule the interval.\n * @param popup - The popup whose `closed` flag is observed.\n * @param onClosed - Invoked exactly once when the popup is detected closed.\n * @param intervalMs - Polling interval in milliseconds (default `500`).\n * @returns A function that stops the polling. Safe to call multiple times.\n */\nexport function watchPopupClosed(\n win : Window,\n popup : Window,\n onClosed: () => void,\n intervalMs = 500,\n): () => void {\n\n const handle = win.setInterval(() => {\n\n if (popup.closed) {\n win.clearInterval(handle);\n onClosed();\n }\n }, intervalMs);\n\n return () => win.clearInterval(handle);\n}\n","import { frameToEvent, isTrustedEntFrame } from './bridge.js';\nimport { openCenteredPopup, watchPopupClosed } from './popup.js';\nimport type { EmbedEvent, EmbedOpenOptions } from './types.js';\n\n/** Default popup lifetime: 15 minutes from `EmbedClient.open()` call. */\nconst DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;\n\n/**\n * Public entry point of the SDK.\n *\n * Usage:\n * ```ts\n * const ev = await EmbedClient.open({ launchUrl, allowedOrigin });\n * if (ev.type === 'result') { ... }\n * ```\n *\n * The Promise resolves **exactly once** with a discriminated\n * {@link EmbedEvent}. It never rejects for protocol outcomes\n * (`cancelled` / `terminal` / `closed`): it only rejects for\n * *programming* errors (missing options, popup blocked by the browser).\n */\nexport class EmbedClient {\n\n private constructor() {\n /* static-only */\n }\n\n /**\n * Open the ENT embed popup and resolve when the session terminates.\n *\n * Lifecycle:\n * 1. {@link validateOptions} — throw `TypeError` for bad input.\n * 2. `window.open` — reject if the browser blocks the popup.\n * 3. Install a `message` listener filtered by {@link isTrustedEntFrame}.\n * 4. Poll `popup.closed` via {@link watchPopupClosed}.\n * 5. Arm a timeout (default {@link DEFAULT_TIMEOUT_MS}).\n * 6. The first of (`result` / `cancelled` / `terminal` / `closed`)\n * wins, cleans up the other watchers, and resolves the Promise.\n *\n * @param options - See {@link EmbedOpenOptions}.\n * @returns The terminal {@link EmbedEvent} for this session.\n */\n static open(options: EmbedOpenOptions): Promise<EmbedEvent> {\n\n validateOptions(options);\n\n const win = options.windowRef ?? globalThis.window;\n\n if (!win || typeof win.open !== 'function')\n return Promise.reject(new Error('[ent-embed-sdk] No window available to open popup'));\n\n const popup = openCenteredPopup(win, options.launchUrl, options.popupFeatures);\n\n if (!popup)\n return Promise.reject(\n new Error('[ent-embed-sdk] Popup blocked by browser. Call EmbedClient.open() from a user gesture.'),\n );\n\n\n return new Promise<EmbedEvent>((resolve) => {\n\n let settled = false;\n let stopWatchClosed: (() => void) | null = null;\n let timeoutHandle : ReturnType<typeof setTimeout> | null = null;\n\n /**\n * Resolve the outer Promise exactly once. Subsequent calls are no-ops,\n * which lets the message listener, the `closed` poller and the timeout\n * all race safely.\n *\n * @param event - The event delivered to the integrator.\n * @param closePopup - Whether to call `popup.close()` after settling.\n * We skip the close when the user already closed\n * the popup manually (cross-origin `close()` can\n * throw in some browsers).\n */\n const settle = (event: EmbedEvent, closePopup: boolean) => {\n\n if (settled)\n return;\n\n settled = true;\n\n win.removeEventListener('message', onMessage);\n\n if (stopWatchClosed)\n stopWatchClosed();\n\n if (timeoutHandle !== null)\n clearTimeout(timeoutHandle);\n\n safeNotify(options.onEvent, event);\n\n if (closePopup && popup && !popup.closed) {\n\n try {\n popup.close();\n } catch {\n /* cross-origin popups may refuse close() — ignored */\n }\n }\n\n resolve(event);\n };\n\n /**\n * `message` event handler. Drops anything that is not a trusted ENT\n * frame, then maps the frame to an {@link EmbedEvent} and settles.\n */\n const onMessage = (event: MessageEvent) => {\n\n if (!isTrustedEntFrame(event, options.allowedOrigin, popup))\n return;\n\n const mapped = frameToEvent(event.data);\n\n if (!mapped)\n return;\n\n // result/terminal/cancelled are all terminal for the SDK lifecycle.\n settle(mapped, /* closePopup */ true);\n };\n\n win.addEventListener('message', onMessage);\n\n stopWatchClosed = watchPopupClosed(win, popup, () => {\n settle({ type: 'closed', reason: 'user-closed' }, /* closePopup */ false);\n });\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n if (timeoutMs > 0 && Number.isFinite(timeoutMs)) {\n\n timeoutHandle = setTimeout(() => {\n settle({ type: 'closed', reason: 'timeout' }, /* closePopup */ true);\n }, timeoutMs);\n }\n });\n }\n}\n\n/**\n * Throw a {@link TypeError} when {@link EmbedOpenOptions} are missing or\n * structurally invalid.\n *\n * In particular, `allowedOrigin` must be an **exact origin**: wildcards are\n * rejected to prevent accidental opening of the bridge to untrusted hosts.\n */\nfunction validateOptions(options: EmbedOpenOptions): void {\n\n if (!options || typeof options !== 'object')\n throw new TypeError('[ent-embed-sdk] options object is required');\n\n if (!options.launchUrl || typeof options.launchUrl !== 'string')\n throw new TypeError('[ent-embed-sdk] options.launchUrl (string) is required');\n\n if (!options.allowedOrigin || typeof options.allowedOrigin !== 'string')\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin (string) is required');\n\n if (options.allowedOrigin === '*' || options.allowedOrigin.includes('*'))\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin must be an exact origin (no wildcards)');\n\n if (options.mode && options.mode !== 'popup')\n throw new TypeError(`[ent-embed-sdk] mode \"${options.mode}\" is not supported in this version`);\n}\n\n/**\n * Invoke the optional `onEvent` observer in a fail-safe way: integrator\n * exceptions must never break the SDK contract (single Promise resolution).\n */\nfunction safeNotify(hook: EmbedOpenOptions['onEvent'], event: EmbedEvent): void {\n\n if (!hook)\n return;\n\n try {\n hook(event);\n } catch {\n /* user hook errors must not break the SDK contract */\n }\n}\n\nexport type {\n EmbedEvent,\n EmbedOpenOptions,\n EmbedClosedReason,\n EmbedTerminalCode,\n EntCodingResult,\n EntDiagnosis,\n EntProcedure,\n EntDrg,\n EntPostMessageFrame,\n} from './types.js';\n"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- /*! @nextage/ent-embed-sdk — see https://www.npmjs.com/package/@nextage/ent-embed-sdk */(function(s,i){typeof exports=="object"&&typeof module!="undefined"?i(exports):typeof define=="function"&&define.amd?define(["exports"],i):(s=typeof globalThis!="undefined"?globalThis:s||self,i(s.EntEmbedSdk={}))})(this,(function(s){"use strict";function i(e,t,o){if(e.origin!==t||o&&e.source!==o)return!1;const r=e.data;if(!r||typeof r!="object")return!1;const n=r.type;return n==="ent:result"||n==="ent:cancelled"||n==="ent:terminal"}function b(e){switch(e.type){case"ent:result":{const t=h(e.payload);return t?{type:"result",result:t,raw:e}:null}case"ent:cancelled":{const t=e.payload&&typeof e.payload=="object"?e.payload.reason:void 0;return{type:"cancelled",...typeof t=="string"?{reason:t}:{},raw:e}}case"ent:terminal":{const t=e.payload&&typeof e.payload=="object"?e.payload:{},o=typeof t.code=="string"?t.code:"embed.errors.session.terminal",r=typeof t.message=="string"?t.message:void 0;return{type:"terminal",code:o,...r?{message:r}:{},raw:e}}default:return null}}function h(e){if(!e||typeof e!="object")return null;const t=e,o=Array.isArray(t.diagnoses)?t.diagnoses:[],r=Array.isArray(t.procedures)?t.procedures:[],n={diagnoses:o,procedures:r};return t.drg&&typeof t.drg=="object"&&(n.drg=t.drg),t.meta&&typeof t.meta=="object"&&(n.meta=t.meta),n}const E="popup=yes,noopener=no,noreferrer=no,width=1100,height=780,resizable=yes,scrollbars=yes";function v(e,t,o){const r=o!=null?o:T(e,E);return e.open(t,"_blank",r)}function T(e,t){var o,r,n;const p=(o=e.screen)!=null?o:{},l=w(t,"width",1100),a=w(t,"height",780),d=Math.max(0,Math.round((((r=p.availWidth)!=null?r:l)-l)/2)),u=Math.max(0,Math.round((((n=p.availHeight)!=null?n:a)-a)/2));return`${t},left=${d},top=${u}`}function w(e,t,o){const r=e.match(new RegExp(`(?:^|,)${t}=(\\d+)`));return r?Number(r[1]):o}function j(e,t,o,r=500){const n=e.setInterval(()=>{t.closed&&(e.clearInterval(n),o())},r);return()=>e.clearInterval(n)}const k=900*1e3;class O{constructor(){}static open(t){var o;x(t);const r=(o=t.windowRef)!=null?o:globalThis.window;if(!r||typeof r.open!="function")return Promise.reject(new Error("[ent-embed-sdk] No window available to open popup"));const n=v(r,t.launchUrl,t.popupFeatures);return n?new Promise(p=>{var l;let a=!1,d=null,u=null;const m=(c,f)=>{if(!a){if(a=!0,r.removeEventListener("message",g),d&&d(),u!==null&&clearTimeout(u),C(t.onEvent,c),f&&n&&!n.closed)try{n.close()}catch(P){}p(c)}},g=c=>{if(!i(c,t.allowedOrigin,n))return;const f=b(c.data);f&&m(f,!0)};r.addEventListener("message",g),d=j(r,n,()=>{m({type:"closed",reason:"user-closed"},!1)});const y=(l=t.timeoutMs)!=null?l:k;y>0&&Number.isFinite(y)&&(u=setTimeout(()=>{m({type:"closed",reason:"timeout"},!0)},y))}):Promise.reject(new Error("[ent-embed-sdk] Popup blocked by browser. Call EmbedClient.open() from a user gesture."))}}function x(e){if(!e||typeof e!="object")throw new TypeError("[ent-embed-sdk] options object is required");if(!e.launchUrl||typeof e.launchUrl!="string")throw new TypeError("[ent-embed-sdk] options.launchUrl (string) is required");if(!e.allowedOrigin||typeof e.allowedOrigin!="string")throw new TypeError("[ent-embed-sdk] options.allowedOrigin (string) is required");if(e.allowedOrigin==="*"||e.allowedOrigin.includes("*"))throw new TypeError("[ent-embed-sdk] options.allowedOrigin must be an exact origin (no wildcards)");if(e.mode&&e.mode!=="popup")throw new TypeError(`[ent-embed-sdk] mode "${e.mode}" is not supported in this version`)}function C(e,t){if(e)try{e(t)}catch(o){}}s.EmbedClient=O}));
1
+ /*! @nextage/ent-embed-sdk — see https://www.npmjs.com/package/@nextage/ent-embed-sdk */(function(s,i){typeof exports=="object"&&typeof module!="undefined"?i(exports):typeof define=="function"&&define.amd?define(["exports"],i):(s=typeof globalThis!="undefined"?globalThis:s||self,i(s.EntEmbedSdk={}))})(this,(function(s){"use strict";const i=new Set(["ent:result","ent:cancelled","ent:terminal"]),b="embed.errors.session.terminal";function E(e,n,t){if(e.origin!==n||t&&e.source!==t)return!1;const r=e.data;if(!r||typeof r!="object")return!1;const o=r.type;return typeof o=="string"&&i.has(o)}function v(e){switch(e.type){case"ent:result":return T(e);case"ent:cancelled":return j(e);case"ent:terminal":return k(e);default:return null}}function T(e){const n=x(e.payload);return n?{type:"result",result:n,raw:e}:null}function j(e){const n=m(e.payload,"reason");return{type:"cancelled",...n?{reason:n}:{},raw:e}}function k(e){var n;const t=(n=m(e.payload,"code"))!=null?n:b,r=m(e.payload,"message");return{type:"terminal",code:t,...r?{message:r}:{},raw:e}}function m(e,n){if(!e||typeof e!="object")return;const t=e[n];return typeof t=="string"?t:void 0}function x(e){if(!e||typeof e!="object")return null;const n=e,t=Array.isArray(n.diagnoses)?n.diagnoses:[],r=Array.isArray(n.procedures)?n.procedures:[],o={diagnoses:t,procedures:r};return n.drg&&typeof n.drg=="object"&&(o.drg=n.drg),n.meta&&typeof n.meta=="object"&&(o.meta=n.meta),o}const O="popup=yes,noopener=no,noreferrer=no,width=1100,height=780,resizable=yes,scrollbars=yes";function C(e,n,t){const r=t!=null?t:P(e,O);return e.open(n,"_blank",r)}function P(e,n){var t,r,o;const p=(t=e.screen)!=null?t:{},l=g(n,"width",1100),a=g(n,"height",780),u=Math.max(0,Math.round((((r=p.availWidth)!=null?r:l)-l)/2)),d=Math.max(0,Math.round((((o=p.availHeight)!=null?o:a)-a)/2));return`${n},left=${u},top=${d}`}function g(e,n,t){const r=new RegExp(String.raw`(?:^|,)${n}=(\d+)`).exec(e);return r?Number(r[1]):t}function $(e,n,t,r=500){const o=e.setInterval(()=>{n.closed&&(e.clearInterval(o),t())},r);return()=>e.clearInterval(o)}const M=900*1e3;class A{constructor(){}static open(n){var t;U(n);const r=(t=n.windowRef)!=null?t:globalThis.window;if(!r||typeof r.open!="function")return Promise.reject(new Error("[ent-embed-sdk] No window available to open popup"));const o=C(r,n.launchUrl,n.popupFeatures);return o?new Promise(p=>{var l;let a=!1,u=null,d=null;const y=(c,f)=>{if(!a){if(a=!0,r.removeEventListener("message",h),u&&u(),d!==null&&clearTimeout(d),q(n.onEvent,c),f&&o&&!o.closed)try{o.close()}catch(F){}p(c)}},h=c=>{if(!E(c,n.allowedOrigin,o))return;const f=v(c.data);f&&y(f,!0)};r.addEventListener("message",h),u=$(r,o,()=>{y({type:"closed",reason:"user-closed"},!1)});const w=(l=n.timeoutMs)!=null?l:M;w>0&&Number.isFinite(w)&&(d=setTimeout(()=>{y({type:"closed",reason:"timeout"},!0)},w))}):Promise.reject(new Error("[ent-embed-sdk] Popup blocked by browser. Call EmbedClient.open() from a user gesture."))}}function U(e){if(!e||typeof e!="object")throw new TypeError("[ent-embed-sdk] options object is required");if(!e.launchUrl||typeof e.launchUrl!="string")throw new TypeError("[ent-embed-sdk] options.launchUrl (string) is required");if(!e.allowedOrigin||typeof e.allowedOrigin!="string")throw new TypeError("[ent-embed-sdk] options.allowedOrigin (string) is required");if(e.allowedOrigin==="*"||e.allowedOrigin.includes("*"))throw new TypeError("[ent-embed-sdk] options.allowedOrigin must be an exact origin (no wildcards)");if(e.mode&&e.mode!=="popup")throw new TypeError(`[ent-embed-sdk] mode "${e.mode}" is not supported in this version`)}function q(e,n){if(e)try{e(n)}catch(t){}}s.EmbedClient=A}));
2
2
  //# sourceMappingURL=ent-embed-sdk.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ent-embed-sdk.umd.js","sources":["../src/bridge.ts","../src/popup.ts","../src/index.ts"],"sourcesContent":["import type { EmbedEvent, EntCodingResult, EntPostMessageFrame } from './types.js';\n\n/**\n * Validate that a postMessage frame originates from the trusted ENT origin\n * and matches the documented shape. Implements the three OWASP checks:\n *\n * 1. `event.origin === allowedOrigin` (constant comparison, no regex).\n * 2. `event.source === popupWindow` (no other window can spoof it).\n * 3. `data` is a plain object with a recognised `type`.\n */\nexport function isTrustedEntFrame(\n event: MessageEvent,\n allowedOrigin: string,\n popup: Window | null,\n): event is MessageEvent<EntPostMessageFrame> {\n if (event.origin !== allowedOrigin) return false;\n if (popup && event.source !== popup) return false;\n const data = event.data;\n if (!data || typeof data !== 'object') return false;\n const type = (data as { type?: unknown }).type;\n return type === 'ent:result' || type === 'ent:cancelled' || type === 'ent:terminal';\n}\n\n/**\n * Map a trusted frame to the public `EmbedEvent` discriminated union.\n * Returns `null` if the payload is malformed for the given type.\n */\nexport function frameToEvent(frame: EntPostMessageFrame): EmbedEvent | null {\n switch (frame.type) {\n case 'ent:result': {\n const result = normalizeResult(frame.payload);\n if (!result) return null;\n return { type: 'result', result, raw: frame };\n }\n case 'ent:cancelled': {\n const reason =\n frame.payload && typeof frame.payload === 'object'\n ? (frame.payload as { reason?: unknown }).reason\n : undefined;\n return {\n type: 'cancelled',\n ...(typeof reason === 'string' ? { reason } : {}),\n raw: frame,\n };\n }\n case 'ent:terminal': {\n const p =\n frame.payload && typeof frame.payload === 'object'\n ? (frame.payload as { code?: unknown; message?: unknown })\n : {};\n const code = typeof p.code === 'string' ? p.code : 'embed.errors.session.terminal';\n const message = typeof p.message === 'string' ? p.message : undefined;\n return {\n type: 'terminal',\n code,\n ...(message ? { message } : {}),\n raw: frame,\n };\n }\n default:\n return null;\n }\n}\n\nfunction normalizeResult(payload: unknown): EntCodingResult | null {\n if (!payload || typeof payload !== 'object') return null;\n const p = payload as Record<string, unknown>;\n const diagnoses = Array.isArray(p['diagnoses']) ? (p['diagnoses'] as EntCodingResult['diagnoses']) : [];\n const procedures = Array.isArray(p['procedures'])\n ? (p['procedures'] as EntCodingResult['procedures'])\n : [];\n const result: EntCodingResult = { diagnoses, procedures };\n if (p['drg'] && typeof p['drg'] === 'object') {\n result.drg = p['drg'] as EntCodingResult['drg'];\n }\n if (p['meta'] && typeof p['meta'] === 'object') {\n result.meta = p['meta'] as Record<string, unknown>;\n }\n return result;\n}\n","/**\n * Cross-browser helpers to open a popup centered on the parent window and\n * detect when the user closes it without producing an event.\n */\n\nconst DEFAULT_FEATURES =\n 'popup=yes,noopener=no,noreferrer=no,width=1100,height=780,resizable=yes,scrollbars=yes';\n\nexport function openCenteredPopup(\n win: Window,\n url: string,\n features: string | undefined,\n): Window | null {\n const computed = features ?? withCenteredGeometry(win, DEFAULT_FEATURES);\n // `_blank` + named features is enough for modern browsers; we deliberately\n // do NOT pass `noopener` because we rely on the returned reference for\n // postMessage origin validation (`event.source === popup`).\n return win.open(url, '_blank', computed);\n}\n\nfunction withCenteredGeometry(win: Window, base: string): string {\n const screen = win.screen ?? ({} as Screen);\n const width = parseFeature(base, 'width', 1100);\n const height = parseFeature(base, 'height', 780);\n const left = Math.max(0, Math.round(((screen.availWidth ?? width) - width) / 2));\n const top = Math.max(0, Math.round(((screen.availHeight ?? height) - height) / 2));\n return `${base},left=${left},top=${top}`;\n}\n\nfunction parseFeature(features: string, key: string, fallback: number): number {\n const m = features.match(new RegExp(`(?:^|,)${key}=(\\\\d+)`));\n return m ? Number(m[1]) : fallback;\n}\n\n/**\n * Poll `popup.closed` at a low frequency. We use polling instead of\n * `beforeunload`/`unload` because cross-origin popups do not expose those\n * events to the opener. Returns a function that stops the poll.\n */\nexport function watchPopupClosed(\n win: Window,\n popup: Window,\n onClosed: () => void,\n intervalMs = 500,\n): () => void {\n const handle = win.setInterval(() => {\n if (popup.closed) {\n win.clearInterval(handle);\n onClosed();\n }\n }, intervalMs);\n return () => win.clearInterval(handle);\n}\n","import { frameToEvent, isTrustedEntFrame } from './bridge.js';\nimport { openCenteredPopup, watchPopupClosed } from './popup.js';\nimport type { EmbedEvent, EmbedOpenOptions } from './types.js';\n\nconst DEFAULT_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes\n\n/**\n * Public entry point of the SDK.\n *\n * Usage:\n * ```ts\n * const ev = await EmbedClient.open({ launchUrl, allowedOrigin });\n * if (ev.type === 'result') { ... }\n * ```\n *\n * The Promise resolves exactly once with a discriminated `EmbedEvent`.\n * It never rejects for protocol outcomes (cancel / terminal / closed):\n * it only rejects for *programming* errors (missing options, popup blocked).\n */\nexport class EmbedClient {\n private constructor() {\n /* static-only */\n }\n\n static open(options: EmbedOpenOptions): Promise<EmbedEvent> {\n validateOptions(options);\n const win = options.windowRef ?? globalThis.window;\n if (!win || typeof win.open !== 'function') {\n return Promise.reject(new Error('[ent-embed-sdk] No window available to open popup'));\n }\n\n const popup = openCenteredPopup(win, options.launchUrl, options.popupFeatures);\n if (!popup) {\n return Promise.reject(\n new Error('[ent-embed-sdk] Popup blocked by browser. Call EmbedClient.open() from a user gesture.'),\n );\n }\n\n return new Promise<EmbedEvent>((resolve) => {\n let settled = false;\n let stopWatchClosed: (() => void) | null = null;\n let timeoutHandle: ReturnType<typeof setTimeout> | null = null;\n\n const settle = (event: EmbedEvent, closePopup: boolean) => {\n if (settled) return;\n settled = true;\n win.removeEventListener('message', onMessage);\n if (stopWatchClosed) stopWatchClosed();\n if (timeoutHandle !== null) clearTimeout(timeoutHandle);\n safeNotify(options.onEvent, event);\n if (closePopup && popup && !popup.closed) {\n try {\n popup.close();\n } catch {\n /* cross-origin popups may refuse close() — ignored */\n }\n }\n resolve(event);\n };\n\n const onMessage = (event: MessageEvent) => {\n if (!isTrustedEntFrame(event, options.allowedOrigin, popup)) return;\n const mapped = frameToEvent(event.data);\n if (!mapped) return;\n // result/terminal/cancelled are all terminal for the SDK lifecycle.\n settle(mapped, /* closePopup */ true);\n };\n\n win.addEventListener('message', onMessage);\n\n stopWatchClosed = watchPopupClosed(win, popup, () => {\n settle({ type: 'closed', reason: 'user-closed' }, /* closePopup */ false);\n });\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n if (timeoutMs > 0 && Number.isFinite(timeoutMs)) {\n timeoutHandle = setTimeout(() => {\n settle({ type: 'closed', reason: 'timeout' }, /* closePopup */ true);\n }, timeoutMs);\n }\n });\n }\n}\n\nfunction validateOptions(options: EmbedOpenOptions): void {\n if (!options || typeof options !== 'object') {\n throw new TypeError('[ent-embed-sdk] options object is required');\n }\n if (!options.launchUrl || typeof options.launchUrl !== 'string') {\n throw new TypeError('[ent-embed-sdk] options.launchUrl (string) is required');\n }\n if (!options.allowedOrigin || typeof options.allowedOrigin !== 'string') {\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin (string) is required');\n }\n if (options.allowedOrigin === '*' || options.allowedOrigin.includes('*')) {\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin must be an exact origin (no wildcards)');\n }\n if (options.mode && options.mode !== 'popup') {\n throw new TypeError(`[ent-embed-sdk] mode \"${options.mode}\" is not supported in this version`);\n }\n}\n\nfunction safeNotify(hook: EmbedOpenOptions['onEvent'], event: EmbedEvent): void {\n if (!hook) return;\n try {\n hook(event);\n } catch {\n /* user hook errors must not break the SDK contract */\n }\n}\n\nexport type {\n EmbedEvent,\n EmbedOpenOptions,\n EmbedClosedReason,\n EmbedTerminalCode,\n EntCodingResult,\n EntDiagnosis,\n EntProcedure,\n EntDrg,\n EntPostMessageFrame,\n} from './types.js';\n"],"names":["isTrustedEntFrame","event","allowedOrigin","popup","data","type","frameToEvent","frame","result","normalizeResult","reason","p","code","message","payload","diagnoses","procedures","DEFAULT_FEATURES","openCenteredPopup","win","url","features","computed","withCenteredGeometry","base","_a","_b","_c","screen","width","parseFeature","height","left","top","key","fallback","m","watchPopupClosed","onClosed","intervalMs","handle","DEFAULT_TIMEOUT_MS","EmbedClient","options","validateOptions","resolve","settled","stopWatchClosed","timeoutHandle","settle","closePopup","onMessage","safeNotify","e","mapped","timeoutMs","hook"],"mappings":"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"}
1
+ {"version":3,"file":"ent-embed-sdk.umd.js","sources":["../src/bridge.ts","../src/popup.ts","../src/index.ts"],"sourcesContent":["import type { EmbedEvent, EntCodingResult, EntPostMessageFrame } from './types.js';\n\n/**\n * Set of `type` discriminants accepted from the ENT embed page.\n * Centralises the protocol surface so both the runtime guard and the\n * mapper agree on what is supported.\n */\nconst KNOWN_FRAME_TYPES = new Set<EntPostMessageFrame['type']>([\n 'ent:result',\n 'ent:cancelled',\n 'ent:terminal',\n]);\n\n/** Default i18n code emitted when the embed page does not supply one. */\nconst DEFAULT_TERMINAL_CODE = 'embed.errors.session.terminal';\n\n/**\n * Validate that a postMessage frame originates from the trusted ENT origin\n * and matches the documented shape.\n *\n * Implements the three OWASP checks required for a safe cross-origin bridge:\n *\n * 1. `event.origin === allowedOrigin` (constant comparison, no regex).\n * 2. `event.source === popupWindow` (no other window can spoof it).\n * 3. `data` is a plain object whose `type` is in {@link KNOWN_FRAME_TYPES}.\n *\n * @param event - The raw `MessageEvent` received by `window`.\n * @param allowedOrigin - Exact origin (scheme + host + port) trusted by the SDK.\n * @param popup - Window reference returned by `window.open`, or `null`\n * if not yet available; when present, must equal\n * `event.source`.\n * @returns `true` (with a type predicate) when the frame can be safely\n * forwarded to {@link frameToEvent}.\n */\nexport function isTrustedEntFrame(\n event : MessageEvent,\n allowedOrigin: string,\n popup : Window | null,\n): event is MessageEvent<EntPostMessageFrame> {\n\n if (event.origin !== allowedOrigin)\n return false;\n\n if (popup && event.source !== popup)\n return false;\n\n const data = event.data;\n\n if (!data || typeof data !== 'object')\n return false;\n\n const type = (data as { type?: unknown }).type;\n\n return typeof type === 'string'\n && KNOWN_FRAME_TYPES.has(type as EntPostMessageFrame['type']);\n}\n\n/**\n * Map a trusted frame to the public {@link EmbedEvent} discriminated union.\n *\n * Returns `null` when the payload is malformed for the given `type` (e.g. a\n * `ent:result` without a usable result object): the SDK ignores such frames\n * and keeps listening, leaving the popup open.\n *\n * @param frame - A frame already validated by {@link isTrustedEntFrame}.\n */\nexport function frameToEvent(frame: EntPostMessageFrame): EmbedEvent | null {\n\n switch (frame.type) {\n case 'ent:result' : return mapResultFrame(frame);\n case 'ent:cancelled': return mapCancelledFrame(frame);\n case 'ent:terminal' : return mapTerminalFrame(frame);\n default : return null;\n }\n}\n\n/**\n * Map a `ent:result` frame. Returns `null` when {@link normalizeResult}\n * rejects the payload.\n */\nfunction mapResultFrame(frame: EntPostMessageFrame): EmbedEvent | null {\n\n const result = normalizeResult(frame.payload);\n\n if (!result)\n return null;\n\n return { type: 'result', result, raw: frame };\n}\n\n/**\n * Map a `ent:cancelled` frame. The optional `reason` string is forwarded\n * verbatim when present; any other shape is silently dropped.\n */\nfunction mapCancelledFrame(frame: EntPostMessageFrame): EmbedEvent {\n\n const reason = readStringField(frame.payload, 'reason');\n\n return {\n type: 'cancelled',\n ...(reason ? { reason } : {}),\n raw : frame,\n };\n}\n\n/**\n * Map a `ent:terminal` frame. A missing or invalid `code` falls back to\n * {@link DEFAULT_TERMINAL_CODE} so the integrator always receives a stable\n * i18n key.\n */\nfunction mapTerminalFrame(frame: EntPostMessageFrame): EmbedEvent {\n\n const code = readStringField(frame.payload, 'code') ?? DEFAULT_TERMINAL_CODE;\n const message = readStringField(frame.payload, 'message');\n\n return {\n type: 'terminal',\n code,\n ...(message ? { message } : {}),\n raw: frame,\n };\n}\n\n/**\n * Read a string field from an unknown payload, returning `undefined` when\n * the payload is not an object or the field is missing/non-string.\n * Centralises the defensive type-narrowing used by all frame mappers.\n */\nfunction readStringField(payload: unknown, field: string): string | undefined {\n\n if (!payload || typeof payload !== 'object')\n return undefined;\n\n const value = (payload as Record<string, unknown>)[field];\n\n return typeof value === 'string' ? value : undefined;\n}\n\n/**\n * Normalise the `payload` of a `ent:result` frame into an {@link EntCodingResult}.\n *\n * - `diagnoses` and `procedures` default to empty arrays when missing.\n * - `drg` and `meta` are forwarded only when present and object-shaped.\n *\n * @returns The normalised result, or `null` if the payload is not an object.\n */\nfunction normalizeResult(payload: unknown): EntCodingResult | null {\n \n if (!payload || typeof payload !== 'object')\n return null;\n\n const p = payload as Record<string, unknown>;\n const diagnoses = Array.isArray(p['diagnoses']) ? (p['diagnoses'] as EntCodingResult['diagnoses']) : [];\n const procedures = Array.isArray(p['procedures'])\n ? (p['procedures'] as EntCodingResult['procedures'])\n : [];\n\n const result: EntCodingResult = { diagnoses, procedures };\n\n if (p['drg'] && typeof p['drg'] === 'object')\n result.drg = p['drg'] as EntCodingResult['drg'];\n\n if (p['meta'] && typeof p['meta'] === 'object')\n result.meta = p['meta'] as Record<string, unknown>;\n\n return result;\n}\n","/**\n * Cross-browser helpers to open a popup centered on the parent window and\n * detect when the user closes it without producing an event.\n *\n * The SDK keeps the popup reference alive so it can:\n * - validate `event.source === popup` when receiving postMessage frames;\n * - call `popup.close()` after a terminal protocol event;\n * - poll `popup.closed` to detect manual dismissal.\n */\n\n/**\n * Default `window.open` features used when the caller does not override\n * {@link openCenteredPopup}'s `features` argument.\n *\n * Notes:\n * - `noopener=no` / `noreferrer=no` are intentional: the SDK needs the\n * `Window` reference returned by `window.open` for origin validation.\n * - Width/height are passed through {@link withCenteredGeometry} so the\n * popup is centered on the current screen.\n */\nconst DEFAULT_FEATURES =\n 'popup=yes,noopener=no,noreferrer=no,width=1100,height=780,resizable=yes,scrollbars=yes';\n\n/**\n * Open a centered popup pointing at `url`.\n *\n * @param win - The opener window (usually `globalThis.window`).\n * @param url - The launch URL returned by the ENT backend.\n * @param features - Optional override for the `window.open` features string.\n * When omitted, {@link DEFAULT_FEATURES} is used and\n * centered via {@link withCenteredGeometry}.\n * @returns The popup `Window`, or `null` when the browser blocks it (e.g.\n * the call did not happen inside a user gesture).\n */\nexport function openCenteredPopup(\n win : Window,\n url : string,\n features: string | undefined,\n): Window | null {\n\n const computed = features ?? withCenteredGeometry(win, DEFAULT_FEATURES);\n // `_blank` + named features is enough for modern browsers; we deliberately\n // do NOT pass `noopener` because we rely on the returned reference for\n // postMessage origin validation (`event.source === popup`).\n return win.open(url, '_blank', computed);\n}\n\n/**\n * Append `left=`/`top=` to a features string so the popup is centered on the\n * available screen area. Falls back to the popup's intrinsic size when the\n * runtime does not expose `screen.availWidth`/`availHeight` (jsdom, SSR).\n */\nfunction withCenteredGeometry(win: Window, base: string): string {\n\n const screen = win.screen ?? ({} as Screen);\n const width = parseFeature(base, 'width', 1100);\n const height = parseFeature(base, 'height', 780);\n const left = Math.max(0, Math.round(((screen.availWidth ?? width) - width) / 2));\n const top = Math.max(0, Math.round(((screen.availHeight ?? height) - height) / 2));\n\n return `${base},left=${left},top=${top}`;\n}\n\n/**\n * Extract a numeric `window.open` feature value from a comma-separated\n * features string (e.g. `\"width=800,height=600\"`).\n *\n * @returns The parsed integer, or `fallback` when the key is missing or\n * malformed.\n */\nfunction parseFeature(features: string, key: string, fallback: number): number {\n\n const m = new RegExp(String.raw`(?:^|,)${key}=(\\d+)`).exec(features);\n return m ? Number(m[1]) : fallback;\n}\n\n/**\n * Poll `popup.closed` at a low frequency to detect manual dismissal of the\n * popup.\n *\n * We use polling instead of `beforeunload`/`unload` because cross-origin\n * popups do not expose those events to the opener.\n *\n * @param win - The opener window used to schedule the interval.\n * @param popup - The popup whose `closed` flag is observed.\n * @param onClosed - Invoked exactly once when the popup is detected closed.\n * @param intervalMs - Polling interval in milliseconds (default `500`).\n * @returns A function that stops the polling. Safe to call multiple times.\n */\nexport function watchPopupClosed(\n win : Window,\n popup : Window,\n onClosed: () => void,\n intervalMs = 500,\n): () => void {\n\n const handle = win.setInterval(() => {\n\n if (popup.closed) {\n win.clearInterval(handle);\n onClosed();\n }\n }, intervalMs);\n\n return () => win.clearInterval(handle);\n}\n","import { frameToEvent, isTrustedEntFrame } from './bridge.js';\nimport { openCenteredPopup, watchPopupClosed } from './popup.js';\nimport type { EmbedEvent, EmbedOpenOptions } from './types.js';\n\n/** Default popup lifetime: 15 minutes from `EmbedClient.open()` call. */\nconst DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;\n\n/**\n * Public entry point of the SDK.\n *\n * Usage:\n * ```ts\n * const ev = await EmbedClient.open({ launchUrl, allowedOrigin });\n * if (ev.type === 'result') { ... }\n * ```\n *\n * The Promise resolves **exactly once** with a discriminated\n * {@link EmbedEvent}. It never rejects for protocol outcomes\n * (`cancelled` / `terminal` / `closed`): it only rejects for\n * *programming* errors (missing options, popup blocked by the browser).\n */\nexport class EmbedClient {\n\n private constructor() {\n /* static-only */\n }\n\n /**\n * Open the ENT embed popup and resolve when the session terminates.\n *\n * Lifecycle:\n * 1. {@link validateOptions} — throw `TypeError` for bad input.\n * 2. `window.open` — reject if the browser blocks the popup.\n * 3. Install a `message` listener filtered by {@link isTrustedEntFrame}.\n * 4. Poll `popup.closed` via {@link watchPopupClosed}.\n * 5. Arm a timeout (default {@link DEFAULT_TIMEOUT_MS}).\n * 6. The first of (`result` / `cancelled` / `terminal` / `closed`)\n * wins, cleans up the other watchers, and resolves the Promise.\n *\n * @param options - See {@link EmbedOpenOptions}.\n * @returns The terminal {@link EmbedEvent} for this session.\n */\n static open(options: EmbedOpenOptions): Promise<EmbedEvent> {\n\n validateOptions(options);\n\n const win = options.windowRef ?? globalThis.window;\n\n if (!win || typeof win.open !== 'function')\n return Promise.reject(new Error('[ent-embed-sdk] No window available to open popup'));\n\n const popup = openCenteredPopup(win, options.launchUrl, options.popupFeatures);\n\n if (!popup)\n return Promise.reject(\n new Error('[ent-embed-sdk] Popup blocked by browser. Call EmbedClient.open() from a user gesture.'),\n );\n\n\n return new Promise<EmbedEvent>((resolve) => {\n\n let settled = false;\n let stopWatchClosed: (() => void) | null = null;\n let timeoutHandle : ReturnType<typeof setTimeout> | null = null;\n\n /**\n * Resolve the outer Promise exactly once. Subsequent calls are no-ops,\n * which lets the message listener, the `closed` poller and the timeout\n * all race safely.\n *\n * @param event - The event delivered to the integrator.\n * @param closePopup - Whether to call `popup.close()` after settling.\n * We skip the close when the user already closed\n * the popup manually (cross-origin `close()` can\n * throw in some browsers).\n */\n const settle = (event: EmbedEvent, closePopup: boolean) => {\n\n if (settled)\n return;\n\n settled = true;\n\n win.removeEventListener('message', onMessage);\n\n if (stopWatchClosed)\n stopWatchClosed();\n\n if (timeoutHandle !== null)\n clearTimeout(timeoutHandle);\n\n safeNotify(options.onEvent, event);\n\n if (closePopup && popup && !popup.closed) {\n\n try {\n popup.close();\n } catch {\n /* cross-origin popups may refuse close() — ignored */\n }\n }\n\n resolve(event);\n };\n\n /**\n * `message` event handler. Drops anything that is not a trusted ENT\n * frame, then maps the frame to an {@link EmbedEvent} and settles.\n */\n const onMessage = (event: MessageEvent) => {\n\n if (!isTrustedEntFrame(event, options.allowedOrigin, popup))\n return;\n\n const mapped = frameToEvent(event.data);\n\n if (!mapped)\n return;\n\n // result/terminal/cancelled are all terminal for the SDK lifecycle.\n settle(mapped, /* closePopup */ true);\n };\n\n win.addEventListener('message', onMessage);\n\n stopWatchClosed = watchPopupClosed(win, popup, () => {\n settle({ type: 'closed', reason: 'user-closed' }, /* closePopup */ false);\n });\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n if (timeoutMs > 0 && Number.isFinite(timeoutMs)) {\n\n timeoutHandle = setTimeout(() => {\n settle({ type: 'closed', reason: 'timeout' }, /* closePopup */ true);\n }, timeoutMs);\n }\n });\n }\n}\n\n/**\n * Throw a {@link TypeError} when {@link EmbedOpenOptions} are missing or\n * structurally invalid.\n *\n * In particular, `allowedOrigin` must be an **exact origin**: wildcards are\n * rejected to prevent accidental opening of the bridge to untrusted hosts.\n */\nfunction validateOptions(options: EmbedOpenOptions): void {\n\n if (!options || typeof options !== 'object')\n throw new TypeError('[ent-embed-sdk] options object is required');\n\n if (!options.launchUrl || typeof options.launchUrl !== 'string')\n throw new TypeError('[ent-embed-sdk] options.launchUrl (string) is required');\n\n if (!options.allowedOrigin || typeof options.allowedOrigin !== 'string')\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin (string) is required');\n\n if (options.allowedOrigin === '*' || options.allowedOrigin.includes('*'))\n throw new TypeError('[ent-embed-sdk] options.allowedOrigin must be an exact origin (no wildcards)');\n\n if (options.mode && options.mode !== 'popup')\n throw new TypeError(`[ent-embed-sdk] mode \"${options.mode}\" is not supported in this version`);\n}\n\n/**\n * Invoke the optional `onEvent` observer in a fail-safe way: integrator\n * exceptions must never break the SDK contract (single Promise resolution).\n */\nfunction safeNotify(hook: EmbedOpenOptions['onEvent'], event: EmbedEvent): void {\n\n if (!hook)\n return;\n\n try {\n hook(event);\n } catch {\n /* user hook errors must not break the SDK contract */\n }\n}\n\nexport type {\n EmbedEvent,\n EmbedOpenOptions,\n EmbedClosedReason,\n EmbedTerminalCode,\n EntCodingResult,\n EntDiagnosis,\n EntProcedure,\n EntDrg,\n EntPostMessageFrame,\n} from './types.js';\n"],"names":["KNOWN_FRAME_TYPES","DEFAULT_TERMINAL_CODE","isTrustedEntFrame","event","allowedOrigin","popup","data","type","frameToEvent","frame","mapResultFrame","mapCancelledFrame","mapTerminalFrame","result","normalizeResult","reason","readStringField","_a","code","message","payload","field","value","p","diagnoses","procedures","DEFAULT_FEATURES","openCenteredPopup","win","url","features","computed","withCenteredGeometry","base","_b","_c","screen","width","parseFeature","height","left","top","key","fallback","m","watchPopupClosed","onClosed","intervalMs","handle","DEFAULT_TIMEOUT_MS","EmbedClient","options","validateOptions","resolve","settled","stopWatchClosed","timeoutHandle","settle","closePopup","onMessage","safeNotify","e","mapped","timeoutMs","hook"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -119,12 +119,28 @@ interface EntPostMessageFrame {
119
119
  * if (ev.type === 'result') { ... }
120
120
  * ```
121
121
  *
122
- * The Promise resolves exactly once with a discriminated `EmbedEvent`.
123
- * It never rejects for protocol outcomes (cancel / terminal / closed):
124
- * it only rejects for *programming* errors (missing options, popup blocked).
122
+ * The Promise resolves **exactly once** with a discriminated
123
+ * {@link EmbedEvent}. It never rejects for protocol outcomes
124
+ * (`cancelled` / `terminal` / `closed`): it only rejects for
125
+ * *programming* errors (missing options, popup blocked by the browser).
125
126
  */
126
127
  declare class EmbedClient {
127
128
  private constructor();
129
+ /**
130
+ * Open the ENT embed popup and resolve when the session terminates.
131
+ *
132
+ * Lifecycle:
133
+ * 1. {@link validateOptions} — throw `TypeError` for bad input.
134
+ * 2. `window.open` — reject if the browser blocks the popup.
135
+ * 3. Install a `message` listener filtered by {@link isTrustedEntFrame}.
136
+ * 4. Poll `popup.closed` via {@link watchPopupClosed}.
137
+ * 5. Arm a timeout (default {@link DEFAULT_TIMEOUT_MS}).
138
+ * 6. The first of (`result` / `cancelled` / `terminal` / `closed`)
139
+ * wins, cleans up the other watchers, and resolves the Promise.
140
+ *
141
+ * @param options - See {@link EmbedOpenOptions}.
142
+ * @returns The terminal {@link EmbedEvent} for this session.
143
+ */
128
144
  static open(options: EmbedOpenOptions): Promise<EmbedEvent>;
129
145
  }
130
146
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextage/ent-embed-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Client SDK for embedding the ENT coding flow via postMessage (popup mode).",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -8,11 +8,10 @@
8
8
  "homepage": "https://www.npmjs.com/package/@nextage/ent-embed-sdk",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://github.com/zcshcd/ent.git",
12
- "directory": "ent-embed-sdk"
11
+ "url": "git+https://bitbucket.org/nextage-dev/ent-embed-sdk.git"
13
12
  },
14
13
  "bugs": {
15
- "url": "https://github.com/zcshcd/ent/issues"
14
+ "url": "https://bitbucket.org/nextage-dev/ent-embed-sdk/issues"
16
15
  },
17
16
  "publishConfig": {
18
17
  "access": "public",
@@ -48,7 +47,9 @@
48
47
  "lint": "tsc --noEmit",
49
48
  "prepack": "npm run build",
50
49
  "pack:dry": "npm pack --dry-run",
51
- "release:check": "npm run typecheck && npm test && npm run build && npm pack --dry-run"
50
+ "release:check": "npm run typecheck && npm test && npm run build && npm pack --dry-run",
51
+ "release:publish": "./scripts/publish.sh",
52
+ "release:smoke": "./scripts/smoke-test.sh"
52
53
  },
53
54
  "keywords": [
54
55
  "ent",