@xoocity/wallet-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * XooWallet 核心类
3
+ * 管理 FAB 按钮、iframe、postMessage 通信和事件
4
+ */
5
+ import type { XooWalletConfig, XooWalletEvents, UserInfo, PayOptions } from './types';
6
+ type EventKey = keyof XooWalletEvents;
7
+ export declare class XooWallet {
8
+ private config;
9
+ private iframe;
10
+ private fab;
11
+ private eventListeners;
12
+ private _isOpen;
13
+ private _isLoggedIn;
14
+ private _user;
15
+ private _ready;
16
+ private constructor();
17
+ /**
18
+ * 初始化 SDK(静态工厂方法)
19
+ */
20
+ static init(config: XooWalletConfig): XooWallet;
21
+ /** 打开钱包界面 */
22
+ open(): void;
23
+ /** 关闭/收起钱包 */
24
+ close(): void;
25
+ /** 切换开关 */
26
+ toggle(): void;
27
+ /** 打开登录界面 */
28
+ login(): void;
29
+ /** 登出 */
30
+ logout(): void;
31
+ /** 发起支付 */
32
+ pay(options: PayOptions): void;
33
+ /** 钱包是否已打开 */
34
+ isOpen(): boolean;
35
+ /** 用户是否已登录 */
36
+ isLoggedIn(): boolean;
37
+ /** 获取当前用户信息 */
38
+ getUser(): UserInfo | null;
39
+ /** iframe 是否已就绪 */
40
+ isReady(): boolean;
41
+ /** 显示悬浮按钮 */
42
+ show(): void;
43
+ /** 隐藏悬浮按钮 */
44
+ hide(): void;
45
+ /** 注册事件监听 */
46
+ on<K extends EventKey>(event: K, handler: XooWalletEvents[K]): void;
47
+ /** 移除事件监听 */
48
+ off<K extends EventKey>(event: K, handler: XooWalletEvents[K]): void;
49
+ /** 销毁 SDK 实例,移除所有 DOM 元素 */
50
+ destroy(): void;
51
+ private emit;
52
+ private handleIframeMessage;
53
+ }
54
+ export {};
@@ -0,0 +1,23 @@
1
+ /**
2
+ * FAB 悬浮按钮
3
+ * 纯 DOM 操作,不依赖 React
4
+ */
5
+ export type FabPosition = 'bottom-right' | 'bottom-left';
6
+ export declare class FabButton {
7
+ private button;
8
+ private isOpen;
9
+ private position;
10
+ private onClick;
11
+ constructor(position: FabPosition, onClick: () => void);
12
+ /** 创建并插入 FAB 按钮 */
13
+ create(): void;
14
+ /** 更新按钮图标 */
15
+ setOpen(open: boolean): void;
16
+ /** 显示按钮 */
17
+ show(): void;
18
+ /** 隐藏按钮 */
19
+ hide(): void;
20
+ /** 销毁按钮 */
21
+ destroy(): void;
22
+ private handleClick;
23
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * iframe 管理器
3
+ * 创建容器(iframe + 关闭按钮)、遮罩层,处理显示/隐藏和 postMessage 通信
4
+ */
5
+ import type { SdkCommand, IframeMessage } from './types';
6
+ export type IframePosition = 'bottom-right' | 'bottom-left';
7
+ export type PopupPosition = 'center' | 'bottom-right' | 'bottom-left';
8
+ export declare class IframeManager {
9
+ private container;
10
+ private iframe;
11
+ private closeBtn;
12
+ private overlay;
13
+ private visible;
14
+ private popupPosition;
15
+ private walletOrigin;
16
+ private onMessage;
17
+ private onCloseClick;
18
+ /** 创建 iframe 元素 */
19
+ create(walletUrl: string, partnerKey: string, position: IframePosition, params?: {
20
+ lang?: string;
21
+ theme?: string;
22
+ popupPosition?: PopupPosition;
23
+ }): void;
24
+ /** 设置消息处理回调 */
25
+ setMessageHandler(handler: (msg: IframeMessage) => void): void;
26
+ /** 设置关闭按钮点击回调 */
27
+ setCloseClickHandler(handler: () => void): void;
28
+ /** 显示 iframe */
29
+ show(): void;
30
+ /** 隐藏 iframe */
31
+ hide(): void;
32
+ /** 是否可见 */
33
+ isVisible(): boolean;
34
+ /** 向 iframe 发送命令 */
35
+ postCommand(command: SdkCommand): void;
36
+ /** 销毁 */
37
+ destroy(): void;
38
+ private handleMessage;
39
+ private handleCloseClick;
40
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * XOO Wallet SDK
3
+ *
4
+ * npm 使用:
5
+ * import { XooWallet } from '@xoo/wallet-sdk';
6
+ * const wallet = XooWallet.init({ apiKey: 'pk_xxx' });
7
+ *
8
+ * script 标签使用:
9
+ * <script src="https://wallet.xoo.com/sdk/xoowallet-sdk.umd.js"></script>
10
+ * <script>
11
+ * var wallet = XooWallet.init({ apiKey: 'pk_xxx' });
12
+ * </script>
13
+ */
14
+ export { XooWallet } from './XooWallet';
15
+ export type { XooWalletConfig, XooWalletEvents, UserInfo, PayOptions, } from './types';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 内联样式 - 避免外部 CSS 依赖和样式冲突
3
+ * 所有 class 使用 xoo-widget- 前缀
4
+ */
5
+ export declare function injectStyles(zIndex: number): void;
6
+ export declare function removeStyles(): void;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * XOO Wallet SDK 类型定义
3
+ */
4
+ /** SDK 初始化配置 */
5
+ export interface XooWalletConfig {
6
+ /** 合作方 API Key(必填) */
7
+ apiKey: string;
8
+ /** 钱包页面地址,默认 https://wallet.xoo.com */
9
+ walletUrl?: string;
10
+ /** FAB 按钮位置 */
11
+ position?: 'bottom-right' | 'bottom-left';
12
+ /** 弹窗位置,默认 center(居中),也可跟随 FAB 按钮 */
13
+ popupPosition?: 'center' | 'bottom-right' | 'bottom-left';
14
+ /** 自动显示 FAB 按钮,默认 true */
15
+ autoShow?: boolean;
16
+ /** 主题 */
17
+ theme?: 'dark' | 'light';
18
+ /** 语言 */
19
+ lang?: 'en' | 'zh' | 'ms';
20
+ /** z-index,默认 99999 */
21
+ zIndex?: number;
22
+ }
23
+ /** 用户信息 */
24
+ export interface UserInfo {
25
+ userId: string;
26
+ address: string;
27
+ email?: string;
28
+ loginType?: string;
29
+ }
30
+ /** 支付参数 */
31
+ export interface PayOptions {
32
+ /** 收款地址 */
33
+ to: string;
34
+ /** 金额 */
35
+ amount: string;
36
+ /** 代币符号(可选,默认原生代币) */
37
+ token?: string;
38
+ }
39
+ /** 事件映射 */
40
+ export interface XooWalletEvents {
41
+ ready: () => void;
42
+ 'auth:success': (user: UserInfo) => void;
43
+ 'auth:logout': () => void;
44
+ 'tx:success': (data: {
45
+ txHash: string;
46
+ }) => void;
47
+ 'tx:failed': (data: {
48
+ error: string;
49
+ }) => void;
50
+ }
51
+ /** iframe → SDK 消息 */
52
+ export type IframeMessage = {
53
+ type: 'xoo-ready';
54
+ } | {
55
+ type: 'xoo-auth-success';
56
+ user: UserInfo;
57
+ } | {
58
+ type: 'xoo-auth-logout';
59
+ } | {
60
+ type: 'xoo-tx-success';
61
+ txHash: string;
62
+ } | {
63
+ type: 'xoo-tx-failed';
64
+ error: string;
65
+ } | {
66
+ type: 'xoo-resize';
67
+ height: number;
68
+ } | {
69
+ type: 'xoo-close';
70
+ };
71
+ /** SDK → iframe 命令 */
72
+ export type SdkCommand = {
73
+ type: 'xoo-cmd-open';
74
+ } | {
75
+ type: 'xoo-cmd-close';
76
+ } | {
77
+ type: 'xoo-cmd-login';
78
+ } | {
79
+ type: 'xoo-cmd-logout';
80
+ } | {
81
+ type: 'xoo-cmd-pay';
82
+ to: string;
83
+ amount: string;
84
+ token?: string;
85
+ };
@@ -0,0 +1,528 @@
1
+ const CLOSE_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
2
+ class IframeManager {
3
+ constructor() {
4
+ this.container = null;
5
+ this.iframe = null;
6
+ this.closeBtn = null;
7
+ this.overlay = null;
8
+ this.visible = false;
9
+ this.popupPosition = "center";
10
+ this.walletOrigin = "";
11
+ this.onMessage = null;
12
+ this.onCloseClick = null;
13
+ this.handleMessage = (event) => {
14
+ var _a;
15
+ if (event.origin !== this.walletOrigin) return;
16
+ const data = event.data;
17
+ if (!data || typeof data.type !== "string" || !data.type.startsWith("xoo-")) return;
18
+ if (data.type.startsWith("xoo-cmd-")) return;
19
+ (_a = this.onMessage) == null ? void 0 : _a.call(this, data);
20
+ };
21
+ this.handleCloseClick = () => {
22
+ var _a;
23
+ (_a = this.onCloseClick) == null ? void 0 : _a.call(this);
24
+ };
25
+ }
26
+ /** 创建 iframe 元素 */
27
+ create(walletUrl, partnerKey, position, params) {
28
+ if (this.container) return;
29
+ this.popupPosition = (params == null ? void 0 : params.popupPosition) || "center";
30
+ const url = new URL(walletUrl);
31
+ this.walletOrigin = url.origin;
32
+ url.searchParams.set("embed", "true");
33
+ url.searchParams.set("partnerKey", partnerKey);
34
+ if (params == null ? void 0 : params.lang) url.searchParams.set("lang", params.lang);
35
+ if (params == null ? void 0 : params.theme) url.searchParams.set("theme", params.theme);
36
+ if (this.popupPosition === "center") {
37
+ this.overlay = document.createElement("div");
38
+ this.overlay.className = "xoo-widget-overlay xoo-widget-overlay--hidden";
39
+ document.body.appendChild(this.overlay);
40
+ }
41
+ const posClass = this.popupPosition === "center" ? "center" : position;
42
+ this.container = document.createElement("div");
43
+ this.container.className = `xoo-widget-container xoo-widget-container--${posClass} xoo-widget-container--hidden`;
44
+ this.iframe = document.createElement("iframe");
45
+ this.iframe.src = url.toString();
46
+ this.iframe.className = "xoo-widget-iframe";
47
+ this.iframe.setAttribute(
48
+ "sandbox",
49
+ "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
50
+ );
51
+ this.iframe.setAttribute("allow", "clipboard-write");
52
+ this.iframe.setAttribute("title", "XOO Wallet");
53
+ this.container.appendChild(this.iframe);
54
+ this.closeBtn = document.createElement("button");
55
+ this.closeBtn.className = "xoo-widget-close";
56
+ this.closeBtn.innerHTML = CLOSE_SVG;
57
+ this.closeBtn.setAttribute("aria-label", "Close");
58
+ this.closeBtn.addEventListener("click", this.handleCloseClick);
59
+ this.container.appendChild(this.closeBtn);
60
+ document.body.appendChild(this.container);
61
+ window.addEventListener("message", this.handleMessage);
62
+ }
63
+ /** 设置消息处理回调 */
64
+ setMessageHandler(handler) {
65
+ this.onMessage = handler;
66
+ }
67
+ /** 设置关闭按钮点击回调 */
68
+ setCloseClickHandler(handler) {
69
+ this.onCloseClick = handler;
70
+ }
71
+ /** 显示 iframe */
72
+ show() {
73
+ if (!this.container) return;
74
+ this.visible = true;
75
+ this.container.classList.remove("xoo-widget-container--hidden");
76
+ this.container.classList.add("xoo-widget-container--visible");
77
+ if (this.overlay) {
78
+ this.overlay.classList.remove("xoo-widget-overlay--hidden");
79
+ this.overlay.classList.add("xoo-widget-overlay--visible");
80
+ }
81
+ }
82
+ /** 隐藏 iframe */
83
+ hide() {
84
+ if (!this.container) return;
85
+ this.visible = false;
86
+ this.container.classList.remove("xoo-widget-container--visible");
87
+ this.container.classList.add("xoo-widget-container--hidden");
88
+ if (this.overlay) {
89
+ this.overlay.classList.remove("xoo-widget-overlay--visible");
90
+ this.overlay.classList.add("xoo-widget-overlay--hidden");
91
+ }
92
+ }
93
+ /** 是否可见 */
94
+ isVisible() {
95
+ return this.visible;
96
+ }
97
+ /** 向 iframe 发送命令 */
98
+ postCommand(command) {
99
+ var _a;
100
+ if (!((_a = this.iframe) == null ? void 0 : _a.contentWindow)) return;
101
+ this.iframe.contentWindow.postMessage(command, this.walletOrigin);
102
+ }
103
+ /** 销毁 */
104
+ destroy() {
105
+ window.removeEventListener("message", this.handleMessage);
106
+ if (this.closeBtn) {
107
+ this.closeBtn.removeEventListener("click", this.handleCloseClick);
108
+ }
109
+ if (this.overlay) {
110
+ this.overlay.remove();
111
+ this.overlay = null;
112
+ }
113
+ if (this.container) {
114
+ this.container.remove();
115
+ this.container = null;
116
+ }
117
+ this.iframe = null;
118
+ this.closeBtn = null;
119
+ this.onMessage = null;
120
+ this.onCloseClick = null;
121
+ this.visible = false;
122
+ }
123
+ }
124
+ const WALLET_ICON = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21 7H3a1 1 0 0 0-1 1v11a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8a1 1 0 0 0-1-1zm-1 12H4V9h16v10zm-2-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zM20 3H4a2 2 0 0 0-2 2v1h20V5a2 2 0 0 0-2-2z"/></svg>`;
125
+ const CLOSE_ICON = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18.3 5.71a1 1 0 0 0-1.42 0L12 10.59 7.12 5.71a1 1 0 0 0-1.42 1.42L10.59 12l-4.88 4.88a1 1 0 1 0 1.42 1.42L12 13.41l4.88 4.88a1 1 0 0 0 1.42-1.42L13.41 12l4.88-4.88a1 1 0 0 0 0-1.41z"/></svg>`;
126
+ class FabButton {
127
+ constructor(position, onClick) {
128
+ this.button = null;
129
+ this.isOpen = false;
130
+ this.handleClick = () => {
131
+ this.onClick();
132
+ };
133
+ this.position = position;
134
+ this.onClick = onClick;
135
+ }
136
+ /** 创建并插入 FAB 按钮 */
137
+ create() {
138
+ if (this.button) return;
139
+ this.button = document.createElement("button");
140
+ this.button.className = `xoo-widget-fab xoo-widget-fab--${this.position}`;
141
+ this.button.innerHTML = WALLET_ICON;
142
+ this.button.setAttribute("aria-label", "Open XOO Wallet");
143
+ this.button.addEventListener("click", this.handleClick);
144
+ document.body.appendChild(this.button);
145
+ }
146
+ /** 更新按钮图标 */
147
+ setOpen(open) {
148
+ this.isOpen = open;
149
+ if (!this.button) return;
150
+ if (open) {
151
+ this.button.innerHTML = CLOSE_ICON;
152
+ this.button.classList.add("xoo-widget-fab--open");
153
+ this.button.setAttribute("aria-label", "Close XOO Wallet");
154
+ } else {
155
+ this.button.innerHTML = WALLET_ICON;
156
+ this.button.classList.remove("xoo-widget-fab--open");
157
+ this.button.setAttribute("aria-label", "Open XOO Wallet");
158
+ }
159
+ }
160
+ /** 显示按钮 */
161
+ show() {
162
+ if (this.button) this.button.style.display = "flex";
163
+ }
164
+ /** 隐藏按钮 */
165
+ hide() {
166
+ if (this.button) this.button.style.display = "none";
167
+ }
168
+ /** 销毁按钮 */
169
+ destroy() {
170
+ if (this.button) {
171
+ this.button.removeEventListener("click", this.handleClick);
172
+ this.button.remove();
173
+ this.button = null;
174
+ }
175
+ }
176
+ }
177
+ function injectStyles(zIndex) {
178
+ if (document.getElementById("xoo-widget-styles")) return;
179
+ const style = document.createElement("style");
180
+ style.id = "xoo-widget-styles";
181
+ style.textContent = `
182
+ .xoo-widget-fab {
183
+ position: fixed;
184
+ width: 56px;
185
+ height: 56px;
186
+ border-radius: 50%;
187
+ border: none;
188
+ cursor: pointer;
189
+ display: flex;
190
+ align-items: center;
191
+ justify-content: center;
192
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
193
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
194
+ z-index: ${zIndex + 1};
195
+ background: linear-gradient(135deg, #3b82f6, #1d4ed8);
196
+ padding: 0;
197
+ outline: none;
198
+ }
199
+ .xoo-widget-fab:hover {
200
+ transform: scale(1.05);
201
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
202
+ }
203
+ .xoo-widget-fab:active {
204
+ transform: scale(0.95);
205
+ }
206
+ .xoo-widget-fab svg {
207
+ width: 28px;
208
+ height: 28px;
209
+ fill: white;
210
+ transition: transform 0.3s ease;
211
+ }
212
+ .xoo-widget-fab.xoo-widget-fab--open svg {
213
+ transform: rotate(180deg);
214
+ }
215
+ .xoo-widget-fab--bottom-right {
216
+ bottom: 20px;
217
+ right: 20px;
218
+ }
219
+ .xoo-widget-fab--bottom-left {
220
+ bottom: 20px;
221
+ left: 20px;
222
+ }
223
+
224
+ /* ---- 弹窗容器(包裹 iframe + 关闭按钮) ---- */
225
+ .xoo-widget-container {
226
+ position: fixed;
227
+ width: 375px;
228
+ height: 640px;
229
+ max-height: 80vh;
230
+ border-radius: 16px;
231
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
232
+ z-index: ${zIndex};
233
+ overflow: hidden;
234
+ transition: opacity 0.2s ease, transform 0.2s ease;
235
+ }
236
+ .xoo-widget-container--center {
237
+ top: 0;
238
+ left: 0;
239
+ right: 0;
240
+ bottom: 0;
241
+ margin: auto;
242
+ }
243
+ .xoo-widget-container--bottom-right {
244
+ bottom: 88px;
245
+ right: 20px;
246
+ }
247
+ .xoo-widget-container--bottom-left {
248
+ bottom: 88px;
249
+ left: 20px;
250
+ }
251
+ .xoo-widget-container--hidden {
252
+ opacity: 0;
253
+ transform: translateY(10px) scale(0.95);
254
+ pointer-events: none;
255
+ }
256
+ .xoo-widget-container--visible {
257
+ opacity: 1;
258
+ transform: translateY(0) scale(1);
259
+ }
260
+
261
+ /* ---- iframe 填充容器 ---- */
262
+ .xoo-widget-iframe {
263
+ width: 100%;
264
+ height: 100%;
265
+ border: none;
266
+ background: #ffffff;
267
+ }
268
+
269
+ /* ---- 关闭按钮(容器内,iframe 上层) ---- */
270
+ .xoo-widget-close {
271
+ position: absolute;
272
+ top: 3px;
273
+ right: 3px;
274
+ width: 28px;
275
+ height: 28px;
276
+ border-radius: 50%;
277
+ border: none;
278
+ background: rgba(0, 0, 0, 0.06);
279
+ cursor: pointer;
280
+ display: flex;
281
+ align-items: center;
282
+ justify-content: center;
283
+ z-index: 2;
284
+ color: #999;
285
+ padding: 0;
286
+ outline: none;
287
+ transition: background 0.15s ease, color 0.15s ease;
288
+ }
289
+ .xoo-widget-close:hover {
290
+ background: rgba(0, 0, 0, 0.12);
291
+ color: #333;
292
+ }
293
+ .xoo-widget-close svg {
294
+ width: 14px;
295
+ height: 14px;
296
+ }
297
+
298
+ /* ---- 遮罩层(仅视觉,不拦截点击) ---- */
299
+ .xoo-widget-overlay {
300
+ position: fixed;
301
+ top: 0;
302
+ left: 0;
303
+ width: 100%;
304
+ height: 100%;
305
+ background: rgba(0, 0, 0, 0.4);
306
+ z-index: ${zIndex - 1};
307
+ transition: opacity 0.2s ease;
308
+ }
309
+ .xoo-widget-overlay--hidden {
310
+ opacity: 0;
311
+ pointer-events: none;
312
+ }
313
+ .xoo-widget-overlay--visible {
314
+ opacity: 1;
315
+ pointer-events: none;
316
+ }
317
+
318
+ @media (max-width: 420px) {
319
+ .xoo-widget-container {
320
+ width: calc(100vw - 16px);
321
+ height: calc(100vh - 100px);
322
+ max-height: none;
323
+ left: 8px !important;
324
+ right: 8px !important;
325
+ border-radius: 12px;
326
+ }
327
+ .xoo-widget-container--center {
328
+ top: 0;
329
+ bottom: 0;
330
+ margin: auto;
331
+ }
332
+ }
333
+ `;
334
+ document.head.appendChild(style);
335
+ }
336
+ function removeStyles() {
337
+ const style = document.getElementById("xoo-widget-styles");
338
+ if (style) style.remove();
339
+ }
340
+ const DEFAULT_WALLET_URL = "https://web.xoowallet.com";
341
+ const DEFAULT_Z_INDEX = 99999;
342
+ class XooWallet {
343
+ constructor(config) {
344
+ this.eventListeners = /* @__PURE__ */ new Map();
345
+ this._isOpen = false;
346
+ this._isLoggedIn = false;
347
+ this._user = null;
348
+ this._ready = false;
349
+ this.config = {
350
+ apiKey: config.apiKey,
351
+ walletUrl: config.walletUrl || DEFAULT_WALLET_URL,
352
+ position: config.position || "bottom-right",
353
+ popupPosition: config.popupPosition || "center",
354
+ autoShow: config.autoShow !== false,
355
+ zIndex: config.zIndex || DEFAULT_Z_INDEX,
356
+ theme: config.theme,
357
+ lang: config.lang
358
+ };
359
+ injectStyles(this.config.zIndex);
360
+ this.fab = new FabButton(this.config.position, () => this.toggle());
361
+ this.fab.create();
362
+ if (!this.config.autoShow) {
363
+ this.fab.hide();
364
+ }
365
+ this.iframe = new IframeManager();
366
+ this.iframe.setMessageHandler((msg) => this.handleIframeMessage(msg));
367
+ this.iframe.setCloseClickHandler(() => this.close());
368
+ this.iframe.create(this.config.walletUrl, this.config.apiKey, this.config.position, {
369
+ lang: this.config.lang,
370
+ theme: this.config.theme,
371
+ popupPosition: this.config.popupPosition
372
+ });
373
+ }
374
+ /**
375
+ * 初始化 SDK(静态工厂方法)
376
+ */
377
+ static init(config) {
378
+ if (!config.apiKey) {
379
+ throw new Error("[XooWallet] apiKey is required");
380
+ }
381
+ return new XooWallet(config);
382
+ }
383
+ // ==================== 控制方法 ====================
384
+ /** 打开钱包界面 */
385
+ open() {
386
+ if (this._isOpen) return;
387
+ this._isOpen = true;
388
+ this.iframe.show();
389
+ this.fab.setOpen(true);
390
+ this.iframe.postCommand({ type: "xoo-cmd-open" });
391
+ }
392
+ /** 关闭/收起钱包 */
393
+ close() {
394
+ if (!this._isOpen) return;
395
+ this._isOpen = false;
396
+ this.iframe.hide();
397
+ this.fab.setOpen(false);
398
+ this.iframe.postCommand({ type: "xoo-cmd-close" });
399
+ }
400
+ /** 切换开关 */
401
+ toggle() {
402
+ if (this._isOpen) {
403
+ this.close();
404
+ } else {
405
+ this.open();
406
+ }
407
+ }
408
+ /** 打开登录界面 */
409
+ login() {
410
+ this.open();
411
+ this.iframe.postCommand({ type: "xoo-cmd-login" });
412
+ }
413
+ /** 登出 */
414
+ logout() {
415
+ this.iframe.postCommand({ type: "xoo-cmd-logout" });
416
+ }
417
+ /** 发起支付 */
418
+ pay(options) {
419
+ this.open();
420
+ this.iframe.postCommand({
421
+ type: "xoo-cmd-pay",
422
+ to: options.to,
423
+ amount: options.amount,
424
+ token: options.token
425
+ });
426
+ }
427
+ // ==================== 状态查询 ====================
428
+ /** 钱包是否已打开 */
429
+ isOpen() {
430
+ return this._isOpen;
431
+ }
432
+ /** 用户是否已登录 */
433
+ isLoggedIn() {
434
+ return this._isLoggedIn;
435
+ }
436
+ /** 获取当前用户信息 */
437
+ getUser() {
438
+ return this._user;
439
+ }
440
+ /** iframe 是否已就绪 */
441
+ isReady() {
442
+ return this._ready;
443
+ }
444
+ // ==================== FAB 按钮控制 ====================
445
+ /** 显示悬浮按钮 */
446
+ show() {
447
+ this.fab.show();
448
+ }
449
+ /** 隐藏悬浮按钮 */
450
+ hide() {
451
+ this.fab.hide();
452
+ if (this._isOpen) this.close();
453
+ }
454
+ // ==================== 事件系统 ====================
455
+ /** 注册事件监听 */
456
+ on(event, handler) {
457
+ if (!this.eventListeners.has(event)) {
458
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
459
+ }
460
+ this.eventListeners.get(event).add(handler);
461
+ }
462
+ /** 移除事件监听 */
463
+ off(event, handler) {
464
+ var _a;
465
+ (_a = this.eventListeners.get(event)) == null ? void 0 : _a.delete(handler);
466
+ }
467
+ // ==================== 销毁 ====================
468
+ /** 销毁 SDK 实例,移除所有 DOM 元素 */
469
+ destroy() {
470
+ this.iframe.destroy();
471
+ this.fab.destroy();
472
+ removeStyles();
473
+ this.eventListeners.clear();
474
+ this._isOpen = false;
475
+ this._isLoggedIn = false;
476
+ this._user = null;
477
+ this._ready = false;
478
+ }
479
+ // ==================== 内部方法 ====================
480
+ emit(event, ...args) {
481
+ const handlers = this.eventListeners.get(event);
482
+ if (!handlers) return;
483
+ for (const handler of handlers) {
484
+ try {
485
+ handler(...args);
486
+ } catch (err) {
487
+ console.error(`[XooWallet] Event handler error (${event}):`, err);
488
+ }
489
+ }
490
+ }
491
+ handleIframeMessage(msg) {
492
+ switch (msg.type) {
493
+ case "xoo-ready":
494
+ this._ready = true;
495
+ this.emit("ready");
496
+ break;
497
+ case "xoo-auth-success":
498
+ this._isLoggedIn = true;
499
+ this._user = msg.user;
500
+ this.emit("auth:success", msg.user);
501
+ break;
502
+ case "xoo-auth-logout":
503
+ this._isLoggedIn = false;
504
+ this._user = null;
505
+ this.emit("auth:logout");
506
+ break;
507
+ case "xoo-tx-success":
508
+ this.emit("tx:success", { txHash: msg.txHash });
509
+ break;
510
+ case "xoo-tx-failed":
511
+ this.emit("tx:failed", { error: msg.error });
512
+ break;
513
+ case "xoo-close":
514
+ this._isOpen = false;
515
+ this.iframe.hide();
516
+ this.fab.setOpen(false);
517
+ break;
518
+ }
519
+ }
520
+ }
521
+ (function() {
522
+ if (typeof globalThis !== "undefined" && globalThis.XooWallet && globalThis.XooWallet.XooWallet) {
523
+ globalThis.XooWallet = globalThis.XooWallet.XooWallet;
524
+ }
525
+ })();
526
+ export {
527
+ XooWallet
528
+ };
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).XooWallet={})}(this,function(t){"use strict";class e{constructor(){this.container=null,this.iframe=null,this.closeBtn=null,this.overlay=null,this.visible=!1,this.popupPosition="center",this.walletOrigin="",this.onMessage=null,this.onCloseClick=null,this.handleMessage=t=>{var e;if(t.origin!==this.walletOrigin)return;const i=t.data;i&&"string"==typeof i.type&&i.type.startsWith("xoo-")&&(i.type.startsWith("xoo-cmd-")||null==(e=this.onMessage)||e.call(this,i))},this.handleCloseClick=()=>{var t;null==(t=this.onCloseClick)||t.call(this)}}create(t,e,i,n){if(this.container)return;this.popupPosition=(null==n?void 0:n.popupPosition)||"center";const o=new URL(t);this.walletOrigin=o.origin,o.searchParams.set("embed","true"),o.searchParams.set("partnerKey",e),(null==n?void 0:n.lang)&&o.searchParams.set("lang",n.lang),(null==n?void 0:n.theme)&&o.searchParams.set("theme",n.theme),"center"===this.popupPosition&&(this.overlay=document.createElement("div"),this.overlay.className="xoo-widget-overlay xoo-widget-overlay--hidden",document.body.appendChild(this.overlay));const s="center"===this.popupPosition?"center":i;this.container=document.createElement("div"),this.container.className=`xoo-widget-container xoo-widget-container--${s} xoo-widget-container--hidden`,this.iframe=document.createElement("iframe"),this.iframe.src=o.toString(),this.iframe.className="xoo-widget-iframe",this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"),this.iframe.setAttribute("allow","clipboard-write"),this.iframe.setAttribute("title","XOO Wallet"),this.container.appendChild(this.iframe),this.closeBtn=document.createElement("button"),this.closeBtn.className="xoo-widget-close",this.closeBtn.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',this.closeBtn.setAttribute("aria-label","Close"),this.closeBtn.addEventListener("click",this.handleCloseClick),this.container.appendChild(this.closeBtn),document.body.appendChild(this.container),window.addEventListener("message",this.handleMessage)}setMessageHandler(t){this.onMessage=t}setCloseClickHandler(t){this.onCloseClick=t}show(){this.container&&(this.visible=!0,this.container.classList.remove("xoo-widget-container--hidden"),this.container.classList.add("xoo-widget-container--visible"),this.overlay&&(this.overlay.classList.remove("xoo-widget-overlay--hidden"),this.overlay.classList.add("xoo-widget-overlay--visible")))}hide(){this.container&&(this.visible=!1,this.container.classList.remove("xoo-widget-container--visible"),this.container.classList.add("xoo-widget-container--hidden"),this.overlay&&(this.overlay.classList.remove("xoo-widget-overlay--visible"),this.overlay.classList.add("xoo-widget-overlay--hidden")))}isVisible(){return this.visible}postCommand(t){var e;(null==(e=this.iframe)?void 0:e.contentWindow)&&this.iframe.contentWindow.postMessage(t,this.walletOrigin)}destroy(){window.removeEventListener("message",this.handleMessage),this.closeBtn&&this.closeBtn.removeEventListener("click",this.handleCloseClick),this.overlay&&(this.overlay.remove(),this.overlay=null),this.container&&(this.container.remove(),this.container=null),this.iframe=null,this.closeBtn=null,this.onMessage=null,this.onCloseClick=null,this.visible=!1}}const i='<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21 7H3a1 1 0 0 0-1 1v11a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8a1 1 0 0 0-1-1zm-1 12H4V9h16v10zm-2-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zM20 3H4a2 2 0 0 0-2 2v1h20V5a2 2 0 0 0-2-2z"/></svg>';class n{constructor(t,e){this.button=null,this.isOpen=!1,this.handleClick=()=>{this.onClick()},this.position=t,this.onClick=e}create(){this.button||(this.button=document.createElement("button"),this.button.className=`xoo-widget-fab xoo-widget-fab--${this.position}`,this.button.innerHTML=i,this.button.setAttribute("aria-label","Open XOO Wallet"),this.button.addEventListener("click",this.handleClick),document.body.appendChild(this.button))}setOpen(t){this.isOpen=t,this.button&&(t?(this.button.innerHTML='<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18.3 5.71a1 1 0 0 0-1.42 0L12 10.59 7.12 5.71a1 1 0 0 0-1.42 1.42L10.59 12l-4.88 4.88a1 1 0 1 0 1.42 1.42L12 13.41l4.88 4.88a1 1 0 0 0 1.42-1.42L13.41 12l4.88-4.88a1 1 0 0 0 0-1.41z"/></svg>',this.button.classList.add("xoo-widget-fab--open"),this.button.setAttribute("aria-label","Close XOO Wallet")):(this.button.innerHTML=i,this.button.classList.remove("xoo-widget-fab--open"),this.button.setAttribute("aria-label","Open XOO Wallet")))}show(){this.button&&(this.button.style.display="flex")}hide(){this.button&&(this.button.style.display="none")}destroy(){this.button&&(this.button.removeEventListener("click",this.handleClick),this.button.remove(),this.button=null)}}class o{constructor(t){this.eventListeners=new Map,this._isOpen=!1,this._isLoggedIn=!1,this._user=null,this._ready=!1,this.config={apiKey:t.apiKey,walletUrl:t.walletUrl||"https://web.xoowallet.com",position:t.position||"bottom-right",popupPosition:t.popupPosition||"center",autoShow:!1!==t.autoShow,zIndex:t.zIndex||99999,theme:t.theme,lang:t.lang},function(t){if(document.getElementById("xoo-widget-styles"))return;const e=document.createElement("style");e.id="xoo-widget-styles",e.textContent=`\n .xoo-widget-fab {\n position: fixed;\n width: 56px;\n height: 56px;\n border-radius: 50%;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n z-index: ${t+1};\n background: linear-gradient(135deg, #3b82f6, #1d4ed8);\n padding: 0;\n outline: none;\n }\n .xoo-widget-fab:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);\n }\n .xoo-widget-fab:active {\n transform: scale(0.95);\n }\n .xoo-widget-fab svg {\n width: 28px;\n height: 28px;\n fill: white;\n transition: transform 0.3s ease;\n }\n .xoo-widget-fab.xoo-widget-fab--open svg {\n transform: rotate(180deg);\n }\n .xoo-widget-fab--bottom-right {\n bottom: 20px;\n right: 20px;\n }\n .xoo-widget-fab--bottom-left {\n bottom: 20px;\n left: 20px;\n }\n\n /* ---- 弹窗容器(包裹 iframe + 关闭按钮) ---- */\n .xoo-widget-container {\n position: fixed;\n width: 375px;\n height: 640px;\n max-height: 80vh;\n border-radius: 16px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n z-index: ${t};\n overflow: hidden;\n transition: opacity 0.2s ease, transform 0.2s ease;\n }\n .xoo-widget-container--center {\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n margin: auto;\n }\n .xoo-widget-container--bottom-right {\n bottom: 88px;\n right: 20px;\n }\n .xoo-widget-container--bottom-left {\n bottom: 88px;\n left: 20px;\n }\n .xoo-widget-container--hidden {\n opacity: 0;\n transform: translateY(10px) scale(0.95);\n pointer-events: none;\n }\n .xoo-widget-container--visible {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n\n /* ---- iframe 填充容器 ---- */\n .xoo-widget-iframe {\n width: 100%;\n height: 100%;\n border: none;\n background: #ffffff;\n }\n\n /* ---- 关闭按钮(容器内,iframe 上层) ---- */\n .xoo-widget-close {\n position: absolute;\n top: 3px;\n right: 3px;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n border: none;\n background: rgba(0, 0, 0, 0.06);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 2;\n color: #999;\n padding: 0;\n outline: none;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .xoo-widget-close:hover {\n background: rgba(0, 0, 0, 0.12);\n color: #333;\n }\n .xoo-widget-close svg {\n width: 14px;\n height: 14px;\n }\n\n /* ---- 遮罩层(仅视觉,不拦截点击) ---- */\n .xoo-widget-overlay {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.4);\n z-index: ${t-1};\n transition: opacity 0.2s ease;\n }\n .xoo-widget-overlay--hidden {\n opacity: 0;\n pointer-events: none;\n }\n .xoo-widget-overlay--visible {\n opacity: 1;\n pointer-events: none;\n }\n\n @media (max-width: 420px) {\n .xoo-widget-container {\n width: calc(100vw - 16px);\n height: calc(100vh - 100px);\n max-height: none;\n left: 8px !important;\n right: 8px !important;\n border-radius: 12px;\n }\n .xoo-widget-container--center {\n top: 0;\n bottom: 0;\n margin: auto;\n }\n }\n `,document.head.appendChild(e)}(this.config.zIndex),this.fab=new n(this.config.position,()=>this.toggle()),this.fab.create(),this.config.autoShow||this.fab.hide(),this.iframe=new e,this.iframe.setMessageHandler(t=>this.handleIframeMessage(t)),this.iframe.setCloseClickHandler(()=>this.close()),this.iframe.create(this.config.walletUrl,this.config.apiKey,this.config.position,{lang:this.config.lang,theme:this.config.theme,popupPosition:this.config.popupPosition})}static init(t){if(!t.apiKey)throw new Error("[XooWallet] apiKey is required");return new o(t)}open(){this._isOpen||(this._isOpen=!0,this.iframe.show(),this.fab.setOpen(!0),this.iframe.postCommand({type:"xoo-cmd-open"}))}close(){this._isOpen&&(this._isOpen=!1,this.iframe.hide(),this.fab.setOpen(!1),this.iframe.postCommand({type:"xoo-cmd-close"}))}toggle(){this._isOpen?this.close():this.open()}login(){this.open(),this.iframe.postCommand({type:"xoo-cmd-login"})}logout(){this.iframe.postCommand({type:"xoo-cmd-logout"})}pay(t){this.open(),this.iframe.postCommand({type:"xoo-cmd-pay",to:t.to,amount:t.amount,token:t.token})}isOpen(){return this._isOpen}isLoggedIn(){return this._isLoggedIn}getUser(){return this._user}isReady(){return this._ready}show(){this.fab.show()}hide(){this.fab.hide(),this._isOpen&&this.close()}on(t,e){this.eventListeners.has(t)||this.eventListeners.set(t,new Set),this.eventListeners.get(t).add(e)}off(t,e){var i;null==(i=this.eventListeners.get(t))||i.delete(e)}destroy(){this.iframe.destroy(),this.fab.destroy(),function(){const t=document.getElementById("xoo-widget-styles");t&&t.remove()}(),this.eventListeners.clear(),this._isOpen=!1,this._isLoggedIn=!1,this._user=null,this._ready=!1}emit(t,...e){const i=this.eventListeners.get(t);if(i)for(const o of i)try{o(...e)}catch(n){console.error(`[XooWallet] Event handler error (${t}):`,n)}}handleIframeMessage(t){switch(t.type){case"xoo-ready":this._ready=!0,this.emit("ready");break;case"xoo-auth-success":this._isLoggedIn=!0,this._user=t.user,this.emit("auth:success",t.user);break;case"xoo-auth-logout":this._isLoggedIn=!1,this._user=null,this.emit("auth:logout");break;case"xoo-tx-success":this.emit("tx:success",{txHash:t.txHash});break;case"xoo-tx-failed":this.emit("tx:failed",{error:t.error});break;case"xoo-close":this._isOpen=!1,this.iframe.hide(),this.fab.setOpen(!1)}}}t.XooWallet=o,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})}),"undefined"!=typeof globalThis&&globalThis.XooWallet&&globalThis.XooWallet.XooWallet&&(globalThis.XooWallet=globalThis.XooWallet.XooWallet);
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@xoocity/wallet-sdk",
3
+ "version": "1.0.0",
4
+ "description": "XOO Wallet embeddable SDK - add crypto wallet functionality to any website",
5
+ "type": "module",
6
+ "main": "./dist/xoowallet-sdk.umd.js",
7
+ "module": "./dist/xoowallet-sdk.es.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/xoowallet-sdk.es.js",
13
+ "require": "./dist/xoowallet-sdk.umd.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "dev": "vite build --watch",
21
+ "build": "vite build && tsc --emitDeclarationOnly",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.6.3",
26
+ "vite": "^6.0.0"
27
+ },
28
+ "keywords": [
29
+ "xoo",
30
+ "wallet",
31
+ "crypto",
32
+ "embed",
33
+ "widget",
34
+ "sdk"
35
+ ],
36
+ "license": "MIT"
37
+ }