@savers_app/react-native-sandbox-sdk 1.2.7 → 1.2.8
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/README.md +4 -2
- package/lib/module/core/runtime.js +14 -0
- package/lib/module/core/runtime.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/services/webview/DualWebViewBridgeController.js +267 -0
- package/lib/module/services/webview/DualWebViewBridgeController.js.map +1 -0
- package/lib/module/services/webview/dualWebViewBridge.types.js +19 -0
- package/lib/module/services/webview/dualWebViewBridge.types.js.map +1 -0
- package/lib/module/utils/config.js +2 -0
- package/lib/module/utils/config.js.map +1 -1
- package/lib/typescript/src/core/runtime.d.ts +4 -0
- package/lib/typescript/src/core/runtime.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/services/webview/DualWebViewBridgeController.d.ts +15 -0
- package/lib/typescript/src/services/webview/DualWebViewBridgeController.d.ts.map +1 -0
- package/lib/typescript/src/services/webview/dualWebViewBridge.types.d.ts +20 -0
- package/lib/typescript/src/services/webview/dualWebViewBridge.types.d.ts.map +1 -0
- package/lib/typescript/src/utils/config.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/core/runtime.ts +21 -0
- package/src/index.tsx +2 -0
- package/src/services/webview/DualWebViewBridgeController.tsx +330 -0
- package/src/services/webview/dualWebViewBridge.types.ts +32 -0
- package/src/utils/config.ts +1 -0
package/README.md
CHANGED
|
@@ -88,7 +88,8 @@ SaversAppSDK.initialized({
|
|
|
88
88
|
apiKey: 'YOUR_API_KEY',
|
|
89
89
|
encryptionKey: 'BASE64_256_BIT_ENCRYPTION_KEY',
|
|
90
90
|
pRefCode: 'PROGRAM_REF_CODE',
|
|
91
|
-
authMode: 'CUSTOMER_SIGN_IN_UP_MODE' // 'EMAIL' | 'PHONE'
|
|
91
|
+
authMode: 'CUSTOMER_SIGN_IN_UP_MODE', // 'EMAIL' | 'PHONE'
|
|
92
|
+
environment: 'PROD', // optional: 'SANDBOX' (https://testm.saversapp.com) | 'PROD' (https://m.saversapp.com) for generateUrl
|
|
92
93
|
});
|
|
93
94
|
```
|
|
94
95
|
|
|
@@ -142,7 +143,7 @@ const url = await generateUrl({
|
|
|
142
143
|
// sessionId is optional; when omitted, the SDK uses its stored session id
|
|
143
144
|
// deviceInfo is optional; the SDK builds it internally using device id (and optional coordinates)
|
|
144
145
|
});
|
|
145
|
-
// returns https://m.saversapp.com
|
|
146
|
+
// returns https://m.saversapp.com/... or https://testm.saversapp.com/... depending on `environment` in init (default follows build-time config)
|
|
146
147
|
```
|
|
147
148
|
|
|
148
149
|
Profile requirements:
|
|
@@ -187,6 +188,7 @@ export function App() {
|
|
|
187
188
|
pRefCode: 'PROGRAM_REF_CODE',
|
|
188
189
|
authMode: 'EMAIL', // or 'PHONE'
|
|
189
190
|
navigationRef, // pass the ref to the SDK
|
|
191
|
+
environment: 'PROD', // optional: 'SANDBOX' | 'PROD' for generateUrl hosted origin
|
|
190
192
|
});
|
|
191
193
|
|
|
192
194
|
return <NavigationContainer ref={navigationRef}>{/* your navigators */}</NavigationContainer>;
|
|
@@ -9,9 +9,14 @@ import { setApiKey, setEncryptionKey, setPRefCode, setAuthMode } from "../data/s
|
|
|
9
9
|
import { getDeviceId } from "../data/storage/deviceIdManager.js";
|
|
10
10
|
import { setReactRef } from "../data/storage/reactRefManager.js";
|
|
11
11
|
|
|
12
|
+
// Hosted app base URL (test vs production merchant web)
|
|
13
|
+
import { setEnvironment } from "../utils/config.js";
|
|
14
|
+
|
|
12
15
|
// Services
|
|
13
16
|
// import { getDeviceLocation } from '../services/device/location';
|
|
14
17
|
|
|
18
|
+
/** Controls which hosted merchant web origin `generateUrl` uses. */
|
|
19
|
+
|
|
15
20
|
export function initializeSDK(providedKeys) {
|
|
16
21
|
const keys = providedKeys;
|
|
17
22
|
const missing = [];
|
|
@@ -25,6 +30,15 @@ export function initializeSDK(providedKeys) {
|
|
|
25
30
|
logger.warn(msg);
|
|
26
31
|
throw new Error(msg);
|
|
27
32
|
}
|
|
33
|
+
if (keys.environment !== undefined) {
|
|
34
|
+
if (keys.environment !== 'SANDBOX' && keys.environment !== 'PROD') {
|
|
35
|
+
throw new Error(`[SDK] Invalid environment: ${String(keys.environment)}. Use 'SANDBOX' or 'PROD'.`);
|
|
36
|
+
}
|
|
37
|
+
setEnvironment(keys.environment === 'SANDBOX' ? 'sandbox' : 'production');
|
|
38
|
+
logger.info('[SDK] Hosted app environment', {
|
|
39
|
+
environment: keys.environment
|
|
40
|
+
});
|
|
41
|
+
}
|
|
28
42
|
setApiKey(keys.apiKey);
|
|
29
43
|
setEncryptionKey(keys.encryptionKey);
|
|
30
44
|
setPRefCode(keys.pRefCode);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["logger","SDKRequirements","setApiKey","setEncryptionKey","setPRefCode","setAuthMode","getDeviceId","setReactRef","initializeSDK","providedKeys","keys","missing","apiKey","push","encryptionKey","pRefCode","authMode","navigationRef","length","msg","join","warn","Error","
|
|
1
|
+
{"version":3,"names":["logger","SDKRequirements","setApiKey","setEncryptionKey","setPRefCode","setAuthMode","getDeviceId","setReactRef","setEnvironment","initializeSDK","providedKeys","keys","missing","apiKey","push","encryptionKey","pRefCode","authMode","navigationRef","length","msg","join","warn","Error","environment","undefined","String","info","then","id","deviceId","Boolean","catch","asyncStorage","geolocation","deviceInfo","SaversAppSDK","initialized"],"sourceRoot":"../../../src","sources":["core/runtime.ts"],"mappings":";;AAAA;AACA,SAASA,MAAM,QAAQ,oBAAiB;AACxC,SAASC,eAAe,QAAQ,+BAA4B;;AAE5D;AACA,SACEC,SAAS,EACTC,gBAAgB,EAChBC,WAAW,EACXC,WAAW,QACN,gCAA6B;AACpC,SAASC,WAAW,QAAQ,oCAAiC;AAC7D,SAASC,WAAW,QAAQ,oCAAiC;;AAE7D;AACA,SAASC,cAAc,QAAQ,oBAAiB;;AAEhD;AACA;;AAEA;;AAGA,OAAO,SAASC,aAAaA,CAACC,YAQ7B,EAAE;EACD,MAAMC,IAAI,GAAGD,YAAY;EACzB,MAAME,OAAiB,GAAG,EAAE;EAC5B,IAAI,CAACD,IAAI,CAACE,MAAM,EAAED,OAAO,CAACE,IAAI,CAAC,QAAQ,CAAC;EACxC,IAAI,CAACH,IAAI,CAACI,aAAa,EAAEH,OAAO,CAACE,IAAI,CAAC,eAAe,CAAC;EACtD,IAAI,CAACH,IAAI,CAACK,QAAQ,EAAEJ,OAAO,CAACE,IAAI,CAAC,UAAU,CAAC;EAC5C,IAAI,CAACH,IAAI,CAACM,QAAQ,EAAEL,OAAO,CAACE,IAAI,CAAC,UAAU,CAAC;EAC5C,IAAI,CAACH,IAAI,CAACO,aAAa,EAAEN,OAAO,CAACE,IAAI,CAAC,eAAe,CAAC;EAEtD,IAAIF,OAAO,CAACO,MAAM,EAAE;IAClB,MAAMC,GAAG,GAAG,gCAAgCR,OAAO,CAACS,IAAI,CAAC,IAAI,CAAC,EAAE;IAChErB,MAAM,CAACsB,IAAI,CAACF,GAAG,CAAC;IAChB,MAAM,IAAIG,KAAK,CAACH,GAAG,CAAC;EACtB;EAEA,IAAIT,IAAI,CAACa,WAAW,KAAKC,SAAS,EAAE;IAClC,IAAId,IAAI,CAACa,WAAW,KAAK,SAAS,IAAIb,IAAI,CAACa,WAAW,KAAK,MAAM,EAAE;MACjE,MAAM,IAAID,KAAK,CACb,8BAA8BG,MAAM,CAACf,IAAI,CAACa,WAAW,CAAC,4BACxD,CAAC;IACH;IACAhB,cAAc,CAACG,IAAI,CAACa,WAAW,KAAK,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;IACzExB,MAAM,CAAC2B,IAAI,CAAC,8BAA8B,EAAE;MAC1CH,WAAW,EAAEb,IAAI,CAACa;IACpB,CAAC,CAAC;EACJ;EAEAtB,SAAS,CAACS,IAAI,CAACE,MAAM,CAAC;EACtBV,gBAAgB,CAACQ,IAAI,CAACI,aAAa,CAAC;EACpCX,WAAW,CAACO,IAAI,CAACK,QAAQ,CAAC;EAC1BX,WAAW,CAACM,IAAI,CAACM,QAAQ,CAAC;EAC1BV,WAAW,CAACI,IAAI,CAACO,aAAa,CAAC;EAE/BZ,WAAW,CAAC,CAAC,CACVsB,IAAI,CAAEC,EAAE,IAAK;IACZ7B,MAAM,CAAC2B,IAAI,CAAC,8BAA8B,EAAE;MAAEG,QAAQ,EAAEC,OAAO,CAACF,EAAE;IAAE,CAAC,CAAC;EACxE,CAAC,CAAC,CACDG,KAAK,CAAC,MAAM;IACXhC,MAAM,CAACsB,IAAI,CAAC,6BAA6B,CAAC;EAC5C,CAAC,CAAC;;EAEJ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA,MAAM;IAAEW,YAAY;IAAEC,WAAW;IAAEC;EAAW,CAAC,GAAGlC,eAAe;EACjE,IAAIgC,YAAY,IAAIC,WAAW,IAAIC,UAAU,EAAE;EAC/C,IAAI,CAACF,YAAY,EAAE;IACjBjC,MAAM,CAACsB,IAAI,CACT,4GACF,CAAC;EACH;EACA,IAAI,CAACY,WAAW,EAAE;IAChBlC,MAAM,CAACsB,IAAI,CACT,+FACF,CAAC;EACH;EAEA,IAAI,CAACa,UAAU,EAAE;IACfnC,MAAM,CAACsB,IAAI,CACT,uFACF,CAAC;EACH;AACF;AAEA,OAAO,MAAMc,YAAY,GAAG;EAC1BC,WAAW,EAAE5B;AACf,CAAC","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -8,6 +8,8 @@ export * from "./utils/errors.js";
|
|
|
8
8
|
export * from "./utils/logger.js";
|
|
9
9
|
export * from "./utils/validator.js";
|
|
10
10
|
export * from "./services/webview/messageHandler.js";
|
|
11
|
+
export * from "./services/webview/DualWebViewBridgeController.js";
|
|
12
|
+
export * from "./services/webview/dualWebViewBridge.types.js";
|
|
11
13
|
export * from "./data/storage/deviceIdManager.js";
|
|
12
14
|
export * from "./services/navigation/dialPad.js";
|
|
13
15
|
export * from "./services/navigation/openBrowser.js";
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,cAAc,kCAA+B;AAC7C,cAAc,2CAAwC;AACtD,cAAc,6CAA0C;AACxD,cAAc,4BAAyB;AACvC,cAAc,mBAAgB;AAC9B,cAAc,mBAAgB;AAC9B,cAAc,sBAAmB;AACjC,cAAc,sCAAmC;AACjD,cAAc,mCAAgC;AAC9C,cAAc,kCAA+B;AAC7C,cAAc,sCAAmC;AACjD,cAAc,kCAA+B;AAC7C,cAAc,gCAA6B;AAC3C,cAAc,8BAA2B;AACzC,cAAc,mBAAgB;AAC9B,cAAc,+BAA4B;AAC1C,cAAc,mCAAgC","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,cAAc,kCAA+B;AAC7C,cAAc,2CAAwC;AACtD,cAAc,6CAA0C;AACxD,cAAc,4BAAyB;AACvC,cAAc,mBAAgB;AAC9B,cAAc,mBAAgB;AAC9B,cAAc,sBAAmB;AACjC,cAAc,sCAAmC;AACjD,cAAc,mDAAgD;AAC9D,cAAc,+CAA4C;AAC1D,cAAc,mCAAgC;AAC9C,cAAc,kCAA+B;AAC7C,cAAc,sCAAmC;AACjD,cAAc,kCAA+B;AAC7C,cAAc,gCAA6B;AAC3C,cAAc,8BAA2B;AACzC,cAAc,mBAAgB;AAC9B,cAAc,+BAA4B;AAC1C,cAAc,mCAAgC","ignoreList":[]}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { Image, StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
5
|
+
import WebView from 'react-native-webview';
|
|
6
|
+
import { parseDualWebViewEnvelope } from "./dualWebViewBridge.types.js";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
const DEFAULT_TRAVEL_PORTAL_URL = 'https://sandbox.travelercashback.com';
|
|
9
|
+
|
|
10
|
+
/** Must match `applicationNameForUserAgent` on clo-app WebViews (DualWebViewBridgeController). */
|
|
11
|
+
const WEBVIEW_UA_MARKER = 'CloAppWebView';
|
|
12
|
+
|
|
13
|
+
/** Legacy / Hub: open travel from first WebView without JSON envelope. */
|
|
14
|
+
const TRAVEL_DEEP_LINK = 'saversapp://travel';
|
|
15
|
+
const LEGACY_OPEN_TRAVEL_ACTIONS = ['OPEN_TRAVEL_PORTAL', 'OPEN_TRAVEL'];
|
|
16
|
+
function buildInjectNativeBridgeScript(detail) {
|
|
17
|
+
return `
|
|
18
|
+
(function(){
|
|
19
|
+
var detail = ${JSON.stringify(detail)};
|
|
20
|
+
window.dispatchEvent(new CustomEvent('nativeBridge', { detail: detail }));
|
|
21
|
+
true;
|
|
22
|
+
})();
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
export const DualWebViewBridgeController = ({
|
|
26
|
+
saversAppUrl,
|
|
27
|
+
travelPortalUrl = DEFAULT_TRAVEL_PORTAL_URL,
|
|
28
|
+
partnerLogo,
|
|
29
|
+
renderTravelBackIcon,
|
|
30
|
+
initialSurface = 'savers',
|
|
31
|
+
onSaversSdkMessage,
|
|
32
|
+
onLoadEndSavers,
|
|
33
|
+
onLoadEndTravel
|
|
34
|
+
}) => {
|
|
35
|
+
const saversRef = useRef(null);
|
|
36
|
+
const travelRef = useRef(null);
|
|
37
|
+
const [surface, setSurface] = useState(initialSurface);
|
|
38
|
+
const [saversKey, setSaversKey] = useState(0);
|
|
39
|
+
const [travelKey, setTravelKey] = useState(0);
|
|
40
|
+
const openTravel = useCallback(() => {
|
|
41
|
+
setSurface('travel');
|
|
42
|
+
setTravelKey(k => k + 1);
|
|
43
|
+
}, []);
|
|
44
|
+
const backToSavers = useCallback(() => {
|
|
45
|
+
setSurface('savers');
|
|
46
|
+
setSaversKey(k => k + 1);
|
|
47
|
+
}, []);
|
|
48
|
+
const relayTo = useCallback((target, payload, from) => {
|
|
49
|
+
const detail = {
|
|
50
|
+
bridge: 'dual-webview',
|
|
51
|
+
from,
|
|
52
|
+
action: 'relay',
|
|
53
|
+
payload
|
|
54
|
+
};
|
|
55
|
+
const ref = target === 'savers' ? saversRef : travelRef;
|
|
56
|
+
ref.current?.injectJavaScript(buildInjectNativeBridgeScript(detail));
|
|
57
|
+
}, []);
|
|
58
|
+
const handleBridgeEnvelope = useCallback(env => {
|
|
59
|
+
switch (env.action) {
|
|
60
|
+
case 'open_travel':
|
|
61
|
+
openTravel();
|
|
62
|
+
return true;
|
|
63
|
+
case 'close_travel':
|
|
64
|
+
backToSavers();
|
|
65
|
+
return true;
|
|
66
|
+
case 'relay':
|
|
67
|
+
if (env.to === 'savers') {
|
|
68
|
+
relayTo('savers', env.payload, env.from);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (env.to === 'travel') {
|
|
72
|
+
relayTo('travel', env.payload, env.from);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
default:
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}, [backToSavers, openTravel, relayTo]);
|
|
80
|
+
const tryLegacyOpenTravel = useCallback(raw => {
|
|
81
|
+
if (!raw || typeof raw !== 'string') {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const data = JSON.parse(raw);
|
|
86
|
+
if (data?.action === 'OPEN_TRAVEL_PORTAL' || data?.type === 'OPEN_TRAVEL' || data?.openTravel === true) {
|
|
87
|
+
openTravel();
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
/* ignore */
|
|
92
|
+
}
|
|
93
|
+
if (LEGACY_OPEN_TRAVEL_ACTIONS.some(s => raw.includes(s))) {
|
|
94
|
+
openTravel();
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}, [openTravel]);
|
|
99
|
+
const onMessageSavers = useCallback(e => {
|
|
100
|
+
const raw = e?.nativeEvent?.data ?? '';
|
|
101
|
+
const env = parseDualWebViewEnvelope(raw);
|
|
102
|
+
if (env && env.from === 'savers' && handleBridgeEnvelope(env)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (tryLegacyOpenTravel(raw)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const postBack = data => {
|
|
109
|
+
const detail = {
|
|
110
|
+
bridge: 'dual-webview',
|
|
111
|
+
from: 'native',
|
|
112
|
+
action: 'relay',
|
|
113
|
+
payload: data
|
|
114
|
+
};
|
|
115
|
+
saversRef.current?.injectJavaScript(buildInjectNativeBridgeScript(detail));
|
|
116
|
+
};
|
|
117
|
+
onSaversSdkMessage?.(raw, postBack);
|
|
118
|
+
}, [handleBridgeEnvelope, onSaversSdkMessage, tryLegacyOpenTravel]);
|
|
119
|
+
const onMessageTravel = useCallback(e => {
|
|
120
|
+
const raw = e?.nativeEvent?.data ?? '';
|
|
121
|
+
const env = parseDualWebViewEnvelope(raw);
|
|
122
|
+
if (env && env.from === 'travel' && handleBridgeEnvelope(env)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (tryLegacyOpenTravel(raw)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}, [handleBridgeEnvelope, tryLegacyOpenTravel]);
|
|
129
|
+
const onShouldStartLoadSavers = useCallback(request => {
|
|
130
|
+
const {
|
|
131
|
+
url
|
|
132
|
+
} = request;
|
|
133
|
+
if (url.startsWith(TRAVEL_DEEP_LINK) || url === 'saversapp://travel/') {
|
|
134
|
+
openTravel();
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
}, [openTravel]);
|
|
139
|
+
const travelSource = useMemo(() => ({
|
|
140
|
+
uri: travelPortalUrl
|
|
141
|
+
}), [travelPortalUrl]);
|
|
142
|
+
const saversSource = useMemo(() => ({
|
|
143
|
+
uri: saversAppUrl
|
|
144
|
+
}), [saversAppUrl]);
|
|
145
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
146
|
+
style: styles.root,
|
|
147
|
+
children: [surface === 'savers' ? /*#__PURE__*/_jsx(WebView, {
|
|
148
|
+
ref: saversRef,
|
|
149
|
+
source: saversSource,
|
|
150
|
+
originWhitelist: ['*'],
|
|
151
|
+
applicationNameForUserAgent: WEBVIEW_UA_MARKER,
|
|
152
|
+
onMessage: onMessageSavers,
|
|
153
|
+
onShouldStartLoadWithRequest: onShouldStartLoadSavers,
|
|
154
|
+
onLoadEnd: onLoadEndSavers,
|
|
155
|
+
style: styles.webview,
|
|
156
|
+
sharedCookiesEnabled: true,
|
|
157
|
+
incognito: false,
|
|
158
|
+
webviewDebuggingEnabled: __DEV__
|
|
159
|
+
}, saversKey) : null, surface === 'travel' ? /*#__PURE__*/_jsxs(View, {
|
|
160
|
+
style: styles.travelColumn,
|
|
161
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
162
|
+
style: styles.header,
|
|
163
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
164
|
+
onPress: backToSavers,
|
|
165
|
+
hitSlop: 16,
|
|
166
|
+
accessibilityRole: "button",
|
|
167
|
+
accessibilityLabel: "Back",
|
|
168
|
+
style: styles.backButton,
|
|
169
|
+
children: renderTravelBackIcon ? renderTravelBackIcon() : /*#__PURE__*/_jsxs(View, {
|
|
170
|
+
style: styles.backIconWrapper,
|
|
171
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
172
|
+
style: styles.backIconShaft
|
|
173
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
174
|
+
style: styles.backIconHead
|
|
175
|
+
})]
|
|
176
|
+
})
|
|
177
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
178
|
+
style: styles.headerSpacer
|
|
179
|
+
}), partnerLogo ? /*#__PURE__*/_jsx(Image, {
|
|
180
|
+
source: {
|
|
181
|
+
uri: partnerLogo
|
|
182
|
+
},
|
|
183
|
+
resizeMode: "contain",
|
|
184
|
+
style: styles.logo
|
|
185
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
186
|
+
style: styles.logoPlaceholder
|
|
187
|
+
})]
|
|
188
|
+
}), /*#__PURE__*/_jsx(WebView, {
|
|
189
|
+
ref: travelRef,
|
|
190
|
+
source: travelSource,
|
|
191
|
+
originWhitelist: ['*'],
|
|
192
|
+
applicationNameForUserAgent: WEBVIEW_UA_MARKER,
|
|
193
|
+
onMessage: onMessageTravel,
|
|
194
|
+
onLoadEnd: onLoadEndTravel,
|
|
195
|
+
style: styles.webview,
|
|
196
|
+
sharedCookiesEnabled: true,
|
|
197
|
+
incognito: false,
|
|
198
|
+
webviewDebuggingEnabled: __DEV__
|
|
199
|
+
}, travelKey)]
|
|
200
|
+
}) : null]
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
const styles = StyleSheet.create({
|
|
204
|
+
root: {
|
|
205
|
+
flex: 1
|
|
206
|
+
},
|
|
207
|
+
travelColumn: {
|
|
208
|
+
flex: 1
|
|
209
|
+
},
|
|
210
|
+
webview: {
|
|
211
|
+
flex: 1
|
|
212
|
+
},
|
|
213
|
+
header: {
|
|
214
|
+
flexDirection: 'row',
|
|
215
|
+
alignItems: 'center',
|
|
216
|
+
backgroundColor: '#fff',
|
|
217
|
+
paddingHorizontal: 16,
|
|
218
|
+
paddingTop: 12,
|
|
219
|
+
paddingBottom: 12,
|
|
220
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
221
|
+
borderBottomColor: '#E5E5E5'
|
|
222
|
+
},
|
|
223
|
+
backButton: {
|
|
224
|
+
width: 32,
|
|
225
|
+
height: 32,
|
|
226
|
+
justifyContent: 'center',
|
|
227
|
+
alignItems: 'center'
|
|
228
|
+
},
|
|
229
|
+
backIconWrapper: {
|
|
230
|
+
width: 20,
|
|
231
|
+
height: 16,
|
|
232
|
+
justifyContent: 'center',
|
|
233
|
+
position: 'relative'
|
|
234
|
+
},
|
|
235
|
+
backIconShaft: {
|
|
236
|
+
position: 'absolute',
|
|
237
|
+
left: 6,
|
|
238
|
+
right: 0,
|
|
239
|
+
height: 2,
|
|
240
|
+
backgroundColor: '#111827',
|
|
241
|
+
borderRadius: 1
|
|
242
|
+
},
|
|
243
|
+
backIconHead: {
|
|
244
|
+
width: 10,
|
|
245
|
+
height: 10,
|
|
246
|
+
borderLeftWidth: 2,
|
|
247
|
+
borderBottomWidth: 2,
|
|
248
|
+
borderColor: '#111827',
|
|
249
|
+
transform: [{
|
|
250
|
+
rotate: '45deg'
|
|
251
|
+
}],
|
|
252
|
+
marginLeft: 1
|
|
253
|
+
},
|
|
254
|
+
headerSpacer: {
|
|
255
|
+
flex: 1
|
|
256
|
+
},
|
|
257
|
+
logo: {
|
|
258
|
+
width: 120,
|
|
259
|
+
height: 32
|
|
260
|
+
},
|
|
261
|
+
logoPlaceholder: {
|
|
262
|
+
width: 120,
|
|
263
|
+
height: 32
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
export default DualWebViewBridgeController;
|
|
267
|
+
//# sourceMappingURL=DualWebViewBridgeController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","useCallback","useMemo","useRef","useState","Image","StyleSheet","TouchableOpacity","View","WebView","parseDualWebViewEnvelope","jsx","_jsx","jsxs","_jsxs","DEFAULT_TRAVEL_PORTAL_URL","WEBVIEW_UA_MARKER","TRAVEL_DEEP_LINK","LEGACY_OPEN_TRAVEL_ACTIONS","buildInjectNativeBridgeScript","detail","JSON","stringify","DualWebViewBridgeController","saversAppUrl","travelPortalUrl","partnerLogo","renderTravelBackIcon","initialSurface","onSaversSdkMessage","onLoadEndSavers","onLoadEndTravel","saversRef","travelRef","surface","setSurface","saversKey","setSaversKey","travelKey","setTravelKey","openTravel","k","backToSavers","relayTo","target","payload","from","bridge","action","ref","current","injectJavaScript","handleBridgeEnvelope","env","to","tryLegacyOpenTravel","raw","data","parse","type","some","s","includes","onMessageSavers","e","nativeEvent","postBack","onMessageTravel","onShouldStartLoadSavers","request","url","startsWith","travelSource","uri","saversSource","style","styles","root","children","source","originWhitelist","applicationNameForUserAgent","onMessage","onShouldStartLoadWithRequest","onLoadEnd","webview","sharedCookiesEnabled","incognito","webviewDebuggingEnabled","__DEV__","travelColumn","header","onPress","hitSlop","accessibilityRole","accessibilityLabel","backButton","backIconWrapper","backIconShaft","backIconHead","headerSpacer","resizeMode","logo","logoPlaceholder","create","flex","flexDirection","alignItems","backgroundColor","paddingHorizontal","paddingTop","paddingBottom","borderBottomWidth","hairlineWidth","borderBottomColor","width","height","justifyContent","position","left","right","borderRadius","borderLeftWidth","borderColor","transform","rotate","marginLeft"],"sourceRoot":"../../../../src","sources":["services/webview/DualWebViewBridgeController.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Messages posted from either WebView via ReactNativeWebView.postMessage(string).
|
|
5
|
+
* Pages should JSON.stringify envelopes before posting.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export function parseDualWebViewEnvelope(raw) {
|
|
9
|
+
try {
|
|
10
|
+
const o = JSON.parse(raw);
|
|
11
|
+
if (o?.bridge === 'dual-webview' && o?.from && o?.action) {
|
|
12
|
+
return o;
|
|
13
|
+
}
|
|
14
|
+
} catch {
|
|
15
|
+
/* not JSON */
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=dualWebViewBridge.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["parseDualWebViewEnvelope","raw","o","JSON","parse","bridge","from","action"],"sourceRoot":"../../../../src","sources":["services/webview/dualWebViewBridge.types.ts"],"mappings":";;AAAA;AACA;AACA;AACA;;AAgBA,OAAO,SAASA,wBAAwBA,CACtCC,GAAW,EACuB;EAClC,IAAI;IACF,MAAMC,CAAC,GAAGC,IAAI,CAACC,KAAK,CAACH,GAAG,CAA8B;IACtD,IAAIC,CAAC,EAAEG,MAAM,KAAK,cAAc,IAAIH,CAAC,EAAEI,IAAI,IAAIJ,CAAC,EAAEK,MAAM,EAAE;MACxD,OAAOL,CAAC;IACV;EACF,CAAC,CAAC,MAAM;IACN;EAAA;EAEF,OAAO,IAAI;AACb","ignoreList":[]}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// Default environment (build-time injected)
|
|
6
6
|
const injectedEnv = 'sandbox';
|
|
7
7
|
const DEFAULT_ENVIRONMENT = injectedEnv === 'production' ? 'production' : 'sandbox';
|
|
8
|
+
|
|
9
|
+
/** Hosted merchant web origins; aligned with `SaversSdkHostedEnvironment` via `setEnvironment` in SDK init. */
|
|
8
10
|
const ENV_URLS = {
|
|
9
11
|
sandbox: 'https://testm.saversapp.com/',
|
|
10
12
|
production: 'https://m.saversapp.com/'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["injectedEnv","DEFAULT_ENVIRONMENT","ENV_URLS","sandbox","production","currentEnvironment","BASE_URL","setEnvironment","env","getEnvironment","getBaseUrl"],"sourceRoot":"../../../src","sources":["utils/config.ts"],"mappings":";;AAAA;;AAIA;AACA,MAAMA,WAAW,GAAG,SAAmB;AACvC,MAAMC,mBAAgC,GACpCD,WAAW,KAAK,YAAY,GAAG,YAAY,GAAG,SAAS;
|
|
1
|
+
{"version":3,"names":["injectedEnv","DEFAULT_ENVIRONMENT","ENV_URLS","sandbox","production","currentEnvironment","BASE_URL","setEnvironment","env","getEnvironment","getBaseUrl"],"sourceRoot":"../../../src","sources":["utils/config.ts"],"mappings":";;AAAA;;AAIA;AACA,MAAMA,WAAW,GAAG,SAAmB;AACvC,MAAMC,mBAAgC,GACpCD,WAAW,KAAK,YAAY,GAAG,YAAY,GAAG,SAAS;;AAEzD;AACA,MAAME,QAAqC,GAAG;EAC5CC,OAAO,EAAE,8BAA8B;EACvCC,UAAU,EAAE;AACd,CAAC;;AAED;AACA,IAAIC,kBAA+B,GAAGJ,mBAAmB;;AAEzD;AACA,IAAIK,QAAgB,GAAGJ,QAAQ,CAACG,kBAAkB,CAAC;;AAEnD;AACA,OAAO,MAAME,cAAc,GAAIC,GAAgB,IAAK;EAClDH,kBAAkB,GAAGG,GAAG;EACxBF,QAAQ,GAAGJ,QAAQ,CAACM,GAAG,CAAC;AAC1B,CAAC;;AAED;AACA,OAAO,MAAMC,cAAc,GAAGA,CAAA,KAAmB;EAC/C,OAAOJ,kBAAkB;AAC3B,CAAC;;AAED;AACA,OAAO,MAAMK,UAAU,GAAGA,CAAA,KAAc;EACtC,OAAOJ,QAAQ;AACjB,CAAC","ignoreList":[]}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
/** Controls which hosted merchant web origin `generateUrl` uses. */
|
|
2
|
+
export type SaversSdkHostedEnvironment = 'SANDBOX' | 'PROD';
|
|
1
3
|
export declare function initializeSDK(providedKeys: {
|
|
2
4
|
apiKey: string;
|
|
3
5
|
encryptionKey: string;
|
|
4
6
|
pRefCode: string;
|
|
5
7
|
authMode: string;
|
|
6
8
|
navigationRef: any;
|
|
9
|
+
/** When set, `generateUrl` uses testm (SANDBOX) or m (PROD). When omitted, build-time default from config applies. */
|
|
10
|
+
environment?: SaversSdkHostedEnvironment;
|
|
7
11
|
}): void;
|
|
8
12
|
export declare const SaversAppSDK: {
|
|
9
13
|
initialized: typeof initializeSDK;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/core/runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/core/runtime.ts"],"names":[],"mappings":"AAoBA,oEAAoE;AACpE,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,MAAM,CAAC;AAE5D,wBAAgB,aAAa,CAAC,YAAY,EAAE;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,GAAG,CAAC;IACnB,sHAAsH;IACtH,WAAW,CAAC,EAAE,0BAA0B,CAAC;CAC1C,QAqEA;AAED,eAAO,MAAM,YAAY;;CAExB,CAAC"}
|
|
@@ -6,6 +6,8 @@ export * from './utils/errors';
|
|
|
6
6
|
export * from './utils/logger';
|
|
7
7
|
export * from './utils/validator';
|
|
8
8
|
export * from './services/webview/messageHandler';
|
|
9
|
+
export * from './services/webview/DualWebViewBridgeController';
|
|
10
|
+
export * from './services/webview/dualWebViewBridge.types';
|
|
9
11
|
export * from './data/storage/deviceIdManager';
|
|
10
12
|
export * from './services/navigation/dialPad';
|
|
11
13
|
export * from './services/navigation/openBrowser';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wCAAwC,CAAC;AACvD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wCAAwC,CAAC;AACvD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mCAAmC,CAAC;AAClD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,gCAAgC,CAAC;AAC/C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type Surface = 'savers' | 'travel';
|
|
3
|
+
export type DualWebViewBridgeControllerProps = {
|
|
4
|
+
saversAppUrl: string;
|
|
5
|
+
travelPortalUrl?: string;
|
|
6
|
+
partnerLogo?: string;
|
|
7
|
+
renderTravelBackIcon?: () => React.ReactNode;
|
|
8
|
+
initialSurface?: Surface;
|
|
9
|
+
onSaversSdkMessage?: (raw: string, postBack: (data: unknown) => void) => void;
|
|
10
|
+
onLoadEndSavers?: () => void;
|
|
11
|
+
onLoadEndTravel?: () => void;
|
|
12
|
+
};
|
|
13
|
+
export declare const DualWebViewBridgeController: React.FC<DualWebViewBridgeControllerProps>;
|
|
14
|
+
export default DualWebViewBridgeController;
|
|
15
|
+
//# sourceMappingURL=DualWebViewBridgeController.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DualWebViewBridgeController.d.ts","sourceRoot":"","sources":["../../../../../src/services/webview/DualWebViewBridgeController.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAiBtE,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE1C,MAAM,MAAM,gCAAgC,GAAG;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAC7C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,CAAC;IAC9E,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;CAC9B,CAAC;AAcF,eAAO,MAAM,2BAA2B,EAAE,KAAK,CAAC,EAAE,CAChD,gCAAgC,CAoOjC,CAAC;AA0DF,eAAe,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages posted from either WebView via ReactNativeWebView.postMessage(string).
|
|
3
|
+
* Pages should JSON.stringify envelopes before posting.
|
|
4
|
+
*/
|
|
5
|
+
export type DualWebViewBridgeEnvelope = {
|
|
6
|
+
bridge: 'dual-webview';
|
|
7
|
+
/** Who sent this message (set by the page). */
|
|
8
|
+
from: 'savers' | 'travel';
|
|
9
|
+
/**
|
|
10
|
+
* relay — forward payload to the other WebView via injectJavaScript + CustomEvent.
|
|
11
|
+
* open_travel — show travel surface (reloads travel WebView).
|
|
12
|
+
* close_travel — same as native back (reloads Savers WebView).
|
|
13
|
+
*/
|
|
14
|
+
action: 'relay' | 'open_travel' | 'close_travel';
|
|
15
|
+
/** For relay: which surface should receive the event. */
|
|
16
|
+
to?: 'savers' | 'travel';
|
|
17
|
+
payload?: unknown;
|
|
18
|
+
};
|
|
19
|
+
export declare function parseDualWebViewEnvelope(raw: string): DualWebViewBridgeEnvelope | null;
|
|
20
|
+
//# sourceMappingURL=dualWebViewBridge.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dualWebViewBridge.types.d.ts","sourceRoot":"","sources":["../../../../../src/services/webview/dualWebViewBridge.types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,MAAM,EAAE,cAAc,CAAC;IACvB,+CAA+C;IAC/C,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B;;;;OAIG;IACH,MAAM,EAAE,OAAO,GAAG,aAAa,GAAG,cAAc,CAAC;IACjD,yDAAyD;IACzD,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,GACV,yBAAyB,GAAG,IAAI,CAUlC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/utils/config.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/utils/config.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,YAAY,CAAC;AAoBnD,eAAO,MAAM,cAAc,GAAI,KAAK,WAAW,SAG9C,CAAC;AAGF,eAAO,MAAM,cAAc,QAAO,WAEjC,CAAC;AAGF,eAAO,MAAM,UAAU,QAAO,MAE7B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savers_app/react-native-sandbox-sdk",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"description": "Cross-platform React Native SDK exposing native features (maps, dial pad, browser), device ID/location and session utilities, a URL generator, and a WebView message bridge to trigger actions from web content.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"react-native": "./src/index.tsx",
|
|
@@ -93,6 +93,8 @@
|
|
|
93
93
|
"react": "19.1.0",
|
|
94
94
|
"react-native": "0.81.5",
|
|
95
95
|
"react-native-builder-bob": "^0.40.13",
|
|
96
|
+
"react-native-safe-area-context": "^5.5.2",
|
|
97
|
+
"react-native-webview": "^13.16.0",
|
|
96
98
|
"release-it": "^19.0.4",
|
|
97
99
|
"typescript": "^5.9.2"
|
|
98
100
|
},
|
|
@@ -104,7 +106,9 @@
|
|
|
104
106
|
"react": "*",
|
|
105
107
|
"react-native": "*",
|
|
106
108
|
"react-native-aes-gcm-crypto": "*",
|
|
107
|
-
"react-native-device-info": "*"
|
|
109
|
+
"react-native-device-info": "*",
|
|
110
|
+
"react-native-safe-area-context": "*",
|
|
111
|
+
"react-native-webview": "*"
|
|
108
112
|
},
|
|
109
113
|
"workspaces": [
|
|
110
114
|
"example",
|
package/src/core/runtime.ts
CHANGED
|
@@ -12,15 +12,23 @@ import {
|
|
|
12
12
|
import { getDeviceId } from '../data/storage/deviceIdManager';
|
|
13
13
|
import { setReactRef } from '../data/storage/reactRefManager';
|
|
14
14
|
|
|
15
|
+
// Hosted app base URL (test vs production merchant web)
|
|
16
|
+
import { setEnvironment } from '../utils/config';
|
|
17
|
+
|
|
15
18
|
// Services
|
|
16
19
|
// import { getDeviceLocation } from '../services/device/location';
|
|
17
20
|
|
|
21
|
+
/** Controls which hosted merchant web origin `generateUrl` uses. */
|
|
22
|
+
export type SaversSdkHostedEnvironment = 'SANDBOX' | 'PROD';
|
|
23
|
+
|
|
18
24
|
export function initializeSDK(providedKeys: {
|
|
19
25
|
apiKey: string;
|
|
20
26
|
encryptionKey: string;
|
|
21
27
|
pRefCode: string;
|
|
22
28
|
authMode: string;
|
|
23
29
|
navigationRef: any;
|
|
30
|
+
/** When set, `generateUrl` uses testm (SANDBOX) or m (PROD). When omitted, build-time default from config applies. */
|
|
31
|
+
environment?: SaversSdkHostedEnvironment;
|
|
24
32
|
}) {
|
|
25
33
|
const keys = providedKeys;
|
|
26
34
|
const missing: string[] = [];
|
|
@@ -35,6 +43,19 @@ export function initializeSDK(providedKeys: {
|
|
|
35
43
|
logger.warn(msg);
|
|
36
44
|
throw new Error(msg);
|
|
37
45
|
}
|
|
46
|
+
|
|
47
|
+
if (keys.environment !== undefined) {
|
|
48
|
+
if (keys.environment !== 'SANDBOX' && keys.environment !== 'PROD') {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`[SDK] Invalid environment: ${String(keys.environment)}. Use 'SANDBOX' or 'PROD'.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
setEnvironment(keys.environment === 'SANDBOX' ? 'sandbox' : 'production');
|
|
54
|
+
logger.info('[SDK] Hosted app environment', {
|
|
55
|
+
environment: keys.environment,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
38
59
|
setApiKey(keys.apiKey);
|
|
39
60
|
setEncryptionKey(keys.encryptionKey);
|
|
40
61
|
setPRefCode(keys.pRefCode);
|
package/src/index.tsx
CHANGED
|
@@ -6,6 +6,8 @@ export * from './utils/errors';
|
|
|
6
6
|
export * from './utils/logger';
|
|
7
7
|
export * from './utils/validator';
|
|
8
8
|
export * from './services/webview/messageHandler';
|
|
9
|
+
export * from './services/webview/DualWebViewBridgeController';
|
|
10
|
+
export * from './services/webview/dualWebViewBridge.types';
|
|
9
11
|
export * from './data/storage/deviceIdManager';
|
|
10
12
|
export * from './services/navigation/dialPad';
|
|
11
13
|
export * from './services/navigation/openBrowser';
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { Image, StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
import WebView from 'react-native-webview';
|
|
4
|
+
import {
|
|
5
|
+
type DualWebViewBridgeEnvelope,
|
|
6
|
+
parseDualWebViewEnvelope,
|
|
7
|
+
} from './dualWebViewBridge.types';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_TRAVEL_PORTAL_URL = 'https://sandbox.travelercashback.com';
|
|
10
|
+
|
|
11
|
+
/** Must match `applicationNameForUserAgent` on clo-app WebViews (DualWebViewBridgeController). */
|
|
12
|
+
const WEBVIEW_UA_MARKER = 'CloAppWebView';
|
|
13
|
+
|
|
14
|
+
/** Legacy / Hub: open travel from first WebView without JSON envelope. */
|
|
15
|
+
const TRAVEL_DEEP_LINK = 'saversapp://travel';
|
|
16
|
+
const LEGACY_OPEN_TRAVEL_ACTIONS = ['OPEN_TRAVEL_PORTAL', 'OPEN_TRAVEL'];
|
|
17
|
+
|
|
18
|
+
export type Surface = 'savers' | 'travel';
|
|
19
|
+
|
|
20
|
+
export type DualWebViewBridgeControllerProps = {
|
|
21
|
+
saversAppUrl: string;
|
|
22
|
+
travelPortalUrl?: string;
|
|
23
|
+
partnerLogo?: string;
|
|
24
|
+
renderTravelBackIcon?: () => React.ReactNode;
|
|
25
|
+
initialSurface?: Surface;
|
|
26
|
+
onSaversSdkMessage?: (raw: string, postBack: (data: unknown) => void) => void;
|
|
27
|
+
onLoadEndSavers?: () => void;
|
|
28
|
+
onLoadEndTravel?: () => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function buildInjectNativeBridgeScript(
|
|
32
|
+
detail: Record<string, unknown>
|
|
33
|
+
): string {
|
|
34
|
+
return `
|
|
35
|
+
(function(){
|
|
36
|
+
var detail = ${JSON.stringify(detail)};
|
|
37
|
+
window.dispatchEvent(new CustomEvent('nativeBridge', { detail: detail }));
|
|
38
|
+
true;
|
|
39
|
+
})();
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const DualWebViewBridgeController: React.FC<
|
|
44
|
+
DualWebViewBridgeControllerProps
|
|
45
|
+
> = ({
|
|
46
|
+
saversAppUrl,
|
|
47
|
+
travelPortalUrl = DEFAULT_TRAVEL_PORTAL_URL,
|
|
48
|
+
partnerLogo,
|
|
49
|
+
renderTravelBackIcon,
|
|
50
|
+
initialSurface = 'savers',
|
|
51
|
+
onSaversSdkMessage,
|
|
52
|
+
onLoadEndSavers,
|
|
53
|
+
onLoadEndTravel,
|
|
54
|
+
}) => {
|
|
55
|
+
const saversRef = useRef<WebView>(null);
|
|
56
|
+
const travelRef = useRef<WebView>(null);
|
|
57
|
+
|
|
58
|
+
const [surface, setSurface] = useState<Surface>(initialSurface);
|
|
59
|
+
const [saversKey, setSaversKey] = useState(0);
|
|
60
|
+
const [travelKey, setTravelKey] = useState(0);
|
|
61
|
+
|
|
62
|
+
const openTravel = useCallback(() => {
|
|
63
|
+
setSurface('travel');
|
|
64
|
+
setTravelKey((k) => k + 1);
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
const backToSavers = useCallback(() => {
|
|
68
|
+
setSurface('savers');
|
|
69
|
+
setSaversKey((k) => k + 1);
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
const relayTo = useCallback(
|
|
73
|
+
(
|
|
74
|
+
target: 'savers' | 'travel',
|
|
75
|
+
payload: unknown,
|
|
76
|
+
from: 'savers' | 'travel'
|
|
77
|
+
) => {
|
|
78
|
+
const detail = {
|
|
79
|
+
bridge: 'dual-webview',
|
|
80
|
+
from,
|
|
81
|
+
action: 'relay' as const,
|
|
82
|
+
payload,
|
|
83
|
+
};
|
|
84
|
+
const ref = target === 'savers' ? saversRef : travelRef;
|
|
85
|
+
ref.current?.injectJavaScript(buildInjectNativeBridgeScript(detail));
|
|
86
|
+
},
|
|
87
|
+
[]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const handleBridgeEnvelope = useCallback(
|
|
91
|
+
(env: DualWebViewBridgeEnvelope): boolean => {
|
|
92
|
+
switch (env.action) {
|
|
93
|
+
case 'open_travel':
|
|
94
|
+
openTravel();
|
|
95
|
+
return true;
|
|
96
|
+
case 'close_travel':
|
|
97
|
+
backToSavers();
|
|
98
|
+
return true;
|
|
99
|
+
case 'relay':
|
|
100
|
+
if (env.to === 'savers') {
|
|
101
|
+
relayTo('savers', env.payload, env.from);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (env.to === 'travel') {
|
|
105
|
+
relayTo('travel', env.payload, env.from);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
default:
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
[backToSavers, openTravel, relayTo]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const tryLegacyOpenTravel = useCallback(
|
|
117
|
+
(raw: string): boolean => {
|
|
118
|
+
if (!raw || typeof raw !== 'string') {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const data = JSON.parse(raw);
|
|
123
|
+
if (
|
|
124
|
+
data?.action === 'OPEN_TRAVEL_PORTAL' ||
|
|
125
|
+
data?.type === 'OPEN_TRAVEL' ||
|
|
126
|
+
data?.openTravel === true
|
|
127
|
+
) {
|
|
128
|
+
openTravel();
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
/* ignore */
|
|
133
|
+
}
|
|
134
|
+
if (LEGACY_OPEN_TRAVEL_ACTIONS.some((s) => raw.includes(s))) {
|
|
135
|
+
openTravel();
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
},
|
|
140
|
+
[openTravel]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const onMessageSavers = useCallback(
|
|
144
|
+
(e: { nativeEvent: { data: string } }) => {
|
|
145
|
+
const raw = e?.nativeEvent?.data ?? '';
|
|
146
|
+
const env = parseDualWebViewEnvelope(raw);
|
|
147
|
+
|
|
148
|
+
if (env && env.from === 'savers' && handleBridgeEnvelope(env)) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (tryLegacyOpenTravel(raw)) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const postBack = (data: unknown) => {
|
|
156
|
+
const detail = {
|
|
157
|
+
bridge: 'dual-webview',
|
|
158
|
+
from: 'native',
|
|
159
|
+
action: 'relay',
|
|
160
|
+
payload: data,
|
|
161
|
+
};
|
|
162
|
+
saversRef.current?.injectJavaScript(
|
|
163
|
+
buildInjectNativeBridgeScript(detail)
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
onSaversSdkMessage?.(raw, postBack);
|
|
167
|
+
},
|
|
168
|
+
[handleBridgeEnvelope, onSaversSdkMessage, tryLegacyOpenTravel]
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const onMessageTravel = useCallback(
|
|
172
|
+
(e: { nativeEvent: { data: string } }) => {
|
|
173
|
+
const raw = e?.nativeEvent?.data ?? '';
|
|
174
|
+
const env = parseDualWebViewEnvelope(raw);
|
|
175
|
+
|
|
176
|
+
if (env && env.from === 'travel' && handleBridgeEnvelope(env)) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (tryLegacyOpenTravel(raw)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
[handleBridgeEnvelope, tryLegacyOpenTravel]
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const onShouldStartLoadSavers = useCallback(
|
|
187
|
+
(request: { url: string }) => {
|
|
188
|
+
const { url } = request;
|
|
189
|
+
if (url.startsWith(TRAVEL_DEEP_LINK) || url === 'saversapp://travel/') {
|
|
190
|
+
openTravel();
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
},
|
|
195
|
+
[openTravel]
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const travelSource = useMemo(
|
|
199
|
+
() => ({ uri: travelPortalUrl }),
|
|
200
|
+
[travelPortalUrl]
|
|
201
|
+
);
|
|
202
|
+
const saversSource = useMemo(() => ({ uri: saversAppUrl }), [saversAppUrl]);
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<View style={styles.root}>
|
|
206
|
+
{surface === 'savers' ? (
|
|
207
|
+
<WebView
|
|
208
|
+
key={saversKey}
|
|
209
|
+
ref={saversRef}
|
|
210
|
+
source={saversSource}
|
|
211
|
+
originWhitelist={['*']}
|
|
212
|
+
applicationNameForUserAgent={WEBVIEW_UA_MARKER}
|
|
213
|
+
onMessage={onMessageSavers}
|
|
214
|
+
onShouldStartLoadWithRequest={onShouldStartLoadSavers}
|
|
215
|
+
onLoadEnd={onLoadEndSavers}
|
|
216
|
+
style={styles.webview}
|
|
217
|
+
sharedCookiesEnabled
|
|
218
|
+
incognito={false}
|
|
219
|
+
webviewDebuggingEnabled={__DEV__}
|
|
220
|
+
/>
|
|
221
|
+
) : null}
|
|
222
|
+
|
|
223
|
+
{surface === 'travel' ? (
|
|
224
|
+
<View style={styles.travelColumn}>
|
|
225
|
+
<View style={styles.header}>
|
|
226
|
+
<TouchableOpacity
|
|
227
|
+
onPress={backToSavers}
|
|
228
|
+
hitSlop={16}
|
|
229
|
+
accessibilityRole="button"
|
|
230
|
+
accessibilityLabel="Back"
|
|
231
|
+
style={styles.backButton}
|
|
232
|
+
>
|
|
233
|
+
{renderTravelBackIcon ? (
|
|
234
|
+
renderTravelBackIcon()
|
|
235
|
+
) : (
|
|
236
|
+
<View style={styles.backIconWrapper}>
|
|
237
|
+
<View style={styles.backIconShaft} />
|
|
238
|
+
<View style={styles.backIconHead} />
|
|
239
|
+
</View>
|
|
240
|
+
)}
|
|
241
|
+
</TouchableOpacity>
|
|
242
|
+
|
|
243
|
+
<View style={styles.headerSpacer} />
|
|
244
|
+
|
|
245
|
+
{partnerLogo ? (
|
|
246
|
+
<Image
|
|
247
|
+
source={{ uri: partnerLogo }}
|
|
248
|
+
resizeMode="contain"
|
|
249
|
+
style={styles.logo}
|
|
250
|
+
/>
|
|
251
|
+
) : (
|
|
252
|
+
<View style={styles.logoPlaceholder} />
|
|
253
|
+
)}
|
|
254
|
+
</View>
|
|
255
|
+
<WebView
|
|
256
|
+
key={travelKey}
|
|
257
|
+
ref={travelRef}
|
|
258
|
+
source={travelSource}
|
|
259
|
+
originWhitelist={['*']}
|
|
260
|
+
applicationNameForUserAgent={WEBVIEW_UA_MARKER}
|
|
261
|
+
onMessage={onMessageTravel}
|
|
262
|
+
onLoadEnd={onLoadEndTravel}
|
|
263
|
+
style={styles.webview}
|
|
264
|
+
sharedCookiesEnabled
|
|
265
|
+
incognito={false}
|
|
266
|
+
webviewDebuggingEnabled={__DEV__}
|
|
267
|
+
/>
|
|
268
|
+
</View>
|
|
269
|
+
) : null}
|
|
270
|
+
</View>
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const styles = StyleSheet.create({
|
|
275
|
+
root: { flex: 1 },
|
|
276
|
+
travelColumn: { flex: 1 },
|
|
277
|
+
webview: { flex: 1 },
|
|
278
|
+
header: {
|
|
279
|
+
flexDirection: 'row',
|
|
280
|
+
alignItems: 'center',
|
|
281
|
+
backgroundColor: '#fff',
|
|
282
|
+
paddingHorizontal: 16,
|
|
283
|
+
paddingTop: 12,
|
|
284
|
+
paddingBottom: 12,
|
|
285
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
286
|
+
borderBottomColor: '#E5E5E5',
|
|
287
|
+
},
|
|
288
|
+
backButton: {
|
|
289
|
+
width: 32,
|
|
290
|
+
height: 32,
|
|
291
|
+
justifyContent: 'center',
|
|
292
|
+
alignItems: 'center',
|
|
293
|
+
},
|
|
294
|
+
backIconWrapper: {
|
|
295
|
+
width: 20,
|
|
296
|
+
height: 16,
|
|
297
|
+
justifyContent: 'center',
|
|
298
|
+
position: 'relative',
|
|
299
|
+
},
|
|
300
|
+
backIconShaft: {
|
|
301
|
+
position: 'absolute',
|
|
302
|
+
left: 6,
|
|
303
|
+
right: 0,
|
|
304
|
+
height: 2,
|
|
305
|
+
backgroundColor: '#111827',
|
|
306
|
+
borderRadius: 1,
|
|
307
|
+
},
|
|
308
|
+
backIconHead: {
|
|
309
|
+
width: 10,
|
|
310
|
+
height: 10,
|
|
311
|
+
borderLeftWidth: 2,
|
|
312
|
+
borderBottomWidth: 2,
|
|
313
|
+
borderColor: '#111827',
|
|
314
|
+
transform: [{ rotate: '45deg' }],
|
|
315
|
+
marginLeft: 1,
|
|
316
|
+
},
|
|
317
|
+
headerSpacer: {
|
|
318
|
+
flex: 1,
|
|
319
|
+
},
|
|
320
|
+
logo: {
|
|
321
|
+
width: 120,
|
|
322
|
+
height: 32,
|
|
323
|
+
},
|
|
324
|
+
logoPlaceholder: {
|
|
325
|
+
width: 120,
|
|
326
|
+
height: 32,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
export default DualWebViewBridgeController;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages posted from either WebView via ReactNativeWebView.postMessage(string).
|
|
3
|
+
* Pages should JSON.stringify envelopes before posting.
|
|
4
|
+
*/
|
|
5
|
+
export type DualWebViewBridgeEnvelope = {
|
|
6
|
+
bridge: 'dual-webview';
|
|
7
|
+
/** Who sent this message (set by the page). */
|
|
8
|
+
from: 'savers' | 'travel';
|
|
9
|
+
/**
|
|
10
|
+
* relay — forward payload to the other WebView via injectJavaScript + CustomEvent.
|
|
11
|
+
* open_travel — show travel surface (reloads travel WebView).
|
|
12
|
+
* close_travel — same as native back (reloads Savers WebView).
|
|
13
|
+
*/
|
|
14
|
+
action: 'relay' | 'open_travel' | 'close_travel';
|
|
15
|
+
/** For relay: which surface should receive the event. */
|
|
16
|
+
to?: 'savers' | 'travel';
|
|
17
|
+
payload?: unknown;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function parseDualWebViewEnvelope(
|
|
21
|
+
raw: string
|
|
22
|
+
): DualWebViewBridgeEnvelope | null {
|
|
23
|
+
try {
|
|
24
|
+
const o = JSON.parse(raw) as DualWebViewBridgeEnvelope;
|
|
25
|
+
if (o?.bridge === 'dual-webview' && o?.from && o?.action) {
|
|
26
|
+
return o;
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
/* not JSON */
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
package/src/utils/config.ts
CHANGED
|
@@ -7,6 +7,7 @@ const injectedEnv = 'sandbox' as string;
|
|
|
7
7
|
const DEFAULT_ENVIRONMENT: Environment =
|
|
8
8
|
injectedEnv === 'production' ? 'production' : 'sandbox';
|
|
9
9
|
|
|
10
|
+
/** Hosted merchant web origins; aligned with `SaversSdkHostedEnvironment` via `setEnvironment` in SDK init. */
|
|
10
11
|
const ENV_URLS: Record<Environment, string> = {
|
|
11
12
|
sandbox: 'https://testm.saversapp.com/',
|
|
12
13
|
production: 'https://m.saversapp.com/',
|