@lympid/embed 0.0.0-stage → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) Lympid. All rights reserved.
2
+
3
+ This package is distributed publicly on the npm registry for the sole
4
+ purpose of letting Lympid partners integrate the Lympid Embed widget
5
+ into their own applications. It is NOT open source: no license is
6
+ granted under this notice for any use outside that purpose (including,
7
+ without limitation, redistribution, modification for a competing
8
+ product, or use by anyone without an active Lympid partner agreement).
9
+
10
+ Your right to use this package is governed by your organization's
11
+ written agreement with Lympid, not by this file. If you received this
12
+ package without such an agreement, you have no license to use it;
13
+ contact partners@lympid.io.
14
+
15
+ The `license: "UNLICENSED"` field in this package's package.json is the
16
+ correct SPDX convention for "no OSS license is granted" — it does not
17
+ mean the package is unusable, only that usage rights come from a
18
+ separate contract rather than from this package itself.
package/README.md CHANGED
@@ -1,3 +1,210 @@
1
- # Temporary Holding Version
1
+ # @lympid/embed
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Client-side SDK for the Lympid Embed widget: a Lympid-hosted modal
4
+ iframe that you mount on your own site. Investors can connect or sign
5
+ in, complete KYC, register a Lympid account (optional), and invest,
6
+ deposit, or withdraw (also optional), without leaving your domain.
7
+
8
+ This package builds the modal, relays `postMessage` traffic to and from
9
+ the iframe, and resizes the panel to the widget's content. It does not
10
+ talk to Lympid's API directly except through the iframe it manages,
11
+ see [Partner API docs: Embed Widget](https://docs.lympid.io) for the
12
+ server-side endpoints (`/api/v1/embed-sessions*`) your backend calls
13
+ when you use the `connected` login model.
14
+
15
+ ## Install
16
+
17
+ ### Via npm (bundler-based apps)
18
+
19
+ ```bash
20
+ npm install @lympid/embed
21
+ # or
22
+ yarn add @lympid/embed
23
+ ```
24
+
25
+ ```ts
26
+ import { init } from '@lympid/embed';
27
+
28
+ const widget = init({ baseUrl: 'https://invest.yourpartnerdomain.com' });
29
+ ```
30
+
31
+ Ships both CommonJS (`require`) and ESM (`import`) builds plus
32
+ TypeScript types; your bundler picks the right one automatically via the
33
+ package's `exports` map.
34
+
35
+ ### Via script tag (no build step)
36
+
37
+ ```html
38
+ <script
39
+ src="https://cdn.lympid.io/embed/v1.0.0/lympid-embed.min.js"
40
+ integrity="sha384-<see the release's .sri file>"
41
+ crossorigin="anonymous"
42
+ ></script>
43
+ <script>
44
+ const widget = LympidEmbed.init({ baseUrl: 'https://invest.yourpartnerdomain.com' });
45
+ </script>
46
+ ```
47
+
48
+ The script tag exposes a single global, `window.LympidEmbed`, with the
49
+ same `init` function as the npm export. Every release publishes to its
50
+ own immutable path, `cdn.lympid.io/embed/v<exact-version>/` (e.g.
51
+ `v1.0.0`, `v1.0.0-rc.1`), never a mutable `latest` alias, always pin
52
+ the exact version you tested against and set the `integrity` hash
53
+ published alongside that release, so a swapped script cannot run in
54
+ your investors' browsers.
55
+
56
+ ## Usage
57
+
58
+ ```ts
59
+ import { init } from '@lympid/embed';
60
+
61
+ const widget = init({
62
+ baseUrl: 'https://invest.yourpartnerdomain.com', // your whitelabel tenant origin
63
+ });
64
+
65
+ widget.open({
66
+ flow: 'connect', // 'connect' | 'sign-in' | 'kyc' | 'register' | 'invest' | 'deposit' | 'withdraw'
67
+
68
+ // Required if you ever call `open({ flow: 'register' })` (harmless to
69
+ // always pass otherwise). Lets the widget identify your
70
+ // configuration before any session exists; re-validated server-side
71
+ // on every registration call, so it's a hint, not a trust boundary.
72
+ // Pass this to `init()`, not `open()`, see `InitOptions.partnerId`.
73
+
74
+ // Required for the `connected` login model only. Called on first open
75
+ // and again silently before the current token expires, so the
76
+ // session never visibly lapses. Must call YOUR OWN backend, which in
77
+ // turn calls POST /api/v1/embed-sessions with your x-api-key.
78
+ getSessionCode: async () => {
79
+ const res = await fetch('/api/lympid/session-code', { method: 'POST' });
80
+ const { handoffCode } = await res.json();
81
+ return handoffCode;
82
+ },
83
+
84
+ onEvent: (event) => {
85
+ if (event.type === 'kyc-approved') refreshDashboard();
86
+ if (event.type === 'registered') refreshDashboard();
87
+ },
88
+ onClose: () => {
89
+ refreshDashboard();
90
+ },
91
+ });
92
+ ```
93
+
94
+ ### In-widget registration (`register` flow)
95
+
96
+ Opt-in per partner (your Lympid contact enables it on your partner
97
+ configuration's `embedEnabledFlows`). Lets an investor create a Lympid
98
+ account without ever leaving your site, instead of requiring one to
99
+ already exist (which is all `connect`/`sign-in` can do). Pass
100
+ `partnerId` to `init()` before opening this flow, it's how the widget
101
+ knows which partner configuration to register against before any
102
+ session exists:
103
+
104
+ ```ts
105
+ const widget = init({
106
+ baseUrl: 'https://invest.yourpartnerdomain.com',
107
+ partnerId: 'YOUR_LYMPID_PARTNER_ID',
108
+ });
109
+
110
+ widget.open({ flow: 'register' });
111
+ ```
112
+
113
+ The exact UX depends on your login model: for `connected`, it's a
114
+ one-time confirm screen when your own backend's handoff resolves to a
115
+ not-yet-existing user; for `lympid-code`, it's a full self-serve
116
+ sign-up form. Both report a `registered` event on success. See the
117
+ `embed-widget` page's "Registration" section for the full request/response
118
+ shapes if you also drive registration from your own backend.
119
+
120
+ ### Financial flows (`invest`, `deposit`, `withdraw`)
121
+
122
+ Each opt-in per partner (your Lympid contact enables the ones you need
123
+ on your partner configuration's `embedEnabledFlows`), independently of
124
+ each other and of `register`. The investor must already have an
125
+ active, authenticated embed session (`connect`/`sign-in` or `register`
126
+ completed first), these flows do not themselves create or extend a
127
+ session.
128
+
129
+ ```ts
130
+ widget.open({ flow: 'deposit' });
131
+ widget.open({ flow: 'withdraw' });
132
+ widget.open({
133
+ flow: 'invest',
134
+ // Required for `invest` only, which RWA the investor is funding.
135
+ rwaId: 'YOUR_RWA_ID',
136
+ });
137
+ ```
138
+
139
+ - **`deposit`**, amount entry, then SEPA bank-transfer instructions
140
+ (IBAN, BIC, reference) to display. Fires `deposit-pending` once the
141
+ instructions are shown, then `deposit-settled` once the transfer is
142
+ detected server-side (the widget polls for this; typically a few
143
+ minutes for a real SEPA transfer).
144
+ - **`withdraw`**, amount, destination IBAN (from the investor's
145
+ already-whitelisted bank accounts), a step-up one-time-code
146
+ confirmation, then submission. Fires `withdrawal-submitted` on
147
+ successful submission and `withdrawal-settled` once the transfer
148
+ completes (SEPA INSTANT, typically ~15-20s).
149
+ - **`invest`**, runs the mandatory MiFID II appropriateness assessment
150
+ first if the investor has no valid one on file, surfaces any
151
+ WARN/BLOCK consent gate the assessment result requires, then a
152
+ step-up one-time-code confirmation, then the investment itself.
153
+ Fires `invested` on success. professional-only RWAs reject retail
154
+ investors with a dedicated in-widget message rather than opening the
155
+ assessment at all.
156
+
157
+ All three financial flows report their own `-error`-suffixed event on
158
+ failure (`invest-error`, `deposit-error`, `withdrawal-error`) with a
159
+ `code` field for programmatic handling, alongside the generic
160
+ `session-code-error` these flows can also hit if the embed session
161
+ expires mid-flow.
162
+
163
+ **Known gap:** `invest` does not yet deliver a PRIIPs Key Information
164
+ Document (KID) before the investment is confirmed, a regulatory
165
+ requirement for most packaged retail investment products that is
166
+ tracked separately and does not block this flow's availability. Talk
167
+ to your Lympid contact before you turn `invest` on if your products
168
+ are in scope for PRIIPs.
169
+
170
+ | Export | Purpose |
171
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
172
+ | `init(options)` | Creates one widget instance. `baseUrl` must be the exact origin (scheme + host + optional port, no path) of your whitelabel tenant, and must match `embedAllowedOrigins` on your partner configuration. `partnerId` is optional but required if you ever call `open({ flow: 'register' })` |
173
+ | `instance.open(options)` | Opens the modal for one flow (`connect`, `sign-in`, `kyc`, `register`, `invest`, `deposit`, or `withdraw`). Replaces any already-open modal from this instance |
174
+ | `instance.close()` | Closes the modal, fires `onClose` |
175
+ | `instance.destroy()` | Closes and removes every DOM node/listener this instance created. Call on unmount; the instance cannot be reused after |
176
+
177
+ Full integration guide, login models, lifecycle events, and partner
178
+ configuration: see the `embed-quickstart` and `embed-widget` pages at
179
+ [docs.lympid.io](https://docs.lympid.io).
180
+
181
+ ## Building from source
182
+
183
+ ```bash
184
+ yarn workspace @lympid/embed build
185
+ ```
186
+
187
+ Runs, in order:
188
+
189
+ 1. `build:cjs`, `tsc` to `dist/cjs` (CommonJS + `.d.ts`)
190
+ 2. `build:esm`, `tsc` to `dist/esm` (ES modules)
191
+ 3. `build:browser`, `esbuild` bundles `dist/cjs/index.js` into a single
192
+ browser IIFE at `dist/browser/lympid-embed.min.js` (+ unminified copy
193
+ and sourcemaps)
194
+
195
+ `yarn verify:bundle` scans everything under `dist/` for internal
196
+ hostnames or credential-shaped strings before a release is allowed to
197
+ publish (wired into `prepublishOnly`).
198
+
199
+ ## Local demo
200
+
201
+ `examples/harness/` is a local page for trying the cross-origin
202
+ `postMessage` and CSP `frame-ancestors` path. Your integration uses
203
+ your own site, not this folder.
204
+
205
+ ## Releases
206
+
207
+ Lympid publishes each version to npm and to the CDN, at its own
208
+ immutable path, `https://cdn.lympid.io/embed/v<exact-version>/`. Pin
209
+ the exact version you tested against in production; there is no
210
+ mutable `latest` alias.
@@ -0,0 +1,319 @@
1
+ "use strict";
2
+ var LympidEmbed = (() => {
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __commonJS = (cb, mod) => function __require() {
5
+ try {
6
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
7
+ } catch (e) {
8
+ throw mod = 0, e;
9
+ }
10
+ };
11
+
12
+ // dist/cjs/protocol.js
13
+ var require_protocol = __commonJS({
14
+ "dist/cjs/protocol.js"(exports) {
15
+ "use strict";
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.WIDGET_TO_HOST_SOURCE = exports.HOST_TO_WIDGET_SOURCE = void 0;
18
+ exports.isWidgetToHostMessage = isWidgetToHostMessage;
19
+ exports.HOST_TO_WIDGET_SOURCE = "lympid-embed-host";
20
+ exports.WIDGET_TO_HOST_SOURCE = "lympid-embed-widget";
21
+ function isWidgetToHostMessage(data) {
22
+ return typeof data === "object" && data !== null && data.source === exports.WIDGET_TO_HOST_SOURCE && typeof data.type === "string";
23
+ }
24
+ }
25
+ });
26
+
27
+ // dist/cjs/bridge.js
28
+ var require_bridge = __commonJS({
29
+ "dist/cjs/bridge.js"(exports) {
30
+ "use strict";
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.createBridge = createBridge;
33
+ var protocol_1 = require_protocol();
34
+ function createBridge(iframe, targetOrigin, onMessage) {
35
+ const listener = (domEvent) => {
36
+ if (domEvent.origin !== targetOrigin) {
37
+ return;
38
+ }
39
+ if (domEvent.source !== iframe.contentWindow) {
40
+ return;
41
+ }
42
+ if (!(0, protocol_1.isWidgetToHostMessage)(domEvent.data)) {
43
+ return;
44
+ }
45
+ onMessage(domEvent.data);
46
+ };
47
+ window.addEventListener("message", listener);
48
+ return {
49
+ send: (message) => {
50
+ if (!iframe.contentWindow) {
51
+ return;
52
+ }
53
+ iframe.contentWindow.postMessage({ ...message, source: protocol_1.HOST_TO_WIDGET_SOURCE }, targetOrigin);
54
+ },
55
+ teardown: () => {
56
+ window.removeEventListener("message", listener);
57
+ }
58
+ };
59
+ }
60
+ }
61
+ });
62
+
63
+ // dist/cjs/modal.js
64
+ var require_modal = __commonJS({
65
+ "dist/cjs/modal.js"(exports) {
66
+ "use strict";
67
+ Object.defineProperty(exports, "__esModule", { value: true });
68
+ exports.createModal = createModal;
69
+ var Z_INDEX = 2147483e3;
70
+ var DEFAULT_HEIGHT_PX = 600;
71
+ var PANEL_BACKGROUND_LIGHT = "#ffffff";
72
+ var PANEL_BACKGROUND_DARK = "#161B22";
73
+ function prefersDarkColorScheme() {
74
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
75
+ }
76
+ function createCloseIcon() {
77
+ const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
78
+ icon.setAttribute("viewBox", "0 0 24 24");
79
+ icon.setAttribute("width", "20");
80
+ icon.setAttribute("height", "20");
81
+ icon.setAttribute("fill", "none");
82
+ icon.setAttribute("aria-hidden", "true");
83
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
84
+ path.setAttribute("d", "M17 7 7 17M7 7l10 10");
85
+ path.setAttribute("stroke", "currentColor");
86
+ path.setAttribute("stroke-width", "1.3");
87
+ path.setAttribute("stroke-linecap", "round");
88
+ path.setAttribute("stroke-linejoin", "round");
89
+ icon.appendChild(path);
90
+ return icon;
91
+ }
92
+ function createModal(container, src, onBackdropClick) {
93
+ const overlay = document.createElement("div");
94
+ overlay.setAttribute("data-lympid-embed", "overlay");
95
+ overlay.style.position = "fixed";
96
+ overlay.style.inset = "0";
97
+ overlay.style.zIndex = String(Z_INDEX);
98
+ overlay.style.display = "flex";
99
+ overlay.style.alignItems = "center";
100
+ overlay.style.justifyContent = "center";
101
+ overlay.style.backgroundColor = "rgba(15, 23, 42, 0.6)";
102
+ const panel = document.createElement("div");
103
+ panel.setAttribute("data-lympid-embed", "panel");
104
+ panel.style.position = "relative";
105
+ panel.style.width = "min(420px, calc(100vw - 32px))";
106
+ panel.style.height = `min(${DEFAULT_HEIGHT_PX}px, calc(100vh - 32px))`;
107
+ panel.style.maxHeight = "calc(100vh - 32px)";
108
+ panel.style.borderRadius = "16px";
109
+ panel.style.overflow = "hidden";
110
+ panel.style.boxShadow = "0 20px 60px rgba(0, 0, 0, 0.35)";
111
+ panel.style.backgroundColor = prefersDarkColorScheme() ? PANEL_BACKGROUND_DARK : PANEL_BACKGROUND_LIGHT;
112
+ panel.style.transition = "height 0.2s ease";
113
+ let colorSchemeMedia = null;
114
+ let handleColorSchemeChange = null;
115
+ if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
116
+ colorSchemeMedia = window.matchMedia("(prefers-color-scheme: dark)");
117
+ handleColorSchemeChange = (event) => {
118
+ panel.style.backgroundColor = event.matches ? PANEL_BACKGROUND_DARK : PANEL_BACKGROUND_LIGHT;
119
+ };
120
+ colorSchemeMedia.addEventListener("change", handleColorSchemeChange);
121
+ }
122
+ const closeButton = document.createElement("button");
123
+ closeButton.setAttribute("data-lympid-embed", "close");
124
+ closeButton.setAttribute("aria-label", "Close");
125
+ closeButton.type = "button";
126
+ closeButton.style.position = "absolute";
127
+ closeButton.style.top = "8px";
128
+ closeButton.style.right = "8px";
129
+ closeButton.style.zIndex = String(Z_INDEX + 1);
130
+ closeButton.style.display = "flex";
131
+ closeButton.style.alignItems = "center";
132
+ closeButton.style.justifyContent = "center";
133
+ closeButton.style.width = "32px";
134
+ closeButton.style.height = "32px";
135
+ closeButton.style.margin = "0";
136
+ closeButton.style.padding = "0";
137
+ closeButton.style.border = "none";
138
+ closeButton.style.borderRadius = "50%";
139
+ closeButton.style.backgroundColor = "rgba(15, 23, 42, 0.06)";
140
+ closeButton.style.color = "#585858";
141
+ closeButton.style.cursor = "pointer";
142
+ closeButton.style.appearance = "none";
143
+ closeButton.style.setProperty("-webkit-appearance", "none");
144
+ closeButton.appendChild(createCloseIcon());
145
+ const iframe = document.createElement("iframe");
146
+ iframe.setAttribute("data-lympid-embed", "iframe");
147
+ iframe.src = src;
148
+ iframe.style.width = "100%";
149
+ iframe.style.height = "100%";
150
+ iframe.style.border = "none";
151
+ iframe.title = "Lympid";
152
+ iframe.setAttribute("allow", "camera; microphone; clipboard-write");
153
+ iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups");
154
+ closeButton.addEventListener("click", onBackdropClick);
155
+ overlay.addEventListener("click", (domEvent) => {
156
+ if (domEvent.target === overlay) {
157
+ onBackdropClick();
158
+ }
159
+ });
160
+ panel.appendChild(closeButton);
161
+ panel.appendChild(iframe);
162
+ overlay.appendChild(panel);
163
+ container.appendChild(overlay);
164
+ return {
165
+ iframe,
166
+ setHeight: (height) => {
167
+ const clamped = Math.max(240, Math.min(height, (typeof window !== "undefined" ? window.innerHeight : height) - 32));
168
+ panel.style.height = `${clamped}px`;
169
+ },
170
+ remove: () => {
171
+ if (colorSchemeMedia && handleColorSchemeChange) {
172
+ colorSchemeMedia.removeEventListener("change", handleColorSchemeChange);
173
+ }
174
+ overlay.remove();
175
+ }
176
+ };
177
+ }
178
+ }
179
+ });
180
+
181
+ // dist/cjs/embed.js
182
+ var require_embed = __commonJS({
183
+ "dist/cjs/embed.js"(exports) {
184
+ "use strict";
185
+ Object.defineProperty(exports, "__esModule", { value: true });
186
+ exports.init = init;
187
+ var bridge_1 = require_bridge();
188
+ var modal_1 = require_modal();
189
+ var protocol_1 = require_protocol();
190
+ function normalizeOrigin(baseUrl) {
191
+ const url = new URL(baseUrl);
192
+ return url.origin;
193
+ }
194
+ function buildIframeSrc(origin, flow, partnerId, rwaId) {
195
+ const params = new URLSearchParams({ flow });
196
+ if (partnerId) {
197
+ params.set("partnerId", partnerId);
198
+ }
199
+ if (rwaId) {
200
+ params.set("rwaId", rwaId);
201
+ }
202
+ return `${origin}/embed?${params.toString()}`;
203
+ }
204
+ function init(options) {
205
+ const origin = normalizeOrigin(options.baseUrl);
206
+ const container = options.container ?? document.body;
207
+ let current = null;
208
+ let destroyed = false;
209
+ function teardownCurrent(fireOnClose) {
210
+ if (!current) {
211
+ return;
212
+ }
213
+ const { modal, bridge, onClose } = current;
214
+ bridge.teardown();
215
+ modal.remove();
216
+ current = null;
217
+ if (fireOnClose && onClose) {
218
+ onClose();
219
+ }
220
+ }
221
+ function open(openOptions) {
222
+ if (destroyed) {
223
+ throw new Error("This LympidEmbedInstance has been destroyed and cannot be reused \u2014 call init() again.");
224
+ }
225
+ teardownCurrent(true);
226
+ const src = buildIframeSrc(origin, openOptions.flow, options.partnerId, openOptions.rwaId);
227
+ const modal = (0, modal_1.createModal)(container, src, () => teardownCurrent(true));
228
+ const bridge = (0, bridge_1.createBridge)(modal.iframe, origin, (message) => {
229
+ switch (message.type) {
230
+ case "ready": {
231
+ bridge.send({
232
+ source: protocol_1.HOST_TO_WIDGET_SOURCE,
233
+ type: "init",
234
+ flow: openOptions.flow,
235
+ hasSessionCodeProvider: !!openOptions.getSessionCode,
236
+ partnerId: options.partnerId,
237
+ rwaId: openOptions.rwaId
238
+ });
239
+ break;
240
+ }
241
+ case "request-session-code": {
242
+ void handleSessionCodeRequest(bridge, openOptions, message.requestId);
243
+ break;
244
+ }
245
+ case "resize": {
246
+ modal.setHeight(message.height);
247
+ break;
248
+ }
249
+ case "event": {
250
+ openOptions.onEvent?.(message.event);
251
+ break;
252
+ }
253
+ case "close": {
254
+ teardownCurrent(true);
255
+ break;
256
+ }
257
+ default:
258
+ break;
259
+ }
260
+ });
261
+ current = { modal, bridge, onClose: openOptions.onClose };
262
+ }
263
+ function close() {
264
+ teardownCurrent(true);
265
+ }
266
+ function destroy() {
267
+ teardownCurrent(true);
268
+ destroyed = true;
269
+ }
270
+ return { open, close, destroy };
271
+ }
272
+ async function handleSessionCodeRequest(bridge, openOptions, requestId) {
273
+ const provider = openOptions.getSessionCode;
274
+ if (!provider) {
275
+ const error = "getSessionCode was not provided to open()";
276
+ openOptions.onEvent?.({ type: "session-code-error", requestId, error });
277
+ bridge.send({
278
+ source: protocol_1.HOST_TO_WIDGET_SOURCE,
279
+ type: "session-code-error",
280
+ requestId,
281
+ error
282
+ });
283
+ return;
284
+ }
285
+ try {
286
+ const code = await provider();
287
+ bridge.send({
288
+ source: protocol_1.HOST_TO_WIDGET_SOURCE,
289
+ type: "session-code",
290
+ requestId,
291
+ code
292
+ });
293
+ } catch (error) {
294
+ const errorMessage = error instanceof Error ? error.message : String(error);
295
+ bridge.send({
296
+ source: protocol_1.HOST_TO_WIDGET_SOURCE,
297
+ type: "session-code-error",
298
+ requestId,
299
+ error: errorMessage
300
+ });
301
+ }
302
+ }
303
+ }
304
+ });
305
+
306
+ // dist/cjs/index.js
307
+ var require_index = __commonJS({
308
+ "dist/cjs/index.js"(exports) {
309
+ Object.defineProperty(exports, "__esModule", { value: true });
310
+ exports.init = void 0;
311
+ var embed_1 = require_embed();
312
+ Object.defineProperty(exports, "init", { enumerable: true, get: function() {
313
+ return embed_1.init;
314
+ } });
315
+ }
316
+ });
317
+ return require_index();
318
+ })();
319
+ //# sourceMappingURL=lympid-embed.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../cjs/protocol.js", "../cjs/bridge.js", "../cjs/modal.js", "../cjs/embed.js", "../cjs/index.js"],
4
+ "sourcesContent": ["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.WIDGET_TO_HOST_SOURCE = exports.HOST_TO_WIDGET_SOURCE = void 0;\nexports.isWidgetToHostMessage = isWidgetToHostMessage;\n/**\n * `postMessage` wire protocol between the host page (this SDK) and the\n * widget iframe (the whitelabel app running in embed mode \u2014 see\n * `whitelabel-embed-mode` in the Phase 1 plan).\n *\n * Both directions are only ever accepted/sent against the exact origin\n * configured via `InitOptions.baseUrl` \u2014 see `createBridge()` in\n * `./bridge.ts`. This file only defines the message *shapes*; it has\n * zero DOM/`window` dependencies so it can be unit-tested directly.\n */\n/** Discriminator so a `postMessage` listener can cheaply ignore every\n * unrelated message landing on the same `window` (browser extensions,\n * other SDKs, etc.) before even checking the origin. */\nexports.HOST_TO_WIDGET_SOURCE = 'lympid-embed-host';\nexports.WIDGET_TO_HOST_SOURCE = 'lympid-embed-widget';\n/** Narrows an arbitrary `MessageEvent.data` payload to a\n * `WidgetToHostMessage`, without throwing on malformed input \u2014 a\n * `postMessage` listener must never crash the host page just because\n * some unrelated script on the same window posted something shaped\n * differently. */\nfunction isWidgetToHostMessage(data) {\n return (typeof data === 'object' &&\n data !== null &&\n data.source === exports.WIDGET_TO_HOST_SOURCE &&\n typeof data.type === 'string');\n}\n", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.createBridge = createBridge;\nconst protocol_1 = require(\"./protocol\");\n/**\n * @param iframe The widget iframe this bridge talks to.\n * @param targetOrigin The exact origin the iframe was loaded from\n * (derived from `InitOptions.baseUrl`, never inferred from the\n * iframe's `src` string, which a compromised/misconfigured iframe\n * could have redirected away from).\n * @param onMessage Called for every well-formed `WidgetToHostMessage`\n * received from exactly `targetOrigin`. Messages from any other\n * origin, or that don't match the `WidgetToHostMessage` shape, are\n * silently dropped.\n */\nfunction createBridge(iframe, targetOrigin, onMessage) {\n const listener = (domEvent) => {\n // Origin check FIRST, before touching `data` at all \u2014 this is the\n // entire security boundary of the bridge. A mismatched origin is\n // dropped unconditionally, regardless of how well-formed the\n // payload looks.\n if (domEvent.origin !== targetOrigin) {\n return;\n }\n if (domEvent.source !== iframe.contentWindow) {\n return;\n }\n if (!(0, protocol_1.isWidgetToHostMessage)(domEvent.data)) {\n return;\n }\n onMessage(domEvent.data);\n };\n window.addEventListener('message', listener);\n return {\n send: (message) => {\n if (!iframe.contentWindow) {\n return;\n }\n iframe.contentWindow.postMessage({ ...message, source: protocol_1.HOST_TO_WIDGET_SOURCE }, targetOrigin);\n },\n teardown: () => {\n window.removeEventListener('message', listener);\n },\n };\n}\n", "\"use strict\";\n/**\n * Modal overlay + iframe DOM construction for the widget.\n *\n * Deliberately framework-agnostic (no React) and dependency-free \u2014 a\n * handful of inline-styled `div`/`iframe` nodes is simpler, smaller, and\n * safer than reaching for a UI library inside a script a partner embeds\n * on their own page (no risk of CSS/class-name collisions with the\n * partner's own stylesheet).\n */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.createModal = createModal;\nconst Z_INDEX = 2147483000; // Comfortably above virtually any host-page UI.\nconst DEFAULT_HEIGHT_PX = 600;\n/** Light/dark panel background, matching the whitelabel app's own\n * `LEDGER_PAPER` surface token (`packages/shared-types/src/\n * design-tokens.ts`) so the modal's own chrome doesn't flash a white\n * box around a dark-themed widget. The host page has no visibility\n * into the iframe's actual in-app theme setting (cross-origin, and the\n * whitelabel app can toggle independently of OS preference), so this\n * follows the OS-level `prefers-color-scheme` media query as the best\n * available signal without a cross-origin theme-sync protocol. */\nconst PANEL_BACKGROUND_LIGHT = '#ffffff';\nconst PANEL_BACKGROUND_DARK = '#161B22';\nfunction prefersDarkColorScheme() {\n return (typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-color-scheme: dark)').matches);\n}\n/** Close glyph centered in the 32px button. Path matches\n * `apps/whitelabel/src/assets/images/svg/24/close.svg`. */\nfunction createCloseIcon() {\n const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n icon.setAttribute('viewBox', '0 0 24 24');\n icon.setAttribute('width', '20');\n icon.setAttribute('height', '20');\n icon.setAttribute('fill', 'none');\n icon.setAttribute('aria-hidden', 'true');\n const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n path.setAttribute('d', 'M17 7 7 17M7 7l10 10');\n path.setAttribute('stroke', 'currentColor');\n path.setAttribute('stroke-width', '1.3');\n path.setAttribute('stroke-linecap', 'round');\n path.setAttribute('stroke-linejoin', 'round');\n icon.appendChild(path);\n return icon;\n}\n/**\n * Builds and appends the backdrop + iframe modal to `container`.\n *\n * @param container Parent node to append the overlay to.\n * @param src The widget iframe's `src` URL (the partner's whitelabel\n * origin + `/embed` route + flow query param).\n * @param onBackdropClick Called when the end user clicks the backdrop\n * or the close button \u2014 the caller decides whether that closes the\n * modal (it always should; kept as a callback so `embed.ts` stays\n * the single owner of close/`onClose` semantics).\n */\nfunction createModal(container, src, onBackdropClick) {\n const overlay = document.createElement('div');\n overlay.setAttribute('data-lympid-embed', 'overlay');\n overlay.style.position = 'fixed';\n overlay.style.inset = '0';\n overlay.style.zIndex = String(Z_INDEX);\n overlay.style.display = 'flex';\n overlay.style.alignItems = 'center';\n overlay.style.justifyContent = 'center';\n overlay.style.backgroundColor = 'rgba(15, 23, 42, 0.6)';\n const panel = document.createElement('div');\n panel.setAttribute('data-lympid-embed', 'panel');\n panel.style.position = 'relative';\n panel.style.width = 'min(420px, calc(100vw - 32px))';\n panel.style.height = `min(${DEFAULT_HEIGHT_PX}px, calc(100vh - 32px))`;\n panel.style.maxHeight = 'calc(100vh - 32px)';\n panel.style.borderRadius = '16px';\n panel.style.overflow = 'hidden';\n panel.style.boxShadow = '0 20px 60px rgba(0, 0, 0, 0.35)';\n panel.style.backgroundColor = prefersDarkColorScheme()\n ? PANEL_BACKGROUND_DARK\n : PANEL_BACKGROUND_LIGHT;\n panel.style.transition = 'height 0.2s ease';\n // Keep the panel's background in step if the OS-level preference\n // changes while the modal is open (e.g. a scheduled light/dark\n // switch, or the user flips it in their OS settings mid-session).\n let colorSchemeMedia = null;\n let handleColorSchemeChange = null;\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n colorSchemeMedia = window.matchMedia('(prefers-color-scheme: dark)');\n handleColorSchemeChange = (event) => {\n panel.style.backgroundColor = event.matches\n ? PANEL_BACKGROUND_DARK\n : PANEL_BACKGROUND_LIGHT;\n };\n colorSchemeMedia.addEventListener('change', handleColorSchemeChange);\n }\n const closeButton = document.createElement('button');\n closeButton.setAttribute('data-lympid-embed', 'close');\n closeButton.setAttribute('aria-label', 'Close');\n closeButton.type = 'button';\n closeButton.style.position = 'absolute';\n closeButton.style.top = '8px';\n closeButton.style.right = '8px';\n closeButton.style.zIndex = String(Z_INDEX + 1);\n closeButton.style.display = 'flex';\n closeButton.style.alignItems = 'center';\n closeButton.style.justifyContent = 'center';\n closeButton.style.width = '32px';\n closeButton.style.height = '32px';\n closeButton.style.margin = '0';\n closeButton.style.padding = '0';\n closeButton.style.border = 'none';\n closeButton.style.borderRadius = '50%';\n closeButton.style.backgroundColor = 'rgba(15, 23, 42, 0.06)';\n closeButton.style.color = '#585858';\n closeButton.style.cursor = 'pointer';\n closeButton.style.appearance = 'none';\n closeButton.style.setProperty('-webkit-appearance', 'none');\n // Same stroke as the whitelabel `Icon` named `close`\n // (`src/assets/images/svg/24/close.svg`), which every other modal\n // uses. A text \"\u00D7\" sits on the font baseline, so it looks high, and\n // a host `button { padding }` rule shifts it further. Flex + a reset\n // padding keeps the glyph centered on the partner page.\n closeButton.appendChild(createCloseIcon());\n const iframe = document.createElement('iframe');\n iframe.setAttribute('data-lympid-embed', 'iframe');\n iframe.src = src;\n iframe.style.width = '100%';\n iframe.style.height = '100%';\n iframe.style.border = 'none';\n iframe.title = 'Lympid';\n // Phase 1 flows (connect/sign-in, KYC) need neither top-level\n // navigation nor same-origin escalation beyond what the whitelabel\n // app itself needs to run (forms, scripts, camera for KYC).\n iframe.setAttribute('allow', 'camera; microphone; clipboard-write');\n iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');\n closeButton.addEventListener('click', onBackdropClick);\n overlay.addEventListener('click', (domEvent) => {\n if (domEvent.target === overlay) {\n onBackdropClick();\n }\n });\n panel.appendChild(closeButton);\n panel.appendChild(iframe);\n overlay.appendChild(panel);\n container.appendChild(overlay);\n return {\n iframe,\n setHeight: (height) => {\n const clamped = Math.max(240, Math.min(height, (typeof window !== 'undefined' ? window.innerHeight : height) - 32));\n panel.style.height = `${clamped}px`;\n },\n remove: () => {\n if (colorSchemeMedia && handleColorSchemeChange) {\n colorSchemeMedia.removeEventListener('change', handleColorSchemeChange);\n }\n overlay.remove();\n },\n };\n}\n", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.init = init;\nconst bridge_1 = require(\"./bridge\");\nconst modal_1 = require(\"./modal\");\nconst protocol_1 = require(\"./protocol\");\n/** Normalises a configured `baseUrl` into a bare origin string\n * (`\"https://host:port\"`, no path/trailing slash) suitable both for\n * building the iframe `src` and for `postMessage` origin checks. Throws\n * early \u2014 at `init()` time, not at `open()` time \u2014 on a malformed URL,\n * since a bad `baseUrl` is a partner-integration mistake that should\n * fail loudly immediately rather than silently drop every widget\n * message later. */\nfunction normalizeOrigin(baseUrl) {\n const url = new URL(baseUrl);\n return url.origin;\n}\nfunction buildIframeSrc(origin, flow, partnerId, rwaId) {\n const params = new URLSearchParams({ flow });\n if (partnerId) {\n params.set('partnerId', partnerId);\n }\n if (rwaId) {\n params.set('rwaId', rwaId);\n }\n return `${origin}/embed?${params.toString()}`;\n}\n/**\n * Creates a `LympidEmbedInstance` bound to one partner's whitelabel\n * origin. See `InitOptions`/`OpenOptions` in `./types.ts` for the full\n * contract, and `apps/whitelabel/src/app/embed/` (whitelabel-embed-mode)\n * for the counterpart that runs inside the iframe.\n */\nfunction init(options) {\n const origin = normalizeOrigin(options.baseUrl);\n const container = options.container ?? document.body;\n let current = null;\n let destroyed = false;\n /** Tears down the currently-open modal/bridge, if any, optionally\n * firing the `onClose` callback that was passed to the `open()` call\n * being torn down. */\n function teardownCurrent(fireOnClose) {\n if (!current) {\n return;\n }\n const { modal, bridge, onClose } = current;\n bridge.teardown();\n modal.remove();\n current = null;\n if (fireOnClose && onClose) {\n onClose();\n }\n }\n function open(openOptions) {\n if (destroyed) {\n throw new Error('This LympidEmbedInstance has been destroyed and cannot be reused \u2014 call init() again.');\n }\n // Replace any already-open modal from this instance first \u2014 only\n // one widget flow can sensibly be open at a time per instance.\n teardownCurrent(true);\n const src = buildIframeSrc(origin, openOptions.flow, options.partnerId, openOptions.rwaId);\n const modal = (0, modal_1.createModal)(container, src, () => teardownCurrent(true));\n const bridge = (0, bridge_1.createBridge)(modal.iframe, origin, (message) => {\n switch (message.type) {\n case 'ready': {\n bridge.send({\n source: protocol_1.HOST_TO_WIDGET_SOURCE,\n type: 'init',\n flow: openOptions.flow,\n hasSessionCodeProvider: !!openOptions.getSessionCode,\n partnerId: options.partnerId,\n rwaId: openOptions.rwaId,\n });\n break;\n }\n case 'request-session-code': {\n void handleSessionCodeRequest(bridge, openOptions, message.requestId);\n break;\n }\n case 'resize': {\n modal.setHeight(message.height);\n break;\n }\n case 'event': {\n openOptions.onEvent?.(message.event);\n break;\n }\n case 'close': {\n teardownCurrent(true);\n break;\n }\n default:\n break;\n }\n });\n current = { modal, bridge, onClose: openOptions.onClose };\n }\n function close() {\n teardownCurrent(true);\n }\n function destroy() {\n teardownCurrent(true);\n destroyed = true;\n }\n return { open, close, destroy };\n}\n/**\n * Resolves `openOptions.getSessionCode()` (if configured) and relays\n * the result back to the widget as `session-code`/`session-code-error`.\n * Extracted so `init()`'s `open()` switch stays a flat dispatch table.\n *\n * @param requestId Correlation id the widget attached to its\n * `request-session-code` message \u2014 echoed back unchanged so the\n * widget can match the response to the right in-flight request even\n * if several overlap.\n */\nasync function handleSessionCodeRequest(bridge, openOptions, requestId) {\n const provider = openOptions.getSessionCode;\n if (!provider) {\n // The widget asked for a session code on a partner that never\n // configured `getSessionCode` \u2014 this is an integration mistake\n // (likely a `connected`-model partner whose host page forgot the\n // callback). Surface it both as an event (for the host page's own\n // logging/monitoring) and back to the widget (so it can show the\n // end user a retry/contact-support state instead of hanging).\n const error = 'getSessionCode was not provided to open()';\n openOptions.onEvent?.({ type: 'session-code-error', requestId, error });\n bridge.send({\n source: protocol_1.HOST_TO_WIDGET_SOURCE,\n type: 'session-code-error',\n requestId,\n error,\n });\n return;\n }\n try {\n const code = await provider();\n bridge.send({\n source: protocol_1.HOST_TO_WIDGET_SOURCE,\n type: 'session-code',\n requestId,\n code,\n });\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n bridge.send({\n source: protocol_1.HOST_TO_WIDGET_SOURCE,\n type: 'session-code-error',\n requestId,\n error: errorMessage,\n });\n }\n}\n", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.init = void 0;\n/**\n * `@lympid/embed` \u2014 public entry point.\n *\n * ```ts\n * import { init } from '@lympid/embed';\n *\n * const widget = init({ baseUrl: 'https://invest.yourpartnerdomain.com' });\n *\n * widget.open({\n * flow: 'connect',\n * // Only needed for partners using the \"connected\" login model \u2014\n * // call YOUR OWN backend here, which in turn calls\n * // POST /api/v1/embed-sessions with your x-api-key.\n * getSessionCode: async () => {\n * const res = await fetch('/api/lympid/session-code', { method: 'POST' });\n * const { handoffCode } = await res.json();\n * return handoffCode;\n * },\n * onEvent: (event) => console.log('lympid event', event),\n * onClose: () => console.log('lympid widget closed'),\n * });\n * ```\n */\nvar embed_1 = require(\"./embed\");\nObject.defineProperty(exports, \"init\", { enumerable: true, get: function () { return embed_1.init; } });\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";var LympidEmbed=(()=>{var f=(t,n)=>()=>{try{return n||t((n={exports:{}}).exports,n),n.exports}catch(i){throw n=0,i}};var w=f(c=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});c.WIDGET_TO_HOST_SOURCE=c.HOST_TO_WIDGET_SOURCE=void 0;c.isWidgetToHostMessage=A;c.HOST_TO_WIDGET_SOURCE="lympid-embed-host";c.WIDGET_TO_HOST_SOURCE="lympid-embed-widget";function A(t){return typeof t=="object"&&t!==null&&t.source===c.WIDGET_TO_HOST_SOURCE&&typeof t.type=="string"}});var v=f(g=>{"use strict";Object.defineProperty(g,"__esModule",{value:!0});g.createBridge=M;var C=w();function M(t,n,i){let r=e=>{e.origin===n&&e.source===t.contentWindow&&(0,C.isWidgetToHostMessage)(e.data)&&i(e.data)};return window.addEventListener("message",r),{send:e=>{t.contentWindow&&t.contentWindow.postMessage({...e,source:C.HOST_TO_WIDGET_SOURCE},n)},teardown:()=>{window.removeEventListener("message",r)}}}});var I=f(_=>{"use strict";Object.defineProperty(_,"__esModule",{value:!0});_.createModal=D;var S=2147483e3,H=600,T="#ffffff",O="#161B22";function k(){return typeof window<"u"&&typeof window.matchMedia=="function"&&window.matchMedia("(prefers-color-scheme: dark)").matches}function R(){let t=document.createElementNS("http://www.w3.org/2000/svg","svg");t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("width","20"),t.setAttribute("height","20"),t.setAttribute("fill","none"),t.setAttribute("aria-hidden","true");let n=document.createElementNS("http://www.w3.org/2000/svg","path");return n.setAttribute("d","M17 7 7 17M7 7l10 10"),n.setAttribute("stroke","currentColor"),n.setAttribute("stroke-width","1.3"),n.setAttribute("stroke-linecap","round"),n.setAttribute("stroke-linejoin","round"),t.appendChild(n),t}function D(t,n,i){let r=document.createElement("div");r.setAttribute("data-lympid-embed","overlay"),r.style.position="fixed",r.style.inset="0",r.style.zIndex=String(S),r.style.display="flex",r.style.alignItems="center",r.style.justifyContent="center",r.style.backgroundColor="rgba(15, 23, 42, 0.6)";let e=document.createElement("div");e.setAttribute("data-lympid-embed","panel"),e.style.position="relative",e.style.width="min(420px, calc(100vw - 32px))",e.style.height=`min(${H}px, calc(100vh - 32px))`,e.style.maxHeight="calc(100vh - 32px)",e.style.borderRadius="16px",e.style.overflow="hidden",e.style.boxShadow="0 20px 60px rgba(0, 0, 0, 0.35)",e.style.backgroundColor=k()?O:T,e.style.transition="height 0.2s ease";let d=null,l=null;typeof window<"u"&&typeof window.matchMedia=="function"&&(d=window.matchMedia("(prefers-color-scheme: dark)"),l=s=>{e.style.backgroundColor=s.matches?O:T},d.addEventListener("change",l));let o=document.createElement("button");o.setAttribute("data-lympid-embed","close"),o.setAttribute("aria-label","Close"),o.type="button",o.style.position="absolute",o.style.top="8px",o.style.right="8px",o.style.zIndex=String(S+1),o.style.display="flex",o.style.alignItems="center",o.style.justifyContent="center",o.style.width="32px",o.style.height="32px",o.style.margin="0",o.style.padding="0",o.style.border="none",o.style.borderRadius="50%",o.style.backgroundColor="rgba(15, 23, 42, 0.06)",o.style.color="#585858",o.style.cursor="pointer",o.style.appearance="none",o.style.setProperty("-webkit-appearance","none"),o.appendChild(R());let a=document.createElement("iframe");return a.setAttribute("data-lympid-embed","iframe"),a.src=n,a.style.width="100%",a.style.height="100%",a.style.border="none",a.title="Lympid",a.setAttribute("allow","camera; microphone; clipboard-write"),a.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups"),o.addEventListener("click",i),r.addEventListener("click",s=>{s.target===r&&i()}),e.appendChild(o),e.appendChild(a),r.appendChild(e),t.appendChild(r),{iframe:a,setHeight:s=>{let y=Math.max(240,Math.min(s,(typeof window<"u"?window.innerHeight:s)-32));e.style.height=`${y}px`},remove:()=>{d&&l&&d.removeEventListener("change",l),r.remove()}}}});var x=f(E=>{"use strict";Object.defineProperty(E,"__esModule",{value:!0});E.init=P;var U=v(),W=I(),b=w();function G(t){return new URL(t).origin}function L(t,n,i,r){let e=new URLSearchParams({flow:n});return i&&e.set("partnerId",i),r&&e.set("rwaId",r),`${t}/embed?${e.toString()}`}function P(t){let n=G(t.baseUrl),i=t.container??document.body,r=null,e=!1;function d(s){if(!r)return;let{modal:y,bridge:p,onClose:u}=r;p.teardown(),y.remove(),r=null,s&&u&&u()}function l(s){if(e)throw new Error("This LympidEmbedInstance has been destroyed and cannot be reused \u2014 call init() again.");d(!0);let y=L(n,s.flow,t.partnerId,s.rwaId),p=(0,W.createModal)(i,y,()=>d(!0)),u=(0,U.createBridge)(p.iframe,n,m=>{switch(m.type){case"ready":{u.send({source:b.HOST_TO_WIDGET_SOURCE,type:"init",flow:s.flow,hasSessionCodeProvider:!!s.getSessionCode,partnerId:t.partnerId,rwaId:s.rwaId});break}case"request-session-code":{j(u,s,m.requestId);break}case"resize":{p.setHeight(m.height);break}case"event":{s.onEvent?.(m.event);break}case"close":{d(!0);break}default:break}});r={modal:p,bridge:u,onClose:s.onClose}}function o(){d(!0)}function a(){d(!0),e=!0}return{open:l,close:o,destroy:a}}async function j(t,n,i){let r=n.getSessionCode;if(!r){let e="getSessionCode was not provided to open()";n.onEvent?.({type:"session-code-error",requestId:i,error:e}),t.send({source:b.HOST_TO_WIDGET_SOURCE,type:"session-code-error",requestId:i,error:e});return}try{let e=await r();t.send({source:b.HOST_TO_WIDGET_SOURCE,type:"session-code",requestId:i,code:e})}catch(e){let d=e instanceof Error?e.message:String(e);t.send({source:b.HOST_TO_WIDGET_SOURCE,type:"session-code-error",requestId:i,error:d})}}});var N=f(h=>{Object.defineProperty(h,"__esModule",{value:!0});h.init=void 0;var B=x();Object.defineProperty(h,"init",{enumerable:!0,get:function(){return B.init}})});return N();})();
2
+ //# sourceMappingURL=lympid-embed.min.js.map