@moonpay/platform-sdk-react-native 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -24,10 +24,11 @@ import * as GooglePaySdk from '@moonpay/platform-protocol/sdks/google-pay';
24
24
  export { Event as GooglePayEvent, SetupError as SetupGooglePayError } from '@moonpay/platform-protocol/sdks/google-pay';
25
25
  import * as WidgetSdk from '@moonpay/platform-protocol/sdks/widget';
26
26
  export { SetupError as SetupWidgetError, Event as WidgetEvent } from '@moonpay/platform-protocol/sdks/widget';
27
+ import { FrameTransport, FrameOptions, ThemeOptions, ListTransactionsParams } from '@moonpay/platform-sdk-core';
28
+ export { ThemeOptions } from '@moonpay/platform-sdk-core';
27
29
  import { GetPaymentMethodsError, GetQuoteError, GetTransactionsError, DevPlatformApiError, CreateIdentityError, GetIdentityError, UpdateIdentityError, VerifyIdentityError, GetIdentityUploadUrlError, SubmitIdentityFilesError } from '@moonpay/platform-protocol/api/errors';
28
30
  import { CreateIdentityRequestBody, Identity, UpdateIdentityRequestBody, IdentityVerificationResponse, IdentityFileUploadUrlRequestBody, IdentityFileUploadUrl, SubmitIdentityFilesRequestBody } from '@moonpay/platform-protocol/api/identity';
29
31
  import { ResetConnectionError } from '@moonpay/platform-protocol/sdks/reset';
30
- import { FrameTransport, FrameOptions, ListTransactionsParams } from '@moonpay/platform-sdk-core';
31
32
  import { ViewStyle } from 'react-native';
32
33
  import { ProtocolMessage } from '@moonpay/platform-protocol/protocol';
33
34
  import { RefObject, ReactNode } from 'react';
@@ -132,10 +133,8 @@ interface ChallengeFrame {
132
133
  }
133
134
 
134
135
  interface ConnectOptions {
135
- /** Theme options for the connect frame. */
136
- theme?: {
137
- appearance?: 'light' | 'dark';
138
- };
136
+ /** Theme for this connect frame. Overrides the provider-level `theme`. */
137
+ theme?: ThemeOptions;
139
138
  /** Callback for connect lifecycle events. */
140
139
  onEvent?: (event: ConnectSdk.Event) => void;
141
140
  }
@@ -306,10 +305,8 @@ interface MoonPayChallengeProps {
306
305
  declare function MoonPayChallenge({ url, onEvent, style }: MoonPayChallengeProps): JSX.Element;
307
306
 
308
307
  interface MoonPayConnectProps {
309
- /** Theme options for the connect frame. Mount-time only. */
310
- theme?: {
311
- appearance?: 'light' | 'dark';
312
- };
308
+ /** Theme for this connect frame. Overrides the provider-level `theme`. Mount-time only. */
309
+ theme?: ThemeOptions;
313
310
  /** Callback for connect lifecycle events. */
314
311
  onEvent?: (event: ConnectSdk.Event) => void;
315
312
  /** Container style. Defaults to flex: 1. */
@@ -398,9 +395,11 @@ interface MoonPayProviderProps {
398
395
  sessionToken: string;
399
396
  apiBaseUrl?: string;
400
397
  frameBaseUrl?: string;
398
+ /** Theme applied to every visible MoonPay frame (connect, widget, Apple/Google Pay, …). */
399
+ theme?: ThemeOptions;
401
400
  children: ReactNode;
402
401
  }
403
- declare function MoonPayProvider({ sessionToken, apiBaseUrl, frameBaseUrl, children, }: MoonPayProviderProps): JSX.Element;
402
+ declare function MoonPayProvider({ sessionToken, apiBaseUrl, frameBaseUrl, theme, children, }: MoonPayProviderProps): JSX.Element;
404
403
  declare function useMoonPay(): {
405
404
  client: RNClient;
406
405
  };
package/dist/index.d.ts CHANGED
@@ -24,10 +24,11 @@ import * as GooglePaySdk from '@moonpay/platform-protocol/sdks/google-pay';
24
24
  export { Event as GooglePayEvent, SetupError as SetupGooglePayError } from '@moonpay/platform-protocol/sdks/google-pay';
25
25
  import * as WidgetSdk from '@moonpay/platform-protocol/sdks/widget';
26
26
  export { SetupError as SetupWidgetError, Event as WidgetEvent } from '@moonpay/platform-protocol/sdks/widget';
27
+ import { FrameTransport, FrameOptions, ThemeOptions, ListTransactionsParams } from '@moonpay/platform-sdk-core';
28
+ export { ThemeOptions } from '@moonpay/platform-sdk-core';
27
29
  import { GetPaymentMethodsError, GetQuoteError, GetTransactionsError, DevPlatformApiError, CreateIdentityError, GetIdentityError, UpdateIdentityError, VerifyIdentityError, GetIdentityUploadUrlError, SubmitIdentityFilesError } from '@moonpay/platform-protocol/api/errors';
28
30
  import { CreateIdentityRequestBody, Identity, UpdateIdentityRequestBody, IdentityVerificationResponse, IdentityFileUploadUrlRequestBody, IdentityFileUploadUrl, SubmitIdentityFilesRequestBody } from '@moonpay/platform-protocol/api/identity';
29
31
  import { ResetConnectionError } from '@moonpay/platform-protocol/sdks/reset';
30
- import { FrameTransport, FrameOptions, ListTransactionsParams } from '@moonpay/platform-sdk-core';
31
32
  import { ViewStyle } from 'react-native';
32
33
  import { ProtocolMessage } from '@moonpay/platform-protocol/protocol';
33
34
  import { RefObject, ReactNode } from 'react';
@@ -132,10 +133,8 @@ interface ChallengeFrame {
132
133
  }
133
134
 
134
135
  interface ConnectOptions {
135
- /** Theme options for the connect frame. */
136
- theme?: {
137
- appearance?: 'light' | 'dark';
138
- };
136
+ /** Theme for this connect frame. Overrides the provider-level `theme`. */
137
+ theme?: ThemeOptions;
139
138
  /** Callback for connect lifecycle events. */
140
139
  onEvent?: (event: ConnectSdk.Event) => void;
141
140
  }
@@ -306,10 +305,8 @@ interface MoonPayChallengeProps {
306
305
  declare function MoonPayChallenge({ url, onEvent, style }: MoonPayChallengeProps): JSX.Element;
307
306
 
308
307
  interface MoonPayConnectProps {
309
- /** Theme options for the connect frame. Mount-time only. */
310
- theme?: {
311
- appearance?: 'light' | 'dark';
312
- };
308
+ /** Theme for this connect frame. Overrides the provider-level `theme`. Mount-time only. */
309
+ theme?: ThemeOptions;
313
310
  /** Callback for connect lifecycle events. */
314
311
  onEvent?: (event: ConnectSdk.Event) => void;
315
312
  /** Container style. Defaults to flex: 1. */
@@ -398,9 +395,11 @@ interface MoonPayProviderProps {
398
395
  sessionToken: string;
399
396
  apiBaseUrl?: string;
400
397
  frameBaseUrl?: string;
398
+ /** Theme applied to every visible MoonPay frame (connect, widget, Apple/Google Pay, …). */
399
+ theme?: ThemeOptions;
401
400
  children: ReactNode;
402
401
  }
403
- declare function MoonPayProvider({ sessionToken, apiBaseUrl, frameBaseUrl, children, }: MoonPayProviderProps): JSX.Element;
402
+ declare function MoonPayProvider({ sessionToken, apiBaseUrl, frameBaseUrl, theme, children, }: MoonPayProviderProps): JSX.Element;
404
403
  declare function useMoonPay(): {
405
404
  client: RNClient;
406
405
  };
package/dist/index.js CHANGED
@@ -6,7 +6,11 @@ import {
6
6
  ConnectionErrorCode,
7
7
  ConnectionStatus
8
8
  } from "@moonpay/platform-protocol/models/connection";
9
- import { decryptCredentials, FRAME_PATHS } from "@moonpay/platform-sdk-core";
9
+ import {
10
+ decryptCredentials,
11
+ FRAME_PATHS,
12
+ themeParams
13
+ } from "@moonpay/platform-sdk-core";
10
14
  function createQuoteCommands(send, channelId) {
11
15
  return {
12
16
  setQuote: (signature) => send({
@@ -112,6 +116,8 @@ var buyButtonSpec = {
112
116
  },
113
117
  mapMessage: (msg) => {
114
118
  switch (msg.kind) {
119
+ case "ready":
120
+ return { kind: "ready" };
115
121
  case "complete":
116
122
  return { kind: "complete", payload: msg.payload };
117
123
  case "challenge":
@@ -174,7 +180,9 @@ var connectSpec = {
174
180
  buildParams: (ctx, props, publicKey) => ({
175
181
  sessionToken: ctx.sessionToken,
176
182
  publicKey,
177
- ...props.theme?.appearance && { appearance: props.theme.appearance }
183
+ // Per-call override; `applyThemeParam` in the engine fills the client
184
+ // default for connect (and every other visible frame) when this is absent.
185
+ ...themeParams(props.theme)
178
186
  }),
179
187
  onMessageEffect: applyCredentialsOnComplete,
180
188
  mapMessage: (msg) => {
@@ -408,6 +416,7 @@ function MoonPayFrame({ transport, hidden, style }) {
408
416
 
409
417
  // src/frame-engine.ts
410
418
  import {
419
+ applyThemeParam,
411
420
  buildFrameUrl,
412
421
  createFrameOrchestrator,
413
422
  generateKeyPair
@@ -479,11 +488,12 @@ function createFrameSession(spec, ctx, props, overrides = {}) {
479
488
  const generated = `${spec.channelPrefix}-${Date.now()}`;
480
489
  const channelId = spec.resolveChannelId ? spec.resolveChannelId(props, generated) : generated;
481
490
  const keyPair = spec.needsKeyPair ? generateKeyPair() : void 0;
482
- const url = spec.buildUrl ? spec.buildUrl(ctx, props, channelId, keyPair?.publicKey) : buildFrameUrl(
491
+ const builtUrl = spec.buildUrl ? spec.buildUrl(ctx, props, channelId, keyPair?.publicKey) : buildFrameUrl(
483
492
  spec.path,
484
493
  { ...spec.buildParams?.(ctx, props, keyPair?.publicKey), channelId },
485
494
  { frameBaseUrl: ctx.frameBaseUrl }
486
495
  );
496
+ const url = spec.hidden ? builtUrl : applyThemeParam(builtUrl, ctx.theme);
487
497
  const transport = (overrides.createTransport ?? (() => new WebViewTransport()))();
488
498
  let resolvedHandle = null;
489
499
  let rejectDisposed;
@@ -579,7 +589,7 @@ import {
579
589
  createContext,
580
590
  useCallback as useCallback2,
581
591
  useContext,
582
- useMemo,
592
+ useMemo as useMemo2,
583
593
  useRef as useRef3,
584
594
  useState as useState2
585
595
  } from "react";
@@ -872,20 +882,47 @@ function createMountSession(specCtx, host) {
872
882
  }
873
883
 
874
884
  // src/VisibleFrameSlotSheet.tsx
875
- import { useCallback, useEffect as useEffect2, useRef as useRef2, useState } from "react";
885
+ import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState } from "react";
876
886
  import {
877
887
  ActivityIndicator,
878
888
  Animated,
879
889
  Easing,
880
890
  Modal,
891
+ PanResponder,
881
892
  Platform,
882
893
  Pressable,
883
894
  StyleSheet,
884
- Text,
885
895
  useWindowDimensions,
886
896
  View as View2
887
897
  } from "react-native";
898
+
899
+ // src/sheet-presentation.ts
900
+ var NOOP = {
901
+ present: false,
902
+ liftSpinner: false,
903
+ dismissSilent: false,
904
+ close: false
905
+ };
906
+ function resolvePresentation(phase, signal) {
907
+ if (phase === "closed")
908
+ return { phase: "closed", ...NOOP };
909
+ switch (signal) {
910
+ case "ready":
911
+ return { ...NOOP, phase: "presented", present: phase === "hidden", liftSpinner: true };
912
+ case "grace":
913
+ if (phase === "presented")
914
+ return { phase: "presented", ...NOOP };
915
+ return { ...NOOP, phase: "presented", present: true };
916
+ case "terminal":
917
+ if (phase === "hidden")
918
+ return { ...NOOP, phase: "closed", dismissSilent: true };
919
+ return { ...NOOP, phase: "closed", close: true };
920
+ }
921
+ }
922
+
923
+ // src/VisibleFrameSlotSheet.tsx
888
924
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
925
+ var PRESENT_GRACE_MS = 3e3;
889
926
  function animateSheet(value, toValue, onDone) {
890
927
  const animation = Platform.OS === "android" ? Animated.timing(value, {
891
928
  toValue,
@@ -912,6 +949,8 @@ function VisibleFrameSlotSheet({
912
949
  const sheetHeight = Math.round(height * 0.9);
913
950
  const translateY = useRef2(new Animated.Value(sheetHeight)).current;
914
951
  const [frameReady, setFrameReady] = useState(false);
952
+ const [presented, setPresented] = useState(false);
953
+ const phaseRef = useRef2("hidden");
915
954
  const onCloseRef = useRef2(onClose);
916
955
  onCloseRef.current = onClose;
917
956
  const closingRef = useRef2(false);
@@ -919,29 +958,80 @@ function VisibleFrameSlotSheet({
919
958
  if (closingRef.current)
920
959
  return;
921
960
  closingRef.current = true;
961
+ phaseRef.current = "closed";
922
962
  animateSheet(translateY, sheetHeight, ({ finished }) => {
923
963
  if (finished)
924
964
  onCloseRef.current();
925
965
  });
926
966
  }, [translateY, sheetHeight]);
967
+ const panResponder = useMemo(
968
+ () => PanResponder.create({
969
+ // Claim the gesture only for a clearly downward drag — never on a tap
970
+ // (so the handle's onPress still fires) or a mostly-horizontal swipe.
971
+ onMoveShouldSetPanResponder: (_evt, { dy, dx }) => dy > 5 && dy > Math.abs(dx),
972
+ onPanResponderGrant: () => {
973
+ translateY.stopAnimation(() => {
974
+ translateY.extractOffset();
975
+ });
976
+ },
977
+ // Track the finger downward; clamp upward travel to the open position.
978
+ onPanResponderMove: (_evt, { dy }) => {
979
+ translateY.setValue(Math.max(0, dy));
980
+ },
981
+ onPanResponderRelease: (_evt, { dy, vy }) => {
982
+ translateY.flattenOffset();
983
+ const draggedPastThreshold = dy > sheetHeight * 0.35;
984
+ const flungDownFast = vy > 0.5;
985
+ if (draggedPastThreshold || flungDownFast) {
986
+ close();
987
+ } else {
988
+ animateSheet(translateY, 0);
989
+ }
990
+ },
991
+ // Another responder stole the gesture: settle to a known state.
992
+ onPanResponderTerminate: () => {
993
+ translateY.flattenOffset();
994
+ animateSheet(translateY, 0);
995
+ }
996
+ }),
997
+ [translateY, sheetHeight, close]
998
+ );
999
+ const dispatch = useCallback(
1000
+ (signal) => {
1001
+ const result = resolvePresentation(phaseRef.current, signal);
1002
+ phaseRef.current = result.phase;
1003
+ if (result.present) {
1004
+ setPresented(true);
1005
+ animateSheet(translateY, 0);
1006
+ }
1007
+ if (result.liftSpinner)
1008
+ setFrameReady(true);
1009
+ if (result.dismissSilent)
1010
+ onCloseRef.current();
1011
+ if (result.close)
1012
+ close();
1013
+ },
1014
+ [translateY, close]
1015
+ );
927
1016
  useEffect2(() => {
928
- animateSheet(translateY, 0);
929
- }, [translateY]);
1017
+ const timer = setTimeout(() => dispatch("grace"), PRESENT_GRACE_MS);
1018
+ return () => clearTimeout(timer);
1019
+ }, [dispatch]);
930
1020
  useEffect2(() => {
931
1021
  const unsubscribe = slot.transport.onMessage((msg) => {
932
1022
  if (msg.kind === "ready")
933
- setFrameReady(true);
1023
+ dispatch("ready");
934
1024
  else if (msg.kind === "complete" || msg.kind === "error")
935
- close();
1025
+ dispatch("terminal");
936
1026
  });
937
1027
  return unsubscribe;
938
- }, [slot.transport, close]);
1028
+ }, [slot.transport, dispatch]);
939
1029
  const backdropOpacity = translateY.interpolate({
940
1030
  inputRange: [0, sheetHeight],
941
1031
  outputRange: [1, 0],
942
1032
  extrapolate: "clamp"
943
1033
  });
944
- return /* @__PURE__ */ jsx2(Modal, { visible: true, transparent: true, animationType: "none", onRequestClose: close, children: /* @__PURE__ */ jsxs(View2, { style: styles.root, children: [
1034
+ return /* @__PURE__ */ jsx2(Modal, { visible: true, transparent: true, animationType: "none", onRequestClose: close, children: /* @__PURE__ */ jsxs(View2, { style: styles.root, pointerEvents: presented ? "auto" : "none", children: [
945
1035
  /* @__PURE__ */ jsx2(Animated.View, { style: [styles.backdrop, { opacity: backdropOpacity }], children: /* @__PURE__ */ jsx2(
946
1036
  Pressable,
947
1037
  {
@@ -954,17 +1044,16 @@ function VisibleFrameSlotSheet({
954
1044
  /* @__PURE__ */ jsxs(Animated.View, { style: [styles.sheet, { height: sheetHeight, transform: [{ translateY }] }], children: [
955
1045
  /* @__PURE__ */ jsx2(MoonPayFrame, { transport: slot.transport }),
956
1046
  !frameReady && /* @__PURE__ */ jsx2(View2, { style: styles.loading, children: /* @__PURE__ */ jsx2(ActivityIndicator, { size: "large", color: "#000" }) }),
957
- /* @__PURE__ */ jsx2(
1047
+ /* @__PURE__ */ jsx2(View2, { style: styles.handleArea, ...panResponder.panHandlers, children: /* @__PURE__ */ jsx2(
958
1048
  Pressable,
959
1049
  {
960
1050
  onPress: close,
961
1051
  accessibilityRole: "button",
962
1052
  accessibilityLabel: "Close",
963
- hitSlop: 8,
964
- style: styles.closeButton,
965
- children: /* @__PURE__ */ jsx2(Text, { style: styles.closeButtonText, children: "\u2715" })
1053
+ hitSlop: 12,
1054
+ children: /* @__PURE__ */ jsx2(View2, { style: styles.handleBar })
966
1055
  }
967
- )
1056
+ ) })
968
1057
  ] })
969
1058
  ] }) });
970
1059
  }
@@ -994,25 +1083,25 @@ var styles = StyleSheet.create({
994
1083
  alignItems: "center",
995
1084
  justifyContent: "center"
996
1085
  },
997
- // Floating X in the top-right corner, over the frame.
998
- closeButton: {
1086
+ // Drag-to-dismiss strip pinned across the top of the sheet, floating over the
1087
+ // frame. Tall enough to be an easy grab target without eating much of the UI.
1088
+ handleArea: {
999
1089
  position: "absolute",
1000
- top: 12,
1001
- right: 12,
1002
- width: 32,
1003
- height: 32,
1004
- borderRadius: 16,
1005
- backgroundColor: "rgba(0, 0, 0, 0.06)",
1090
+ top: 0,
1091
+ left: 0,
1092
+ right: 0,
1093
+ height: 28,
1006
1094
  alignItems: "center",
1007
1095
  justifyContent: "center",
1008
1096
  zIndex: 1,
1009
1097
  elevation: 2
1010
1098
  },
1011
- closeButtonText: {
1012
- fontSize: 17,
1013
- lineHeight: 20,
1014
- color: "#000",
1015
- fontWeight: "500"
1099
+ // The visible pill.
1100
+ handleBar: {
1101
+ width: 36,
1102
+ height: 5,
1103
+ borderRadius: 3,
1104
+ backgroundColor: "rgba(0, 0, 0, 0.2)"
1016
1105
  }
1017
1106
  });
1018
1107
 
@@ -1023,6 +1112,7 @@ function MoonPayProvider({
1023
1112
  sessionToken,
1024
1113
  apiBaseUrl,
1025
1114
  frameBaseUrl,
1115
+ theme,
1026
1116
  children
1027
1117
  }) {
1028
1118
  const [frameSlots, setFrameSlots] = useState2([]);
@@ -1041,14 +1131,14 @@ function MoonPayProvider({
1041
1131
  });
1042
1132
  }
1043
1133
  const core = coreRef.current;
1044
- const client = useMemo(() => {
1045
- const specCtx = { sessionToken, core, frameBaseUrl };
1134
+ const client = useMemo2(() => {
1135
+ const specCtx = { sessionToken, core, frameBaseUrl, theme };
1046
1136
  return createRNClient({
1047
1137
  mountSession: createMountSession(specCtx, { addSlot, removeSlot }),
1048
1138
  core
1049
1139
  });
1050
- }, [sessionToken, frameBaseUrl, core, addSlot, removeSlot]);
1051
- return /* @__PURE__ */ jsxs2(MoonPayContext.Provider, { value: { client, sessionToken, core, frameBaseUrl }, children: [
1140
+ }, [sessionToken, frameBaseUrl, core, addSlot, removeSlot, theme?.appearance]);
1141
+ return /* @__PURE__ */ jsxs2(MoonPayContext.Provider, { value: { client, sessionToken, core, frameBaseUrl, theme }, children: [
1052
1142
  children,
1053
1143
  frameSlots.filter((slot) => slot.hidden).map((slot) => /* @__PURE__ */ jsx3(MoonPayFrame, { transport: slot.transport, hidden: true }, slot.id)),
1054
1144
  frameSlots.filter((slot) => !slot.hidden).map((slot) => /* @__PURE__ */ jsx3(
@@ -1093,7 +1183,7 @@ function MoonPayFrameView({
1093
1183
  defaultStyle,
1094
1184
  onEvent
1095
1185
  }) {
1096
- const { sessionToken, core, frameBaseUrl } = useMoonPayContext();
1186
+ const { sessionToken, core, frameBaseUrl, theme } = useMoonPayContext();
1097
1187
  const [transport, setTransport] = useState3(null);
1098
1188
  const handleRef = useRef4(null);
1099
1189
  const onEventRef = useRef4(onEvent);
@@ -1119,7 +1209,11 @@ function MoonPayFrameView({
1119
1209
  }
1120
1210
  appliedRef.current = applied;
1121
1211
  try {
1122
- session = createFrameSession(spec, { sessionToken, core, frameBaseUrl }, propsRef.current);
1212
+ session = createFrameSession(
1213
+ spec,
1214
+ { sessionToken, core, frameBaseUrl, theme },
1215
+ propsRef.current
1216
+ );
1123
1217
  } catch (e) {
1124
1218
  onEventRef.current?.(
1125
1219
  spec.errorEvent(e instanceof Error ? e.message : "Failed to create frame")