rampkit-expo-dev 0.0.27 → 0.0.28
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 +157 -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 // 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('${') === -1) return;\n \n var resolved = text.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_\\.]*)\\}/g, 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('${') !== -1) {\n var resolvedAttr = attr.value.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_\\.]*)\\}/g, 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,128 @@ 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
|
+
// Build variable map from context
|
|
185
|
+
function buildVarMap() {
|
|
186
|
+
var vars = {};
|
|
187
|
+
var ctx = window.rampkitContext || { device: {}, user: {} };
|
|
188
|
+
var state = window.__rampkitVariables || {};
|
|
189
|
+
|
|
190
|
+
// Device vars (device.xxx)
|
|
191
|
+
if (ctx.device) {
|
|
192
|
+
Object.keys(ctx.device).forEach(function(key) {
|
|
193
|
+
vars['device.' + key] = ctx.device[key];
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// User vars (user.xxx)
|
|
198
|
+
if (ctx.user) {
|
|
199
|
+
Object.keys(ctx.user).forEach(function(key) {
|
|
200
|
+
vars['user.' + key] = ctx.user[key];
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// State vars (varName - no prefix)
|
|
205
|
+
Object.keys(state).forEach(function(key) {
|
|
206
|
+
vars[key] = state[key];
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return vars;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Format a value for display
|
|
213
|
+
function formatValue(value) {
|
|
214
|
+
if (value === undefined || value === null) return '';
|
|
215
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
216
|
+
if (typeof value === 'object') return JSON.stringify(value);
|
|
217
|
+
return String(value);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Resolve templates in a single text node
|
|
221
|
+
function resolveTextNode(node, vars) {
|
|
222
|
+
var text = node.textContent;
|
|
223
|
+
if (!text || text.indexOf('\${') === -1) return;
|
|
224
|
+
|
|
225
|
+
var resolved = text.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_\\.]*)\\}/g, function(match, varName) {
|
|
226
|
+
if (vars.hasOwnProperty(varName)) {
|
|
227
|
+
return formatValue(vars[varName]);
|
|
228
|
+
}
|
|
229
|
+
return match; // Keep original if var not found
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (resolved !== text) {
|
|
233
|
+
node.textContent = resolved;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Resolve templates in all text nodes
|
|
238
|
+
function resolveAllTemplates() {
|
|
239
|
+
var vars = buildVarMap();
|
|
240
|
+
var walker = document.createTreeWalker(
|
|
241
|
+
document.body,
|
|
242
|
+
NodeFilter.SHOW_TEXT,
|
|
243
|
+
null,
|
|
244
|
+
false
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
var node;
|
|
248
|
+
while (node = walker.nextNode()) {
|
|
249
|
+
resolveTextNode(node, vars);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Also resolve in attribute values that might contain templates
|
|
253
|
+
var allElements = document.body.getElementsByTagName('*');
|
|
254
|
+
for (var i = 0; i < allElements.length; i++) {
|
|
255
|
+
var el = allElements[i];
|
|
256
|
+
for (var j = 0; j < el.attributes.length; j++) {
|
|
257
|
+
var attr = el.attributes[j];
|
|
258
|
+
if (attr.value && attr.value.indexOf('\${') !== -1) {
|
|
259
|
+
var resolvedAttr = attr.value.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_\\.]*)\\}/g, function(match, varName) {
|
|
260
|
+
if (vars.hasOwnProperty(varName)) {
|
|
261
|
+
return formatValue(vars[varName]);
|
|
262
|
+
}
|
|
263
|
+
return match;
|
|
264
|
+
});
|
|
265
|
+
if (resolvedAttr !== attr.value) {
|
|
266
|
+
el.setAttribute(attr.name, resolvedAttr);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Run on DOMContentLoaded
|
|
274
|
+
if (document.readyState === 'loading') {
|
|
275
|
+
document.addEventListener('DOMContentLoaded', resolveAllTemplates);
|
|
276
|
+
} else {
|
|
277
|
+
// DOM already ready, run immediately
|
|
278
|
+
resolveAllTemplates();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Also run after a short delay to catch dynamically added content
|
|
282
|
+
setTimeout(resolveAllTemplates, 100);
|
|
283
|
+
|
|
284
|
+
// Expose for manual re-resolution
|
|
285
|
+
window.rampkitResolveTemplates = resolveAllTemplates;
|
|
286
|
+
|
|
287
|
+
// Re-resolve when variables update
|
|
288
|
+
document.addEventListener('rampkit:vars-updated', function() {
|
|
289
|
+
setTimeout(resolveAllTemplates, 0);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
} catch(e) {
|
|
293
|
+
console.log('[Rampkit] Template resolver error:', e);
|
|
294
|
+
}
|
|
295
|
+
true;
|
|
296
|
+
})();
|
|
297
|
+
`;
|
|
176
298
|
function performRampkitHaptic(event) {
|
|
177
299
|
if (!event || event.action !== "haptic") {
|
|
178
300
|
// Backwards compatible default
|
|
@@ -229,7 +351,7 @@ function showRampkitOverlay(opts) {
|
|
|
229
351
|
if (sibling)
|
|
230
352
|
return; // already visible
|
|
231
353
|
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: () => {
|
|
354
|
+
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
355
|
var _a;
|
|
234
356
|
activeCloseHandler = null;
|
|
235
357
|
hideRampkitOverlay();
|
|
@@ -261,7 +383,7 @@ function preloadRampkitOverlay(opts) {
|
|
|
261
383
|
try {
|
|
262
384
|
if (preloadCache.has(opts.onboardingId))
|
|
263
385
|
return;
|
|
264
|
-
const docs = opts.screens.map((s) => buildHtmlDocument(s, opts.variables, opts.requiredScripts));
|
|
386
|
+
const docs = opts.screens.map((s) => buildHtmlDocument(s, opts.variables, opts.requiredScripts, opts.rampkitContext));
|
|
265
387
|
preloadCache.set(opts.onboardingId, docs);
|
|
266
388
|
// Mount a hidden WebView to warm up the WebView process and cache
|
|
267
389
|
if (preloadSibling)
|
|
@@ -273,14 +395,14 @@ function preloadRampkitOverlay(opts) {
|
|
|
273
395
|
opacity: 0,
|
|
274
396
|
top: -1000,
|
|
275
397
|
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 }) }));
|
|
398
|
+
}, 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
399
|
preloadSibling = new react_native_root_siblings_1.default((0, jsx_runtime_1.jsx)(HiddenPreloader, {}));
|
|
278
400
|
}
|
|
279
401
|
catch (e) {
|
|
280
402
|
// best-effort preloading; ignore errors
|
|
281
403
|
}
|
|
282
404
|
}
|
|
283
|
-
function buildHtmlDocument(screen, variables, requiredScripts) {
|
|
405
|
+
function buildHtmlDocument(screen, variables, requiredScripts, rampkitContext) {
|
|
284
406
|
const css = screen.css || "";
|
|
285
407
|
const html = screen.html || "";
|
|
286
408
|
const js = screen.js || "";
|
|
@@ -315,6 +437,31 @@ function buildHtmlDocument(screen, variables, requiredScripts) {
|
|
|
315
437
|
return "";
|
|
316
438
|
}
|
|
317
439
|
})();
|
|
440
|
+
// Default context if not provided
|
|
441
|
+
const context = rampkitContext || {
|
|
442
|
+
device: {
|
|
443
|
+
platform: "unknown",
|
|
444
|
+
model: "unknown",
|
|
445
|
+
locale: "en_US",
|
|
446
|
+
language: "en",
|
|
447
|
+
country: "US",
|
|
448
|
+
currencyCode: "USD",
|
|
449
|
+
currencySymbol: "$",
|
|
450
|
+
appVersion: "1.0.0",
|
|
451
|
+
buildNumber: "1",
|
|
452
|
+
bundleId: "",
|
|
453
|
+
interfaceStyle: "light",
|
|
454
|
+
timezone: 0,
|
|
455
|
+
daysSinceInstall: 0,
|
|
456
|
+
},
|
|
457
|
+
user: {
|
|
458
|
+
id: "",
|
|
459
|
+
isNewUser: true,
|
|
460
|
+
hasAppleSearchAdsAttribution: false,
|
|
461
|
+
sessionId: "",
|
|
462
|
+
installedAt: new Date().toISOString(),
|
|
463
|
+
},
|
|
464
|
+
};
|
|
318
465
|
return `<!doctype html>
|
|
319
466
|
<html>
|
|
320
467
|
<head>
|
|
@@ -328,6 +475,9 @@ ${scripts}
|
|
|
328
475
|
<body>
|
|
329
476
|
${html}
|
|
330
477
|
<script>
|
|
478
|
+
// Device and user context for template resolution
|
|
479
|
+
window.rampkitContext = ${JSON.stringify(context)};
|
|
480
|
+
// State variables from onboarding
|
|
331
481
|
window.__rampkitVariables = ${JSON.stringify(variables || {})};
|
|
332
482
|
${js}
|
|
333
483
|
</script>
|
|
@@ -525,7 +675,7 @@ function Overlay(props) {
|
|
|
525
675
|
return () => sub.remove();
|
|
526
676
|
}, [index, handleRequestClose]);
|
|
527
677
|
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]);
|
|
678
|
+
props.screens.map((s) => buildHtmlDocument(s, props.variables, props.requiredScripts, props.rampkitContext)), [props.prebuiltDocs, props.screens, props.variables, props.requiredScripts, props.rampkitContext]);
|
|
529
679
|
react_1.default.useEffect(() => {
|
|
530
680
|
try {
|
|
531
681
|
console.log("[Rampkit] Overlay mounted: docs=", docs.length);
|
|
@@ -654,7 +804,7 @@ function Overlay(props) {
|
|
|
654
804
|
styles.root,
|
|
655
805
|
!visible && styles.invisible,
|
|
656
806
|
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: () => {
|
|
807
|
+
], 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
808
|
setLoadedCount((c) => c + 1);
|
|
659
809
|
if (i === 0) {
|
|
660
810
|
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