@appfunnel-dev/sdk 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { useFunnelContext } from './chunk-EB5VWGR4.js';
1
+ import { useFunnelContext } from './chunk-H5C2QJIY.js';
2
2
  import { useCallback, useSyncExternalStore, useMemo } from 'react';
3
3
 
4
4
  function useResponse(key) {
@@ -100,5 +100,5 @@ function useNavigation() {
100
100
  }
101
101
 
102
102
  export { useNavigation, useResponse, useResponses };
103
- //# sourceMappingURL=chunk-CMVB7RQE.js.map
104
- //# sourceMappingURL=chunk-CMVB7RQE.js.map
103
+ //# sourceMappingURL=chunk-7JLCKTX6.js.map
104
+ //# sourceMappingURL=chunk-7JLCKTX6.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useResponse.ts","../src/hooks/useNavigation.ts"],"names":["useSyncExternalStore","useCallback"],"mappings":"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","file":"chunk-CMVB7RQE.js","sourcesContent":["import { useSyncExternalStore, useCallback, useMemo } from 'react'\nimport { useFunnelContext } from '../components/FunnelProvider'\nimport type { VariableValue } from '../types'\n\n/**\n * Read/write a single response variable (answers.*).\n *\n * ```tsx\n * const [goal, setGoal] = useResponse<string>('goal')\n * const [interests, setInterests] = useResponse<string[]>('interests')\n * ```\n */\nexport function useResponse<T extends VariableValue>(\n key: string,\n): [T, (value: T) => void] {\n const { variableStore } = useFunnelContext()\n const prefixedKey = `answers.${key}`\n\n const subscribe = useCallback(\n (cb: () => void) => variableStore.subscribe(cb, { keys: [prefixedKey] }),\n [variableStore, prefixedKey],\n )\n const getSnapshot = useCallback(\n () => variableStore.get(prefixedKey) as T,\n [variableStore, prefixedKey],\n )\n\n const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n const setValue = useCallback(\n (v: T) => variableStore.set(prefixedKey, v),\n [variableStore, prefixedKey],\n )\n\n return [value, setValue]\n}\n\n/**\n * Read all response variables as a flat object (without the `answers.` prefix).\n *\n * Only re-renders when an `answers.*` variable changes.\n *\n * ```tsx\n * const responses = useResponses()\n * // { goal: 'weight_loss', interests: ['yoga', 'running'] }\n * ```\n */\nexport function useResponses(): Record<string, VariableValue> {\n const { variableStore } = useFunnelContext()\n\n const subscribe = useCallback(\n (cb: () => void) => variableStore.subscribe(cb, { prefix: 'answers.' }),\n [variableStore],\n )\n const getSnapshot = useCallback(\n () => variableStore.getState(),\n [variableStore],\n )\n const variables = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n return useMemo(() => {\n const result: Record<string, VariableValue> = {}\n for (const [key, value] of Object.entries(variables)) {\n if (key.startsWith('answers.')) {\n result[key.slice(8)] = value\n }\n }\n return result\n }, [variables])\n}\n","import { useCallback, useSyncExternalStore } from 'react'\nimport { useFunnelContext } from '../components/FunnelProvider'\nimport type { NavigationState } from '../types'\n\n/**\n * Navigation hook — evaluates routes, navigates between pages, tracks progress.\n *\n * Only re-renders when the router's current page changes (not on variable changes).\n * Variables are read lazily at navigation time — callbacks are referentially stable.\n */\nexport function useNavigation(): NavigationState {\n const { router, variableStore, tracker } = useFunnelContext()\n\n // Re-render only when the router's current page changes\n useSyncExternalStore(\n useCallback((cb: () => void) => router.subscribe(cb), [router]),\n useCallback(() => router.getSnapshot(), [router]),\n useCallback(() => router.getSnapshot(), [router]),\n )\n\n /** Called after the router has already navigated to `key`. */\n const afterNavigate = useCallback((key: string) => {\n const page = router.getCurrentPage()\n if (page) {\n tracker.track('page.view', {\n pageId: page.key,\n pageKey: page.key,\n pageName: page.name,\n })\n tracker.startPageTracking(page.key)\n }\n variableStore.setMany({\n 'page.currentId': key,\n 'page.currentIndex': router.getPageHistory().length,\n 'page.current': router.getPageHistory().length + 1,\n 'page.startedAt': Date.now(),\n })\n }, [router, tracker, variableStore])\n\n const goToNextPage = useCallback(() => {\n const previousPage = router.getCurrentPage()\n if (previousPage) {\n tracker.stopPageTracking()\n }\n\n // Read variables at call time — not captured in closure\n const variables = variableStore.getState()\n const nextKey = router.goToNextPage(variables)\n if (nextKey) {\n afterNavigate(nextKey)\n }\n }, [router, tracker, variableStore, afterNavigate])\n\n const goBack = useCallback(() => {\n tracker.stopPageTracking()\n const prevKey = router.goBack()\n if (prevKey) {\n afterNavigate(prevKey)\n }\n }, [router, tracker, afterNavigate])\n\n const goToPage = useCallback((pageKey: string) => {\n tracker.stopPageTracking()\n const key = router.goToPage(pageKey)\n if (key) {\n afterNavigate(key)\n }\n }, [router, tracker, afterNavigate])\n\n return {\n goToNextPage,\n goBack,\n goToPage,\n currentPage: router.getCurrentPage(),\n pageHistory: router.getPageHistory(),\n progress: router.getProgress(),\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/hooks/useResponse.ts","../src/hooks/useNavigation.ts"],"names":["useSyncExternalStore","useCallback"],"mappings":"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","file":"chunk-7JLCKTX6.js","sourcesContent":["import { useSyncExternalStore, useCallback, useMemo } from 'react'\nimport { useFunnelContext } from '../components/FunnelProvider'\nimport type { VariableValue } from '../types'\n\n/**\n * Read/write a single response variable (answers.*).\n *\n * ```tsx\n * const [goal, setGoal] = useResponse<string>('goal')\n * const [interests, setInterests] = useResponse<string[]>('interests')\n * ```\n */\nexport function useResponse<T extends VariableValue>(\n key: string,\n): [T, (value: T) => void] {\n const { variableStore } = useFunnelContext()\n const prefixedKey = `answers.${key}`\n\n const subscribe = useCallback(\n (cb: () => void) => variableStore.subscribe(cb, { keys: [prefixedKey] }),\n [variableStore, prefixedKey],\n )\n const getSnapshot = useCallback(\n () => variableStore.get(prefixedKey) as T,\n [variableStore, prefixedKey],\n )\n\n const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n const setValue = useCallback(\n (v: T) => variableStore.set(prefixedKey, v),\n [variableStore, prefixedKey],\n )\n\n return [value, setValue]\n}\n\n/**\n * Read all response variables as a flat object (without the `answers.` prefix).\n *\n * Only re-renders when an `answers.*` variable changes.\n *\n * ```tsx\n * const responses = useResponses()\n * // { goal: 'weight_loss', interests: ['yoga', 'running'] }\n * ```\n */\nexport function useResponses(): Record<string, VariableValue> {\n const { variableStore } = useFunnelContext()\n\n const subscribe = useCallback(\n (cb: () => void) => variableStore.subscribe(cb, { prefix: 'answers.' }),\n [variableStore],\n )\n const getSnapshot = useCallback(\n () => variableStore.getState(),\n [variableStore],\n )\n const variables = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n return useMemo(() => {\n const result: Record<string, VariableValue> = {}\n for (const [key, value] of Object.entries(variables)) {\n if (key.startsWith('answers.')) {\n result[key.slice(8)] = value\n }\n }\n return result\n }, [variables])\n}\n","import { useCallback, useSyncExternalStore } from 'react'\nimport { useFunnelContext } from '../components/FunnelProvider'\nimport type { NavigationState } from '../types'\n\n/**\n * Navigation hook — evaluates routes, navigates between pages, tracks progress.\n *\n * Only re-renders when the router's current page changes (not on variable changes).\n * Variables are read lazily at navigation time — callbacks are referentially stable.\n */\nexport function useNavigation(): NavigationState {\n const { router, variableStore, tracker } = useFunnelContext()\n\n // Re-render only when the router's current page changes\n useSyncExternalStore(\n useCallback((cb: () => void) => router.subscribe(cb), [router]),\n useCallback(() => router.getSnapshot(), [router]),\n useCallback(() => router.getSnapshot(), [router]),\n )\n\n /** Called after the router has already navigated to `key`. */\n const afterNavigate = useCallback((key: string) => {\n const page = router.getCurrentPage()\n if (page) {\n tracker.track('page.view', {\n pageId: page.key,\n pageKey: page.key,\n pageName: page.name,\n })\n tracker.startPageTracking(page.key)\n }\n variableStore.setMany({\n 'page.currentId': key,\n 'page.currentIndex': router.getPageHistory().length,\n 'page.current': router.getPageHistory().length + 1,\n 'page.startedAt': Date.now(),\n })\n }, [router, tracker, variableStore])\n\n const goToNextPage = useCallback(() => {\n const previousPage = router.getCurrentPage()\n if (previousPage) {\n tracker.stopPageTracking()\n }\n\n // Read variables at call time — not captured in closure\n const variables = variableStore.getState()\n const nextKey = router.goToNextPage(variables)\n if (nextKey) {\n afterNavigate(nextKey)\n }\n }, [router, tracker, variableStore, afterNavigate])\n\n const goBack = useCallback(() => {\n tracker.stopPageTracking()\n const prevKey = router.goBack()\n if (prevKey) {\n afterNavigate(prevKey)\n }\n }, [router, tracker, afterNavigate])\n\n const goToPage = useCallback((pageKey: string) => {\n tracker.stopPageTracking()\n const key = router.goToPage(pageKey)\n if (key) {\n afterNavigate(key)\n }\n }, [router, tracker, afterNavigate])\n\n return {\n goToNextPage,\n goBack,\n goToPage,\n currentPage: router.getCurrentPage(),\n pageHistory: router.getPageHistory(),\n progress: router.getProgress(),\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, useState, useRef, useEffect, useMemo, useCallback } from 'react';
1
+ import { createContext, useContext, useRef, useState, useEffect, useMemo, useCallback } from 'react';
2
2
  import { Toaster } from 'sonner';
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
4
 
@@ -376,7 +376,8 @@ var Router = class {
376
376
  this.initialKey = config.pages?.[initialPage] ? initialPage : defaultInitial;
377
377
  } else {
378
378
  const hasSession = this.hasSessionCookie(campaignSlug);
379
- this.initialKey = hasSession && config.pages?.[initialPage] ? initialPage : defaultInitial;
379
+ const hasPendingSession = typeof window !== "undefined" && (window.location.search.includes("customer=") || window.location.search.includes("sid="));
380
+ this.initialKey = (hasSession || hasPendingSession) && config.pages?.[initialPage] ? initialPage : defaultInitial;
380
381
  }
381
382
  } else {
382
383
  this.initialKey = initialPage || defaultInitial;
@@ -1176,6 +1177,121 @@ var I18n = class {
1176
1177
  this.listeners.forEach((l) => l());
1177
1178
  }
1178
1179
  };
1180
+
1181
+ // src/runtime/session.ts
1182
+ var API_BASE_URL2 = "https://api.appfunnel.net";
1183
+ var API_HEADERS = { "ngrok-skip-browser-warning": "1" };
1184
+ async function apiFetch(path) {
1185
+ try {
1186
+ const res = await fetch(`${API_BASE_URL2}${path}`, { headers: API_HEADERS });
1187
+ return res.ok ? res.json() : null;
1188
+ } catch {
1189
+ return null;
1190
+ }
1191
+ }
1192
+ function mergeVariables(store, data) {
1193
+ if (Object.keys(data).length === 0) return;
1194
+ store.setState((prev) => {
1195
+ const merged = { ...prev };
1196
+ for (const [key, value] of Object.entries(data)) {
1197
+ if (value !== void 0) merged[key] = value;
1198
+ }
1199
+ return merged;
1200
+ });
1201
+ }
1202
+ function cleanUrlParams(...keys) {
1203
+ const url = new URL(window.location.href);
1204
+ for (const key of keys) url.searchParams.delete(key);
1205
+ window.history.replaceState({}, "", url.toString());
1206
+ }
1207
+ async function fetchAndRestoreSession(store, sessionId, funnelId) {
1208
+ const result = await apiFetch(`/session/${sessionId}/data`);
1209
+ if (!result?.success) return;
1210
+ if (result.data?.funnelId && result.data.funnelId !== funnelId) return;
1211
+ const savedData = result.data?.data || {};
1212
+ mergeVariables(store, savedData);
1213
+ }
1214
+ async function resolveCheckoutSuccess(ctx, params) {
1215
+ const stripeSessionId = params.get("session_id");
1216
+ cleanUrlParams("checkout", "session_id");
1217
+ const sessionId = ctx.tracker.getSessionId();
1218
+ if (!sessionId) return { ok: false };
1219
+ ctx.store.setMany({
1220
+ "purchase.status": "success",
1221
+ "purchase.success": true,
1222
+ "purchase.cancelled": false
1223
+ });
1224
+ await Promise.all([
1225
+ apiFetch(
1226
+ `/campaign/${ctx.campaignId}/stripe/checkout-status?sessionId=${encodeURIComponent(sessionId)}&checkoutSessionId=${encodeURIComponent(stripeSessionId)}`
1227
+ ).then((result) => {
1228
+ if (!result?.success) return;
1229
+ const updates = {
1230
+ "payment.status": result.paymentStatus || "paid"
1231
+ };
1232
+ if (result.stripeCustomerId) {
1233
+ updates["user.stripeCustomerId"] = result.stripeCustomerId;
1234
+ }
1235
+ if (result.subscriptionId) {
1236
+ updates["stripe.subscriptionId"] = result.subscriptionId;
1237
+ updates["subscription.status"] = result.subscriptionStatus || "active";
1238
+ }
1239
+ if (result.paymentIntentId) {
1240
+ updates["stripe.paymentIntentId"] = result.paymentIntentId;
1241
+ }
1242
+ ctx.store.setMany(updates);
1243
+ ctx.tracker.track("purchase.complete", {
1244
+ eventId: stripeSessionId,
1245
+ amount: result.amountTotal ? result.amountTotal / 100 : 0,
1246
+ currency: result.currency || "usd",
1247
+ email: result.customerEmail
1248
+ });
1249
+ }),
1250
+ fetchAndRestoreSession(ctx.store, sessionId, ctx.funnelId)
1251
+ ]);
1252
+ return { ok: true };
1253
+ }
1254
+ function resolveCheckoutCanceled(ctx) {
1255
+ cleanUrlParams("checkout", "session_id");
1256
+ ctx.store.setMany({
1257
+ "purchase.status": "cancelled",
1258
+ "purchase.success": false,
1259
+ "purchase.cancelled": true
1260
+ });
1261
+ return { ok: true };
1262
+ }
1263
+ async function resolveCustomer(ctx, customerId) {
1264
+ cleanUrlParams("customer");
1265
+ const result = await apiFetch(
1266
+ `/session/by-customer?customerId=${encodeURIComponent(customerId)}&campaignId=${encodeURIComponent(ctx.campaignId)}&funnelId=${encodeURIComponent(ctx.funnelId)}`
1267
+ );
1268
+ if (!result?.success || !result.sessionId) return { ok: false };
1269
+ ctx.tracker.setSessionId(result.sessionId);
1270
+ if (result.data) {
1271
+ mergeVariables(ctx.store, result.data);
1272
+ }
1273
+ return { ok: true };
1274
+ }
1275
+ async function resolveExistingSession(ctx) {
1276
+ const sessionId = ctx.tracker.getSessionId();
1277
+ if (!sessionId) return { ok: true };
1278
+ await fetchAndRestoreSession(ctx.store, sessionId, ctx.funnelId);
1279
+ return { ok: true };
1280
+ }
1281
+ async function resolveSession(ctx, params) {
1282
+ const checkoutStatus = params.get("checkout");
1283
+ const customerId = params.get("customer");
1284
+ if (checkoutStatus === "success" && params.get("session_id")) {
1285
+ return resolveCheckoutSuccess(ctx, params);
1286
+ }
1287
+ if (checkoutStatus === "canceled") {
1288
+ return resolveCheckoutCanceled(ctx);
1289
+ }
1290
+ if (customerId) {
1291
+ return resolveCustomer(ctx, customerId);
1292
+ }
1293
+ return resolveExistingSession(ctx);
1294
+ }
1179
1295
  var FunnelContext = createContext(null);
1180
1296
  function useFunnelContext() {
1181
1297
  const ctx = useContext(FunnelContext);
@@ -1197,12 +1313,16 @@ function FunnelProvider({
1197
1313
  const campaignId = sessionData?.campaignId || config.projectId || "";
1198
1314
  const funnelId = sessionData?.funnelId || config.projectId || "";
1199
1315
  const disableLoadingScreen = config.settings?.disableLoadingScreen === true;
1316
+ const initialUrlParams = useRef(null);
1317
+ if (!initialUrlParams.current && typeof window !== "undefined") {
1318
+ initialUrlParams.current = new URLSearchParams(window.location.search);
1319
+ }
1200
1320
  const [isReady, setIsReady] = useState(() => {
1201
1321
  if (disableLoadingScreen) return true;
1202
1322
  if (sessionData?.variables) return true;
1203
1323
  if (globalThis.__APPFUNNEL_DEV__) return true;
1204
1324
  if (typeof window === "undefined") return false;
1205
- const params = new URLSearchParams(window.location.search);
1325
+ const params = initialUrlParams.current;
1206
1326
  if (!params.has("checkout") && !params.has("customer") && !params.has("sid")) {
1207
1327
  const hasCookie = document.cookie.includes("fs_");
1208
1328
  if (!hasCookie) return true;
@@ -1221,12 +1341,7 @@ function FunnelProvider({
1221
1341
  );
1222
1342
  }
1223
1343
  if (!routerRef.current) {
1224
- routerRef.current = new Router({
1225
- config,
1226
- initialPage,
1227
- basePath,
1228
- campaignSlug
1229
- });
1344
+ routerRef.current = new Router({ config, initialPage, basePath, campaignSlug });
1230
1345
  }
1231
1346
  if (!trackerRef.current) {
1232
1347
  trackerRef.current = new FunnelTracker();
@@ -1249,163 +1364,18 @@ function FunnelProvider({
1249
1364
  useEffect(() => {
1250
1365
  if (sessionData?.variables) return;
1251
1366
  if (globalThis.__APPFUNNEL_DEV__) return;
1252
- const API_BASE_URL2 = "https://api.appfunnel.net";
1253
- const markReady = () => setIsReady(true);
1254
- tracker.init(campaignId, funnelId, campaignSlug, sessionData?.experimentId);
1255
1367
  if (typeof window === "undefined") return;
1256
- const urlParams = new URLSearchParams(window.location.search);
1257
- const checkoutStatus = urlParams.get("checkout");
1258
- const stripeSessionId = urlParams.get("session_id");
1259
- if (checkoutStatus === "success" && stripeSessionId) {
1260
- const url = new URL(window.location.href);
1261
- url.searchParams.delete("checkout");
1262
- url.searchParams.delete("session_id");
1263
- window.history.replaceState({}, "", url.toString());
1264
- const sessionId2 = tracker.getSessionId();
1265
- if (!sessionId2) {
1266
- markReady();
1267
- return;
1368
+ tracker.init(campaignId, funnelId, campaignSlug, sessionData?.experimentId);
1369
+ const params = initialUrlParams.current || new URLSearchParams(window.location.search);
1370
+ resolveSession({ store, tracker, campaignId, funnelId }, params).then((result) => {
1371
+ if (!result.ok) {
1372
+ const initialKey = config.initialPageKey || Object.keys(config.pages ?? {})[0];
1373
+ if (initialKey) router.goToPage(initialKey);
1268
1374
  }
1269
- store.setMany({
1270
- "purchase.status": "success",
1271
- "purchase.success": true,
1272
- "purchase.cancelled": false
1273
- });
1274
- Promise.all([
1275
- // Checkout details (stripeCustomerId, subscription info, etc.)
1276
- fetch(
1277
- `${API_BASE_URL2}/campaign/${campaignId}/stripe/checkout-status?sessionId=${encodeURIComponent(sessionId2)}&checkoutSessionId=${encodeURIComponent(stripeSessionId)}`,
1278
- { headers: { "ngrok-skip-browser-warning": "1" } }
1279
- ).then((res) => res.ok ? res.json() : null).then((result) => {
1280
- if (!result?.success) return;
1281
- const updates = {};
1282
- if (result.stripeCustomerId) {
1283
- updates["user.stripeCustomerId"] = result.stripeCustomerId;
1284
- }
1285
- if (result.subscriptionId) {
1286
- updates["stripe.subscriptionId"] = result.subscriptionId;
1287
- updates["subscription.status"] = result.subscriptionStatus || "active";
1288
- }
1289
- if (result.paymentIntentId) {
1290
- updates["stripe.paymentIntentId"] = result.paymentIntentId;
1291
- }
1292
- updates["payment.status"] = result.paymentStatus || "paid";
1293
- if (Object.keys(updates).length > 0) {
1294
- store.setMany(updates);
1295
- }
1296
- tracker.track("purchase.complete", {
1297
- eventId: stripeSessionId,
1298
- amount: result.amountTotal ? result.amountTotal / 100 : 0,
1299
- currency: result.currency || "usd",
1300
- email: result.customerEmail
1301
- });
1302
- }).catch(() => {
1303
- }),
1304
- // Session variable restoration
1305
- fetch(`${API_BASE_URL2}/session/${sessionId2}/data`, {
1306
- headers: { "ngrok-skip-browser-warning": "1" }
1307
- }).then((res) => res.ok ? res.json() : null).then((result) => {
1308
- if (!result?.success) return;
1309
- if (result.data?.funnelId && result.data.funnelId !== funnelId) return;
1310
- const savedData = result.data?.data || {};
1311
- if (Object.keys(savedData).length === 0) return;
1312
- store.setState((prev) => {
1313
- const merged = { ...prev };
1314
- for (const [key, value] of Object.entries(savedData)) {
1315
- if (value !== void 0) merged[key] = value;
1316
- }
1317
- return merged;
1318
- });
1319
- }).catch(() => {
1320
- })
1321
- ]).finally(markReady);
1322
- return;
1323
- }
1324
- if (checkoutStatus === "canceled") {
1325
- const url = new URL(window.location.href);
1326
- url.searchParams.delete("checkout");
1327
- url.searchParams.delete("session_id");
1328
- window.history.replaceState({}, "", url.toString());
1329
- store.setMany({
1330
- "purchase.status": "cancelled",
1331
- "purchase.success": false,
1332
- "purchase.cancelled": true
1333
- });
1334
- markReady();
1335
- return;
1336
- }
1337
- const customerParam = urlParams.get("customer");
1338
- if (customerParam) {
1339
- const url = new URL(window.location.href);
1340
- url.searchParams.delete("customer");
1341
- window.history.replaceState({}, "", url.toString());
1342
- fetch(
1343
- `${API_BASE_URL2}/session/by-customer?customerId=${encodeURIComponent(customerParam)}&campaignId=${encodeURIComponent(campaignId)}&funnelId=${encodeURIComponent(funnelId)}`,
1344
- { headers: { "ngrok-skip-browser-warning": "1" } }
1345
- ).then((res) => res.ok ? res.json() : null).then((result) => {
1346
- if (!result?.success || !result.sessionId) return;
1347
- tracker.setSessionId(result.sessionId);
1348
- if (!result.isNew && result.data) {
1349
- const savedData = result.data;
1350
- if (Object.keys(savedData).length > 0) {
1351
- store.setState((prev) => {
1352
- const merged = { ...prev };
1353
- for (const [key, value] of Object.entries(savedData)) {
1354
- if (value !== void 0) merged[key] = value;
1355
- }
1356
- return merged;
1357
- });
1358
- }
1359
- }
1360
- }).catch(() => {
1361
- }).finally(markReady);
1362
- return;
1363
- }
1364
- const sessionId = tracker.getSessionId();
1365
- if (!sessionId) {
1366
- markReady();
1367
- return;
1368
- }
1369
- fetch(`${API_BASE_URL2}/session/${sessionId}/data`, {
1370
- headers: { "ngrok-skip-browser-warning": "1" }
1371
- }).then((res) => res.ok ? res.json() : null).then((result) => {
1372
- if (!result?.success) return;
1373
- if (result.data?.funnelId && result.data.funnelId !== funnelId) return;
1374
- const savedData = result.data?.data || {};
1375
- if (Object.keys(savedData).length === 0) return;
1376
- store.setState((prev) => {
1377
- const merged = { ...prev };
1378
- for (const [key, value] of Object.entries(savedData)) {
1379
- if (value !== void 0) merged[key] = value;
1380
- }
1381
- return merged;
1382
- });
1383
- }).catch(() => {
1384
- }).finally(markReady);
1385
- }, []);
1386
- const products = useMemo(() => {
1387
- if (!config.products?.items || !priceData) return [];
1388
- return buildRuntimeProducts(config.products.items, priceData);
1389
- }, [config.products, priceData]);
1390
- const defaultProductId = config.products?.defaultId || products[0]?.id || null;
1391
- const selectedProductIdRef = useRef(defaultProductId);
1392
- const selectProduct = useCallback((productId) => {
1393
- selectedProductIdRef.current = productId;
1394
- store.set("products.selectedProductId", productId);
1395
- }, [store]);
1396
- useEffect(() => {
1397
- const eventBus = new AppFunnelEventBus(store, router, selectProduct);
1398
- eventBus.attach();
1399
- eventBusRef.current = eventBus;
1400
- if (config.integrations && Object.keys(config.integrations).length > 0) {
1401
- initializeIntegrations(config.integrations);
1402
- }
1403
- return () => {
1404
- eventBus.destroy();
1405
- eventBusRef.current = null;
1406
- };
1375
+ }).finally(() => setIsReady(true));
1407
1376
  }, []);
1408
1377
  useEffect(() => {
1378
+ if (!isReady) return;
1409
1379
  tracker.init(campaignId, funnelId, campaignSlug, sessionData?.experimentId);
1410
1380
  if (sessionData?.sessionId) {
1411
1381
  tracker.setSessionId(sessionData.sessionId);
@@ -1428,11 +1398,33 @@ function FunnelProvider({
1428
1398
  tracker.stopPageTracking();
1429
1399
  tracker.flushVariables();
1430
1400
  };
1401
+ }, [isReady]);
1402
+ const products = useMemo(() => {
1403
+ if (!config.products?.items || !priceData) return [];
1404
+ return buildRuntimeProducts(config.products.items, priceData);
1405
+ }, [config.products, priceData]);
1406
+ const defaultProductId = config.products?.defaultId || products[0]?.id || null;
1407
+ const selectedProductIdRef = useRef(defaultProductId);
1408
+ const selectProduct = useCallback((productId) => {
1409
+ selectedProductIdRef.current = productId;
1410
+ store.set("products.selectedProductId", productId);
1411
+ }, [store]);
1412
+ useEffect(() => {
1413
+ const eventBus = new AppFunnelEventBus(store, router, selectProduct);
1414
+ eventBus.attach();
1415
+ eventBusRef.current = eventBus;
1416
+ if (config.integrations && Object.keys(config.integrations).length > 0) {
1417
+ initializeIntegrations(config.integrations);
1418
+ }
1419
+ return () => {
1420
+ eventBus.destroy();
1421
+ eventBusRef.current = null;
1422
+ };
1431
1423
  }, []);
1432
1424
  const sessionStartTime = useRef(Date.now());
1433
1425
  const pageStartTime = useRef(Date.now());
1434
1426
  useEffect(() => {
1435
- const sysVars = computeSystemVariables({
1427
+ store.setMany(computeSystemVariables({
1436
1428
  currentPageKey: router.getCurrentPage()?.key || "",
1437
1429
  pageHistory: router.getPageHistory(),
1438
1430
  pageStartTime: pageStartTime.current,
@@ -1440,8 +1432,7 @@ function FunnelProvider({
1440
1432
  totalPages: Object.keys(config.pages ?? {}).length,
1441
1433
  funnelId,
1442
1434
  campaignId
1443
- });
1444
- store.setMany(sysVars);
1435
+ }));
1445
1436
  if (defaultProductId) {
1446
1437
  store.set("products.selectedProductId", defaultProductId);
1447
1438
  }
@@ -1480,5 +1471,5 @@ function FunnelProvider({
1480
1471
  }
1481
1472
 
1482
1473
  export { FunnelProvider, __require, registerIntegration, useFunnelContext };
1483
- //# sourceMappingURL=chunk-EB5VWGR4.js.map
1484
- //# sourceMappingURL=chunk-EB5VWGR4.js.map
1474
+ //# sourceMappingURL=chunk-H5C2QJIY.js.map
1475
+ //# sourceMappingURL=chunk-H5C2QJIY.js.map