@bolloon/bolloon-agent 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,19 @@
1
+ "use strict";
2
+ /**
3
+ * P2P Modal - 纯 TypeScript 版本
4
+ */
1
5
  class P2PModalUI {
2
- modal = null;
3
- overlay = null;
4
- connectedPeers = /* @__PURE__ */ new Map();
5
- constructor() {
6
- this.createModal();
7
- }
8
- createModal() {
9
- this.overlay = document.createElement("div");
10
- this.overlay.className = "p2p-modal-overlay";
11
- this.overlay.style.cssText = `
6
+ modal = null;
7
+ overlay = null;
8
+ connectedPeers = new Map();
9
+ constructor() {
10
+ this.createModal();
11
+ }
12
+ createModal() {
13
+ // 创建遮罩层
14
+ this.overlay = document.createElement('div');
15
+ this.overlay.className = 'p2p-modal-overlay';
16
+ this.overlay.style.cssText = `
12
17
  display: none;
13
18
  position: fixed;
14
19
  inset: 0;
@@ -17,9 +22,10 @@ class P2PModalUI {
17
22
  align-items: center;
18
23
  justify-content: center;
19
24
  `;
20
- this.modal = document.createElement("div");
21
- this.modal.className = "p2p-modal";
22
- this.modal.style.cssText = `
25
+ // 创建弹窗
26
+ this.modal = document.createElement('div');
27
+ this.modal.className = 'p2p-modal';
28
+ this.modal.style.cssText = `
23
29
  width: 90%;
24
30
  max-width: 650px;
25
31
  max-height: 90vh;
@@ -30,72 +36,81 @@ class P2PModalUI {
30
36
  display: flex;
31
37
  flex-direction: column;
32
38
  `;
33
- this.overlay.appendChild(this.modal);
34
- document.body.appendChild(this.overlay);
35
- this.overlay.addEventListener("click", (e) => {
36
- if (e.target === this.overlay) this.hide();
37
- });
38
- this.render();
39
- }
40
- async render() {
41
- if (!this.modal) return;
42
- await this.loadConnectionHistory();
43
- let identityHtml = '<div class="p2p-loading">\u52A0\u8F7D\u4E2D...</div>';
44
- let identityData = { name: "\u672A\u77E5", did: "\u672A\u77E5", cid: "\u672A\u77E5" };
45
- try {
46
- const irohResp = await fetch("/api/iroh/init", { method: "POST" });
47
- const irohData = await irohResp.json();
48
- if (irohData.cid) {
49
- identityData = {
50
- name: irohData.name || irohData.name?.split("-").slice(-1)[0] || "Bolloon",
51
- did: irohData.did || "\u672A\u77E5",
52
- cid: irohData.cid || "\u672A\u77E5",
53
- nodeId: irohData.irohNodeId ? irohData.irohNodeId.substring(0, 20) + "..." : "\u672A\u77E5"
54
- };
55
- } else {
56
- const resp = await fetch("/api/identity");
57
- identityData = await resp.json();
58
- identityData.cid = identityData.cid || "\u672A\u521D\u59CB\u5316";
59
- identityData.nodeId = identityData.nodeId || "\u672A\u77E5";
60
- }
61
- identityHtml = `
39
+ this.overlay.appendChild(this.modal);
40
+ document.body.appendChild(this.overlay);
41
+ // 点击遮罩关闭
42
+ this.overlay.addEventListener('click', (e) => {
43
+ if (e.target === this.overlay)
44
+ this.hide();
45
+ });
46
+ this.render();
47
+ }
48
+ async render() {
49
+ if (!this.modal)
50
+ return;
51
+ // 加载连接历史
52
+ await this.loadConnectionHistory();
53
+ // 获取身份信息
54
+ let identityHtml = '<div class="p2p-loading">加载中...</div>';
55
+ let identityData = { name: '未知', did: '未知', cid: '未知' };
56
+ try {
57
+ // 尝试获取 iroh 身份(包含 CID)
58
+ const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
59
+ const irohData = await irohResp.json();
60
+ if (irohData.cid) {
61
+ identityData = {
62
+ name: irohData.name || irohData.name?.split('-').slice(-1)[0] || 'Bolloon',
63
+ did: irohData.did || '未知',
64
+ cid: irohData.cid || '未知',
65
+ nodeId: irohData.irohNodeId ? irohData.irohNodeId.substring(0, 20) + '...' : '未知'
66
+ };
67
+ }
68
+ else {
69
+ // 回退到 /api/identity
70
+ const resp = await fetch('/api/identity');
71
+ identityData = await resp.json();
72
+ identityData.cid = identityData.cid || '未初始化';
73
+ identityData.nodeId = identityData.nodeId || '未知';
74
+ }
75
+ identityHtml = `
62
76
  <div class="p2p-section">
63
- <h3>\u8EAB\u4EFD\u4FE1\u606F</h3>
77
+ <h3>身份信息</h3>
64
78
  <div class="p2p-info-row">
65
- <span class="label">\u540D\u79F0:</span>
66
- <span class="value">${this.escapeHtml(identityData.name || "\u672A\u77E5")}</span>
79
+ <span class="label">名称:</span>
80
+ <span class="value">${this.escapeHtml(identityData.name || '未知')}</span>
67
81
  </div>
68
82
  <div class="p2p-info-row">
69
83
  <span class="label">CID:</span>
70
- <span class="value mono">${this.escapeHtml(identityData.cid || "\u672A\u77E5")}</span>
84
+ <span class="value mono">${this.escapeHtml(identityData.cid || '未知')}</span>
71
85
  </div>
72
86
  <div class="p2p-info-row">
73
87
  <span class="label">DID:</span>
74
- <span class="value mono">${this.escapeHtml(identityData.did || "\u672A\u77E5")}</span>
88
+ <span class="value mono">${this.escapeHtml(identityData.did || '未知')}</span>
75
89
  </div>
76
90
  <div class="p2p-info-row">
77
91
  <span class="label">Node ID:</span>
78
- <span class="value mono">${this.escapeHtml(identityData.nodeId || "\u672A\u77E5")}</span>
92
+ <span class="value mono">${this.escapeHtml(identityData.nodeId || '未知')}</span>
79
93
  </div>
80
94
  <div class="p2p-actions">
81
- <button class="p2p-btn-secondary" id="p2p-copy-cid-btn">\u590D\u5236 CID</button>
82
- <button class="p2p-btn-secondary" id="p2p-copy-did-btn">\u590D\u5236 DID</button>
83
- <button class="p2p-btn-secondary" id="p2p-copy-nodeid-btn">\u590D\u5236 Node ID</button>
95
+ <button class="p2p-btn-secondary" id="p2p-copy-cid-btn">复制 CID</button>
96
+ <button class="p2p-btn-secondary" id="p2p-copy-did-btn">复制 DID</button>
97
+ <button class="p2p-btn-secondary" id="p2p-copy-nodeid-btn">复制 Node ID</button>
84
98
  </div>
85
99
  </div>
86
100
  `;
87
- } catch {
88
- identityHtml = '<div class="p2p-error">\u65E0\u6CD5\u52A0\u8F7D\u8EAB\u4EFD\u4FE1\u606F</div>';
89
- }
90
- this.modal.innerHTML = `
101
+ }
102
+ catch {
103
+ identityHtml = '<div class="p2p-error">无法加载身份信息</div>';
104
+ }
105
+ this.modal.innerHTML = `
91
106
  <div class="p2p-header">
92
- <h2>P2P \u7F51\u7EDC</h2>
93
- <button class="p2p-close" id="p2p-close-btn">\xD7</button>
107
+ <h2>P2P 网络</h2>
108
+ <button class="p2p-close" id="p2p-close-btn">×</button>
94
109
  </div>
95
110
  <div class="p2p-tabs">
96
- <button class="p2p-tab active" data-tab="identity">\u8EAB\u4EFD</button>
97
- <button class="p2p-tab" data-tab="connect">\u8FDE\u63A5</button>
98
- <button class="p2p-tab" data-tab="messages">\u6D88\u606F</button>
111
+ <button class="p2p-tab active" data-tab="identity">身份</button>
112
+ <button class="p2p-tab" data-tab="connect">连接</button>
113
+ <button class="p2p-tab" data-tab="messages">消息</button>
99
114
  </div>
100
115
  <div class="p2p-content">
101
116
  <div class="p2p-tab-content active" data-tab="identity">
@@ -103,187 +118,207 @@ class P2PModalUI {
103
118
  </div>
104
119
  <div class="p2p-tab-content" data-tab="connect">
105
120
  <div class="p2p-section">
106
- <h3>\u8FDE\u63A5\u5230\u8282\u70B9</h3>
121
+ <h3>连接到节点</h3>
107
122
  <div class="p2p-connect-form">
108
- <input type="text" id="p2p-connect-input" placeholder="\u8F93\u5165\u8282\u70B9 ID \u6216 CID">
109
- <button class="p2p-btn-primary" id="p2p-connect-btn">\u8FDE\u63A5</button>
123
+ <input type="text" id="p2p-connect-input" placeholder="输入节点 ID CID">
124
+ <button class="p2p-btn-primary" id="p2p-connect-btn">连接</button>
110
125
  </div>
111
126
  <div id="p2p-connect-result" class="p2p-result"></div>
112
127
  </div>
113
128
  <div class="p2p-section">
114
- <h3>\u5DF2\u8FDE\u63A5\u7684\u8282\u70B9</h3>
129
+ <h3>已连接的节点</h3>
115
130
  <div id="p2p-connected-peers" class="p2p-connected-peers">
116
- \u6682\u65E0\u8FDE\u63A5
131
+ 暂无连接
117
132
  </div>
118
133
  </div>
119
134
  </div>
120
135
  <div class="p2p-tab-content" data-tab="messages">
121
136
  <div class="p2p-section">
122
- <h3>\u6536\u5230\u7684\u6D88\u606F</h3>
137
+ <h3>收到的消息</h3>
123
138
  <div id="p2p-messages-list" class="p2p-messages">
124
- \u6682\u65E0\u6D88\u606F
139
+ 暂无消息
125
140
  </div>
126
141
  </div>
127
142
  </div>
128
143
  </div>
129
144
  `;
130
- this.bindEvents();
131
- }
132
- bindEvents() {
133
- if (!this.modal) return;
134
- const closeBtn = this.modal.querySelector("#p2p-close-btn");
135
- closeBtn?.addEventListener("click", () => this.hide());
136
- this.modal.querySelectorAll(".p2p-tab").forEach((tab) => {
137
- tab.addEventListener("click", (e) => {
138
- const target = e.target;
139
- const tabName = target.dataset.tab;
140
- if (tabName) this.switchTab(tabName);
141
- });
142
- });
143
- const copyCidBtn = this.modal.querySelector("#p2p-copy-cid-btn");
144
- copyCidBtn?.addEventListener("click", async () => {
145
- try {
146
- const irohResp = await fetch("/api/iroh/init", { method: "POST" });
147
- const data = await irohResp.json();
148
- if (data.cid) {
149
- await navigator.clipboard.writeText(data.cid);
150
- this.showToast("CID \u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F");
151
- }
152
- } catch (e) {
153
- console.error("\u590D\u5236\u5931\u8D25:", e);
154
- }
155
- });
156
- const copyDidBtn = this.modal.querySelector("#p2p-copy-did-btn");
157
- copyDidBtn?.addEventListener("click", async () => {
158
- try {
159
- const irohResp = await fetch("/api/iroh/init", { method: "POST" });
160
- const data = await irohResp.json();
161
- if (data.did) {
162
- await navigator.clipboard.writeText(data.did);
163
- this.showToast("DID \u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F");
145
+ // 绑定事件
146
+ this.bindEvents();
147
+ }
148
+ bindEvents() {
149
+ if (!this.modal)
150
+ return;
151
+ // 关闭按钮
152
+ const closeBtn = this.modal.querySelector('#p2p-close-btn');
153
+ closeBtn?.addEventListener('click', () => this.hide());
154
+ // 标签切换
155
+ this.modal.querySelectorAll('.p2p-tab').forEach(tab => {
156
+ tab.addEventListener('click', (e) => {
157
+ const target = e.target;
158
+ const tabName = target.dataset.tab;
159
+ if (tabName)
160
+ this.switchTab(tabName);
161
+ });
162
+ });
163
+ // 复制 CID
164
+ const copyCidBtn = this.modal.querySelector('#p2p-copy-cid-btn');
165
+ copyCidBtn?.addEventListener('click', async () => {
166
+ try {
167
+ const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
168
+ const data = await irohResp.json();
169
+ if (data.cid) {
170
+ await navigator.clipboard.writeText(data.cid);
171
+ this.showToast('CID 已复制到剪贴板');
172
+ }
173
+ }
174
+ catch (e) {
175
+ console.error('复制失败:', e);
176
+ }
177
+ });
178
+ // 复制 DID
179
+ const copyDidBtn = this.modal.querySelector('#p2p-copy-did-btn');
180
+ copyDidBtn?.addEventListener('click', async () => {
181
+ try {
182
+ const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
183
+ const data = await irohResp.json();
184
+ if (data.did) {
185
+ await navigator.clipboard.writeText(data.did);
186
+ this.showToast('DID 已复制到剪贴板');
187
+ }
188
+ }
189
+ catch (e) {
190
+ console.error('复制失败:', e);
191
+ }
192
+ });
193
+ // 复制 Node ID
194
+ const copyNodeIdBtn = this.modal.querySelector('#p2p-copy-nodeid-btn');
195
+ copyNodeIdBtn?.addEventListener('click', async () => {
196
+ try {
197
+ const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
198
+ const data = await irohResp.json();
199
+ if (data.irohNodeId) {
200
+ await navigator.clipboard.writeText(data.irohNodeId);
201
+ this.showToast('Node ID 已复制到剪贴板');
202
+ }
203
+ }
204
+ catch (e) {
205
+ console.error('复制失败:', e);
206
+ }
207
+ });
208
+ // 连接按钮
209
+ const connectBtn = this.modal.querySelector('#p2p-connect-btn');
210
+ connectBtn?.addEventListener('click', () => this.handleConnect());
211
+ }
212
+ switchTab(tabName) {
213
+ if (!this.modal)
214
+ return;
215
+ this.modal.querySelectorAll('.p2p-tab').forEach(tab => {
216
+ tab.classList.toggle('active', tab.getAttribute('data-tab') === tabName);
217
+ });
218
+ this.modal.querySelectorAll('.p2p-tab-content').forEach(content => {
219
+ content.classList.toggle('active', content.getAttribute('data-tab') === tabName);
220
+ });
221
+ }
222
+ async handleConnect() {
223
+ const input = document.getElementById('p2p-connect-input');
224
+ const result = document.getElementById('p2p-connect-result');
225
+ if (!input || !result)
226
+ return;
227
+ const cid = input.value.trim();
228
+ if (!cid)
229
+ return;
230
+ result.innerHTML = '<div class="p2p-loading">连接中...</div>';
231
+ result.className = 'p2p-result';
232
+ try {
233
+ const resp = await fetch('/api/iroh/connect', {
234
+ method: 'POST',
235
+ headers: { 'Content-Type': 'application/json' },
236
+ body: JSON.stringify({ cid })
237
+ });
238
+ const data = await resp.json();
239
+ if (data.ok) {
240
+ result.innerHTML = `<div class="p2p-success">连接成功!节点: ${this.escapeHtml(data.nodeName || cid.substring(0, 16))}...</div>`;
241
+ result.className = 'p2p-result success';
242
+ // 清空输入框
243
+ input.value = '';
244
+ // 保存已连接节点到后端
245
+ try {
246
+ await fetch('/api/p2p/history', {
247
+ method: 'POST',
248
+ headers: { 'Content-Type': 'application/json' },
249
+ body: JSON.stringify({
250
+ cid: cid, // 输入的 CID 或 Node ID
251
+ irohNodeId: data.targetNodeId,
252
+ did: data.targetNodeId, // 用 Node ID 作为 DID
253
+ name: data.nodeName || cid.substring(0, 16)
254
+ })
255
+ });
256
+ }
257
+ catch (e) {
258
+ console.error('保存连接失败:', e);
259
+ }
260
+ }
261
+ else {
262
+ result.innerHTML = `<div class="p2p-error">${this.escapeHtml(data.error || '连接失败')}</div>`;
263
+ result.className = 'p2p-result error';
264
+ }
164
265
  }
165
- } catch (e) {
166
- console.error("\u590D\u5236\u5931\u8D25:", e);
167
- }
168
- });
169
- const copyNodeIdBtn = this.modal.querySelector("#p2p-copy-nodeid-btn");
170
- copyNodeIdBtn?.addEventListener("click", async () => {
171
- try {
172
- const irohResp = await fetch("/api/iroh/init", { method: "POST" });
173
- const data = await irohResp.json();
174
- if (data.irohNodeId) {
175
- await navigator.clipboard.writeText(data.irohNodeId);
176
- this.showToast("Node ID \u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F");
266
+ catch (e) {
267
+ const msg = e instanceof Error ? e.message : '连接失败';
268
+ result.innerHTML = `<div class="p2p-error">${this.escapeHtml(msg)}</div>`;
269
+ result.className = 'p2p-result error';
177
270
  }
178
- } catch (e) {
179
- console.error("\u590D\u5236\u5931\u8D25:", e);
180
- }
181
- });
182
- const connectBtn = this.modal.querySelector("#p2p-connect-btn");
183
- connectBtn?.addEventListener("click", () => this.handleConnect());
184
- }
185
- switchTab(tabName) {
186
- if (!this.modal) return;
187
- this.modal.querySelectorAll(".p2p-tab").forEach((tab) => {
188
- tab.classList.toggle("active", tab.getAttribute("data-tab") === tabName);
189
- });
190
- this.modal.querySelectorAll(".p2p-tab-content").forEach((content) => {
191
- content.classList.toggle("active", content.getAttribute("data-tab") === tabName);
192
- });
193
- }
194
- async handleConnect() {
195
- const input = document.getElementById("p2p-connect-input");
196
- const result = document.getElementById("p2p-connect-result");
197
- if (!input || !result) return;
198
- const cid = input.value.trim();
199
- if (!cid) return;
200
- result.innerHTML = '<div class="p2p-loading">\u8FDE\u63A5\u4E2D...</div>';
201
- result.className = "p2p-result";
202
- try {
203
- const resp = await fetch("/api/iroh/connect", {
204
- method: "POST",
205
- headers: { "Content-Type": "application/json" },
206
- body: JSON.stringify({ cid })
207
- });
208
- const data = await resp.json();
209
- if (data.ok) {
210
- result.innerHTML = `<div class="p2p-success">\u8FDE\u63A5\u6210\u529F\uFF01\u8282\u70B9: ${this.escapeHtml(data.nodeName || cid.substring(0, 16))}...</div>`;
211
- result.className = "p2p-result success";
212
- input.value = "";
271
+ }
272
+ async loadConnectionHistory() {
213
273
  try {
214
- await fetch("/api/p2p/history", {
215
- method: "POST",
216
- headers: { "Content-Type": "application/json" },
217
- body: JSON.stringify({
218
- cid,
219
- // 输入的 CID 或 Node ID
220
- irohNodeId: data.targetNodeId,
221
- did: data.targetNodeId,
222
- // 用 Node ID 作为 DID
223
- name: data.nodeName || cid.substring(0, 16)
224
- })
225
- });
226
- } catch (e) {
227
- console.error("\u4FDD\u5B58\u8FDE\u63A5\u5931\u8D25:", e);
274
+ const resp = await fetch('/api/p2p/history');
275
+ const history = await resp.json();
276
+ this.connectedPeers.clear();
277
+ for (const entry of history) {
278
+ this.connectedPeers.set(entry.irohNodeId || entry.did, {
279
+ nodeId: entry.irohNodeId || entry.did,
280
+ name: entry.name || 'Unknown',
281
+ time: entry.lastConnectedAt || Date.now()
282
+ });
283
+ }
284
+ this.updateConnectedPeersDisplay();
285
+ }
286
+ catch (e) {
287
+ console.error('加载连接历史失败:', e);
228
288
  }
229
- } else {
230
- result.innerHTML = `<div class="p2p-error">${this.escapeHtml(data.error || "\u8FDE\u63A5\u5931\u8D25")}</div>`;
231
- result.className = "p2p-result error";
232
- }
233
- } catch (e) {
234
- const msg = e instanceof Error ? e.message : "\u8FDE\u63A5\u5931\u8D25";
235
- result.innerHTML = `<div class="p2p-error">${this.escapeHtml(msg)}</div>`;
236
- result.className = "p2p-result error";
237
- }
238
- }
239
- async loadConnectionHistory() {
240
- try {
241
- const resp = await fetch("/api/p2p/history");
242
- const history = await resp.json();
243
- this.connectedPeers.clear();
244
- for (const entry of history) {
245
- this.connectedPeers.set(entry.irohNodeId || entry.did, {
246
- nodeId: entry.irohNodeId || entry.did,
247
- name: entry.name || "Unknown",
248
- time: entry.lastConnectedAt || Date.now()
249
- });
250
- }
251
- this.updateConnectedPeersDisplay();
252
- } catch (e) {
253
- console.error("\u52A0\u8F7D\u8FDE\u63A5\u5386\u53F2\u5931\u8D25:", e);
254
- }
255
- }
256
- updateConnectedPeersDisplay() {
257
- const container = document.getElementById("p2p-connected-peers");
258
- if (!container) return;
259
- if (this.connectedPeers.size === 0) {
260
- container.innerHTML = '<div class="p2p-empty">\u6682\u65E0\u8FDE\u63A5</div>';
261
- return;
262
289
  }
263
- let html = "";
264
- for (const [nodeId, info] of this.connectedPeers) {
265
- const shortId = nodeId.substring(0, 16) + "...";
266
- const time = new Date(info.time).toLocaleTimeString();
267
- html += `
290
+ updateConnectedPeersDisplay() {
291
+ const container = document.getElementById('p2p-connected-peers');
292
+ if (!container)
293
+ return;
294
+ if (this.connectedPeers.size === 0) {
295
+ container.innerHTML = '<div class="p2p-empty">暂无连接</div>';
296
+ return;
297
+ }
298
+ let html = '';
299
+ for (const [nodeId, info] of this.connectedPeers) {
300
+ const shortId = nodeId.substring(0, 16) + '...';
301
+ const time = new Date(info.time).toLocaleTimeString();
302
+ html += `
268
303
  <div class="p2p-peer-item">
269
304
  <span class="p2p-peer-name">${this.escapeHtml(info.name || shortId)}</span>
270
305
  <span class="p2p-peer-id">${shortId}</span>
271
306
  <span class="p2p-peer-time">${time}</span>
272
307
  </div>
273
308
  `;
309
+ }
310
+ container.innerHTML = html;
274
311
  }
275
- container.innerHTML = html;
276
- }
277
- escapeHtml(str) {
278
- const div = document.createElement("div");
279
- div.textContent = str;
280
- return div.innerHTML;
281
- }
282
- showToast(message) {
283
- const toast = document.createElement("div");
284
- toast.className = "p2p-toast";
285
- toast.textContent = message;
286
- toast.style.cssText = `
312
+ escapeHtml(str) {
313
+ const div = document.createElement('div');
314
+ div.textContent = str;
315
+ return div.innerHTML;
316
+ }
317
+ showToast(message) {
318
+ const toast = document.createElement('div');
319
+ toast.className = 'p2p-toast';
320
+ toast.textContent = message;
321
+ toast.style.cssText = `
287
322
  position: fixed;
288
323
  bottom: 24px;
289
324
  left: 50%;
@@ -296,22 +331,23 @@ class P2PModalUI {
296
331
  font-size: 14px;
297
332
  z-index: 2000;
298
333
  `;
299
- document.body.appendChild(toast);
300
- setTimeout(() => toast.remove(), 2e3);
301
- }
302
- show() {
303
- if (this.overlay) {
304
- this.overlay.style.display = "flex";
305
- this.render();
334
+ document.body.appendChild(toast);
335
+ setTimeout(() => toast.remove(), 2000);
306
336
  }
307
- }
308
- hide() {
309
- if (this.overlay) {
310
- this.overlay.style.display = "none";
337
+ show() {
338
+ if (this.overlay) {
339
+ this.overlay.style.display = 'flex';
340
+ this.render();
341
+ }
342
+ }
343
+ hide() {
344
+ if (this.overlay) {
345
+ this.overlay.style.display = 'none';
346
+ }
311
347
  }
312
- }
313
348
  }
314
- const style = document.createElement("style");
349
+ // 样式
350
+ const style = document.createElement('style');
315
351
  style.textContent = `
316
352
  .p2p-modal-overlay { display: none; }
317
353
  .p2p-header {
@@ -500,8 +536,10 @@ style.textContent = `
500
536
  }
501
537
  `;
502
538
  document.head.appendChild(style);
539
+ // 全局实例
503
540
  const p2pModal = new P2PModalUI();
541
+ // 导出到 window
504
542
  window.p2pModal = p2pModal;
505
543
  window.showP2PModal = () => p2pModal.show();
506
544
  window.hideP2PModal = () => p2pModal.hide();
507
- console.log("[P2P Modal] \u5DF2\u521D\u59CB\u5316");
545
+ console.log('[P2P Modal] 已初始化');