@atom-circuit/embed-sdk 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +106 -0
- package/LICENSE +21 -0
- package/README.md +312 -0
- package/SECURITY.md +88 -0
- package/dist/atom-circuit.iife.js +2 -0
- package/dist/atom-circuit.iife.js.map +1 -0
- package/dist/index.cjs +694 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +391 -0
- package/dist/index.d.ts +391 -0
- package/dist/index.mjs +691 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.cjs +744 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +337 -0
- package/dist/react.d.ts +337 -0
- package/dist/react.mjs +742 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +125 -0
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var penpal = require('penpal');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/react.tsx
|
|
8
|
+
|
|
9
|
+
// src/protocol.ts
|
|
10
|
+
var PROTOCOL_VERSION = "1.0.0";
|
|
11
|
+
var WIDGET_ORIGIN = "https://atomcircuit.net";
|
|
12
|
+
var WIDGET_PATH = "/embed/swap";
|
|
13
|
+
var isObject = (value) => typeof value === "object" && value !== null;
|
|
14
|
+
var isString = (value) => typeof value === "string";
|
|
15
|
+
var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
16
|
+
function isHandshakeMessage(value) {
|
|
17
|
+
if (!isObject(value)) return false;
|
|
18
|
+
if (value["type"] !== "handshake") return false;
|
|
19
|
+
if (!isString(value["protocolVersion"])) return false;
|
|
20
|
+
if (!Array.isArray(value["capabilities"])) return false;
|
|
21
|
+
return value["capabilities"].every(isString);
|
|
22
|
+
}
|
|
23
|
+
function isResizeMessage(value) {
|
|
24
|
+
if (!isObject(value)) return false;
|
|
25
|
+
if (value["type"] !== "atomcircuit:resize") return false;
|
|
26
|
+
return isFiniteNumber(value["height"]) && value["height"] >= 0;
|
|
27
|
+
}
|
|
28
|
+
var WIDGET_EVENT_NAMES = /* @__PURE__ */ new Set([
|
|
29
|
+
"ready",
|
|
30
|
+
"swap:submitted",
|
|
31
|
+
"swap:success",
|
|
32
|
+
"swap:error"
|
|
33
|
+
]);
|
|
34
|
+
function isWidgetEventMessage(value) {
|
|
35
|
+
if (!isObject(value)) return false;
|
|
36
|
+
if (value["type"] !== "atomcircuit:event") return false;
|
|
37
|
+
if (!isString(value["name"])) return false;
|
|
38
|
+
return WIDGET_EVENT_NAMES.has(value["name"]);
|
|
39
|
+
}
|
|
40
|
+
function isProtocolMessage(value) {
|
|
41
|
+
return isHandshakeMessage(value) || isResizeMessage(value) || isWidgetEventMessage(value);
|
|
42
|
+
}
|
|
43
|
+
function isCompatibleProtocol(sdkVersion, remoteVersion) {
|
|
44
|
+
const sdkMajor = sdkVersion.split(".")[0];
|
|
45
|
+
const remoteMajor = remoteVersion.split(".")[0];
|
|
46
|
+
return sdkMajor !== void 0 && sdkMajor === remoteMajor;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/iframe-client.ts
|
|
50
|
+
var noopWarn = (_message) => {
|
|
51
|
+
};
|
|
52
|
+
var IframeClient = class {
|
|
53
|
+
constructor(opts) {
|
|
54
|
+
this.connection = null;
|
|
55
|
+
this.rawListener = null;
|
|
56
|
+
this.destroyed = false;
|
|
57
|
+
this.handshakeReceived = null;
|
|
58
|
+
this.handshakeResolvers = [];
|
|
59
|
+
this.handlers = {
|
|
60
|
+
ready: /* @__PURE__ */ new Set(),
|
|
61
|
+
resize: /* @__PURE__ */ new Set(),
|
|
62
|
+
"swap:submitted": /* @__PURE__ */ new Set(),
|
|
63
|
+
"swap:success": /* @__PURE__ */ new Set(),
|
|
64
|
+
"swap:error": /* @__PURE__ */ new Set()
|
|
65
|
+
};
|
|
66
|
+
this.iframe = opts.iframe;
|
|
67
|
+
this.allowedOrigin = opts.allowedOrigin ?? WIDGET_ORIGIN;
|
|
68
|
+
this.timeoutMs = opts.timeoutMs ?? 15e3;
|
|
69
|
+
this.warn = opts.warn ?? noopWarn;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Opens the Penpal connection and starts listening for stream events.
|
|
73
|
+
* Resolves once the remote handshake has been received.
|
|
74
|
+
*/
|
|
75
|
+
async init() {
|
|
76
|
+
if (this.destroyed) {
|
|
77
|
+
throw new Error("IframeClient: cannot init after destroy()");
|
|
78
|
+
}
|
|
79
|
+
if (this.connection) {
|
|
80
|
+
throw new Error("IframeClient: already initialised");
|
|
81
|
+
}
|
|
82
|
+
const remoteWindow = this.iframe.contentWindow;
|
|
83
|
+
if (!remoteWindow) {
|
|
84
|
+
throw new Error("IframeClient: iframe.contentWindow is null");
|
|
85
|
+
}
|
|
86
|
+
this.rawListener = (event) => {
|
|
87
|
+
this.handleRawMessage(event);
|
|
88
|
+
};
|
|
89
|
+
window.addEventListener("message", this.rawListener);
|
|
90
|
+
const messenger = new penpal.WindowMessenger({
|
|
91
|
+
remoteWindow,
|
|
92
|
+
allowedOrigins: [this.allowedOrigin]
|
|
93
|
+
});
|
|
94
|
+
const hostMethods = {
|
|
95
|
+
handshake: (payload) => {
|
|
96
|
+
this.recordHandshake(payload);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const typedConnect = penpal.connect;
|
|
100
|
+
this.connection = typedConnect({
|
|
101
|
+
messenger,
|
|
102
|
+
methods: hostMethods,
|
|
103
|
+
timeout: this.timeoutMs
|
|
104
|
+
});
|
|
105
|
+
await this.connection.promise;
|
|
106
|
+
return this.waitForHandshake();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Subscribe to a named stream event. Returns an unsubscribe function.
|
|
110
|
+
*/
|
|
111
|
+
on(name, handler) {
|
|
112
|
+
this.handlers[name].add(handler);
|
|
113
|
+
return () => this.off(name, handler);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Remove a registered handler.
|
|
117
|
+
*/
|
|
118
|
+
off(name, handler) {
|
|
119
|
+
this.handlers[name].delete(handler);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Tears down the Penpal connection, removes the raw listener, and clears
|
|
123
|
+
* every event subscriber. Safe to call multiple times.
|
|
124
|
+
*/
|
|
125
|
+
destroy() {
|
|
126
|
+
if (this.destroyed) return;
|
|
127
|
+
this.destroyed = true;
|
|
128
|
+
if (this.rawListener) {
|
|
129
|
+
window.removeEventListener("message", this.rawListener);
|
|
130
|
+
this.rawListener = null;
|
|
131
|
+
}
|
|
132
|
+
if (this.connection) {
|
|
133
|
+
this.connection.destroy();
|
|
134
|
+
this.connection = null;
|
|
135
|
+
}
|
|
136
|
+
for (const set of Object.values(this.handlers)) {
|
|
137
|
+
set.clear();
|
|
138
|
+
}
|
|
139
|
+
this.handshakeResolvers = [];
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Returns the handshake payload received from the iframe, or null if no
|
|
143
|
+
* handshake has been observed yet.
|
|
144
|
+
*/
|
|
145
|
+
getHandshake() {
|
|
146
|
+
return this.handshakeReceived;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Returns true if the iframe advertised the given capability in its
|
|
150
|
+
* handshake. Returns false when no handshake has been received yet or
|
|
151
|
+
* when the capability is not present.
|
|
152
|
+
*
|
|
153
|
+
* Callers wrapping a method that requires a capability should gate on
|
|
154
|
+
* `client.has('cap')` before invoking it. Calls to capabilities the
|
|
155
|
+
* iframe does not advertise are silent no-ops at the call-site (or
|
|
156
|
+
* resolve to `null`); the iframe is the source of truth for what it
|
|
157
|
+
* implements.
|
|
158
|
+
*/
|
|
159
|
+
has(capability) {
|
|
160
|
+
const list = this.handshakeReceived?.capabilities;
|
|
161
|
+
if (!list) return false;
|
|
162
|
+
for (const entry of list) {
|
|
163
|
+
if (entry === capability) return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Test-only seam. Allows unit tests to invoke the message handler without
|
|
169
|
+
* dispatching real `MessageEvent`s through the JSDOM bus.
|
|
170
|
+
*/
|
|
171
|
+
/* v8 ignore next 3 */
|
|
172
|
+
_handleMessageForTest(event) {
|
|
173
|
+
this.handleRawMessage(event);
|
|
174
|
+
}
|
|
175
|
+
/* --------------------------------------------------------------------- */
|
|
176
|
+
handleRawMessage(event) {
|
|
177
|
+
if (event.origin !== this.allowedOrigin) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (event.source !== this.iframe.contentWindow) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const data = event.data;
|
|
184
|
+
if (!isProtocolMessage(data)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (data.type === "handshake") {
|
|
188
|
+
this.recordHandshake(data);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (isResizeMessage(data)) {
|
|
192
|
+
this.emitResize(data.height);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (isWidgetEventMessage(data)) {
|
|
196
|
+
this.dispatchWidgetEvent(data);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
recordHandshake(payload) {
|
|
200
|
+
this.handshakeReceived = payload;
|
|
201
|
+
if (!isCompatibleProtocol(PROTOCOL_VERSION, payload.protocolVersion)) {
|
|
202
|
+
this.warn(
|
|
203
|
+
`Atom Circuit embed: protocol mismatch (sdk=${PROTOCOL_VERSION}, iframe=${payload.protocolVersion}). Some features may be unavailable.`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const resolvers = this.handshakeResolvers;
|
|
207
|
+
this.handshakeResolvers = [];
|
|
208
|
+
for (const resolve of resolvers) {
|
|
209
|
+
resolve(payload);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
waitForHandshake() {
|
|
213
|
+
if (this.handshakeReceived) {
|
|
214
|
+
return Promise.resolve(this.handshakeReceived);
|
|
215
|
+
}
|
|
216
|
+
return new Promise((resolve, reject) => {
|
|
217
|
+
const timer = setTimeout(() => {
|
|
218
|
+
const idx = this.handshakeResolvers.indexOf(wrapped);
|
|
219
|
+
if (idx >= 0) this.handshakeResolvers.splice(idx, 1);
|
|
220
|
+
reject(new Error("IframeClient: handshake timeout"));
|
|
221
|
+
}, this.timeoutMs);
|
|
222
|
+
const wrapped = (value) => {
|
|
223
|
+
clearTimeout(timer);
|
|
224
|
+
resolve(value);
|
|
225
|
+
};
|
|
226
|
+
this.handshakeResolvers.push(wrapped);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
emitResize(height) {
|
|
230
|
+
for (const fn of this.handlers.resize) {
|
|
231
|
+
fn({ height });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
dispatchWidgetEvent(message) {
|
|
235
|
+
const name = message.name;
|
|
236
|
+
switch (name) {
|
|
237
|
+
case "ready":
|
|
238
|
+
this.emitReady(message.payload ?? {
|
|
239
|
+
protocolVersion: this.handshakeReceived?.protocolVersion ?? "unknown"
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
case "swap:submitted":
|
|
243
|
+
this.emitTyped("swap:submitted", message.payload);
|
|
244
|
+
return;
|
|
245
|
+
case "swap:success":
|
|
246
|
+
this.emitTyped("swap:success", message.payload);
|
|
247
|
+
return;
|
|
248
|
+
case "swap:error":
|
|
249
|
+
this.emitTyped("swap:error", message.payload);
|
|
250
|
+
return;
|
|
251
|
+
/* v8 ignore next 2 */
|
|
252
|
+
default:
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
emitReady(payload) {
|
|
257
|
+
for (const fn of this.handlers.ready) {
|
|
258
|
+
fn(payload);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
emitTyped(name, payload) {
|
|
262
|
+
for (const fn of this.handlers[name]) {
|
|
263
|
+
fn(payload);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// src/resize.ts
|
|
269
|
+
var DEFAULT_MIN_HEIGHT = "480px";
|
|
270
|
+
function parsePixels(value) {
|
|
271
|
+
const trimmed = value.trim();
|
|
272
|
+
const match = trimmed.match(/^([0-9]+(?:\.[0-9]+)?)(px)?$/i);
|
|
273
|
+
if (!match) return 0;
|
|
274
|
+
const numStr = match[1];
|
|
275
|
+
if (numStr === void 0) return 0;
|
|
276
|
+
const num = Number.parseFloat(numStr);
|
|
277
|
+
return Number.isFinite(num) ? num : 0;
|
|
278
|
+
}
|
|
279
|
+
function attachResize(opts) {
|
|
280
|
+
const { iframe, client } = opts;
|
|
281
|
+
const minHeight = opts.minHeight ?? DEFAULT_MIN_HEIGHT;
|
|
282
|
+
const minPx = parsePixels(minHeight);
|
|
283
|
+
iframe.style.minHeight = minHeight;
|
|
284
|
+
if (!iframe.style.height) {
|
|
285
|
+
iframe.style.height = minHeight;
|
|
286
|
+
}
|
|
287
|
+
let pending = null;
|
|
288
|
+
let lastApplied = -1;
|
|
289
|
+
let destroyed = false;
|
|
290
|
+
const apply = (height) => {
|
|
291
|
+
if (destroyed) return;
|
|
292
|
+
const clamped = Math.max(height, minPx);
|
|
293
|
+
if (clamped === lastApplied) return;
|
|
294
|
+
iframe.style.height = `${clamped}px`;
|
|
295
|
+
lastApplied = clamped;
|
|
296
|
+
};
|
|
297
|
+
const schedule = (height) => {
|
|
298
|
+
if (destroyed) return;
|
|
299
|
+
if (pending !== null) {
|
|
300
|
+
cancelAnimationFrame(pending);
|
|
301
|
+
}
|
|
302
|
+
if (typeof window === "undefined" || typeof window.requestAnimationFrame !== "function") {
|
|
303
|
+
apply(height);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
pending = window.requestAnimationFrame(() => {
|
|
307
|
+
pending = null;
|
|
308
|
+
apply(height);
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
const unsubscribe = client.on("resize", ({ height }) => {
|
|
312
|
+
schedule(height);
|
|
313
|
+
});
|
|
314
|
+
return {
|
|
315
|
+
destroy() {
|
|
316
|
+
if (destroyed) return;
|
|
317
|
+
destroyed = true;
|
|
318
|
+
if (pending !== null && typeof window !== "undefined" && typeof window.cancelAnimationFrame === "function") {
|
|
319
|
+
window.cancelAnimationFrame(pending);
|
|
320
|
+
}
|
|
321
|
+
pending = null;
|
|
322
|
+
unsubscribe();
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/theme.ts
|
|
328
|
+
var HEX_COLOR_RE = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
329
|
+
var FONT_FAMILY_RE = /^[a-zA-Z0-9 ,'".\-]+$/;
|
|
330
|
+
var FONT_FAMILY_MAX_LEN = 200;
|
|
331
|
+
var MODES = /* @__PURE__ */ new Set([
|
|
332
|
+
"light",
|
|
333
|
+
"dark",
|
|
334
|
+
"auto"
|
|
335
|
+
]);
|
|
336
|
+
var isObject2 = (value) => typeof value === "object" && value !== null;
|
|
337
|
+
var isString2 = (value) => typeof value === "string";
|
|
338
|
+
var isFiniteNumber2 = (value) => typeof value === "number" && Number.isFinite(value);
|
|
339
|
+
function isHexColor(value) {
|
|
340
|
+
return isString2(value) && HEX_COLOR_RE.test(value);
|
|
341
|
+
}
|
|
342
|
+
function isValidMode(value) {
|
|
343
|
+
return isString2(value) && MODES.has(value);
|
|
344
|
+
}
|
|
345
|
+
function isValidFontFamily(value) {
|
|
346
|
+
if (!isString2(value)) return false;
|
|
347
|
+
if (value.length === 0 || value.length > FONT_FAMILY_MAX_LEN) return false;
|
|
348
|
+
return FONT_FAMILY_RE.test(value);
|
|
349
|
+
}
|
|
350
|
+
function validateTheme(theme) {
|
|
351
|
+
if (!isObject2(theme)) return null;
|
|
352
|
+
const out = {};
|
|
353
|
+
if (theme["mode"] !== void 0) {
|
|
354
|
+
if (!isValidMode(theme["mode"])) return null;
|
|
355
|
+
out.mode = theme["mode"];
|
|
356
|
+
}
|
|
357
|
+
if (theme["accentColor"] !== void 0) {
|
|
358
|
+
if (!isHexColor(theme["accentColor"])) return null;
|
|
359
|
+
out.accentColor = theme["accentColor"];
|
|
360
|
+
}
|
|
361
|
+
if (theme["background"] !== void 0) {
|
|
362
|
+
if (!isHexColor(theme["background"])) return null;
|
|
363
|
+
out.background = theme["background"];
|
|
364
|
+
}
|
|
365
|
+
if (theme["foreground"] !== void 0) {
|
|
366
|
+
if (!isHexColor(theme["foreground"])) return null;
|
|
367
|
+
out.foreground = theme["foreground"];
|
|
368
|
+
}
|
|
369
|
+
if (theme["border"] !== void 0) {
|
|
370
|
+
if (!isHexColor(theme["border"])) return null;
|
|
371
|
+
out.border = theme["border"];
|
|
372
|
+
}
|
|
373
|
+
if (theme["radius"] !== void 0) {
|
|
374
|
+
const r = theme["radius"];
|
|
375
|
+
if (!isFiniteNumber2(r) || r < 0 || r > 64) return null;
|
|
376
|
+
out.radius = r;
|
|
377
|
+
}
|
|
378
|
+
if (theme["fontSize"] !== void 0) {
|
|
379
|
+
const fs = theme["fontSize"];
|
|
380
|
+
if (!isFiniteNumber2(fs) || fs < 8 || fs > 32) return null;
|
|
381
|
+
out.fontSize = fs;
|
|
382
|
+
}
|
|
383
|
+
if (theme["fontFamily"] !== void 0) {
|
|
384
|
+
if (!isValidFontFamily(theme["fontFamily"])) return null;
|
|
385
|
+
out.fontFamily = theme["fontFamily"];
|
|
386
|
+
}
|
|
387
|
+
return out;
|
|
388
|
+
}
|
|
389
|
+
var isBoolean = (value) => typeof value === "boolean";
|
|
390
|
+
function validateChrome(chrome) {
|
|
391
|
+
if (!isObject2(chrome)) return null;
|
|
392
|
+
const out = {};
|
|
393
|
+
if (chrome["logo"] !== void 0) {
|
|
394
|
+
if (!isBoolean(chrome["logo"])) return null;
|
|
395
|
+
out.logo = chrome["logo"];
|
|
396
|
+
}
|
|
397
|
+
if (chrome["wallet"] !== void 0) {
|
|
398
|
+
if (!isBoolean(chrome["wallet"])) return null;
|
|
399
|
+
out.wallet = chrome["wallet"];
|
|
400
|
+
}
|
|
401
|
+
if (chrome["validator"] !== void 0) {
|
|
402
|
+
if (!isBoolean(chrome["validator"])) return null;
|
|
403
|
+
out.validator = chrome["validator"];
|
|
404
|
+
}
|
|
405
|
+
if (chrome["footer"] !== void 0) {
|
|
406
|
+
if (!isBoolean(chrome["footer"])) return null;
|
|
407
|
+
out.footer = chrome["footer"];
|
|
408
|
+
}
|
|
409
|
+
return out;
|
|
410
|
+
}
|
|
411
|
+
function encodeTheme(theme, chrome) {
|
|
412
|
+
const compact = {};
|
|
413
|
+
for (const [key, value] of Object.entries(theme)) {
|
|
414
|
+
if (value === null || value === void 0) continue;
|
|
415
|
+
compact[key] = value;
|
|
416
|
+
}
|
|
417
|
+
if (chrome && Object.keys(chrome).length > 0) {
|
|
418
|
+
const chromeCompact = {};
|
|
419
|
+
for (const [key, value] of Object.entries(chrome)) {
|
|
420
|
+
if (value === null || value === void 0) continue;
|
|
421
|
+
chromeCompact[key] = value;
|
|
422
|
+
}
|
|
423
|
+
if (Object.keys(chromeCompact).length > 0) {
|
|
424
|
+
compact["chrome"] = chromeCompact;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const json = JSON.stringify(compact);
|
|
428
|
+
if (typeof btoa === "function") {
|
|
429
|
+
return btoa(json);
|
|
430
|
+
}
|
|
431
|
+
return Buffer.from(json, "utf-8").toString("base64");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// src/mount.ts
|
|
435
|
+
var HANDSHAKE_TIMEOUT_MS = 15e3;
|
|
436
|
+
var SANDBOX_ATTR = "allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-forms";
|
|
437
|
+
var LOADER_SVG = '<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle cx="16" cy="16" r="13" stroke="#7B61FF" stroke-width="2.5" stroke-opacity="0.28"/><path d="M29 16a13 13 0 0 0-13-13" stroke="#33D6FF" stroke-width="2.5" stroke-linecap="round"/></svg>';
|
|
438
|
+
function buildSrc(opts) {
|
|
439
|
+
const origin = (opts.origin ?? WIDGET_ORIGIN).replace(/\/$/, "");
|
|
440
|
+
const path = opts.path ?? WIDGET_PATH;
|
|
441
|
+
const params = new URLSearchParams();
|
|
442
|
+
params.set("ref", opts.referralId);
|
|
443
|
+
params.set("v", PROTOCOL_VERSION);
|
|
444
|
+
const validatedTheme = opts.theme !== void 0 ? validateTheme(opts.theme) : null;
|
|
445
|
+
if (opts.theme !== void 0 && validatedTheme === null && opts.warn) {
|
|
446
|
+
opts.warn(
|
|
447
|
+
"Atom Circuit embed: theme validation failed, falling back to defaults"
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
const validatedChrome = opts.chrome !== void 0 ? validateChrome(opts.chrome) : null;
|
|
451
|
+
if (opts.chrome !== void 0 && validatedChrome === null && opts.warn) {
|
|
452
|
+
opts.warn(
|
|
453
|
+
"Atom Circuit embed: chrome validation failed, falling back to defaults"
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (validatedTheme !== null || validatedChrome !== null) {
|
|
457
|
+
params.set(
|
|
458
|
+
"theme",
|
|
459
|
+
encodeTheme(validatedTheme ?? {}, validatedChrome ?? void 0)
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
const sep = path.includes("?") ? "&" : "?";
|
|
463
|
+
return `${origin}${path}${sep}${params.toString()}`;
|
|
464
|
+
}
|
|
465
|
+
function applyStyle(iframe, style) {
|
|
466
|
+
if (!style) return;
|
|
467
|
+
for (const key of Object.keys(style)) {
|
|
468
|
+
if (key === "height" || key === "width") continue;
|
|
469
|
+
const value = style[key];
|
|
470
|
+
if (typeof value === "string") {
|
|
471
|
+
iframe.style[key] = value;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function applySizing(iframe, wrapper, opts) {
|
|
476
|
+
if (opts.width !== void 0) {
|
|
477
|
+
iframe.style.width = opts.width;
|
|
478
|
+
}
|
|
479
|
+
if (opts.maxWidth !== void 0) {
|
|
480
|
+
iframe.style.maxWidth = opts.maxWidth;
|
|
481
|
+
}
|
|
482
|
+
if (opts.padding !== void 0) {
|
|
483
|
+
wrapper.style.padding = opts.padding;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function mount(container, opts = {}) {
|
|
487
|
+
if (!container || typeof container.appendChild !== "function") {
|
|
488
|
+
throw new TypeError("mount: container must be an HTMLElement");
|
|
489
|
+
}
|
|
490
|
+
const referralIdRaw = typeof opts.referralId === "string" ? opts.referralId.trim() : "";
|
|
491
|
+
const resolvedReferralId = referralIdRaw.length > 0 ? referralIdRaw : "general";
|
|
492
|
+
const warnSink = typeof console !== "undefined" && typeof console.warn === "function" ? (msg) => {
|
|
493
|
+
console.warn(msg);
|
|
494
|
+
} : () => {
|
|
495
|
+
};
|
|
496
|
+
let dismissLoader = () => {
|
|
497
|
+
};
|
|
498
|
+
const reportError = (error) => {
|
|
499
|
+
dismissLoader();
|
|
500
|
+
if (opts.onError) {
|
|
501
|
+
opts.onError(error);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
warnSink(`Atom Circuit embed: ${error.code}: ${error.message}`);
|
|
505
|
+
};
|
|
506
|
+
const wrapper = document.createElement("div");
|
|
507
|
+
wrapper.setAttribute("data-atom-circuit-embed", "");
|
|
508
|
+
wrapper.style.position = "relative";
|
|
509
|
+
wrapper.style.width = "100%";
|
|
510
|
+
wrapper.style.display = "block";
|
|
511
|
+
const iframe = document.createElement("iframe");
|
|
512
|
+
iframe.src = buildSrc({
|
|
513
|
+
referralId: resolvedReferralId,
|
|
514
|
+
...opts.origin !== void 0 ? { origin: opts.origin } : {},
|
|
515
|
+
...opts.path !== void 0 ? { path: opts.path } : {},
|
|
516
|
+
...opts.theme !== void 0 ? { theme: opts.theme } : {},
|
|
517
|
+
...opts.chrome !== void 0 ? { chrome: opts.chrome } : {},
|
|
518
|
+
warn: warnSink
|
|
519
|
+
});
|
|
520
|
+
iframe.setAttribute("sandbox", SANDBOX_ATTR);
|
|
521
|
+
iframe.setAttribute("allow", "clipboard-write; clipboard-read");
|
|
522
|
+
iframe.setAttribute("title", "Atom Circuit swap widget");
|
|
523
|
+
iframe.setAttribute("loading", "lazy");
|
|
524
|
+
iframe.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
|
|
525
|
+
iframe.style.width = "100%";
|
|
526
|
+
iframe.style.border = "0";
|
|
527
|
+
iframe.style.display = "block";
|
|
528
|
+
iframe.style.colorScheme = "normal";
|
|
529
|
+
iframe.style.position = "relative";
|
|
530
|
+
iframe.style.zIndex = "1";
|
|
531
|
+
applyStyle(iframe, opts.style);
|
|
532
|
+
applySizing(iframe, wrapper, opts);
|
|
533
|
+
if (opts.className) {
|
|
534
|
+
iframe.className = opts.className;
|
|
535
|
+
}
|
|
536
|
+
const loader = document.createElement("div");
|
|
537
|
+
loader.setAttribute("data-atom-circuit-loader", "");
|
|
538
|
+
loader.setAttribute("aria-hidden", "true");
|
|
539
|
+
loader.style.position = "absolute";
|
|
540
|
+
loader.style.inset = "0";
|
|
541
|
+
loader.style.display = "flex";
|
|
542
|
+
loader.style.alignItems = "center";
|
|
543
|
+
loader.style.justifyContent = "center";
|
|
544
|
+
loader.style.pointerEvents = "none";
|
|
545
|
+
loader.style.color = "#888888";
|
|
546
|
+
loader.style.transition = "opacity 0.08s ease-out";
|
|
547
|
+
loader.style.opacity = "1";
|
|
548
|
+
loader.style.zIndex = "2";
|
|
549
|
+
loader.innerHTML = LOADER_SVG;
|
|
550
|
+
const loaderSvg = loader.querySelector("svg");
|
|
551
|
+
let spinAnimation = null;
|
|
552
|
+
if (loaderSvg && typeof loaderSvg.animate === "function") {
|
|
553
|
+
loaderSvg.style.transformOrigin = "center";
|
|
554
|
+
spinAnimation = loaderSvg.animate(
|
|
555
|
+
[{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }],
|
|
556
|
+
{ duration: 900, iterations: Infinity, easing: "linear" }
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
let loaderDismissed = false;
|
|
560
|
+
dismissLoader = () => {
|
|
561
|
+
if (loaderDismissed) return;
|
|
562
|
+
loaderDismissed = true;
|
|
563
|
+
loader.style.opacity = "0";
|
|
564
|
+
if (spinAnimation) {
|
|
565
|
+
try {
|
|
566
|
+
spinAnimation.cancel();
|
|
567
|
+
} catch {
|
|
568
|
+
}
|
|
569
|
+
spinAnimation = null;
|
|
570
|
+
}
|
|
571
|
+
setTimeout(() => {
|
|
572
|
+
if (loader.parentNode) loader.parentNode.removeChild(loader);
|
|
573
|
+
}, 100);
|
|
574
|
+
};
|
|
575
|
+
const onIframeError = (event) => {
|
|
576
|
+
reportError({
|
|
577
|
+
code: "iframe_load_failed",
|
|
578
|
+
message: "Iframe failed to load the widget URL",
|
|
579
|
+
cause: event
|
|
580
|
+
});
|
|
581
|
+
};
|
|
582
|
+
iframe.addEventListener("error", onIframeError);
|
|
583
|
+
const onIframeLoad = () => {
|
|
584
|
+
dismissLoader();
|
|
585
|
+
};
|
|
586
|
+
iframe.addEventListener("load", onIframeLoad);
|
|
587
|
+
try {
|
|
588
|
+
wrapper.appendChild(iframe);
|
|
589
|
+
wrapper.appendChild(loader);
|
|
590
|
+
container.appendChild(wrapper);
|
|
591
|
+
} catch (err) {
|
|
592
|
+
iframe.removeEventListener("error", onIframeError);
|
|
593
|
+
iframe.removeEventListener("load", onIframeLoad);
|
|
594
|
+
throw err;
|
|
595
|
+
}
|
|
596
|
+
const client = new IframeClient({
|
|
597
|
+
iframe,
|
|
598
|
+
allowedOrigin: opts.origin ?? WIDGET_ORIGIN
|
|
599
|
+
});
|
|
600
|
+
const resize = attachResize({
|
|
601
|
+
iframe,
|
|
602
|
+
client,
|
|
603
|
+
...opts.minHeight !== void 0 ? { minHeight: opts.minHeight } : {}
|
|
604
|
+
});
|
|
605
|
+
const subscriptions = [];
|
|
606
|
+
if (opts.onReady) {
|
|
607
|
+
const fn = opts.onReady;
|
|
608
|
+
subscriptions.push(client.on("ready", (p) => fn(p)));
|
|
609
|
+
}
|
|
610
|
+
if (opts.onResize) {
|
|
611
|
+
const fn = opts.onResize;
|
|
612
|
+
subscriptions.push(client.on("resize", (info) => fn(info)));
|
|
613
|
+
}
|
|
614
|
+
if (opts.onSwapSubmitted) {
|
|
615
|
+
const fn = opts.onSwapSubmitted;
|
|
616
|
+
subscriptions.push(client.on("swap:submitted", (p) => fn(p)));
|
|
617
|
+
}
|
|
618
|
+
if (opts.onSwapSuccess) {
|
|
619
|
+
const fn = opts.onSwapSuccess;
|
|
620
|
+
subscriptions.push(client.on("swap:success", (p) => fn(p)));
|
|
621
|
+
}
|
|
622
|
+
if (opts.onSwapError) {
|
|
623
|
+
const fn = opts.onSwapError;
|
|
624
|
+
subscriptions.push(client.on("swap:error", (p) => fn(p)));
|
|
625
|
+
}
|
|
626
|
+
let timeoutHandle = null;
|
|
627
|
+
let bridgeReady = false;
|
|
628
|
+
const clearTimer = () => {
|
|
629
|
+
if (timeoutHandle !== null) {
|
|
630
|
+
clearTimeout(timeoutHandle);
|
|
631
|
+
timeoutHandle = null;
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
const readySuppressUnsub = client.on("ready", () => {
|
|
635
|
+
bridgeReady = true;
|
|
636
|
+
clearTimer();
|
|
637
|
+
dismissLoader();
|
|
638
|
+
readySuppressUnsub();
|
|
639
|
+
});
|
|
640
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
641
|
+
timeoutHandle = setTimeout(() => {
|
|
642
|
+
reject(new Error(`Iframe handshake timed out after ${HANDSHAKE_TIMEOUT_MS}ms`));
|
|
643
|
+
}, HANDSHAKE_TIMEOUT_MS);
|
|
644
|
+
});
|
|
645
|
+
Promise.race([client.init(), timeoutPromise]).then(() => {
|
|
646
|
+
clearTimer();
|
|
647
|
+
}).catch((err) => {
|
|
648
|
+
clearTimer();
|
|
649
|
+
if (bridgeReady) return;
|
|
650
|
+
if (destroyed) return;
|
|
651
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
652
|
+
const code = classifyInitError(message);
|
|
653
|
+
reportError({ code, message, cause: err });
|
|
654
|
+
});
|
|
655
|
+
let destroyed = false;
|
|
656
|
+
const destroy = () => {
|
|
657
|
+
if (destroyed) return;
|
|
658
|
+
destroyed = true;
|
|
659
|
+
if (timeoutHandle !== null) {
|
|
660
|
+
clearTimeout(timeoutHandle);
|
|
661
|
+
timeoutHandle = null;
|
|
662
|
+
}
|
|
663
|
+
iframe.removeEventListener("error", onIframeError);
|
|
664
|
+
iframe.removeEventListener("load", onIframeLoad);
|
|
665
|
+
for (const unsub of subscriptions) unsub();
|
|
666
|
+
resize.destroy();
|
|
667
|
+
client.destroy();
|
|
668
|
+
dismissLoader();
|
|
669
|
+
if (wrapper.parentNode) {
|
|
670
|
+
wrapper.parentNode.removeChild(wrapper);
|
|
671
|
+
} else if (iframe.parentNode) {
|
|
672
|
+
iframe.parentNode.removeChild(iframe);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
return { iframe, wrapper, client, destroy };
|
|
676
|
+
}
|
|
677
|
+
function classifyInitError(message) {
|
|
678
|
+
const lower = message.toLowerCase();
|
|
679
|
+
if (lower.includes("handshake") || lower.includes("timed out") || lower.includes("timeout")) {
|
|
680
|
+
return "handshake_failed";
|
|
681
|
+
}
|
|
682
|
+
if (lower.includes("protocol mismatch") || lower.includes("protocolversion")) {
|
|
683
|
+
return "protocol_incompatible";
|
|
684
|
+
}
|
|
685
|
+
if (lower.includes("origin mismatch") || lower.includes("allowed origin")) {
|
|
686
|
+
return "origin_mismatch";
|
|
687
|
+
}
|
|
688
|
+
if (lower.includes("contentwindow") || lower.includes("iframe failed") || lower.includes("load")) {
|
|
689
|
+
return "iframe_load_failed";
|
|
690
|
+
}
|
|
691
|
+
return "unknown";
|
|
692
|
+
}
|
|
693
|
+
var WRAPPER_STYLE = {
|
|
694
|
+
width: "100%",
|
|
695
|
+
display: "block"
|
|
696
|
+
};
|
|
697
|
+
function AtomCircuitSwap(props) {
|
|
698
|
+
const containerRef = react.useRef(null);
|
|
699
|
+
const propsRef = react.useRef(props);
|
|
700
|
+
propsRef.current = props;
|
|
701
|
+
react.useEffect(() => {
|
|
702
|
+
const container = containerRef.current;
|
|
703
|
+
if (!container) return;
|
|
704
|
+
const opts = {
|
|
705
|
+
referralId: propsRef.current.referralId,
|
|
706
|
+
...propsRef.current.origin !== void 0 ? { origin: propsRef.current.origin } : {},
|
|
707
|
+
...propsRef.current.path !== void 0 ? { path: propsRef.current.path } : {},
|
|
708
|
+
...propsRef.current.minHeight !== void 0 ? { minHeight: propsRef.current.minHeight } : {},
|
|
709
|
+
...propsRef.current.theme !== void 0 ? { theme: propsRef.current.theme } : {},
|
|
710
|
+
...propsRef.current.chrome !== void 0 ? { chrome: propsRef.current.chrome } : {},
|
|
711
|
+
...propsRef.current.width !== void 0 ? { width: propsRef.current.width } : {},
|
|
712
|
+
...propsRef.current.maxWidth !== void 0 ? { maxWidth: propsRef.current.maxWidth } : {},
|
|
713
|
+
...propsRef.current.padding !== void 0 ? { padding: propsRef.current.padding } : {},
|
|
714
|
+
onReady: (payload) => propsRef.current.onReady?.(payload),
|
|
715
|
+
onResize: (info) => propsRef.current.onResize?.(info),
|
|
716
|
+
onSwapSubmitted: (payload) => propsRef.current.onSwapSubmitted?.(payload),
|
|
717
|
+
onSwapSuccess: (payload) => propsRef.current.onSwapSuccess?.(payload),
|
|
718
|
+
onSwapError: (payload) => propsRef.current.onSwapError?.(payload),
|
|
719
|
+
onError: (error) => propsRef.current.onError?.(error)
|
|
720
|
+
};
|
|
721
|
+
let handle = null;
|
|
722
|
+
try {
|
|
723
|
+
handle = mount(container, opts);
|
|
724
|
+
} catch {
|
|
725
|
+
handle = null;
|
|
726
|
+
}
|
|
727
|
+
return () => {
|
|
728
|
+
handle?.destroy();
|
|
729
|
+
};
|
|
730
|
+
}, [props.referralId, props.origin, props.path]);
|
|
731
|
+
const wrapperStyle = props.style ? { ...WRAPPER_STYLE, ...props.style } : WRAPPER_STYLE;
|
|
732
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
733
|
+
"div",
|
|
734
|
+
{
|
|
735
|
+
ref: containerRef,
|
|
736
|
+
className: props.className,
|
|
737
|
+
style: wrapperStyle,
|
|
738
|
+
"data-atom-circuit-embed": ""
|
|
739
|
+
}
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
exports.AtomCircuitSwap = AtomCircuitSwap;
|
|
744
|
+
//# sourceMappingURL=react.cjs.map
|