@tangle-network/blueprint-ui 0.5.5 → 0.5.7
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/dist/chunk-37ADATBT.js +55 -0
- package/dist/chunk-37ADATBT.js.map +1 -0
- package/dist/chunk-F2QBCGUW.js +1582 -0
- package/dist/chunk-F2QBCGUW.js.map +1 -0
- package/dist/chunk-TM5ROMDV.js +57 -0
- package/dist/chunk-TM5ROMDV.js.map +1 -0
- package/dist/components.d.ts +195 -0
- package/dist/components.js +1168 -0
- package/dist/components.js.map +1 -0
- package/dist/detectParentOrigin-BYruoIdc.d.ts +26 -0
- package/dist/iframe/index.d.ts +146 -0
- package/dist/iframe/index.js +607 -0
- package/dist/iframe/index.js.map +1 -0
- package/dist/iframe/testing-index.d.ts +82 -0
- package/dist/iframe/testing-index.js +560 -0
- package/dist/iframe/testing-index.js.map +1 -0
- package/dist/index.d.ts +8620 -0
- package/dist/index.js +870 -0
- package/dist/index.js.map +1 -0
- package/dist/parentBridgeProtocol-BSgLXg9g.d.ts +204 -0
- package/dist/preset.d.ts +60 -0
- package/dist/preset.js +7 -0
- package/dist/preset.js.map +1 -0
- package/dist/styles.css +568 -0
- package/dist/tangleIframeClient-C7NFG_Dw.d.ts +133 -0
- package/dist/useRegistrationCommand-Df1mvvwE.d.ts +151 -0
- package/dist/wallet/index.d.ts +134 -0
- package/dist/wallet/index.js +472 -0
- package/dist/wallet/index.js.map +1 -0
- package/package.json +1 -1
- package/src/components.ts +0 -3
- package/src/index.ts +0 -6
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NO_WALLET_ADDRESS,
|
|
3
|
+
TANGLE_CLOUD_ORIGINS_DEFAULT,
|
|
4
|
+
TANGLE_IFRAME_PROTOCOL_VERSION,
|
|
5
|
+
detectTangleCloudParentOrigin,
|
|
6
|
+
makeCorrelationId
|
|
7
|
+
} from "../chunk-TM5ROMDV.js";
|
|
8
|
+
|
|
9
|
+
// src/iframe/TangleIframeProvider.tsx
|
|
10
|
+
import {
|
|
11
|
+
createContext,
|
|
12
|
+
useContext,
|
|
13
|
+
useEffect,
|
|
14
|
+
useMemo,
|
|
15
|
+
useRef,
|
|
16
|
+
useState
|
|
17
|
+
} from "react";
|
|
18
|
+
|
|
19
|
+
// src/iframe/tangleIframeClient.ts
|
|
20
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
|
|
21
|
+
var CONNECT_REQUEST_TIMEOUT_MS = 3e5;
|
|
22
|
+
var HANDSHAKE_RETRY_MS = 250;
|
|
23
|
+
var HANDSHAKE_RETRY_BUDGET_MS = 1e4;
|
|
24
|
+
var NULL_WALLET = {
|
|
25
|
+
address: null,
|
|
26
|
+
chainId: null,
|
|
27
|
+
isConnected: false
|
|
28
|
+
};
|
|
29
|
+
var NULL_SERVICE = {
|
|
30
|
+
blueprintId: null,
|
|
31
|
+
serviceId: null,
|
|
32
|
+
operators: [],
|
|
33
|
+
jobs: [],
|
|
34
|
+
mode: null,
|
|
35
|
+
chain: null
|
|
36
|
+
};
|
|
37
|
+
var TangleIframeClient = class {
|
|
38
|
+
constructor(options) {
|
|
39
|
+
this.options = options;
|
|
40
|
+
}
|
|
41
|
+
options;
|
|
42
|
+
wallet = NULL_WALLET;
|
|
43
|
+
service = NULL_SERVICE;
|
|
44
|
+
handshakeAcked = false;
|
|
45
|
+
handshakeWaiters = [];
|
|
46
|
+
installed = false;
|
|
47
|
+
handshakeRetry = null;
|
|
48
|
+
listeners = {
|
|
49
|
+
wallet: /* @__PURE__ */ new Set(),
|
|
50
|
+
service: /* @__PURE__ */ new Set(),
|
|
51
|
+
job: /* @__PURE__ */ new Set()
|
|
52
|
+
};
|
|
53
|
+
pendingJobs = /* @__PURE__ */ new Map();
|
|
54
|
+
/** Wire the global message listener + initial handshake. Idempotent. */
|
|
55
|
+
install() {
|
|
56
|
+
if (this.installed || typeof window === "undefined") return;
|
|
57
|
+
this.installed = true;
|
|
58
|
+
window.addEventListener("message", this.handleParentMessage);
|
|
59
|
+
this.postHandshake();
|
|
60
|
+
if (this.handshakeRetry === null) {
|
|
61
|
+
let elapsed = 0;
|
|
62
|
+
this.handshakeRetry = setInterval(() => {
|
|
63
|
+
elapsed += HANDSHAKE_RETRY_MS;
|
|
64
|
+
if (this.handshakeAcked || elapsed >= HANDSHAKE_RETRY_BUDGET_MS) {
|
|
65
|
+
this.clearHandshakeRetry();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.postHandshake();
|
|
69
|
+
}, HANDSHAKE_RETRY_MS);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
uninstall() {
|
|
73
|
+
if (!this.installed || typeof window === "undefined") return;
|
|
74
|
+
this.installed = false;
|
|
75
|
+
this.clearHandshakeRetry();
|
|
76
|
+
window.removeEventListener("message", this.handleParentMessage);
|
|
77
|
+
for (const [, pending] of this.pendingJobs) {
|
|
78
|
+
clearTimeout(pending.timer);
|
|
79
|
+
pending.reject(new Error("Tangle iframe client uninstalled"));
|
|
80
|
+
}
|
|
81
|
+
this.pendingJobs.clear();
|
|
82
|
+
}
|
|
83
|
+
// ── State accessors ─────────────────────────────────────────────────────
|
|
84
|
+
getWallet() {
|
|
85
|
+
return this.wallet;
|
|
86
|
+
}
|
|
87
|
+
getService() {
|
|
88
|
+
return this.service;
|
|
89
|
+
}
|
|
90
|
+
// ── Subscription API (used by React hooks) ──────────────────────────────
|
|
91
|
+
subscribe(event, listener) {
|
|
92
|
+
this.listeners[event].add(listener);
|
|
93
|
+
return () => {
|
|
94
|
+
this.listeners[event].delete(listener);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// ── Wallet operations ───────────────────────────────────────────────────
|
|
98
|
+
/**
|
|
99
|
+
* Ask the parent dapp to connect a wallet — opening its connect modal if
|
|
100
|
+
* none is connected. The iframe is sandboxed and cannot reach a wallet
|
|
101
|
+
* itself, so connection is always delegated to the parent. Resolves with the
|
|
102
|
+
* connected address (or `null` if the user dismissed without connecting).
|
|
103
|
+
*
|
|
104
|
+
* Uses a long timeout (the user is interacting with a modal). Already-
|
|
105
|
+
* connected parents resolve immediately.
|
|
106
|
+
*/
|
|
107
|
+
async connect() {
|
|
108
|
+
await this.ensureBootstrapped();
|
|
109
|
+
const data = await this.dispatchWallet(
|
|
110
|
+
"tangle.app.requestConnect",
|
|
111
|
+
{},
|
|
112
|
+
CONNECT_REQUEST_TIMEOUT_MS
|
|
113
|
+
);
|
|
114
|
+
const { account, chainId } = data;
|
|
115
|
+
const address = account === NO_WALLET_ADDRESS ? null : account;
|
|
116
|
+
this.updateWallet({ address, chainId, isConnected: address !== null });
|
|
117
|
+
return address;
|
|
118
|
+
}
|
|
119
|
+
async signMessage(message) {
|
|
120
|
+
await this.ensureBootstrapped();
|
|
121
|
+
return this.dispatchWallet("tangle.app.signMessage", {
|
|
122
|
+
chainId: this.wallet.chainId ?? 0,
|
|
123
|
+
message
|
|
124
|
+
}).then((data) => data.signature);
|
|
125
|
+
}
|
|
126
|
+
async sendTransaction(tx) {
|
|
127
|
+
await this.ensureBootstrapped();
|
|
128
|
+
return this.dispatchWallet("tangle.app.signTransaction", {
|
|
129
|
+
chainId: this.wallet.chainId ?? 0,
|
|
130
|
+
to: tx.to,
|
|
131
|
+
data: tx.data,
|
|
132
|
+
...tx.value !== void 0 ? { value: tx.value.toString(10) } : {}
|
|
133
|
+
}).then((data) => data.txHash);
|
|
134
|
+
}
|
|
135
|
+
async switchChain(chainId) {
|
|
136
|
+
await this.ensureBootstrapped();
|
|
137
|
+
return this.dispatchWallet("tangle.app.switchChain", { chainId }).then(
|
|
138
|
+
(data) => data.chainId
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* EIP-712 typed-data signing. The parent renders the typed-data fields in
|
|
143
|
+
* its approval modal; the user audits what they're signing. Use for
|
|
144
|
+
* operator envelopes, off-chain attestations, anything that needs a
|
|
145
|
+
* signature outside the standard blueprint-job RFQ flow.
|
|
146
|
+
*
|
|
147
|
+
* Shape mirrors viem's `signTypedData` argument. Do not include the
|
|
148
|
+
* EIP712Domain entry in `types` — the parent injects it from `domain`.
|
|
149
|
+
*/
|
|
150
|
+
async signTypedData(args) {
|
|
151
|
+
await this.ensureBootstrapped();
|
|
152
|
+
return this.dispatchWallet("tangle.app.signTypedData", {
|
|
153
|
+
chainId: this.wallet.chainId ?? 0,
|
|
154
|
+
domain: args.domain,
|
|
155
|
+
types: args.types,
|
|
156
|
+
primaryType: args.primaryType,
|
|
157
|
+
message: args.message
|
|
158
|
+
}).then((data) => data.signature);
|
|
159
|
+
}
|
|
160
|
+
// ── Job invocation ──────────────────────────────────────────────────────
|
|
161
|
+
/**
|
|
162
|
+
* Invoke a blueprint job. Returns a Promise that resolves on terminal
|
|
163
|
+
* status (`success` or `error`); subscribe to the `job` event for
|
|
164
|
+
* intermediate streaming chunks.
|
|
165
|
+
*
|
|
166
|
+
* Streaming opt-in: pass `stream: true` if the publisher's job emits
|
|
167
|
+
* chunks (LLM generation, video encoding). One-shot jobs (embeddings,
|
|
168
|
+
* classifications) skip the streaming machinery.
|
|
169
|
+
*/
|
|
170
|
+
async callJob(args) {
|
|
171
|
+
await this.ensureBootstrapped();
|
|
172
|
+
const correlationId = makeCorrelationId("tangle.app.callJob");
|
|
173
|
+
const timeout = this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
174
|
+
return new Promise((resolve, reject) => {
|
|
175
|
+
const invocation = {
|
|
176
|
+
correlationId,
|
|
177
|
+
status: "pending",
|
|
178
|
+
chunks: []
|
|
179
|
+
};
|
|
180
|
+
const timer = setTimeout(() => {
|
|
181
|
+
this.pendingJobs.delete(correlationId);
|
|
182
|
+
reject(
|
|
183
|
+
bridgeError(4900, `Job did not respond within ${timeout}ms`)
|
|
184
|
+
);
|
|
185
|
+
}, timeout);
|
|
186
|
+
this.pendingJobs.set(correlationId, {
|
|
187
|
+
resolve,
|
|
188
|
+
reject,
|
|
189
|
+
timer,
|
|
190
|
+
invocation
|
|
191
|
+
});
|
|
192
|
+
const message = {
|
|
193
|
+
kind: "tangle.app.callJob",
|
|
194
|
+
correlationId,
|
|
195
|
+
jobIndex: args.jobIndex,
|
|
196
|
+
inputs: args.inputs,
|
|
197
|
+
...args.stream !== void 0 ? { stream: args.stream } : {}
|
|
198
|
+
};
|
|
199
|
+
this.postToParent(message);
|
|
200
|
+
this.emit("job", invocation);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// ── Internals ───────────────────────────────────────────────────────────
|
|
204
|
+
clearHandshakeRetry() {
|
|
205
|
+
if (this.handshakeRetry !== null) {
|
|
206
|
+
clearInterval(this.handshakeRetry);
|
|
207
|
+
this.handshakeRetry = null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
postHandshake() {
|
|
211
|
+
this.postToParent({
|
|
212
|
+
kind: "tangle.app.handshake",
|
|
213
|
+
appId: this.options.appId,
|
|
214
|
+
version: TANGLE_IFRAME_PROTOCOL_VERSION
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
postToParent(message) {
|
|
218
|
+
if (typeof window === "undefined") return;
|
|
219
|
+
try {
|
|
220
|
+
window.parent.postMessage(message, this.options.parentOrigin);
|
|
221
|
+
} catch {
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
handleParentMessage = (event) => {
|
|
225
|
+
if (event.origin !== this.options.parentOrigin) return;
|
|
226
|
+
const data = event.data;
|
|
227
|
+
if (typeof data !== "object" || data === null) return;
|
|
228
|
+
const message = data;
|
|
229
|
+
switch (message.kind) {
|
|
230
|
+
case "tangle.app.handshakeAck":
|
|
231
|
+
this.handshakeAcked = true;
|
|
232
|
+
this.clearHandshakeRetry();
|
|
233
|
+
for (const resolve of this.handshakeWaiters) resolve();
|
|
234
|
+
this.handshakeWaiters = [];
|
|
235
|
+
return;
|
|
236
|
+
case "tangle.app.readAccountResult":
|
|
237
|
+
if (message.ok) {
|
|
238
|
+
this.updateWallet({
|
|
239
|
+
address: message.data.account === NO_WALLET_ADDRESS ? null : message.data.account,
|
|
240
|
+
chainId: message.data.chainId,
|
|
241
|
+
isConnected: message.data.account !== NO_WALLET_ADDRESS
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
case "tangle.app.accountChanged":
|
|
246
|
+
this.updateWallet({
|
|
247
|
+
address: message.account,
|
|
248
|
+
chainId: this.wallet.chainId,
|
|
249
|
+
isConnected: message.account !== null
|
|
250
|
+
});
|
|
251
|
+
return;
|
|
252
|
+
case "tangle.app.chainChanged":
|
|
253
|
+
this.updateWallet({
|
|
254
|
+
address: this.wallet.address,
|
|
255
|
+
chainId: message.chainId,
|
|
256
|
+
isConnected: this.wallet.isConnected
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
case "tangle.app.serviceContext":
|
|
260
|
+
this.updateService(message);
|
|
261
|
+
return;
|
|
262
|
+
case "tangle.app.jobResult":
|
|
263
|
+
this.handleJobResult(message);
|
|
264
|
+
return;
|
|
265
|
+
// Wallet-shape responses (signMessageResult etc.) are routed by
|
|
266
|
+
// dispatchWallet's promise resolver, not here.
|
|
267
|
+
default:
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
async dispatchWallet(kind, payload, timeoutMs) {
|
|
272
|
+
return new Promise((resolve, reject) => {
|
|
273
|
+
const correlationId = makeCorrelationId(kind);
|
|
274
|
+
const timeout = timeoutMs ?? this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
275
|
+
const expectedKind = {
|
|
276
|
+
"tangle.app.signMessage": "tangle.app.signMessageResult",
|
|
277
|
+
"tangle.app.signTransaction": "tangle.app.signTransactionResult",
|
|
278
|
+
"tangle.app.signTypedData": "tangle.app.signTypedDataResult",
|
|
279
|
+
"tangle.app.switchChain": "tangle.app.switchChainResult",
|
|
280
|
+
"tangle.app.requestConnect": "tangle.app.connectResult"
|
|
281
|
+
}[kind];
|
|
282
|
+
const timer = setTimeout(() => {
|
|
283
|
+
window.removeEventListener("message", listener);
|
|
284
|
+
reject(bridgeError(4900, `Parent did not respond to ${kind} in ${timeout}ms`));
|
|
285
|
+
}, timeout);
|
|
286
|
+
const listener = (event) => {
|
|
287
|
+
if (event.origin !== this.options.parentOrigin) return;
|
|
288
|
+
const data = event.data;
|
|
289
|
+
if (typeof data !== "object" || data === null) return;
|
|
290
|
+
const msg = data;
|
|
291
|
+
if (msg.kind !== expectedKind || !("correlationId" in msg) || msg.correlationId !== correlationId) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
clearTimeout(timer);
|
|
295
|
+
window.removeEventListener("message", listener);
|
|
296
|
+
const env = msg;
|
|
297
|
+
if (env.ok) {
|
|
298
|
+
resolve(env.data);
|
|
299
|
+
} else {
|
|
300
|
+
reject(bridgeError(4001, env.error ?? "Parent rejected request"));
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
window.addEventListener("message", listener);
|
|
304
|
+
this.postToParent({ kind, correlationId, ...payload });
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
handleJobResult(message) {
|
|
308
|
+
const pending = this.pendingJobs.get(message.correlationId);
|
|
309
|
+
if (!pending) return;
|
|
310
|
+
const updated = {
|
|
311
|
+
correlationId: message.correlationId,
|
|
312
|
+
status: message.status,
|
|
313
|
+
chunks: message.chunk !== void 0 ? [...pending.invocation.chunks, message.chunk] : pending.invocation.chunks,
|
|
314
|
+
...message.data !== void 0 ? { data: message.data } : {},
|
|
315
|
+
...message.error !== void 0 ? { error: message.error } : {},
|
|
316
|
+
...message.progress !== void 0 ? { progress: message.progress } : {}
|
|
317
|
+
};
|
|
318
|
+
pending.invocation = updated;
|
|
319
|
+
this.emit("job", updated);
|
|
320
|
+
if (message.status === "success" || message.status === "error") {
|
|
321
|
+
clearTimeout(pending.timer);
|
|
322
|
+
this.pendingJobs.delete(message.correlationId);
|
|
323
|
+
if (message.status === "success") {
|
|
324
|
+
pending.resolve(updated);
|
|
325
|
+
} else {
|
|
326
|
+
pending.reject(bridgeError(4001, message.error ?? "Job failed"));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
updateWallet(next) {
|
|
331
|
+
if (this.wallet.address === next.address && this.wallet.chainId === next.chainId && this.wallet.isConnected === next.isConnected) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
this.wallet = next;
|
|
335
|
+
this.emit("wallet", next);
|
|
336
|
+
}
|
|
337
|
+
updateService(broadcast) {
|
|
338
|
+
const next = {
|
|
339
|
+
blueprintId: broadcast.blueprintId,
|
|
340
|
+
serviceId: broadcast.serviceId,
|
|
341
|
+
operators: broadcast.operators,
|
|
342
|
+
jobs: broadcast.jobs,
|
|
343
|
+
mode: broadcast.mode,
|
|
344
|
+
chain: broadcast.chain ?? null
|
|
345
|
+
};
|
|
346
|
+
this.service = next;
|
|
347
|
+
this.emit("service", next);
|
|
348
|
+
}
|
|
349
|
+
emit(event, value) {
|
|
350
|
+
for (const listener of [...this.listeners[event]]) {
|
|
351
|
+
try {
|
|
352
|
+
listener(value);
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async ensureBootstrapped() {
|
|
358
|
+
if (this.handshakeAcked) return;
|
|
359
|
+
this.install();
|
|
360
|
+
await new Promise((resolve) => {
|
|
361
|
+
this.handshakeWaiters.push(resolve);
|
|
362
|
+
const retry = setInterval(() => {
|
|
363
|
+
if (this.handshakeAcked) {
|
|
364
|
+
clearInterval(retry);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
this.postHandshake();
|
|
368
|
+
}, 500);
|
|
369
|
+
setTimeout(() => clearInterval(retry), 1e4);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
function bridgeError(code, message) {
|
|
374
|
+
const err = new Error(message);
|
|
375
|
+
err.code = code;
|
|
376
|
+
return err;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/iframe/TangleIframeProvider.tsx
|
|
380
|
+
import { jsx } from "react/jsx-runtime";
|
|
381
|
+
var TangleIframeContext = createContext(null);
|
|
382
|
+
var NULL_WALLET2 = {
|
|
383
|
+
address: null,
|
|
384
|
+
chainId: null,
|
|
385
|
+
isConnected: false
|
|
386
|
+
};
|
|
387
|
+
var NULL_SERVICE2 = {
|
|
388
|
+
blueprintId: null,
|
|
389
|
+
serviceId: null,
|
|
390
|
+
operators: [],
|
|
391
|
+
jobs: [],
|
|
392
|
+
mode: null,
|
|
393
|
+
chain: null
|
|
394
|
+
};
|
|
395
|
+
function TangleIframeProvider({
|
|
396
|
+
appId,
|
|
397
|
+
parentOrigin: explicitOrigin,
|
|
398
|
+
extraOrigins,
|
|
399
|
+
mode: requestedMode = "auto",
|
|
400
|
+
children
|
|
401
|
+
}) {
|
|
402
|
+
const resolution = useMemo(() => {
|
|
403
|
+
if (requestedMode === "dev") {
|
|
404
|
+
return { mode: "dev", parentOrigin: null };
|
|
405
|
+
}
|
|
406
|
+
const detected = explicitOrigin ?? detectTangleCloudParentOrigin({ extraOrigins });
|
|
407
|
+
if (requestedMode === "bridge") {
|
|
408
|
+
if (!detected) {
|
|
409
|
+
console.error(
|
|
410
|
+
'[TangleIframeProvider] mode="bridge" but no trusted parent was detected. Falling back to dev mode.'
|
|
411
|
+
);
|
|
412
|
+
return { mode: "dev", parentOrigin: null };
|
|
413
|
+
}
|
|
414
|
+
return { mode: "bridge", parentOrigin: detected };
|
|
415
|
+
}
|
|
416
|
+
return detected ? { mode: "bridge", parentOrigin: detected } : { mode: "dev", parentOrigin: null };
|
|
417
|
+
}, [requestedMode, explicitOrigin, extraOrigins]);
|
|
418
|
+
const clientRef = useRef(null);
|
|
419
|
+
const [wallet, setWallet] = useState(NULL_WALLET2);
|
|
420
|
+
const [service, setService] = useState(NULL_SERVICE2);
|
|
421
|
+
const [isReady, setIsReady] = useState(false);
|
|
422
|
+
useEffect(() => {
|
|
423
|
+
if (resolution.mode === "dev") {
|
|
424
|
+
setIsReady(true);
|
|
425
|
+
return void 0;
|
|
426
|
+
}
|
|
427
|
+
const options = {
|
|
428
|
+
parentOrigin: resolution.parentOrigin,
|
|
429
|
+
appId
|
|
430
|
+
};
|
|
431
|
+
const client = new TangleIframeClient(options);
|
|
432
|
+
clientRef.current = client;
|
|
433
|
+
const unsubWallet = client.subscribe("wallet", setWallet);
|
|
434
|
+
const unsubService = client.subscribe("service", setService);
|
|
435
|
+
client.install();
|
|
436
|
+
setIsReady(true);
|
|
437
|
+
return () => {
|
|
438
|
+
unsubWallet();
|
|
439
|
+
unsubService();
|
|
440
|
+
client.uninstall();
|
|
441
|
+
clientRef.current = null;
|
|
442
|
+
setIsReady(false);
|
|
443
|
+
};
|
|
444
|
+
}, [resolution, appId]);
|
|
445
|
+
const value = useMemo(
|
|
446
|
+
() => ({
|
|
447
|
+
client: clientRef.current,
|
|
448
|
+
wallet,
|
|
449
|
+
service,
|
|
450
|
+
mode: resolution.mode,
|
|
451
|
+
isReady
|
|
452
|
+
}),
|
|
453
|
+
[wallet, service, resolution.mode, isReady]
|
|
454
|
+
);
|
|
455
|
+
return /* @__PURE__ */ jsx(TangleIframeContext.Provider, { value, children });
|
|
456
|
+
}
|
|
457
|
+
function useTangleIframeContext() {
|
|
458
|
+
const ctx = useContext(TangleIframeContext);
|
|
459
|
+
if (!ctx) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
"useTangleIframeContext must be used inside <TangleIframeProvider>."
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
return ctx;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/iframe/hooks.ts
|
|
468
|
+
import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
|
|
469
|
+
import {
|
|
470
|
+
createPublicClient,
|
|
471
|
+
http
|
|
472
|
+
} from "viem";
|
|
473
|
+
function useTangleWallet() {
|
|
474
|
+
const { client, wallet } = useTangleIframeContext();
|
|
475
|
+
const connect = useCallback(() => {
|
|
476
|
+
if (!client) throw new Error("Wallet not available in dev mode.");
|
|
477
|
+
return client.connect();
|
|
478
|
+
}, [client]);
|
|
479
|
+
const signMessage = useCallback(
|
|
480
|
+
(message) => {
|
|
481
|
+
if (!client) throw new Error("Wallet not available in dev mode.");
|
|
482
|
+
return client.signMessage(message);
|
|
483
|
+
},
|
|
484
|
+
[client]
|
|
485
|
+
);
|
|
486
|
+
const sendTransaction = useCallback(
|
|
487
|
+
(tx) => {
|
|
488
|
+
if (!client) throw new Error("Wallet not available in dev mode.");
|
|
489
|
+
return client.sendTransaction(tx);
|
|
490
|
+
},
|
|
491
|
+
[client]
|
|
492
|
+
);
|
|
493
|
+
const signTypedData = useCallback(
|
|
494
|
+
(args) => {
|
|
495
|
+
if (!client) throw new Error("Wallet not available in dev mode.");
|
|
496
|
+
return client.signTypedData(args);
|
|
497
|
+
},
|
|
498
|
+
[client]
|
|
499
|
+
);
|
|
500
|
+
const switchChain = useCallback(
|
|
501
|
+
(chainId) => {
|
|
502
|
+
if (!client) throw new Error("Wallet not available in dev mode.");
|
|
503
|
+
return client.switchChain(chainId);
|
|
504
|
+
},
|
|
505
|
+
[client]
|
|
506
|
+
);
|
|
507
|
+
return {
|
|
508
|
+
...wallet,
|
|
509
|
+
connect,
|
|
510
|
+
signMessage,
|
|
511
|
+
sendTransaction,
|
|
512
|
+
signTypedData,
|
|
513
|
+
switchChain
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function useChainContext() {
|
|
517
|
+
return useTangleIframeContext().service.chain;
|
|
518
|
+
}
|
|
519
|
+
function useTanglePublicClient() {
|
|
520
|
+
const chain = useChainContext();
|
|
521
|
+
return useMemo2(() => {
|
|
522
|
+
if (!chain) return null;
|
|
523
|
+
const chainConfig = {
|
|
524
|
+
id: chain.id,
|
|
525
|
+
name: chain.name,
|
|
526
|
+
nativeCurrency: chain.nativeCurrency !== void 0 ? { ...chain.nativeCurrency } : { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
527
|
+
rpcUrls: {
|
|
528
|
+
default: { http: [chain.rpcUrl] }
|
|
529
|
+
},
|
|
530
|
+
...chain.blockExplorerUrl ? {
|
|
531
|
+
blockExplorers: {
|
|
532
|
+
default: { name: "Explorer", url: chain.blockExplorerUrl }
|
|
533
|
+
}
|
|
534
|
+
} : {}
|
|
535
|
+
};
|
|
536
|
+
return createPublicClient({
|
|
537
|
+
chain: chainConfig,
|
|
538
|
+
transport: http(chain.rpcUrl)
|
|
539
|
+
});
|
|
540
|
+
}, [chain]);
|
|
541
|
+
}
|
|
542
|
+
function useTangleService() {
|
|
543
|
+
return useTangleIframeContext().service;
|
|
544
|
+
}
|
|
545
|
+
function useCallJob() {
|
|
546
|
+
const { client } = useTangleIframeContext();
|
|
547
|
+
const [invocation, setInvocation] = useState2(null);
|
|
548
|
+
const [latestId, setLatestId] = useState2(null);
|
|
549
|
+
useEffect2(() => {
|
|
550
|
+
if (!client) return void 0;
|
|
551
|
+
return client.subscribe("job", (next) => {
|
|
552
|
+
setLatestId((prevLatest) => {
|
|
553
|
+
if (prevLatest === null || prevLatest === next.correlationId) {
|
|
554
|
+
setInvocation(next);
|
|
555
|
+
return next.correlationId;
|
|
556
|
+
}
|
|
557
|
+
return prevLatest;
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
}, [client]);
|
|
561
|
+
const call = useCallback(
|
|
562
|
+
async (args) => {
|
|
563
|
+
if (!client) {
|
|
564
|
+
throw new Error(
|
|
565
|
+
"Job invocation not available in dev mode without a configured stub. See `setDevJobHandler` in the testing harness."
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
setInvocation(null);
|
|
569
|
+
const result = await client.callJob(args);
|
|
570
|
+
setLatestId(result.correlationId);
|
|
571
|
+
return result;
|
|
572
|
+
},
|
|
573
|
+
[client]
|
|
574
|
+
);
|
|
575
|
+
const reset = useCallback(() => {
|
|
576
|
+
setInvocation(null);
|
|
577
|
+
setLatestId(null);
|
|
578
|
+
}, []);
|
|
579
|
+
return useMemo2(
|
|
580
|
+
() => ({ call, invocation, reset, isPending: invocation?.status === "pending" || invocation?.status === "streaming" }),
|
|
581
|
+
[call, invocation, reset]
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
function useTangleAddress() {
|
|
585
|
+
return useTangleIframeContext().wallet.address;
|
|
586
|
+
}
|
|
587
|
+
function useTangleReady() {
|
|
588
|
+
return useTangleIframeContext().isReady;
|
|
589
|
+
}
|
|
590
|
+
function useTangleMode() {
|
|
591
|
+
return useTangleIframeContext().mode;
|
|
592
|
+
}
|
|
593
|
+
export {
|
|
594
|
+
TANGLE_CLOUD_ORIGINS_DEFAULT,
|
|
595
|
+
TangleIframeClient,
|
|
596
|
+
TangleIframeProvider,
|
|
597
|
+
useCallJob,
|
|
598
|
+
useChainContext,
|
|
599
|
+
useTangleAddress,
|
|
600
|
+
useTangleIframeContext,
|
|
601
|
+
useTangleMode,
|
|
602
|
+
useTanglePublicClient,
|
|
603
|
+
useTangleReady,
|
|
604
|
+
useTangleService,
|
|
605
|
+
useTangleWallet
|
|
606
|
+
};
|
|
607
|
+
//# sourceMappingURL=index.js.map
|