@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 +18 -0
- package/README.md +209 -2
- package/dist/browser/lympid-embed.js +319 -0
- package/dist/browser/lympid-embed.js.map +7 -0
- package/dist/browser/lympid-embed.min.js +2 -0
- package/dist/browser/lympid-embed.min.js.map +7 -0
- package/dist/cjs/bridge.d.ts +31 -0
- package/dist/cjs/bridge.js +45 -0
- package/dist/cjs/embed.d.ts +8 -0
- package/dist/cjs/embed.js +154 -0
- package/dist/cjs/index.d.ts +25 -0
- package/dist/cjs/index.js +28 -0
- package/dist/cjs/modal.d.ts +30 -0
- package/dist/cjs/modal.js +159 -0
- package/dist/cjs/protocol.d.ts +71 -0
- package/dist/cjs/protocol.js +30 -0
- package/dist/cjs/types.d.ts +150 -0
- package/dist/cjs/types.js +19 -0
- package/dist/esm/bridge.js +42 -0
- package/dist/esm/embed.js +151 -0
- package/dist/esm/index.js +24 -0
- package/dist/esm/modal.js +156 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/protocol.js +26 -0
- package/dist/esm/types.js +18 -0
- package/package.json +59 -4
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
|
-
#
|
|
1
|
+
# @lympid/embed
|
|
2
2
|
|
|
3
|
-
|
|
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
|