rampkit-expo-dev 0.0.27 → 0.0.29
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/build/DeviceInfoCollector.d.ts +6 -1
- package/build/DeviceInfoCollector.js +47 -0
- package/build/RampKit.js +6 -0
- package/build/RampkitOverlay.d.ts +5 -0
- package/build/RampkitOverlay.js +161 -7
- package/build/index.d.ts +2 -2
- package/build/index.js +2 -1
- package/build/types.d.ts +53 -0
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* RampKit Device Info Collector
|
|
3
3
|
* Collects device information using native modules for the /app-users endpoint
|
|
4
4
|
*/
|
|
5
|
-
import { DeviceInfo } from "./types";
|
|
5
|
+
import { DeviceInfo, RampKitContext } from "./types";
|
|
6
6
|
/**
|
|
7
7
|
* Get session start time
|
|
8
8
|
*/
|
|
@@ -19,3 +19,8 @@ export declare function collectDeviceInfo(): Promise<DeviceInfo>;
|
|
|
19
19
|
* Reset session (call when app is fully restarted)
|
|
20
20
|
*/
|
|
21
21
|
export declare function resetSession(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Build RampKit context from DeviceInfo for WebView template resolution
|
|
24
|
+
* This creates the device/user context that gets injected as window.rampkitContext
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildRampKitContext(deviceInfo: DeviceInfo): RampKitContext;
|
|
@@ -11,6 +11,7 @@ exports.getSessionStartTime = getSessionStartTime;
|
|
|
11
11
|
exports.getSessionDurationSeconds = getSessionDurationSeconds;
|
|
12
12
|
exports.collectDeviceInfo = collectDeviceInfo;
|
|
13
13
|
exports.resetSession = resetSession;
|
|
14
|
+
exports.buildRampKitContext = buildRampKitContext;
|
|
14
15
|
const react_native_1 = require("react-native");
|
|
15
16
|
const RampKitNative_1 = __importDefault(require("./RampKitNative"));
|
|
16
17
|
const constants_1 = require("./constants");
|
|
@@ -198,3 +199,49 @@ function resetSession() {
|
|
|
198
199
|
sessionId = null;
|
|
199
200
|
sessionStartTime = null;
|
|
200
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Build RampKit context from DeviceInfo for WebView template resolution
|
|
204
|
+
* This creates the device/user context that gets injected as window.rampkitContext
|
|
205
|
+
*/
|
|
206
|
+
function buildRampKitContext(deviceInfo) {
|
|
207
|
+
// Calculate days since install
|
|
208
|
+
const daysSinceInstall = calculateDaysSinceInstall(deviceInfo.installDate);
|
|
209
|
+
const device = {
|
|
210
|
+
platform: deviceInfo.platform,
|
|
211
|
+
model: deviceInfo.deviceModel,
|
|
212
|
+
locale: deviceInfo.deviceLocale,
|
|
213
|
+
language: deviceInfo.deviceLanguageCode || deviceInfo.deviceLocale.split("_")[0] || "en",
|
|
214
|
+
country: deviceInfo.regionCode || deviceInfo.deviceLocale.split("_")[1] || "US",
|
|
215
|
+
currencyCode: deviceInfo.deviceCurrencyCode || "USD",
|
|
216
|
+
currencySymbol: deviceInfo.deviceCurrencySymbol || "$",
|
|
217
|
+
appVersion: deviceInfo.appVersion || "1.0.0",
|
|
218
|
+
buildNumber: deviceInfo.buildNumber || "1",
|
|
219
|
+
bundleId: deviceInfo.bundleId || "",
|
|
220
|
+
interfaceStyle: deviceInfo.interfaceStyle,
|
|
221
|
+
timezone: deviceInfo.timezoneOffsetSeconds,
|
|
222
|
+
daysSinceInstall,
|
|
223
|
+
};
|
|
224
|
+
const user = {
|
|
225
|
+
id: deviceInfo.appUserId,
|
|
226
|
+
isNewUser: deviceInfo.isFirstLaunch,
|
|
227
|
+
hasAppleSearchAdsAttribution: deviceInfo.isAppleSearchAdsAttribution,
|
|
228
|
+
sessionId: deviceInfo.appSessionId,
|
|
229
|
+
installedAt: deviceInfo.installDate,
|
|
230
|
+
};
|
|
231
|
+
return { device, user };
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Calculate days since install from install date string
|
|
235
|
+
*/
|
|
236
|
+
function calculateDaysSinceInstall(installDateString) {
|
|
237
|
+
try {
|
|
238
|
+
const installDate = new Date(installDateString);
|
|
239
|
+
const now = new Date();
|
|
240
|
+
const diffMs = now.getTime() - installDate.getTime();
|
|
241
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
242
|
+
return Math.max(0, diffDays);
|
|
243
|
+
}
|
|
244
|
+
catch (_a) {
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
}
|
package/build/RampKit.js
CHANGED
|
@@ -205,6 +205,10 @@ class RampKitCore {
|
|
|
205
205
|
const requiredScripts = Array.isArray(data.requiredScripts)
|
|
206
206
|
? data.requiredScripts
|
|
207
207
|
: [];
|
|
208
|
+
// Build device/user context for template resolution
|
|
209
|
+
const rampkitContext = this.deviceInfo
|
|
210
|
+
? (0, DeviceInfoCollector_1.buildRampKitContext)(this.deviceInfo)
|
|
211
|
+
: undefined;
|
|
208
212
|
// Track onboarding started event
|
|
209
213
|
const onboardingId = data.onboardingId || data.id || "unknown";
|
|
210
214
|
EventManager_1.eventManager.trackOnboardingStarted(onboardingId, screens.length);
|
|
@@ -215,6 +219,7 @@ class RampKitCore {
|
|
|
215
219
|
screens,
|
|
216
220
|
variables,
|
|
217
221
|
requiredScripts,
|
|
222
|
+
rampkitContext,
|
|
218
223
|
});
|
|
219
224
|
}
|
|
220
225
|
catch (_) { }
|
|
@@ -223,6 +228,7 @@ class RampKitCore {
|
|
|
223
228
|
screens,
|
|
224
229
|
variables,
|
|
225
230
|
requiredScripts,
|
|
231
|
+
rampkitContext,
|
|
226
232
|
onOnboardingFinished: (payload) => {
|
|
227
233
|
var _a;
|
|
228
234
|
// Track onboarding completed
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { RampKitContext } from "./types";
|
|
1
2
|
export declare const injectedHardening = "\n(function(){\n try {\n var meta = document.querySelector('meta[name=\"viewport\"]');\n if (!meta) { meta = document.createElement('meta'); meta.name = 'viewport'; document.head.appendChild(meta); }\n meta.setAttribute('content','width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover');\n var style = document.createElement('style');\n style.textContent='html,body{overflow-x:hidden!important;} html,body,*{-webkit-user-select:none!important;user-select:none!important;-webkit-touch-callout:none!important;-ms-user-select:none!important;touch-action: pan-y;} *{-webkit-tap-highlight-color: rgba(0,0,0,0)!important;} ::selection{background: transparent!important;} ::-moz-selection{background: transparent!important;} a,img{-webkit-user-drag:none!important;user-drag:none!important;-webkit-touch-callout:none!important} input,textarea{caret-color:transparent!important;-webkit-user-select:none!important;user-select:none!important}';\n document.head.appendChild(style);\n var prevent=function(e){e.preventDefault&&e.preventDefault();};\n document.addEventListener('gesturestart',prevent,{passive:false});\n document.addEventListener('gesturechange',prevent,{passive:false});\n document.addEventListener('gestureend',prevent,{passive:false});\n document.addEventListener('dblclick',prevent,{passive:false});\n document.addEventListener('wheel',function(e){ if(e.ctrlKey) e.preventDefault(); },{passive:false});\n document.addEventListener('touchmove',function(e){ if(e.scale && e.scale !== 1) e.preventDefault(); },{passive:false});\n document.addEventListener('selectstart',prevent,{passive:false,capture:true});\n document.addEventListener('contextmenu',prevent,{passive:false,capture:true});\n document.addEventListener('copy',prevent,{passive:false,capture:true});\n document.addEventListener('cut',prevent,{passive:false,capture:true});\n document.addEventListener('paste',prevent,{passive:false,capture:true});\n document.addEventListener('dragstart',prevent,{passive:false,capture:true});\n // Belt-and-suspenders: aggressively clear any attempted selection\n var clearSel=function(){\n try{var sel=window.getSelection&&window.getSelection(); if(sel&&sel.removeAllRanges) sel.removeAllRanges();}catch(_){} }\n document.addEventListener('selectionchange',clearSel,{passive:true,capture:true});\n document.onselectstart=function(){ clearSel(); return false; };\n try{ document.documentElement.style.webkitUserSelect='none'; document.documentElement.style.userSelect='none'; }catch(_){ }\n try{ document.body.style.webkitUserSelect='none'; document.body.style.userSelect='none'; }catch(_){ }\n var __selTimer = setInterval(clearSel, 160);\n window.addEventListener('pagehide',function(){ try{ clearInterval(__selTimer); }catch(_){} });\n // Continuously enforce no-select on all elements and new nodes\n var enforceNoSelect = function(el){\n try{\n el.style && (el.style.webkitUserSelect='none', el.style.userSelect='none', el.style.webkitTouchCallout='none');\n el.setAttribute && (el.setAttribute('unselectable','on'), el.setAttribute('contenteditable','false'));\n }catch(_){}\n }\n try{\n var all=document.getElementsByTagName('*');\n for(var i=0;i<all.length;i++){ enforceNoSelect(all[i]); }\n var obs = new MutationObserver(function(muts){\n for(var j=0;j<muts.length;j++){\n var m=muts[j];\n if(m.type==='childList'){\n m.addedNodes && m.addedNodes.forEach && m.addedNodes.forEach(function(n){ if(n && n.nodeType===1){ enforceNoSelect(n); var q=n.getElementsByTagName? n.getElementsByTagName('*'): []; for(var k=0;k<q.length;k++){ enforceNoSelect(q[k]); }}});\n } else if(m.type==='attributes'){\n enforceNoSelect(m.target);\n }\n }\n });\n obs.observe(document.documentElement,{ childList:true, subtree:true, attributes:true, attributeFilter:['contenteditable','style'] });\n }catch(_){ }\n } catch(_) {}\n})(); true;\n";
|
|
2
3
|
export declare const injectedNoSelect = "\n(function(){\n try {\n if (window.__rkNoSelectApplied) return true;\n window.__rkNoSelectApplied = true;\n var style = document.getElementById('rk-no-select-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'rk-no-select-style';\n style.innerHTML = \"\n * {\n user-select: none !important;\n -webkit-user-select: none !important;\n -webkit-touch-callout: none !important;\n }\n ::selection {\n background: transparent !important;\n }\n \";\n document.head.appendChild(style);\n }\n var prevent = function(e){ if(e && e.preventDefault) e.preventDefault(); return false; };\n document.addEventListener('contextmenu', prevent, { passive: false, capture: true });\n document.addEventListener('selectstart', prevent, { passive: false, capture: true });\n } catch (_) {}\n true;\n})();\n";
|
|
3
4
|
export declare const injectedVarsHandler = "\n(function(){\n try {\n if (window.__rkVarsHandlerApplied) return true;\n window.__rkVarsHandlerApplied = true;\n \n // Handler function that updates variables and notifies the page\n window.__rkHandleVarsUpdate = function(vars) {\n if (!vars || typeof vars !== 'object') return;\n // Update the global variables object\n window.__rampkitVariables = vars;\n // Dispatch a custom event that the page's JS can listen to for re-rendering\n try {\n document.dispatchEvent(new CustomEvent('rampkit:vars-updated', { detail: vars }));\n } catch(e) {}\n // Also try calling a global handler if the page defined one\n try {\n if (typeof window.onRampkitVarsUpdate === 'function') {\n window.onRampkitVarsUpdate(vars);\n }\n } catch(e) {}\n };\n \n // Listen for message events from React Native\n document.addEventListener('message', function(event) {\n try {\n var data = event.data;\n if (data && data.type === 'rampkit:variables' && data.vars) {\n window.__rkHandleVarsUpdate(data.vars);\n }\n } catch(e) {}\n }, false);\n \n // Also listen on window for compatibility\n window.addEventListener('message', function(event) {\n try {\n var data = event.data;\n if (data && data.type === 'rampkit:variables' && data.vars) {\n window.__rkHandleVarsUpdate(data.vars);\n }\n } catch(e) {}\n }, false);\n } catch (_) {}\n true;\n})();\n";
|
|
5
|
+
export declare const injectedTemplateResolver = "\n(function(){\n try {\n if (window.__rkTemplateResolverApplied) return true;\n window.__rkTemplateResolverApplied = true;\n \n // Template pattern: matches ${varName}\n var TEMPLATE_MARKER = '$' + '{';\n var TEMPLATE_REGEX = /\\x24\\x7B([A-Za-z_][A-Za-z0-9_.]*)\\x7D/g;\n \n // Build variable map from context\n function buildVarMap() {\n var vars = {};\n var ctx = window.rampkitContext || { device: {}, user: {} };\n var state = window.__rampkitVariables || {};\n \n // Device vars (device.xxx)\n if (ctx.device) {\n Object.keys(ctx.device).forEach(function(key) {\n vars['device.' + key] = ctx.device[key];\n });\n }\n \n // User vars (user.xxx)\n if (ctx.user) {\n Object.keys(ctx.user).forEach(function(key) {\n vars['user.' + key] = ctx.user[key];\n });\n }\n \n // State vars (varName - no prefix)\n Object.keys(state).forEach(function(key) {\n vars[key] = state[key];\n });\n \n return vars;\n }\n \n // Format a value for display\n function formatValue(value) {\n if (value === undefined || value === null) return '';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (typeof value === 'object') return JSON.stringify(value);\n return String(value);\n }\n \n // Resolve templates in a single text node\n function resolveTextNode(node, vars) {\n var text = node.textContent;\n if (!text || text.indexOf(TEMPLATE_MARKER) === -1) return;\n \n var resolved = text.replace(TEMPLATE_REGEX, function(match, varName) {\n if (vars.hasOwnProperty(varName)) {\n return formatValue(vars[varName]);\n }\n return match; // Keep original if var not found\n });\n \n if (resolved !== text) {\n node.textContent = resolved;\n }\n }\n \n // Resolve templates in all text nodes\n function resolveAllTemplates() {\n var vars = buildVarMap();\n var walker = document.createTreeWalker(\n document.body,\n NodeFilter.SHOW_TEXT,\n null,\n false\n );\n \n var node;\n while (node = walker.nextNode()) {\n resolveTextNode(node, vars);\n }\n \n // Also resolve in attribute values that might contain templates\n var allElements = document.body.getElementsByTagName('*');\n for (var i = 0; i < allElements.length; i++) {\n var el = allElements[i];\n for (var j = 0; j < el.attributes.length; j++) {\n var attr = el.attributes[j];\n if (attr.value && attr.value.indexOf(TEMPLATE_MARKER) !== -1) {\n var resolvedAttr = attr.value.replace(TEMPLATE_REGEX, function(match, varName) {\n if (vars.hasOwnProperty(varName)) {\n return formatValue(vars[varName]);\n }\n return match;\n });\n if (resolvedAttr !== attr.value) {\n el.setAttribute(attr.name, resolvedAttr);\n }\n }\n }\n }\n }\n \n // Run on DOMContentLoaded\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', resolveAllTemplates);\n } else {\n // DOM already ready, run immediately\n resolveAllTemplates();\n }\n \n // Also run after a short delay to catch dynamically added content\n setTimeout(resolveAllTemplates, 100);\n \n // Expose for manual re-resolution\n window.rampkitResolveTemplates = resolveAllTemplates;\n \n // Re-resolve when variables update\n document.addEventListener('rampkit:vars-updated', function() {\n setTimeout(resolveAllTemplates, 0);\n });\n \n } catch(e) {\n console.log('[Rampkit] Template resolver error:', e);\n }\n true;\n})();\n";
|
|
4
6
|
export type ScreenPayload = {
|
|
5
7
|
id: string;
|
|
6
8
|
html: string;
|
|
@@ -12,6 +14,7 @@ export declare function showRampkitOverlay(opts: {
|
|
|
12
14
|
screens: ScreenPayload[];
|
|
13
15
|
variables?: Record<string, any>;
|
|
14
16
|
requiredScripts?: string[];
|
|
17
|
+
rampkitContext?: RampKitContext;
|
|
15
18
|
onClose?: () => void;
|
|
16
19
|
onOnboardingFinished?: (payload?: any) => void;
|
|
17
20
|
onShowPaywall?: (payload?: any) => void;
|
|
@@ -27,12 +30,14 @@ export declare function preloadRampkitOverlay(opts: {
|
|
|
27
30
|
screens: ScreenPayload[];
|
|
28
31
|
variables?: Record<string, any>;
|
|
29
32
|
requiredScripts?: string[];
|
|
33
|
+
rampkitContext?: RampKitContext;
|
|
30
34
|
}): void;
|
|
31
35
|
declare function Overlay(props: {
|
|
32
36
|
onboardingId: string;
|
|
33
37
|
screens: ScreenPayload[];
|
|
34
38
|
variables?: Record<string, any>;
|
|
35
39
|
requiredScripts?: string[];
|
|
40
|
+
rampkitContext?: RampKitContext;
|
|
36
41
|
prebuiltDocs?: string[];
|
|
37
42
|
onRequestClose: () => void;
|
|
38
43
|
onOnboardingFinished?: (payload?: any) => void;
|
package/build/RampkitOverlay.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.injectedVarsHandler = exports.injectedNoSelect = exports.injectedHardening = void 0;
|
|
39
|
+
exports.injectedTemplateResolver = exports.injectedVarsHandler = exports.injectedNoSelect = exports.injectedHardening = void 0;
|
|
40
40
|
exports.showRampkitOverlay = showRampkitOverlay;
|
|
41
41
|
exports.hideRampkitOverlay = hideRampkitOverlay;
|
|
42
42
|
exports.closeRampkitOverlay = closeRampkitOverlay;
|
|
@@ -173,6 +173,132 @@ exports.injectedVarsHandler = `
|
|
|
173
173
|
true;
|
|
174
174
|
})();
|
|
175
175
|
`;
|
|
176
|
+
// Template resolution script that replaces ${device.xxx} and ${user.xxx} with actual values
|
|
177
|
+
// This runs on DOMContentLoaded and can be re-triggered via window.rampkitResolveTemplates()
|
|
178
|
+
exports.injectedTemplateResolver = `
|
|
179
|
+
(function(){
|
|
180
|
+
try {
|
|
181
|
+
if (window.__rkTemplateResolverApplied) return true;
|
|
182
|
+
window.__rkTemplateResolverApplied = true;
|
|
183
|
+
|
|
184
|
+
// Template pattern: matches ${'$'}{varName}
|
|
185
|
+
var TEMPLATE_MARKER = '$' + '{';
|
|
186
|
+
var TEMPLATE_REGEX = /\\x24\\x7B([A-Za-z_][A-Za-z0-9_.]*)\\x7D/g;
|
|
187
|
+
|
|
188
|
+
// Build variable map from context
|
|
189
|
+
function buildVarMap() {
|
|
190
|
+
var vars = {};
|
|
191
|
+
var ctx = window.rampkitContext || { device: {}, user: {} };
|
|
192
|
+
var state = window.__rampkitVariables || {};
|
|
193
|
+
|
|
194
|
+
// Device vars (device.xxx)
|
|
195
|
+
if (ctx.device) {
|
|
196
|
+
Object.keys(ctx.device).forEach(function(key) {
|
|
197
|
+
vars['device.' + key] = ctx.device[key];
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// User vars (user.xxx)
|
|
202
|
+
if (ctx.user) {
|
|
203
|
+
Object.keys(ctx.user).forEach(function(key) {
|
|
204
|
+
vars['user.' + key] = ctx.user[key];
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// State vars (varName - no prefix)
|
|
209
|
+
Object.keys(state).forEach(function(key) {
|
|
210
|
+
vars[key] = state[key];
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return vars;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Format a value for display
|
|
217
|
+
function formatValue(value) {
|
|
218
|
+
if (value === undefined || value === null) return '';
|
|
219
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
220
|
+
if (typeof value === 'object') return JSON.stringify(value);
|
|
221
|
+
return String(value);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Resolve templates in a single text node
|
|
225
|
+
function resolveTextNode(node, vars) {
|
|
226
|
+
var text = node.textContent;
|
|
227
|
+
if (!text || text.indexOf(TEMPLATE_MARKER) === -1) return;
|
|
228
|
+
|
|
229
|
+
var resolved = text.replace(TEMPLATE_REGEX, function(match, varName) {
|
|
230
|
+
if (vars.hasOwnProperty(varName)) {
|
|
231
|
+
return formatValue(vars[varName]);
|
|
232
|
+
}
|
|
233
|
+
return match; // Keep original if var not found
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (resolved !== text) {
|
|
237
|
+
node.textContent = resolved;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Resolve templates in all text nodes
|
|
242
|
+
function resolveAllTemplates() {
|
|
243
|
+
var vars = buildVarMap();
|
|
244
|
+
var walker = document.createTreeWalker(
|
|
245
|
+
document.body,
|
|
246
|
+
NodeFilter.SHOW_TEXT,
|
|
247
|
+
null,
|
|
248
|
+
false
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
var node;
|
|
252
|
+
while (node = walker.nextNode()) {
|
|
253
|
+
resolveTextNode(node, vars);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Also resolve in attribute values that might contain templates
|
|
257
|
+
var allElements = document.body.getElementsByTagName('*');
|
|
258
|
+
for (var i = 0; i < allElements.length; i++) {
|
|
259
|
+
var el = allElements[i];
|
|
260
|
+
for (var j = 0; j < el.attributes.length; j++) {
|
|
261
|
+
var attr = el.attributes[j];
|
|
262
|
+
if (attr.value && attr.value.indexOf(TEMPLATE_MARKER) !== -1) {
|
|
263
|
+
var resolvedAttr = attr.value.replace(TEMPLATE_REGEX, function(match, varName) {
|
|
264
|
+
if (vars.hasOwnProperty(varName)) {
|
|
265
|
+
return formatValue(vars[varName]);
|
|
266
|
+
}
|
|
267
|
+
return match;
|
|
268
|
+
});
|
|
269
|
+
if (resolvedAttr !== attr.value) {
|
|
270
|
+
el.setAttribute(attr.name, resolvedAttr);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Run on DOMContentLoaded
|
|
278
|
+
if (document.readyState === 'loading') {
|
|
279
|
+
document.addEventListener('DOMContentLoaded', resolveAllTemplates);
|
|
280
|
+
} else {
|
|
281
|
+
// DOM already ready, run immediately
|
|
282
|
+
resolveAllTemplates();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Also run after a short delay to catch dynamically added content
|
|
286
|
+
setTimeout(resolveAllTemplates, 100);
|
|
287
|
+
|
|
288
|
+
// Expose for manual re-resolution
|
|
289
|
+
window.rampkitResolveTemplates = resolveAllTemplates;
|
|
290
|
+
|
|
291
|
+
// Re-resolve when variables update
|
|
292
|
+
document.addEventListener('rampkit:vars-updated', function() {
|
|
293
|
+
setTimeout(resolveAllTemplates, 0);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
} catch(e) {
|
|
297
|
+
console.log('[Rampkit] Template resolver error:', e);
|
|
298
|
+
}
|
|
299
|
+
true;
|
|
300
|
+
})();
|
|
301
|
+
`;
|
|
176
302
|
function performRampkitHaptic(event) {
|
|
177
303
|
if (!event || event.action !== "haptic") {
|
|
178
304
|
// Backwards compatible default
|
|
@@ -229,7 +355,7 @@ function showRampkitOverlay(opts) {
|
|
|
229
355
|
if (sibling)
|
|
230
356
|
return; // already visible
|
|
231
357
|
const prebuiltDocs = preloadCache.get(opts.onboardingId);
|
|
232
|
-
sibling = new react_native_root_siblings_1.default(((0, jsx_runtime_1.jsx)(Overlay, { onboardingId: opts.onboardingId, screens: opts.screens, variables: opts.variables, requiredScripts: opts.requiredScripts, prebuiltDocs: prebuiltDocs, onRequestClose: () => {
|
|
358
|
+
sibling = new react_native_root_siblings_1.default(((0, jsx_runtime_1.jsx)(Overlay, { onboardingId: opts.onboardingId, screens: opts.screens, variables: opts.variables, requiredScripts: opts.requiredScripts, rampkitContext: opts.rampkitContext, prebuiltDocs: prebuiltDocs, onRequestClose: () => {
|
|
233
359
|
var _a;
|
|
234
360
|
activeCloseHandler = null;
|
|
235
361
|
hideRampkitOverlay();
|
|
@@ -261,7 +387,7 @@ function preloadRampkitOverlay(opts) {
|
|
|
261
387
|
try {
|
|
262
388
|
if (preloadCache.has(opts.onboardingId))
|
|
263
389
|
return;
|
|
264
|
-
const docs = opts.screens.map((s) => buildHtmlDocument(s, opts.variables, opts.requiredScripts));
|
|
390
|
+
const docs = opts.screens.map((s) => buildHtmlDocument(s, opts.variables, opts.requiredScripts, opts.rampkitContext));
|
|
265
391
|
preloadCache.set(opts.onboardingId, docs);
|
|
266
392
|
// Mount a hidden WebView to warm up the WebView process and cache
|
|
267
393
|
if (preloadSibling)
|
|
@@ -273,14 +399,14 @@ function preloadRampkitOverlay(opts) {
|
|
|
273
399
|
opacity: 0,
|
|
274
400
|
top: -1000,
|
|
275
401
|
left: -1000,
|
|
276
|
-
}, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { originWhitelist: ["*"], source: { html: docs[0] || "<html></html>" }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, hideKeyboardAccessoryView: true }) }));
|
|
402
|
+
}, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { originWhitelist: ["*"], source: { html: docs[0] || "<html></html>" }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler + exports.injectedTemplateResolver, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, hideKeyboardAccessoryView: true }) }));
|
|
277
403
|
preloadSibling = new react_native_root_siblings_1.default((0, jsx_runtime_1.jsx)(HiddenPreloader, {}));
|
|
278
404
|
}
|
|
279
405
|
catch (e) {
|
|
280
406
|
// best-effort preloading; ignore errors
|
|
281
407
|
}
|
|
282
408
|
}
|
|
283
|
-
function buildHtmlDocument(screen, variables, requiredScripts) {
|
|
409
|
+
function buildHtmlDocument(screen, variables, requiredScripts, rampkitContext) {
|
|
284
410
|
const css = screen.css || "";
|
|
285
411
|
const html = screen.html || "";
|
|
286
412
|
const js = screen.js || "";
|
|
@@ -315,6 +441,31 @@ function buildHtmlDocument(screen, variables, requiredScripts) {
|
|
|
315
441
|
return "";
|
|
316
442
|
}
|
|
317
443
|
})();
|
|
444
|
+
// Default context if not provided
|
|
445
|
+
const context = rampkitContext || {
|
|
446
|
+
device: {
|
|
447
|
+
platform: "unknown",
|
|
448
|
+
model: "unknown",
|
|
449
|
+
locale: "en_US",
|
|
450
|
+
language: "en",
|
|
451
|
+
country: "US",
|
|
452
|
+
currencyCode: "USD",
|
|
453
|
+
currencySymbol: "$",
|
|
454
|
+
appVersion: "1.0.0",
|
|
455
|
+
buildNumber: "1",
|
|
456
|
+
bundleId: "",
|
|
457
|
+
interfaceStyle: "light",
|
|
458
|
+
timezone: 0,
|
|
459
|
+
daysSinceInstall: 0,
|
|
460
|
+
},
|
|
461
|
+
user: {
|
|
462
|
+
id: "",
|
|
463
|
+
isNewUser: true,
|
|
464
|
+
hasAppleSearchAdsAttribution: false,
|
|
465
|
+
sessionId: "",
|
|
466
|
+
installedAt: new Date().toISOString(),
|
|
467
|
+
},
|
|
468
|
+
};
|
|
318
469
|
return `<!doctype html>
|
|
319
470
|
<html>
|
|
320
471
|
<head>
|
|
@@ -328,6 +479,9 @@ ${scripts}
|
|
|
328
479
|
<body>
|
|
329
480
|
${html}
|
|
330
481
|
<script>
|
|
482
|
+
// Device and user context for template resolution
|
|
483
|
+
window.rampkitContext = ${JSON.stringify(context)};
|
|
484
|
+
// State variables from onboarding
|
|
331
485
|
window.__rampkitVariables = ${JSON.stringify(variables || {})};
|
|
332
486
|
${js}
|
|
333
487
|
</script>
|
|
@@ -525,7 +679,7 @@ function Overlay(props) {
|
|
|
525
679
|
return () => sub.remove();
|
|
526
680
|
}, [index, handleRequestClose]);
|
|
527
681
|
const docs = (0, react_1.useMemo)(() => props.prebuiltDocs ||
|
|
528
|
-
props.screens.map((s) => buildHtmlDocument(s, props.variables, props.requiredScripts)), [props.prebuiltDocs, props.screens, props.variables, props.requiredScripts]);
|
|
682
|
+
props.screens.map((s) => buildHtmlDocument(s, props.variables, props.requiredScripts, props.rampkitContext)), [props.prebuiltDocs, props.screens, props.variables, props.requiredScripts, props.rampkitContext]);
|
|
529
683
|
react_1.default.useEffect(() => {
|
|
530
684
|
try {
|
|
531
685
|
console.log("[Rampkit] Overlay mounted: docs=", docs.length);
|
|
@@ -654,7 +808,7 @@ function Overlay(props) {
|
|
|
654
808
|
styles.root,
|
|
655
809
|
!visible && styles.invisible,
|
|
656
810
|
visible && { opacity: overlayOpacity },
|
|
657
|
-
], pointerEvents: visible && !isClosing ? "auto" : "none", children: [(0, jsx_runtime_1.jsx)(react_native_pager_view_1.default, { ref: pagerRef, style: react_native_1.StyleSheet.absoluteFill, scrollEnabled: false, initialPage: 0, onPageSelected: onPageSelected, offscreenPageLimit: props.screens.length, overScrollMode: "never", children: docs.map((doc, i) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.page, renderToHardwareTextureAndroid: true, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { ref: (r) => (webviewsRef.current[i] = r), style: styles.webview, originWhitelist: ["*"], source: { html: doc }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, overScrollMode: "never", scalesPageToFit: false, showsHorizontalScrollIndicator: false, dataDetectorTypes: "none", allowsLinkPreview: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, javaScriptEnabled: true, domStorageEnabled: true, hideKeyboardAccessoryView: true, onLoadEnd: () => {
|
|
811
|
+
], pointerEvents: visible && !isClosing ? "auto" : "none", children: [(0, jsx_runtime_1.jsx)(react_native_pager_view_1.default, { ref: pagerRef, style: react_native_1.StyleSheet.absoluteFill, scrollEnabled: false, initialPage: 0, onPageSelected: onPageSelected, offscreenPageLimit: props.screens.length, overScrollMode: "never", children: docs.map((doc, i) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.page, renderToHardwareTextureAndroid: true, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { ref: (r) => (webviewsRef.current[i] = r), style: styles.webview, originWhitelist: ["*"], source: { html: doc }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler + exports.injectedTemplateResolver, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, overScrollMode: "never", scalesPageToFit: false, showsHorizontalScrollIndicator: false, dataDetectorTypes: "none", allowsLinkPreview: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, javaScriptEnabled: true, domStorageEnabled: true, hideKeyboardAccessoryView: true, onLoadEnd: () => {
|
|
658
812
|
setLoadedCount((c) => c + 1);
|
|
659
813
|
if (i === 0) {
|
|
660
814
|
setFirstPageLoaded(true);
|
package/build/index.d.ts
CHANGED
|
@@ -6,10 +6,10 @@ import { RampKitCore } from "./RampKit";
|
|
|
6
6
|
export declare const RampKit: RampKitCore;
|
|
7
7
|
export { getRampKitUserId } from "./userId";
|
|
8
8
|
export { eventManager } from "./EventManager";
|
|
9
|
-
export { collectDeviceInfo, getSessionDurationSeconds, getSessionStartTime, } from "./DeviceInfoCollector";
|
|
9
|
+
export { collectDeviceInfo, getSessionDurationSeconds, getSessionStartTime, buildRampKitContext, } from "./DeviceInfoCollector";
|
|
10
10
|
export { default as RampKitNative } from "./RampKitNative";
|
|
11
11
|
export type { NativeDeviceInfo, NativeLaunchData } from "./RampKitNative";
|
|
12
12
|
export { Haptics, StoreReview, Notifications, TransactionObserver } from "./RampKitNative";
|
|
13
13
|
export type { ImpactStyle, NotificationType, NotificationOptions, NotificationPermissionResult } from "./RampKitNative";
|
|
14
|
-
export type { DeviceInfo, RampKitEvent, EventDevice, EventContext, RampKitConfig, RampKitEventName, AppSessionStartedProperties, AppSessionEndedProperties, AppBackgroundedProperties, AppForegroundedProperties, OnboardingStartedProperties, OnboardingScreenViewedProperties, OnboardingQuestionAnsweredProperties, OnboardingCompletedProperties, OnboardingAbandonedProperties, ScreenViewProperties, CtaTapProperties, NotificationsPromptShownProperties, NotificationsResponseProperties, PaywallShownProperties, PaywallPrimaryActionTapProperties, PaywallClosedProperties, PurchaseStartedProperties, PurchaseCompletedProperties, PurchaseFailedProperties, } from "./types";
|
|
14
|
+
export type { DeviceInfo, RampKitEvent, EventDevice, EventContext, RampKitConfig, RampKitEventName, RampKitContext, RampKitDeviceContext, RampKitUserContext, AppSessionStartedProperties, AppSessionEndedProperties, AppBackgroundedProperties, AppForegroundedProperties, OnboardingStartedProperties, OnboardingScreenViewedProperties, OnboardingQuestionAnsweredProperties, OnboardingCompletedProperties, OnboardingAbandonedProperties, ScreenViewProperties, CtaTapProperties, NotificationsPromptShownProperties, NotificationsResponseProperties, PaywallShownProperties, PaywallPrimaryActionTapProperties, PaywallClosedProperties, PurchaseStartedProperties, PurchaseCompletedProperties, PurchaseFailedProperties, } from "./types";
|
|
15
15
|
export { SDK_VERSION, CAPABILITIES } from "./constants";
|
package/build/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
7
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
8
|
};
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.CAPABILITIES = exports.SDK_VERSION = exports.TransactionObserver = exports.Notifications = exports.StoreReview = exports.Haptics = exports.RampKitNative = exports.getSessionStartTime = exports.getSessionDurationSeconds = exports.collectDeviceInfo = exports.eventManager = exports.getRampKitUserId = exports.RampKit = void 0;
|
|
10
|
+
exports.CAPABILITIES = exports.SDK_VERSION = exports.TransactionObserver = exports.Notifications = exports.StoreReview = exports.Haptics = exports.RampKitNative = exports.buildRampKitContext = exports.getSessionStartTime = exports.getSessionDurationSeconds = exports.collectDeviceInfo = exports.eventManager = exports.getRampKitUserId = exports.RampKit = void 0;
|
|
11
11
|
const RampKit_1 = require("./RampKit");
|
|
12
12
|
// Main SDK singleton instance
|
|
13
13
|
exports.RampKit = RampKit_1.RampKitCore.instance;
|
|
@@ -22,6 +22,7 @@ var DeviceInfoCollector_1 = require("./DeviceInfoCollector");
|
|
|
22
22
|
Object.defineProperty(exports, "collectDeviceInfo", { enumerable: true, get: function () { return DeviceInfoCollector_1.collectDeviceInfo; } });
|
|
23
23
|
Object.defineProperty(exports, "getSessionDurationSeconds", { enumerable: true, get: function () { return DeviceInfoCollector_1.getSessionDurationSeconds; } });
|
|
24
24
|
Object.defineProperty(exports, "getSessionStartTime", { enumerable: true, get: function () { return DeviceInfoCollector_1.getSessionStartTime; } });
|
|
25
|
+
Object.defineProperty(exports, "buildRampKitContext", { enumerable: true, get: function () { return DeviceInfoCollector_1.buildRampKitContext; } });
|
|
25
26
|
// Export native module for direct access
|
|
26
27
|
var RampKitNative_1 = require("./RampKitNative");
|
|
27
28
|
Object.defineProperty(exports, "RampKitNative", { enumerable: true, get: function () { return __importDefault(RampKitNative_1).default; } });
|
package/build/types.d.ts
CHANGED
|
@@ -176,3 +176,56 @@ export interface RampKitConfig {
|
|
|
176
176
|
onShowPaywall?: (payload?: any) => void;
|
|
177
177
|
showPaywall?: (payload?: any) => void;
|
|
178
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Device context variables available in templates as ${device.xxx}
|
|
181
|
+
*/
|
|
182
|
+
export interface RampKitDeviceContext {
|
|
183
|
+
/** Platform: "iOS", "Android", or "iPadOS" */
|
|
184
|
+
platform: string;
|
|
185
|
+
/** Device model: "iPhone 15 Pro", "Pixel 7", etc. */
|
|
186
|
+
model: string;
|
|
187
|
+
/** Full locale identifier: "en_US", "fr_FR", etc. */
|
|
188
|
+
locale: string;
|
|
189
|
+
/** Language code: "en", "fr", etc. */
|
|
190
|
+
language: string;
|
|
191
|
+
/** Country/region code: "US", "FR", etc. */
|
|
192
|
+
country: string;
|
|
193
|
+
/** Currency code: "USD", "EUR", etc. */
|
|
194
|
+
currencyCode: string;
|
|
195
|
+
/** Currency symbol: "$", "€", etc. */
|
|
196
|
+
currencySymbol: string;
|
|
197
|
+
/** App version string: "1.0.0" */
|
|
198
|
+
appVersion: string;
|
|
199
|
+
/** Build number: "123" */
|
|
200
|
+
buildNumber: string;
|
|
201
|
+
/** Bundle identifier: "com.example.app" */
|
|
202
|
+
bundleId: string;
|
|
203
|
+
/** Interface style: "light" or "dark" */
|
|
204
|
+
interfaceStyle: string;
|
|
205
|
+
/** Timezone offset in seconds from GMT */
|
|
206
|
+
timezone: number;
|
|
207
|
+
/** Days since app was first installed */
|
|
208
|
+
daysSinceInstall: number;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* User context variables available in templates as ${user.xxx}
|
|
212
|
+
*/
|
|
213
|
+
export interface RampKitUserContext {
|
|
214
|
+
/** Unique user identifier */
|
|
215
|
+
id: string;
|
|
216
|
+
/** Whether this is the user's first session */
|
|
217
|
+
isNewUser: boolean;
|
|
218
|
+
/** Whether user came from Apple Search Ads */
|
|
219
|
+
hasAppleSearchAdsAttribution: boolean;
|
|
220
|
+
/** Current session identifier */
|
|
221
|
+
sessionId: string;
|
|
222
|
+
/** ISO date string when app was first installed */
|
|
223
|
+
installedAt: string;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Full context object injected into WebView as window.rampkitContext
|
|
227
|
+
*/
|
|
228
|
+
export interface RampKitContext {
|
|
229
|
+
device: RampKitDeviceContext;
|
|
230
|
+
user: RampKitUserContext;
|
|
231
|
+
}
|
package/package.json
CHANGED