@moonpay/platform-sdk-react-native 0.0.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,702 @@
1
+ // src/frame-component.tsx
2
+ import { useEffect, useRef } from "react";
3
+ import { View } from "react-native";
4
+ import WebView from "react-native-webview";
5
+ import { jsx } from "react/jsx-runtime";
6
+ function MoonPayFrame({ transport, hidden, style }) {
7
+ const webViewRef = useRef(null);
8
+ useEffect(() => {
9
+ transport.attachWebView(webViewRef);
10
+ return () => {
11
+ };
12
+ }, [transport]);
13
+ const url = transport.url;
14
+ if (!url)
15
+ return null;
16
+ const handleMessage = (event) => {
17
+ transport.handleWebViewMessage(event.nativeEvent.data);
18
+ };
19
+ const containerStyle = hidden ? { width: 0, height: 0, overflow: "hidden" } : { flex: 1, ...style };
20
+ return /* @__PURE__ */ jsx(View, { style: containerStyle, children: /* @__PURE__ */ jsx(
21
+ WebView,
22
+ {
23
+ ref: webViewRef,
24
+ source: { uri: url },
25
+ onMessage: handleMessage,
26
+ allowsInlineMediaPlayback: true,
27
+ javaScriptEnabled: true,
28
+ domStorageEnabled: true,
29
+ style: { flex: 1 }
30
+ }
31
+ ) });
32
+ }
33
+
34
+ // src/provider.tsx
35
+ import {
36
+ ConnectionStatus,
37
+ err,
38
+ ok
39
+ } from "@moonpay/platform-protocol";
40
+ import {
41
+ buildFrameUrl,
42
+ createClientCore,
43
+ createFrameOrchestrator,
44
+ decryptCredentials,
45
+ FRAME_PATHS,
46
+ generateKeyPair
47
+ } from "@moonpay/platform-sdk-core";
48
+ import {
49
+ createContext,
50
+ useCallback,
51
+ useContext,
52
+ useMemo,
53
+ useRef as useRef2,
54
+ useState
55
+ } from "react";
56
+
57
+ // src/webview-transport.ts
58
+ var WebViewTransport = class {
59
+ webViewRef = null;
60
+ handlers = [];
61
+ _url = "";
62
+ _options = null;
63
+ /** Called by the frame component when it mounts the WebView. */
64
+ attachWebView(ref) {
65
+ this.webViewRef = ref;
66
+ }
67
+ /** Called by the WebView's onMessage prop to route incoming messages. */
68
+ handleWebViewMessage(data) {
69
+ try {
70
+ const msg = JSON.parse(data);
71
+ if (!msg.kind)
72
+ return;
73
+ for (const handler of this.handlers) {
74
+ handler(msg);
75
+ }
76
+ } catch {
77
+ }
78
+ }
79
+ get url() {
80
+ return this._url;
81
+ }
82
+ get options() {
83
+ return this._options;
84
+ }
85
+ // FrameTransport interface
86
+ create(url, options) {
87
+ this._url = url;
88
+ this._options = options;
89
+ }
90
+ sendMessage(message) {
91
+ if (!this.webViewRef?.current) {
92
+ throw new Error("Cannot send message: WebView not attached");
93
+ }
94
+ const js = `
95
+ window.postMessage(${JSON.stringify(JSON.stringify(message))}, '*');
96
+ true;
97
+ `;
98
+ this.webViewRef.current.injectJavaScript(js);
99
+ }
100
+ onMessage(handler) {
101
+ this.handlers.push(handler);
102
+ return () => {
103
+ const idx = this.handlers.indexOf(handler);
104
+ if (idx >= 0)
105
+ this.handlers.splice(idx, 1);
106
+ };
107
+ }
108
+ dispose() {
109
+ this.handlers.length = 0;
110
+ this.webViewRef = null;
111
+ this._url = "";
112
+ this._options = null;
113
+ }
114
+ };
115
+
116
+ // src/provider.tsx
117
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
118
+ var MoonPayContext = createContext(null);
119
+ var slotCounter = 0;
120
+ function MoonPayProvider({
121
+ sessionToken,
122
+ apiBaseUrl,
123
+ frameBaseUrl,
124
+ children
125
+ }) {
126
+ const [frameSlots, setFrameSlots] = useState([]);
127
+ const addSlot = useCallback((slot) => {
128
+ setFrameSlots((prev) => [...prev, slot]);
129
+ }, []);
130
+ const removeSlot = useCallback((id) => {
131
+ setFrameSlots((prev) => prev.filter((s) => s.id !== id));
132
+ }, []);
133
+ const coreRef = useRef2(null);
134
+ if (!coreRef.current) {
135
+ coreRef.current = createClientCore({
136
+ apiBaseUrl,
137
+ frameBaseUrl,
138
+ createTransport: () => new WebViewTransport()
139
+ });
140
+ }
141
+ const core = coreRef.current;
142
+ const client = useMemo(() => {
143
+ function createManagedFrame(opts) {
144
+ const transport = new WebViewTransport();
145
+ const slotId = `slot-${++slotCounter}`;
146
+ const promise = new Promise((resolve, reject) => {
147
+ transport.create(opts.url, { container: null, hidden: opts.hidden });
148
+ addSlot({ id: slotId, transport, hidden: opts.hidden });
149
+ createFrameOrchestrator({
150
+ transport,
151
+ channelId: opts.channelId,
152
+ url: opts.url,
153
+ container: null,
154
+ hidden: opts.hidden,
155
+ handshakeTimeout: opts.handshakeTimeout ?? 15e3
156
+ }).then((handle) => resolve({ handle, slotId })).catch((e) => {
157
+ removeSlot(slotId);
158
+ reject(e);
159
+ });
160
+ });
161
+ return { promise, transport };
162
+ }
163
+ async function getConnection() {
164
+ const { privateKey, publicKey } = generateKeyPair();
165
+ const channelId = `check-${Date.now()}`;
166
+ const url = buildFrameUrl(
167
+ FRAME_PATHS.checkConnection,
168
+ { sessionToken, channelId, publicKey },
169
+ { frameBaseUrl }
170
+ );
171
+ try {
172
+ const { promise } = createManagedFrame({
173
+ url,
174
+ channelId,
175
+ hidden: true,
176
+ handshakeTimeout: 1e4
177
+ });
178
+ const { handle, slotId } = await promise;
179
+ return new Promise((resolve) => {
180
+ const timeout = setTimeout(() => {
181
+ handle.dispose();
182
+ removeSlot(slotId);
183
+ resolve(err({ message: "Get connection timed out" }));
184
+ }, 1e4);
185
+ handle.onMessage((msg) => {
186
+ if (msg.kind === "error") {
187
+ clearTimeout(timeout);
188
+ handle.dispose();
189
+ removeSlot(slotId);
190
+ const payload = msg.payload;
191
+ resolve(err({ message: payload.message }));
192
+ return;
193
+ }
194
+ if (msg.kind === "complete") {
195
+ clearTimeout(timeout);
196
+ handle.dispose();
197
+ removeSlot(slotId);
198
+ const connection = msg.payload;
199
+ if (connection.status === ConnectionStatus.active) {
200
+ const creds = decryptCredentials(connection.credentials, privateKey);
201
+ core.context.setAccessToken(creds.accessToken);
202
+ core.context.setClientToken(creds.clientToken);
203
+ }
204
+ resolve(ok(connection));
205
+ }
206
+ });
207
+ });
208
+ } catch (e) {
209
+ return err({
210
+ message: e instanceof Error ? e.message : "Failed to check connection"
211
+ });
212
+ }
213
+ }
214
+ async function connect(opts) {
215
+ const { privateKey, publicKey } = generateKeyPair();
216
+ const channelId = `connect-${Date.now()}`;
217
+ const url = buildFrameUrl(
218
+ FRAME_PATHS.connect,
219
+ {
220
+ sessionToken,
221
+ channelId,
222
+ publicKey,
223
+ ...opts.theme?.appearance && { appearance: opts.theme.appearance }
224
+ },
225
+ { frameBaseUrl }
226
+ );
227
+ try {
228
+ const { promise } = createManagedFrame({ url, channelId, hidden: false });
229
+ const { handle, slotId } = await promise;
230
+ return new Promise((resolve) => {
231
+ handle.onMessage((msg) => {
232
+ if (msg.kind === "ready") {
233
+ opts.onEvent?.({ kind: "ready" });
234
+ return;
235
+ }
236
+ if (msg.kind === "error") {
237
+ const payload = msg.payload;
238
+ opts.onEvent?.({ kind: "error", payload });
239
+ handle.dispose();
240
+ removeSlot(slotId);
241
+ resolve(err({ message: "Connection error" }));
242
+ return;
243
+ }
244
+ if (msg.kind === "complete") {
245
+ const connection = msg.payload;
246
+ if (connection.status === ConnectionStatus.active) {
247
+ const creds = decryptCredentials(connection.credentials, privateKey);
248
+ core.context.setAccessToken(creds.accessToken);
249
+ core.context.setClientToken(creds.clientToken);
250
+ }
251
+ opts.onEvent?.({ kind: "complete", payload: connection });
252
+ resolve(
253
+ ok({
254
+ dispose: () => {
255
+ handle.dispose();
256
+ removeSlot(slotId);
257
+ }
258
+ })
259
+ );
260
+ }
261
+ });
262
+ });
263
+ } catch (e) {
264
+ return err({
265
+ message: e instanceof Error ? e.message : "Connect failed"
266
+ });
267
+ }
268
+ }
269
+ async function setupWidget(opts) {
270
+ const channelId = `widget-${Date.now()}`;
271
+ const url = buildFrameUrl(
272
+ FRAME_PATHS.widget,
273
+ {
274
+ flow: "buy",
275
+ clientToken: core.context.clientToken,
276
+ quoteSignature: opts.quote,
277
+ channelId
278
+ },
279
+ { frameBaseUrl }
280
+ );
281
+ try {
282
+ const { promise } = createManagedFrame({ url, channelId, hidden: false });
283
+ const { handle, slotId } = await promise;
284
+ handle.onMessage((msg) => {
285
+ switch (msg.kind) {
286
+ case "ready":
287
+ opts.onEvent?.({ kind: "ready" });
288
+ break;
289
+ case "transactionCreated":
290
+ opts.onEvent?.({
291
+ kind: "transactionCreated",
292
+ payload: msg.payload
293
+ });
294
+ break;
295
+ case "complete":
296
+ opts.onEvent?.({
297
+ kind: "complete",
298
+ payload: msg.payload
299
+ });
300
+ break;
301
+ case "error": {
302
+ const payload = msg.payload;
303
+ opts.onEvent?.({
304
+ kind: "error",
305
+ payload: { code: payload.code, message: payload.message }
306
+ });
307
+ break;
308
+ }
309
+ case "close":
310
+ opts.onEvent?.({ kind: "close" });
311
+ break;
312
+ }
313
+ });
314
+ return ok({
315
+ dispose: () => {
316
+ handle.dispose();
317
+ removeSlot(slotId);
318
+ }
319
+ });
320
+ } catch (e) {
321
+ return err({
322
+ kind: "genericError",
323
+ message: e instanceof Error ? e.message : "Widget setup failed"
324
+ });
325
+ }
326
+ }
327
+ async function setupApplePay(opts) {
328
+ const channelId = `applepay-${Date.now()}`;
329
+ const url = buildFrameUrl(
330
+ FRAME_PATHS.applePay,
331
+ {
332
+ clientToken: core.context.clientToken,
333
+ signature: opts.quote,
334
+ channelId
335
+ },
336
+ { frameBaseUrl }
337
+ );
338
+ try {
339
+ const { promise } = createManagedFrame({ url, channelId, hidden: false });
340
+ const { handle, slotId } = await promise;
341
+ const setQuote = (signature) => {
342
+ handle.sendMessage({
343
+ version: 2,
344
+ meta: { channelId },
345
+ kind: "setQuote",
346
+ payload: { quote: { signature } }
347
+ });
348
+ };
349
+ handle.onMessage((msg) => {
350
+ switch (msg.kind) {
351
+ case "ready":
352
+ opts.onEvent?.({ kind: "ready" });
353
+ break;
354
+ case "complete":
355
+ opts.onEvent?.({
356
+ kind: "complete",
357
+ payload: msg.payload
358
+ });
359
+ break;
360
+ case "error": {
361
+ const payload = msg.payload;
362
+ if (payload.code === "quoteExpired") {
363
+ opts.onEvent?.({ kind: "quoteExpired", payload: { setQuote } });
364
+ } else if (payload.code === "applePayUnavailable") {
365
+ opts.onEvent?.({ kind: "unsupported" });
366
+ } else {
367
+ const kind = payload.code === "generic" ? "genericError" : payload.code;
368
+ opts.onEvent?.({
369
+ kind: "error",
370
+ payload: { kind, message: payload.message }
371
+ });
372
+ }
373
+ break;
374
+ }
375
+ }
376
+ });
377
+ return ok({
378
+ setQuote,
379
+ dispose: () => {
380
+ handle.dispose();
381
+ removeSlot(slotId);
382
+ }
383
+ });
384
+ } catch (e) {
385
+ return err({
386
+ kind: "genericError",
387
+ message: e instanceof Error ? e.message : "Apple Pay setup failed"
388
+ });
389
+ }
390
+ }
391
+ async function setupBuy(opts) {
392
+ const channelId = `buy-${Date.now()}`;
393
+ const url = buildFrameUrl(
394
+ FRAME_PATHS.buy,
395
+ {
396
+ channelId,
397
+ clientToken: core.context.clientToken,
398
+ signature: opts.quote,
399
+ ...opts.externalTransactionId && { externalTransactionId: opts.externalTransactionId }
400
+ },
401
+ { frameBaseUrl }
402
+ );
403
+ try {
404
+ const { promise } = createManagedFrame({ url, channelId, hidden: true });
405
+ const { handle, slotId } = await promise;
406
+ const setQuote = (signature) => {
407
+ handle.sendMessage({
408
+ version: 2,
409
+ meta: { channelId },
410
+ kind: "setQuote",
411
+ payload: { quote: { signature } }
412
+ });
413
+ };
414
+ handle.onMessage((msg) => {
415
+ switch (msg.kind) {
416
+ case "ready":
417
+ opts.onEvent?.({ kind: "ready" });
418
+ break;
419
+ case "complete":
420
+ opts.onEvent?.({
421
+ kind: "complete",
422
+ payload: msg.payload
423
+ });
424
+ break;
425
+ case "challenge":
426
+ opts.onEvent?.({
427
+ kind: "challenge",
428
+ payload: msg.payload
429
+ });
430
+ break;
431
+ case "error":
432
+ opts.onEvent?.({
433
+ kind: "error",
434
+ payload: msg.payload
435
+ });
436
+ break;
437
+ }
438
+ });
439
+ return ok({
440
+ setQuote,
441
+ dispose: () => {
442
+ handle.dispose();
443
+ removeSlot(slotId);
444
+ }
445
+ });
446
+ } catch (e) {
447
+ return err({
448
+ kind: "genericError",
449
+ message: e instanceof Error ? e.message : "Buy setup failed"
450
+ });
451
+ }
452
+ }
453
+ async function setupBuyButton(opts) {
454
+ const channelId = `buy-button-${Date.now()}`;
455
+ const url = buildFrameUrl(
456
+ FRAME_PATHS.buyButton,
457
+ {
458
+ channelId,
459
+ clientToken: core.context.clientToken,
460
+ signature: opts.quote
461
+ },
462
+ { frameBaseUrl }
463
+ );
464
+ try {
465
+ const { promise } = createManagedFrame({ url, channelId, hidden: false });
466
+ const { handle, slotId } = await promise;
467
+ const setQuote = (signature) => {
468
+ handle.sendMessage({
469
+ version: 2,
470
+ meta: { channelId },
471
+ kind: "setQuote",
472
+ payload: { quote: { signature } }
473
+ });
474
+ };
475
+ handle.onMessage((msg) => {
476
+ switch (msg.kind) {
477
+ case "complete":
478
+ opts.onEvent?.({
479
+ kind: "complete",
480
+ payload: msg.payload
481
+ });
482
+ break;
483
+ case "challenge":
484
+ opts.onEvent?.({
485
+ kind: "challenge",
486
+ payload: msg.payload
487
+ });
488
+ break;
489
+ case "error":
490
+ opts.onEvent?.({
491
+ kind: "error",
492
+ payload: msg.payload
493
+ });
494
+ break;
495
+ }
496
+ });
497
+ return ok({
498
+ setQuote,
499
+ dispose: () => {
500
+ handle.dispose();
501
+ removeSlot(slotId);
502
+ }
503
+ });
504
+ } catch (e) {
505
+ return err({
506
+ kind: "genericError",
507
+ message: e instanceof Error ? e.message : "Buy button setup failed"
508
+ });
509
+ }
510
+ }
511
+ async function setupGooglePay(opts) {
512
+ const channelId = `googlepay-${Date.now()}`;
513
+ const url = buildFrameUrl(
514
+ FRAME_PATHS.googlePay,
515
+ {
516
+ clientToken: core.context.clientToken,
517
+ signature: opts.quote,
518
+ channelId,
519
+ ...opts.externalTransactionId && { externalTransactionId: opts.externalTransactionId }
520
+ },
521
+ { frameBaseUrl }
522
+ );
523
+ try {
524
+ const { promise } = createManagedFrame({ url, channelId, hidden: false });
525
+ const { handle, slotId } = await promise;
526
+ const setQuote = (signature) => {
527
+ handle.sendMessage({
528
+ version: 2,
529
+ meta: { channelId },
530
+ kind: "setQuote",
531
+ payload: { quote: { signature } }
532
+ });
533
+ };
534
+ handle.onMessage((msg) => {
535
+ switch (msg.kind) {
536
+ case "ready":
537
+ opts.onEvent?.({ kind: "ready" });
538
+ break;
539
+ case "complete":
540
+ opts.onEvent?.({
541
+ kind: "complete",
542
+ payload: msg.payload
543
+ });
544
+ break;
545
+ case "challenge":
546
+ opts.onEvent?.({
547
+ kind: "challenge",
548
+ payload: msg.payload
549
+ });
550
+ break;
551
+ case "error": {
552
+ const payload = msg.payload;
553
+ if (payload.code === "quoteExpired") {
554
+ opts.onEvent?.({ kind: "quoteExpired", payload: { setQuote } });
555
+ } else if (payload.code === "googlePayUnavailable") {
556
+ opts.onEvent?.({ kind: "unsupported" });
557
+ } else {
558
+ const kind = payload.code === "generic" ? "genericError" : payload.code;
559
+ opts.onEvent?.({
560
+ kind: "error",
561
+ payload: { kind, message: payload.message }
562
+ });
563
+ }
564
+ break;
565
+ }
566
+ }
567
+ });
568
+ return ok({
569
+ setQuote,
570
+ dispose: () => {
571
+ handle.dispose();
572
+ removeSlot(slotId);
573
+ }
574
+ });
575
+ } catch (e) {
576
+ return err({
577
+ kind: "genericError",
578
+ message: e instanceof Error ? e.message : "Google Pay setup failed"
579
+ });
580
+ }
581
+ }
582
+ async function setupChallenge(opts) {
583
+ let channelId;
584
+ try {
585
+ channelId = new URL(opts.url).searchParams.get("channelId") ?? "";
586
+ if (!channelId)
587
+ throw new Error("missing channelId");
588
+ } catch {
589
+ return err({ kind: "configurationError", message: "Challenge URL is missing channelId" });
590
+ }
591
+ try {
592
+ const { promise } = createManagedFrame({ url: opts.url, channelId, hidden: false });
593
+ const { handle, slotId } = await promise;
594
+ handle.onMessage((msg) => {
595
+ switch (msg.kind) {
596
+ case "ready":
597
+ opts.onEvent?.({ kind: "ready" });
598
+ break;
599
+ case "complete":
600
+ opts.onEvent?.({
601
+ kind: "complete",
602
+ payload: msg.payload
603
+ });
604
+ break;
605
+ case "cancelled":
606
+ opts.onEvent?.({
607
+ kind: "cancelled",
608
+ payload: msg.payload
609
+ });
610
+ break;
611
+ case "error":
612
+ opts.onEvent?.({
613
+ kind: "error",
614
+ payload: msg.payload
615
+ });
616
+ break;
617
+ }
618
+ });
619
+ return ok({
620
+ dispose: () => {
621
+ handle.dispose();
622
+ removeSlot(slotId);
623
+ }
624
+ });
625
+ } catch (e) {
626
+ return err({
627
+ kind: "genericError",
628
+ message: e instanceof Error ? e.message : "Challenge setup failed"
629
+ });
630
+ }
631
+ }
632
+ async function setupAddCard(opts) {
633
+ const channelId = `add-card-${Date.now()}`;
634
+ const url = buildFrameUrl(
635
+ FRAME_PATHS.addCard,
636
+ { channelId, clientToken: core.context.clientToken },
637
+ { frameBaseUrl }
638
+ );
639
+ try {
640
+ const { promise } = createManagedFrame({ url, channelId, hidden: false });
641
+ const { handle, slotId } = await promise;
642
+ handle.onMessage((msg) => {
643
+ switch (msg.kind) {
644
+ case "complete":
645
+ opts.onEvent?.({ kind: "complete", payload: msg.payload });
646
+ break;
647
+ case "error":
648
+ opts.onEvent?.({
649
+ kind: "error",
650
+ payload: msg.payload
651
+ });
652
+ break;
653
+ }
654
+ });
655
+ return ok({
656
+ dispose: () => {
657
+ handle.dispose();
658
+ removeSlot(slotId);
659
+ }
660
+ });
661
+ } catch (e) {
662
+ return err({
663
+ kind: "genericError",
664
+ message: e instanceof Error ? e.message : "Add card setup failed"
665
+ });
666
+ }
667
+ }
668
+ return {
669
+ getConnection,
670
+ connect,
671
+ getPaymentMethods: () => core.getPaymentMethods(),
672
+ getQuote: (params) => core.getQuote(params),
673
+ listTransactions: (params) => core.listTransactions(params),
674
+ getTransaction: (id) => core.getTransaction(id),
675
+ setupWidget,
676
+ setupApplePay,
677
+ setupBuy,
678
+ setupBuyButton,
679
+ setupGooglePay,
680
+ setupChallenge,
681
+ setupAddCard
682
+ };
683
+ }, [sessionToken, frameBaseUrl, core, addSlot, removeSlot]);
684
+ return /* @__PURE__ */ jsxs(MoonPayContext.Provider, { value: { client }, children: [
685
+ children,
686
+ frameSlots.map((slot) => /* @__PURE__ */ jsx2(MoonPayFrame, { transport: slot.transport, hidden: slot.hidden }, slot.id))
687
+ ] });
688
+ }
689
+ function useMoonPay() {
690
+ const ctx = useContext(MoonPayContext);
691
+ if (!ctx) {
692
+ throw new Error("useMoonPay must be used within a <MoonPayProvider>");
693
+ }
694
+ return ctx;
695
+ }
696
+ export {
697
+ MoonPayFrame,
698
+ MoonPayProvider,
699
+ WebViewTransport,
700
+ useMoonPay
701
+ };
702
+ //# sourceMappingURL=index.js.map