@tangle-network/agent-app 0.49.24 → 0.49.26

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,727 @@
1
+ import {
2
+ AsyncView,
3
+ MutationStatus,
4
+ confirmWrite,
5
+ rejectWrite,
6
+ useAsyncResource,
7
+ useConfirmedMutation
8
+ } from "../chunk-3UBAO3N5.js";
9
+
10
+ // src/channels/context.tsx
11
+ import { createContext, useContext } from "react";
12
+ import { jsx } from "react/jsx-runtime";
13
+ var Context = createContext(null);
14
+ function ChannelsProvider({ client, pollInterval = 3e3, children }) {
15
+ if (pollInterval !== false && (!Number.isFinite(pollInterval) || pollInterval < 100)) {
16
+ throw new Error("ChannelsProvider pollInterval must be false or at least 100 ms.");
17
+ }
18
+ return /* @__PURE__ */ jsx(Context.Provider, { value: { client, pollInterval }, children }, client.scope);
19
+ }
20
+ function useChannelsClient() {
21
+ return useChannelsContext().client;
22
+ }
23
+ function useChannelsContext() {
24
+ const value = useContext(Context);
25
+ if (!value) throw new Error("Channel components and hooks require ChannelsProvider.");
26
+ return value;
27
+ }
28
+
29
+ // src/channels/hooks.ts
30
+ import { useCallback, useEffect, useRef } from "react";
31
+ function useChannelResource(load, deps, enabled = true, poll = true) {
32
+ const { client, pollInterval } = useChannelsContext();
33
+ const resource = useAsyncResource({ load, deps: [client, client.scope, ...deps], enabled });
34
+ useEffect(() => {
35
+ if (!enabled || !poll || pollInterval === false || resource.status === "loading" || resource.status === "idle") return;
36
+ const delay = typeof document !== "undefined" && document.hidden ? Math.max(15e3, pollInterval) : pollInterval;
37
+ const timer = setTimeout(resource.retry, delay);
38
+ return () => clearTimeout(timer);
39
+ }, [enabled, poll, pollInterval, resource.status, resource.retry, resource]);
40
+ return enabled ? resource : { status: "idle", retry: resource.retry };
41
+ }
42
+ function useChannelMutation(mutate, onSucceeded) {
43
+ const { client } = useChannelsContext();
44
+ const lock = useRef(null);
45
+ const mutation = useConfirmedMutation({
46
+ mutate: async (input, context) => confirmWrite(await mutate(input, context)),
47
+ onSucceeded
48
+ });
49
+ const { reset, run } = mutation;
50
+ useEffect(() => {
51
+ reset();
52
+ return () => {
53
+ lock.current = null;
54
+ reset();
55
+ };
56
+ }, [client, client.scope, reset]);
57
+ const singleRun = useCallback(async (input) => {
58
+ if (lock.current) return rejectWrite("Another channel operation is still in progress.");
59
+ const token = {};
60
+ lock.current = token;
61
+ try {
62
+ return await run(input);
63
+ } finally {
64
+ if (lock.current === token) lock.current = null;
65
+ }
66
+ }, [run]);
67
+ return { ...mutation, run: singleRun };
68
+ }
69
+ function useChannels() {
70
+ const { client } = useChannelsContext();
71
+ return useChannelResource(() => client.lines.list(), []);
72
+ }
73
+ function useChannelConnections(transport) {
74
+ const { client } = useChannelsContext();
75
+ return useChannelResource((context) => client.setup.connections(transport, context), [transport], true, false);
76
+ }
77
+ function useWhatsAppNumbers(connectionId) {
78
+ const { client } = useChannelsContext();
79
+ return useChannelResource((context) => client.setup.whatsappNumbers(connectionId, context), [connectionId], !!connectionId, false);
80
+ }
81
+ function useConnectChannel(onConnected) {
82
+ const { client } = useChannelsContext();
83
+ return useChannelMutation(async (input) => {
84
+ if (input.kind === "connection") {
85
+ if (!input.input.connectionId.trim()) throw new Error("Choose a connection.");
86
+ if (input.input.transport === "whatsapp" && !input.input.phoneNumberId.trim()) throw new Error("Choose a WhatsApp number.");
87
+ return client.lines.fromConnection(input.input);
88
+ }
89
+ if (input.kind === "order") {
90
+ if (!input.orderId.trim()) throw new Error("Choose a number order.");
91
+ return client.setup.fromOrder(input.orderId);
92
+ }
93
+ const address = input.address.trim().toLowerCase();
94
+ if (!input.connectionId.trim() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(address)) throw new Error("Choose a connection and enter a valid mailbox address.");
95
+ return client.setup.fromEmail({ connectionId: input.connectionId, address });
96
+ }, onConnected);
97
+ }
98
+ function verificationExpired(test, now = Date.now()) {
99
+ return test.expired || test.status !== "verified" && test.expiresAt <= now;
100
+ }
101
+ function useChannel(lineId) {
102
+ const { client } = useChannelsContext();
103
+ const resource = useChannelResource(async (context) => {
104
+ const [line, verification] = await Promise.all([client.lines.get(lineId), client.setup.verification(lineId, context)]);
105
+ if (line.id !== lineId || line.attachment && line.attachment.lineId !== lineId || verification && verification.lineId !== lineId) throw new Error("Channel binding changed. Reload channel setup.");
106
+ return { line, verification };
107
+ }, [lineId], !!lineId);
108
+ const mutation = useChannelMutation(async ({ action, confirm }) => {
109
+ if (resource.status !== "ready") throw new Error("Read the current channel state before changing it.");
110
+ const { line, verification: test } = resource.value;
111
+ if (line.id !== lineId) throw new Error("Channel binding changed. Reload channel setup.");
112
+ if (action !== "reset" && line.status !== "active") throw new Error("This line is not active.");
113
+ if (action === "start") {
114
+ if (test && test.status !== "revoked") throw new Error("Stop the existing test before starting another.");
115
+ await client.setup.start(lineId);
116
+ return;
117
+ }
118
+ if (!test || test.lineId !== lineId) throw new Error("Start a channel test first.");
119
+ if (action === "reset") {
120
+ if (confirm !== true) throw new Error("Confirm stopping this test.");
121
+ await client.setup.reset(lineId, test.id);
122
+ return;
123
+ }
124
+ if (verificationExpired(test)) throw new Error("This test expired. Stop it and start a new test.");
125
+ if (action === "activate") {
126
+ if (test.status !== "verified") throw new Error("Verify inbound delivery and the test reply before activation.");
127
+ const activated = await client.setup.activate(lineId, test.id);
128
+ if (activated.id !== lineId || activated.status !== "active" || activated.attachment?.status !== "active" || activated.attachment.lineId !== lineId) throw new Error("Messaging activation was not confirmed.");
129
+ } else if (action === "send") {
130
+ if (test.status !== "received") throw new Error("Wait for the inbound test message before sending a reply.");
131
+ await client.setup.send(lineId, test.id);
132
+ } else {
133
+ if (test.status !== "configuring" && test.status !== "uncertain") throw new Error("This test is not waiting for a setup check.");
134
+ await client.setup.resume(lineId, test.id);
135
+ }
136
+ }, resource.retry);
137
+ useEffect(() => mutation.reset(), [lineId, mutation.reset]);
138
+ return { resource, ...mutation };
139
+ }
140
+ function useChannelConversations(lineId) {
141
+ const { client } = useChannelsContext();
142
+ return useChannelResource(async () => {
143
+ const threads = await client.lines.threads(lineId).list();
144
+ if (threads.some((thread) => thread.lineId !== lineId)) throw new Error("The thread response belongs to another line.");
145
+ return threads;
146
+ }, [lineId], !!lineId);
147
+ }
148
+ function useChannelConversation(lineId, threadId) {
149
+ const { client } = useChannelsContext();
150
+ return useChannelResource(async () => {
151
+ const messages = await client.lines.threads(lineId).messages(threadId);
152
+ if (messages.some((message) => message.threadId !== threadId)) throw new Error("The message response belongs to another conversation.");
153
+ return [...new Map(messages.map((message) => [message.id, message])).values()].sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
154
+ }, [lineId, threadId], !!lineId && !!threadId);
155
+ }
156
+
157
+ // src/channels/components.tsx
158
+ import { useEffect as useEffect3, useId, useState as useState2 } from "react";
159
+
160
+ // src/channels/numbers.ts
161
+ import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
162
+ var NUMBER_CHARGE_NOTICE = "This is a one-time activation charge. Tangle does not bill recurring carrier rental for this number. Your provider\u2019s message rates are separate.";
163
+ var NUMBER_RETRY_NOTICE = "Retry uses the same approved quote, not a new purchase. Do not order again while the result is unknown.";
164
+ function holdsNumber(order) {
165
+ return order.cancellation !== "released" && order.cancellation !== "abandoned";
166
+ }
167
+ function numberStage(order) {
168
+ if (order.cancellation === "released" || order.cancellation === "abandoned") return "Released";
169
+ if (order.cancellation === "needs_review") return "Release needs review";
170
+ if (order.cancellation === "requested" || order.status === "cancelled") return "Cancelling";
171
+ if (order.status === "needs_review") return "Needs operator review";
172
+ if (order.status === "ready_for_setup") return "Number acquired \u2014 channel setup next";
173
+ if (order.errorCode === "funding_required") return "Add funds to continue";
174
+ if (order.errorCode === "sms_not_ready") return "Waiting for SMS activation";
175
+ return "Provisioning";
176
+ }
177
+ function useNumberChannel(transport) {
178
+ const { client } = useChannelsContext();
179
+ const ordering = client.ordering;
180
+ const reference = ordering?.references[transport];
181
+ const [quote, setQuote] = useState(null);
182
+ const [receipts, setReceipts] = useState([]);
183
+ const [uncertain, setUncertain] = useState(false);
184
+ const attempted = useRef2(false);
185
+ const resource = useChannelResource(async ({ signal }) => {
186
+ if (!ordering || !reference) throw new Error("Number ordering is unavailable.");
187
+ const readiness = await ordering.numbers.readiness();
188
+ const orders2 = [];
189
+ const seen = /* @__PURE__ */ new Set();
190
+ let cursor;
191
+ let complete = false;
192
+ do {
193
+ if (signal.aborted) throw new Error("Number read was superseded.");
194
+ const page = await ordering.numbers.list({ clientReference: reference, cursor, limit: 100 });
195
+ if (page.orders.some((order) => order.clientReference !== reference || order.transport !== transport)) throw new Error("The number list belongs to another binding.");
196
+ orders2.push(...page.orders);
197
+ if (page.nextCursor == null) {
198
+ complete = page.nextCursor === null;
199
+ break;
200
+ }
201
+ if (seen.has(page.nextCursor) || seen.size >= 100) throw new Error("The number list could not be read completely.");
202
+ seen.add(page.nextCursor);
203
+ cursor = page.nextCursor;
204
+ } while (cursor);
205
+ return { readiness, orders: orders2, complete };
206
+ }, [ordering, reference, transport], !!ordering);
207
+ const loaded = resource.status === "ready" ? resource.value : null;
208
+ const orders = [...new Map([...loaded?.orders ?? [], ...receipts].map((order) => [order.id, order])).values()];
209
+ const held = orders.find(holdsNumber) ?? null;
210
+ const mutation = useChannelMutation(async (input, { signal }) => {
211
+ if (!ordering || !reference) throw new Error("Number ordering is unavailable.");
212
+ if (input.action === "quote") {
213
+ if (attempted.current) throw new Error("The previous purchase is unresolved. Retry its approved quote or check progress.");
214
+ if (!loaded?.complete || !loaded.readiness.configured || !loaded.readiness.transports.includes(transport)) throw new Error("Number availability and existing orders must be confirmed first.");
215
+ if (held) throw new Error("This agent already holds a number order for this transport.");
216
+ const { quote: next } = await ordering.numbers.quote({ transport, clientReference: reference });
217
+ if (next.transport !== transport || next.clientReference !== void 0 && next.clientReference !== reference) throw new Error("The quote belongs to another binding.");
218
+ if (!signal.aborted) setQuote(next);
219
+ return;
220
+ }
221
+ if (input.action === "purchase") {
222
+ if (!quote || !input.consent) throw new Error("Approve the quoted activation charge before ordering.");
223
+ if (!quote.terms.trim() || !Number.isSafeInteger(quote.activationCents) || quote.activationCents < 0 || !quote.token) throw new Error("A valid price and published activation terms are required.");
224
+ if (held) throw new Error("This agent already holds a number order for this transport.");
225
+ if (!attempted.current && (!loaded?.complete || !loaded.readiness.configured || quote.expiresAt <= Date.now())) throw new Error("The quote or order information expired. Read the current state first.");
226
+ attempted.current = true;
227
+ try {
228
+ const { order: order2 } = await ordering.numbers.create({ quoteToken: quote.token, consent: true, clientReference: reference, transport });
229
+ if (order2.clientReference !== reference || order2.transport !== transport) throw new Error("The purchase response belongs to another binding. Check progress before ordering again.");
230
+ if (!signal.aborted) {
231
+ setReceipts((current) => [order2, ...current.filter((item) => item.id !== order2.id)]);
232
+ setQuote(null);
233
+ setUncertain(false);
234
+ }
235
+ } catch (error) {
236
+ if (!signal.aborted) setUncertain(true);
237
+ throw error;
238
+ } finally {
239
+ if (!signal.aborted) resource.retry();
240
+ }
241
+ return;
242
+ }
243
+ if (!orders.some((order2) => order2.id === input.orderId)) throw new Error("Read this number order before changing it.");
244
+ if (input.action === "cancel" && !input.confirm) throw new Error("Confirm cancelling this number.");
245
+ const { order } = await ordering.numbers[input.action](input.orderId);
246
+ if (order.id !== input.orderId || order.clientReference !== reference || order.transport !== transport) throw new Error("The number response belongs to another binding.");
247
+ if (!signal.aborted) {
248
+ setReceipts((current) => [order, ...current.filter((item) => item.id !== order.id)]);
249
+ resource.retry();
250
+ }
251
+ });
252
+ useEffect2(() => {
253
+ setQuote(null);
254
+ setReceipts([]);
255
+ setUncertain(false);
256
+ attempted.current = false;
257
+ mutation.reset();
258
+ }, [client, client.scope, transport, reference, mutation.reset]);
259
+ useEffect2(() => {
260
+ if (loaded) setReceipts((current) => current.filter((receipt) => !loaded.orders.some((order) => order.id === receipt.id)));
261
+ if (loaded?.orders.some(holdsNumber)) {
262
+ setQuote(null);
263
+ setUncertain(false);
264
+ attempted.current = false;
265
+ } else if (!uncertain && !quote && loaded?.complete && orders.length > 0 && orders.every((order) => !holdsNumber(order))) {
266
+ attempted.current = false;
267
+ }
268
+ }, [loaded]);
269
+ return { resource, orders, held, quote, uncertain, ...mutation };
270
+ }
271
+
272
+ // src/channels/ui.tsx
273
+ import { jsx as jsx2 } from "react/jsx-runtime";
274
+ var panelClass = "space-y-4 rounded-xl border border-border bg-card p-6 text-foreground";
275
+ var buttonClass = "inline-flex items-center justify-center rounded-md border border-border px-3 py-2 text-sm font-medium transition hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50";
276
+ var primaryButtonClass = `${buttonClass} border-primary bg-primary text-primary-foreground hover:bg-primary/90`;
277
+ var inputClass = "block w-full rounded-md border border-input bg-background px-3 py-2 text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring";
278
+ function ChannelFailure({ state }) {
279
+ return state.status === "failed" ? /* @__PURE__ */ jsx2(MutationStatus, { state, className: "text-sm text-destructive" }) : null;
280
+ }
281
+ function ChannelState({ resource, children, empty }) {
282
+ return /* @__PURE__ */ jsx2(
283
+ AsyncView,
284
+ {
285
+ state: resource,
286
+ empty: { title: empty, action: { label: "Refresh", onClick: resource.retry } },
287
+ loadingLabel: "Loading channel\u2026",
288
+ retryLabel: "Try again",
289
+ renderIdle: () => /* @__PURE__ */ jsx2("p", { className: "text-sm text-muted-foreground", children: "Choose a channel to continue." }),
290
+ children
291
+ }
292
+ );
293
+ }
294
+
295
+ // src/channels/components.tsx
296
+ import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
297
+ var labels = { imessage: "iMessage", whatsapp: "WhatsApp", sms: "SMS", email: "Email" };
298
+ function channelMessageLink(line, instruction) {
299
+ if (line.transport === "email") {
300
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(line.address)) return null;
301
+ return `mailto:${encodeURIComponent(line.address)}?subject=Channel%20test&body=${encodeURIComponent(instruction)}`;
302
+ }
303
+ const address = line.routerAddress ?? line.address;
304
+ if (!/^\+[1-9]\d{7,14}$/.test(address)) return null;
305
+ if (line.transport === "whatsapp") return `https://wa.me/${address.slice(1)}?text=${encodeURIComponent(instruction)}`;
306
+ return `sms:${address}?body=${encodeURIComponent(line.connect ?? instruction)}`;
307
+ }
308
+ function CopyLine({ text }) {
309
+ const [message, setMessage] = useState2("");
310
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
311
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 rounded-md bg-muted p-3", children: [
312
+ /* @__PURE__ */ jsx3("code", { className: "min-w-0 flex-1 break-all", children: text }),
313
+ /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, "aria-label": `Copy ${text}`, onClick: async () => {
314
+ try {
315
+ await navigator.clipboard.writeText(text);
316
+ setMessage("Copied");
317
+ } catch {
318
+ setMessage("Copy is unavailable. Select and copy the displayed text.");
319
+ }
320
+ }, children: "Copy" })
321
+ ] }),
322
+ message && /* @__PURE__ */ jsx3("p", { role: "status", className: "text-sm", children: message })
323
+ ] });
324
+ }
325
+ function ChannelVerificationPanel({ lineId, className = "" }) {
326
+ const channel = useChannel(lineId);
327
+ const [confirming, setConfirming] = useState2(false);
328
+ useEffect3(() => setConfirming(false), [lineId]);
329
+ const busy = channel.state.status === "pending";
330
+ const act = (action) => void channel.run({ action });
331
+ return /* @__PURE__ */ jsxs("section", { className: `${panelClass} ${className}`, "aria-label": "Channel verification", "aria-busy": busy, children: [
332
+ /* @__PURE__ */ jsx3("h2", { className: "text-lg font-semibold", children: "Verify this channel" }),
333
+ /* @__PURE__ */ jsx3(ChannelState, { resource: channel.resource, empty: "The channel is unavailable.", children: ({ line, verification: test }) => {
334
+ const expired = test ? verificationExpired(test) : false;
335
+ const active = line.status === "active" && line.attachment?.status === "active";
336
+ const link = test ? channelMessageLink(line, test.instruction) : null;
337
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
338
+ /* @__PURE__ */ jsxs("p", { className: "break-all font-medium", children: [
339
+ labels[line.transport],
340
+ " \xB7 ",
341
+ line.address
342
+ ] }),
343
+ line.status !== "active" ? /* @__PURE__ */ jsxs("p", { role: "alert", children: [
344
+ "This line is ",
345
+ line.status,
346
+ ". Check the line\u2019s status before testing or activating it."
347
+ ] }) : active && test?.status === "verified" && !expired ? /* @__PURE__ */ jsxs(Fragment, { children: [
348
+ /* @__PURE__ */ jsxs("p", { role: "status", children: [
349
+ "Both directions verified. Answering ",
350
+ labels[line.transport],
351
+ " messages."
352
+ ] }),
353
+ /* @__PURE__ */ jsx3("p", { children: line.connect && line.routerAddress ? `Share this: send ${line.connect} to ${line.routerAddress}.` : "Share this channel address so people can start a conversation." }),
354
+ /* @__PURE__ */ jsx3(CopyLine, { text: line.connect ?? line.address })
355
+ ] }) : !test || test.status === "revoked" ? /* @__PURE__ */ jsxs(Fragment, { children: [
356
+ active && /* @__PURE__ */ jsx3("p", { children: "Attached to an agent, but no completed delivery test was returned." }),
357
+ /* @__PURE__ */ jsx3("p", { children: "Test inbound delivery and a reply before this agent answers people." }),
358
+ /* @__PURE__ */ jsx3("button", { type: "button", className: primaryButtonClass, disabled: busy, onClick: () => act("start"), children: "Start channel test" })
359
+ ] }) : expired ? /* @__PURE__ */ jsx3("p", { role: "alert", children: "This test expired. Stop it before starting a new test." }) : test.status === "verified" ? /* @__PURE__ */ jsxs(Fragment, { children: [
360
+ /* @__PURE__ */ jsx3("p", { children: "Both directions verified. Messaging is not turned on yet." }),
361
+ /* @__PURE__ */ jsx3("button", { type: "button", className: primaryButtonClass, disabled: busy, onClick: () => act("activate"), children: "Turn on messaging" })
362
+ ] }) : test.status === "configuring" ? /* @__PURE__ */ jsxs(Fragment, { children: [
363
+ /* @__PURE__ */ jsx3("p", { children: "Connecting incoming messages to Tangle." }),
364
+ /* @__PURE__ */ jsx3("button", { type: "button", className: primaryButtonClass, disabled: busy, onClick: () => act("resume"), children: "Check setup" })
365
+ ] }) : test.status === "waiting" ? /* @__PURE__ */ jsxs(Fragment, { children: [
366
+ line.connect && line.routerAddress && /* @__PURE__ */ jsxs(Fragment, { children: [
367
+ /* @__PURE__ */ jsxs("p", { children: [
368
+ "First, send this to ",
369
+ line.routerAddress,
370
+ ". The router replies with your agent\u2019s number."
371
+ ] }),
372
+ /* @__PURE__ */ jsx3(CopyLine, { text: line.connect })
373
+ ] }),
374
+ /* @__PURE__ */ jsx3("p", { children: line.transport === "email" ? `From another email address, send this line to ${line.address}.` : line.connect ? "Then send this in the conversation the router opens:" : `From your phone, send this to ${line.address}:` }),
375
+ /* @__PURE__ */ jsx3(CopyLine, { text: test.instruction }),
376
+ link && /* @__PURE__ */ jsxs("a", { className: primaryButtonClass, href: link, target: line.transport === "whatsapp" ? "_blank" : void 0, rel: "noopener noreferrer", children: [
377
+ "Open ",
378
+ line.transport === "email" ? "Email" : line.transport === "whatsapp" ? "WhatsApp" : "Messages"
379
+ ] })
380
+ ] }) : test.status === "received" ? /* @__PURE__ */ jsxs(Fragment, { children: [
381
+ /* @__PURE__ */ jsx3("p", { role: "status", children: "Your test message arrived." }),
382
+ /* @__PURE__ */ jsx3("button", { type: "button", className: primaryButtonClass, disabled: busy, onClick: () => act("send"), children: "Send test reply" })
383
+ ] }) : test.status === "sending" ? /* @__PURE__ */ jsx3("p", { role: "status", children: "The test reply is being sent. Do not send another." }) : test.status === "uncertain" ? /* @__PURE__ */ jsxs(Fragment, { children: [
384
+ /* @__PURE__ */ jsx3("p", { role: "alert", children: "The reply result is uncertain. Check its status instead of sending another." }),
385
+ /* @__PURE__ */ jsx3("button", { type: "button", className: primaryButtonClass, disabled: busy, onClick: () => act("resume"), children: "Check reply status" })
386
+ ] }) : test.status === "needs_review" ? /* @__PURE__ */ jsx3("p", { role: "alert", children: "Incoming message setup needs operator review. No delivery success is claimed." }) : /* @__PURE__ */ jsx3("p", { children: "Reply with the CONFIRM line in the message you received, in the same conversation." }),
387
+ test?.error && /* @__PURE__ */ jsx3("p", { role: "alert", className: "text-destructive", children: test.error }),
388
+ test && test.status !== "revoked" && !(active && test.status === "verified" && !expired) && /* @__PURE__ */ jsx3(Fragment, { children: !confirming ? /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, disabled: busy, onClick: () => setConfirming(true), children: "Stop this test" }) : /* @__PURE__ */ jsxs("fieldset", { className: "space-y-2", children: [
389
+ /* @__PURE__ */ jsx3("legend", { children: "Stop this test and revoke its setup?" }),
390
+ /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, disabled: busy, onClick: async () => {
391
+ const outcome = await channel.run({ action: "reset", confirm: true });
392
+ if (outcome.succeeded) setConfirming(false);
393
+ }, children: "Confirm stop" }),
394
+ " ",
395
+ /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, disabled: busy, onClick: () => setConfirming(false), children: "Keep testing" })
396
+ ] }) }),
397
+ /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, disabled: busy, onClick: channel.resource.retry, children: "Refresh channel status" })
398
+ ] });
399
+ } }),
400
+ /* @__PURE__ */ jsx3(ChannelFailure, { state: channel.state })
401
+ ] });
402
+ }
403
+ function ConnectionPicker({ transport, onConnected }) {
404
+ const client = useChannelsClient();
405
+ const connections = useChannelConnections(transport);
406
+ const [connectionId, setConnectionId] = useState2("");
407
+ const [numberId, setNumberId] = useState2("");
408
+ const [address, setAddress] = useState2("");
409
+ const [showNumbers, setShowNumbers] = useState2(false);
410
+ const numbers = useWhatsAppNumbers(showNumbers ? connectionId : "");
411
+ const connect = useConnectChannel(onConnected);
412
+ const busy = connect.state.status === "pending";
413
+ useEffect3(() => {
414
+ setConnectionId("");
415
+ setNumberId("");
416
+ setShowNumbers(false);
417
+ setAddress("");
418
+ }, [client, transport]);
419
+ useEffect3(() => {
420
+ if (connections.status === "ready" && connections.value.length === 1) setConnectionId(connections.value[0].id);
421
+ }, [connections]);
422
+ useEffect3(() => {
423
+ if (numbers.status === "ready" && numbers.value.length === 1) setNumberId(numbers.value[0].id);
424
+ }, [numbers]);
425
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
426
+ /* @__PURE__ */ jsx3(ChannelState, { resource: connections, empty: "No eligible Hub connections. Connect an identity in your app\u2019s Integrations first.", children: (items) => /* @__PURE__ */ jsxs("form", { className: "space-y-4", onSubmit: (event) => {
427
+ event.preventDefault();
428
+ if (transport === "email") void connect.run({ kind: "email", connectionId, address });
429
+ else if (transport === "whatsapp") void connect.run({ kind: "connection", input: { transport, connectionId, phoneNumberId: numberId } });
430
+ else void connect.run({ kind: "connection", input: { transport, connectionId } });
431
+ }, children: [
432
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
433
+ "Connection",
434
+ /* @__PURE__ */ jsxs("select", { className: inputClass, value: connectionId, disabled: busy, onChange: (event) => {
435
+ setConnectionId(event.target.value);
436
+ setNumberId("");
437
+ setShowNumbers(false);
438
+ }, required: true, children: [
439
+ /* @__PURE__ */ jsx3("option", { value: "", children: "Choose a connection" }),
440
+ items.map((item) => /* @__PURE__ */ jsx3("option", { value: item.id, children: item.account ?? item.displayName }, item.id))
441
+ ] })
442
+ ] }),
443
+ transport === "email" && /* @__PURE__ */ jsxs("label", { className: "block", children: [
444
+ "Mailbox address",
445
+ /* @__PURE__ */ jsx3("input", { className: inputClass, type: "email", required: true, value: address, disabled: busy, onChange: (event) => setAddress(event.target.value), placeholder: "hello@example.com" })
446
+ ] }),
447
+ transport === "whatsapp" && /* @__PURE__ */ jsxs(Fragment, { children: [
448
+ /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, disabled: !connectionId || busy, onClick: () => {
449
+ setShowNumbers(true);
450
+ numbers.retry();
451
+ }, children: "Show owned numbers" }),
452
+ showNumbers && /* @__PURE__ */ jsx3(ChannelState, { resource: numbers, empty: "This connection has no eligible WhatsApp numbers.", children: (owned) => /* @__PURE__ */ jsxs("label", { className: "block", children: [
453
+ "WhatsApp number",
454
+ /* @__PURE__ */ jsxs("select", { className: inputClass, required: true, value: numberId, disabled: busy, onChange: (event) => setNumberId(event.target.value), children: [
455
+ /* @__PURE__ */ jsx3("option", { value: "", children: "Choose a number" }),
456
+ owned.map((number) => /* @__PURE__ */ jsx3("option", { value: number.id, children: number.address }, number.id))
457
+ ] })
458
+ ] }) })
459
+ ] }),
460
+ /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: "Connect an owned line, then test both directions. Each person must message first. Permissions remain managed in Tangle Hub." }),
461
+ /* @__PURE__ */ jsx3("button", { type: "submit", className: primaryButtonClass, disabled: busy || !connectionId || transport === "email" && !address.trim() || transport === "whatsapp" && (!numberId || !showNumbers || numbers.status !== "ready"), children: busy ? "Connecting\u2026" : "Connect channel" })
462
+ ] }) }),
463
+ /* @__PURE__ */ jsx3(ChannelFailure, { state: connect.state })
464
+ ] });
465
+ }
466
+ function ChannelConnect({ transport, initialLineId = "", className = "" }) {
467
+ const [selected, setSelected] = useState2(initialLineId);
468
+ const lines = useChannels();
469
+ const id = useId();
470
+ useEffect3(() => setSelected(initialLineId), [transport, initialLineId]);
471
+ return /* @__PURE__ */ jsxs("div", { className: `space-y-4 ${className}`, children: [
472
+ /* @__PURE__ */ jsxs("section", { className: panelClass, "aria-label": `${labels[transport]} channel`, children: [
473
+ /* @__PURE__ */ jsxs("h2", { className: "text-lg font-semibold", children: [
474
+ "Connect ",
475
+ labels[transport]
476
+ ] }),
477
+ transport === "imessage" && /* @__PURE__ */ jsx3("p", { children: "Use a free shared iMessage identity, or acquire a dedicated number. A shared identity requires a connect message to the router first." }),
478
+ /* @__PURE__ */ jsx3(ChannelState, { resource: lines, empty: "No lines connected to this agent yet.", children: (all) => {
479
+ const matching = all.filter((line) => line.transport === transport && line.status !== "released");
480
+ return matching.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
481
+ /* @__PURE__ */ jsx3("label", { htmlFor: id, children: "Existing line" }),
482
+ /* @__PURE__ */ jsxs("select", { id, className: inputClass, value: selected, onChange: (event) => setSelected(event.target.value), children: [
483
+ /* @__PURE__ */ jsx3("option", { value: "", children: "Choose or connect a line" }),
484
+ matching.map((line) => /* @__PURE__ */ jsx3("option", { value: line.id, children: line.label ?? line.address }, line.id))
485
+ ] })
486
+ ] }) : /* @__PURE__ */ jsxs("p", { children: [
487
+ "No ",
488
+ labels[transport],
489
+ " lines connected yet."
490
+ ] });
491
+ } }),
492
+ !selected && transport !== "sms" && /* @__PURE__ */ jsx3(ConnectionPicker, { transport, onConnected: (line) => {
493
+ setSelected(line.id);
494
+ lines.retry();
495
+ } }, transport),
496
+ selected && /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, onClick: () => setSelected(""), children: "Choose another line" })
497
+ ] }),
498
+ selected ? /* @__PURE__ */ jsx3(ChannelVerificationPanel, { lineId: selected }, selected) : (transport === "sms" || transport === "imessage") && /* @__PURE__ */ jsx3(NumberChannel, { transport })
499
+ ] });
500
+ }
501
+ function IMessageChannel(props) {
502
+ return /* @__PURE__ */ jsx3(ChannelConnect, { ...props, transport: "imessage" });
503
+ }
504
+ function WhatsAppChannel(props) {
505
+ return /* @__PURE__ */ jsx3(ChannelConnect, { ...props, transport: "whatsapp" });
506
+ }
507
+ function EmailChannel(props) {
508
+ return /* @__PURE__ */ jsx3(ChannelConnect, { ...props, transport: "email" });
509
+ }
510
+ function SMSChannel(props) {
511
+ return /* @__PURE__ */ jsx3(ChannelConnect, { ...props, transport: "sms" });
512
+ }
513
+ function NumberChannel({ transport, className = "" }) {
514
+ const client = useChannelsClient();
515
+ const number = useNumberChannel(transport);
516
+ const [consent, setConsent] = useState2(false);
517
+ const [cancelId, setCancelId] = useState2(null);
518
+ const [lineId, setLineId] = useState2("");
519
+ const connect = useConnectChannel((line) => setLineId(line.id));
520
+ useEffect3(() => {
521
+ setConsent(false);
522
+ }, [number.quote?.token]);
523
+ useEffect3(() => {
524
+ setCancelId(null);
525
+ setLineId("");
526
+ connect.reset();
527
+ }, [transport, client, connect.reset]);
528
+ const busy = number.state.status === "pending" || connect.state.status === "pending";
529
+ const money = (cents) => new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(cents / 100);
530
+ return /* @__PURE__ */ jsxs("section", { className: `${panelClass} ${className}`, "aria-label": `${labels[transport]} number`, "aria-busy": busy, children: [
531
+ /* @__PURE__ */ jsxs("h2", { className: "text-lg font-semibold", children: [
532
+ "Get a ",
533
+ labels[transport],
534
+ " number"
535
+ ] }),
536
+ !client.ordering ? /* @__PURE__ */ jsx3("p", { children: "Number ordering is unavailable. Existing numbers are unchanged." }) : /* @__PURE__ */ jsx3(ChannelState, { resource: number.resource, empty: "Number availability has not been confirmed.", children: ({ readiness, complete }) => /* @__PURE__ */ jsxs(Fragment, { children: [
537
+ !readiness.configured && /* @__PURE__ */ jsx3("p", { children: "New orders are disabled. Existing numbers still require separate cancellation." }),
538
+ !complete && /* @__PURE__ */ jsx3("p", { role: "alert", children: "The order list is incomplete. New purchases are disabled until ownership can be confirmed." }),
539
+ number.held && /* @__PURE__ */ jsx3("p", { children: "This agent already holds an order for this transport. Cancel and release it before ordering another." }),
540
+ /* @__PURE__ */ jsx3("button", { type: "button", className: primaryButtonClass, disabled: busy || !complete || !readiness.configured || !readiness.transports.includes(transport) || !!number.held || number.uncertain, onClick: () => void number.run({ action: "quote" }), children: "See activation price" })
541
+ ] }) }),
542
+ number.quote && /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-md border border-border p-4", children: [
543
+ /* @__PURE__ */ jsxs("h3", { className: "font-medium", children: [
544
+ "Activation \xB7 ",
545
+ money(number.quote.activationCents)
546
+ ] }),
547
+ /* @__PURE__ */ jsx3("p", { className: "whitespace-pre-wrap", children: number.quote.terms || "Published activation terms are missing. Ordering is disabled." }),
548
+ /* @__PURE__ */ jsx3("p", { children: NUMBER_CHARGE_NOTICE }),
549
+ /* @__PURE__ */ jsxs("label", { className: "flex items-start gap-2", children: [
550
+ /* @__PURE__ */ jsx3("input", { type: "checkbox", checked: consent, disabled: busy, onChange: (event) => setConsent(event.target.checked) }),
551
+ "I authorize this one-time activation charge. The number is not live until channel setup is complete."
552
+ ] }),
553
+ !number.uncertain && number.quote.expiresAt <= Date.now() && /* @__PURE__ */ jsx3("p", { role: "alert", children: "This quote expired. Read a new activation price before ordering." }),
554
+ /* @__PURE__ */ jsx3("button", { type: "button", className: primaryButtonClass, disabled: busy || !consent || !number.quote.terms.trim() || !number.uncertain && number.quote.expiresAt <= Date.now(), onClick: () => void number.run({ action: "purchase", consent }), children: number.uncertain ? "Retry approved purchase" : `Order for ${money(number.quote.activationCents)}` })
555
+ ] }),
556
+ number.uncertain && /* @__PURE__ */ jsxs("p", { role: "alert", children: [
557
+ "The purchase response was lost or refused. Its outcome is not confirmed. ",
558
+ NUMBER_RETRY_NOTICE
559
+ ] }),
560
+ number.orders.map((order) => /* @__PURE__ */ jsxs("article", { className: "space-y-3 rounded-md border border-border p-4", children: [
561
+ /* @__PURE__ */ jsx3("h3", { className: "break-all font-medium", children: order.address ?? "Your number order" }),
562
+ /* @__PURE__ */ jsx3("p", { children: numberStage(order) }),
563
+ order.errorCode && /* @__PURE__ */ jsxs("p", { role: "status", children: [
564
+ "Status: ",
565
+ order.errorCode.replace(/_/g, " ")
566
+ ] }),
567
+ order.status === "ready_for_setup" && order.cancellation === "none" && /* @__PURE__ */ jsxs(Fragment, { children: [
568
+ /* @__PURE__ */ jsx3("p", { children: "The number is acquired, not delivery-verified. Test it before connecting conversations." }),
569
+ /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, disabled: busy, onClick: () => void connect.run({ kind: "order", orderId: order.id }), children: "Test this number" })
570
+ ] }),
571
+ /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, disabled: busy, onClick: () => void number.run({ action: "advance", orderId: order.id }), children: "Check progress" }),
572
+ " ",
573
+ order.cancellation === "none" && (cancelId !== order.id ? /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, disabled: busy, onClick: () => setCancelId(order.id), children: "Cancel number" }) : /* @__PURE__ */ jsxs("fieldset", { className: "space-y-2", children: [
574
+ /* @__PURE__ */ jsx3("legend", { children: "Cancel this number? Access will be removed. Paid activation is not automatically refunded." }),
575
+ /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, disabled: busy, onClick: async () => {
576
+ const result = await number.run({ action: "cancel", orderId: order.id, confirm: true });
577
+ if (result.succeeded) setCancelId(null);
578
+ }, children: "Confirm cancellation" }),
579
+ " ",
580
+ /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, disabled: busy, onClick: () => setCancelId(null), children: "Keep number" })
581
+ ] }))
582
+ ] }, order.id)),
583
+ /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, disabled: busy, onClick: number.resource.retry, children: "Refresh number status" }),
584
+ /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: "SMS and iMessage are separate activations. Buying a number does not enable WhatsApp." }),
585
+ /* @__PURE__ */ jsx3(ChannelFailure, { state: number.state }),
586
+ /* @__PURE__ */ jsx3(ChannelFailure, { state: connect.state }),
587
+ lineId && /* @__PURE__ */ jsx3(ChannelVerificationPanel, { lineId }, lineId)
588
+ ] });
589
+ }
590
+ function ChannelConversation({ lineId, threadId, className = "" }) {
591
+ const messages = useChannelConversation(lineId, threadId);
592
+ return /* @__PURE__ */ jsxs("section", { className: `${panelClass} ${className}`, "aria-label": "Channel conversation", children: [
593
+ /* @__PURE__ */ jsx3("h3", { className: "font-semibold", children: "Conversation" }),
594
+ /* @__PURE__ */ jsx3(ChannelState, { resource: messages, empty: "No messages in this conversation yet.", children: (items) => /* @__PURE__ */ jsx3("ol", { role: "log", "aria-live": "polite", "aria-label": "Messages", className: "space-y-3", children: items.map((message) => /* @__PURE__ */ jsxs("li", { className: "space-y-1 rounded-md bg-muted p-3", "data-direction": message.direction, children: [
595
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
596
+ message.direction === "in" ? "Incoming" : "Outgoing",
597
+ " \xB7 ",
598
+ message.kind,
599
+ " \xB7 ",
600
+ /* @__PURE__ */ jsx3("time", { dateTime: message.createdAt, children: message.createdAt })
601
+ ] }),
602
+ /* @__PURE__ */ jsx3("p", { className: "whitespace-pre-wrap break-words", children: message.text ?? "Non-text message" }),
603
+ /* @__PURE__ */ jsxs("p", { className: "text-xs", children: [
604
+ message.status,
605
+ message.errorCode ? ` \xB7 ${message.errorCode}` : ""
606
+ ] })
607
+ ] }, message.id)) }) }),
608
+ /* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, onClick: messages.retry, children: "Refresh messages" })
609
+ ] });
610
+ }
611
+ function ChannelConversations({ lineId, className = "" }) {
612
+ const threads = useChannelConversations(lineId);
613
+ const [selected, setSelected] = useState2(null);
614
+ const threadId = selected?.lineId === lineId ? selected.threadId : "";
615
+ return /* @__PURE__ */ jsxs("div", { className: `space-y-4 ${className}`, children: [
616
+ /* @__PURE__ */ jsxs("section", { className: panelClass, "aria-label": "Channel conversations", children: [
617
+ /* @__PURE__ */ jsx3("h2", { className: "text-lg font-semibold", children: "Conversations" }),
618
+ /* @__PURE__ */ jsx3(ChannelState, { resource: threads, empty: "No conversations yet. Members must message the line first.", children: (items) => /* @__PURE__ */ jsx3("nav", { "aria-label": "Choose a conversation", className: "flex flex-wrap gap-2", children: items.map((thread) => /* @__PURE__ */ jsxs("button", { type: "button", className: buttonClass, "aria-pressed": thread.id === threadId, onClick: () => setSelected({ lineId, threadId: thread.id }), children: [
619
+ thread.memberId,
620
+ " \xB7 ",
621
+ thread.status
622
+ ] }, thread.id)) }) })
623
+ ] }),
624
+ threadId && /* @__PURE__ */ jsx3(ChannelConversation, { lineId, threadId }, `${lineId}:${threadId}`)
625
+ ] });
626
+ }
627
+
628
+ // src/channels/payment.tsx
629
+ import { useEffect as useEffect4, useState as useState3 } from "react";
630
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
631
+ function safeCheckoutUrl(value) {
632
+ if (/^\/(?!\/)/.test(value) && !/[\\\s]/.test(value)) return value;
633
+ const url = new URL(value);
634
+ if (url.protocol !== "https:" || url.username || url.password) throw new Error("Checkout returned an unsafe URL.");
635
+ return url.href;
636
+ }
637
+ function useLinePayment(lineId) {
638
+ const { client } = useChannelsContext();
639
+ const resource = useChannelResource((context) => {
640
+ if (!client.payment) throw new Error("Payments are unavailable for this line.");
641
+ return client.payment.read(lineId, context);
642
+ }, [lineId], !!lineId && !!client.payment);
643
+ const mutation = useChannelMutation(async ({ consent }) => {
644
+ if (!client.payment || resource.status !== "ready") throw new Error("Read the current allowance before checkout.");
645
+ const { plan, allowance, terms } = resource.value;
646
+ if (!consent || !terms.trim()) throw new Error("Accept the published terms before checkout.");
647
+ if (!plan.configured || !plan.paid || !Number.isFinite(plan.paid.priceUsdMonthly) || plan.paid.priceUsdMonthly <= 0 || allowance.decision !== "paywall" || !allowance.paywall || allowance.tier !== "free") throw new Error("Checkout is not available for this allowance.");
648
+ return safeCheckoutUrl((await client.payment.checkout(lineId)).url);
649
+ });
650
+ useEffect4(() => mutation.reset(), [lineId, mutation.reset]);
651
+ return { resource, ...mutation, available: !!client.payment };
652
+ }
653
+ function LinePayPage({ lineId, className = "" }) {
654
+ const payment = useLinePayment(lineId);
655
+ const [consentedTerms, setConsentedTerms] = useState3(null);
656
+ const currentTerms = payment.resource.status === "ready" ? JSON.stringify([lineId, payment.resource.value.terms, payment.resource.value.plan.paid]) : null;
657
+ const consent = currentTerms !== null && consentedTerms === currentTerms;
658
+ return /* @__PURE__ */ jsxs2("section", { className: `${panelClass} ${className}`, "aria-label": "Line subscription", children: [
659
+ /* @__PURE__ */ jsx4("h2", { className: "text-lg font-semibold", children: "Your line plan" }),
660
+ !payment.available ? /* @__PURE__ */ jsx4("p", { children: "Payments are unavailable for this line." }) : /* @__PURE__ */ jsx4(ChannelState, { resource: payment.resource, empty: "No plan is available.", children: (value) => /* @__PURE__ */ jsxs2(Fragment2, { children: [
661
+ /* @__PURE__ */ jsx4("h3", { className: "font-medium", children: value.label }),
662
+ /* @__PURE__ */ jsxs2("p", { children: [
663
+ value.allowance.turns.used,
664
+ " of ",
665
+ value.allowance.turns.limit,
666
+ " turns used today."
667
+ ] }),
668
+ value.allowance.tier === "paid" ? /* @__PURE__ */ jsxs2("p", { role: "status", children: [
669
+ "Your paid allowance is active.",
670
+ value.allowance.decision === "allowance_reached" ? " Its current limit is exhausted; another checkout cannot lift it." : ""
671
+ ] }) : value.allowance.decision === "paywall" && value.plan.paid ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
672
+ /* @__PURE__ */ jsxs2("p", { children: [
673
+ new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(value.plan.paid.priceUsdMonthly),
674
+ " per month \xB7 ",
675
+ value.plan.paid.turnsPerDay,
676
+ " turns per day."
677
+ ] }),
678
+ /* @__PURE__ */ jsx4("p", { className: "whitespace-pre-wrap", children: value.terms || "Plan terms are unavailable. Checkout is disabled." }),
679
+ /* @__PURE__ */ jsxs2("label", { className: "flex items-start gap-2", children: [
680
+ /* @__PURE__ */ jsx4("input", { type: "checkbox", checked: consent, onChange: (event) => setConsentedTerms(event.target.checked ? currentTerms : null) }),
681
+ "I accept these subscription terms."
682
+ ] }),
683
+ /* @__PURE__ */ jsx4("button", { type: "button", className: primaryButtonClass, disabled: !consent || !value.terms.trim() || payment.state.status === "pending", onClick: () => void payment.run({ consent }), children: "Continue to checkout" }),
684
+ /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted-foreground", children: "Payment is confirmed by the server after settlement, not by returning to this page." })
685
+ ] }) : /* @__PURE__ */ jsxs2("p", { children: [
686
+ value.allowance.decision === "allowance_reached" ? "Your allowance is exhausted. Paying does not lift this limit today." : "Your current allowance is available.",
687
+ " Resets at ",
688
+ value.allowance.resetsAt,
689
+ "."
690
+ ] }),
691
+ /* @__PURE__ */ jsx4("button", { type: "button", className: buttonClass, onClick: payment.resource.retry, children: "Check payment status" })
692
+ ] }) }),
693
+ payment.state.status === "succeeded" && consent && /* @__PURE__ */ jsx4("a", { className: "underline", href: payment.state.value, rel: "noopener noreferrer", children: "Open secure checkout" }),
694
+ /* @__PURE__ */ jsx4(ChannelFailure, { state: payment.state })
695
+ ] });
696
+ }
697
+ export {
698
+ ChannelConnect,
699
+ ChannelConversation,
700
+ ChannelConversations,
701
+ ChannelVerificationPanel,
702
+ ChannelsProvider,
703
+ EmailChannel,
704
+ IMessageChannel,
705
+ LinePayPage,
706
+ NUMBER_CHARGE_NOTICE,
707
+ NUMBER_RETRY_NOTICE,
708
+ NumberChannel,
709
+ SMSChannel,
710
+ WhatsAppChannel,
711
+ channelMessageLink,
712
+ holdsNumber,
713
+ numberStage,
714
+ safeCheckoutUrl,
715
+ useChannel,
716
+ useChannelConnections,
717
+ useChannelConversation,
718
+ useChannelConversations,
719
+ useChannels,
720
+ useChannelsClient,
721
+ useConnectChannel,
722
+ useLinePayment,
723
+ useNumberChannel,
724
+ useWhatsAppNumbers,
725
+ verificationExpired
726
+ };
727
+ //# sourceMappingURL=index.js.map