@funnelsgrove/runtime 0.7.26 → 0.7.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.
@@ -13,6 +13,7 @@ var __rest = (this && this.__rest) || function (s, e) {
13
13
  import { jsx as _jsx } from "react/jsx-runtime";
14
14
  import { createContext, useContext, useEffect, useMemo, useState, } from 'react';
15
15
  import { loadFunnelRuntimeConfig, resolvePublishedFunnelRuntimeConfig, } from '../services/funnel-runtime-config.js';
16
+ import { isPreviewFrameRuntime } from '../services/preview-frame.service.js';
16
17
  const FunnelRuntimeConfigContext = createContext({
17
18
  status: 'fallback',
18
19
  config: null,
@@ -32,6 +33,12 @@ function ActiveFunnelRuntimeConfigBoundary({ funnelId, funnelVersionId, children
32
33
  error: null,
33
34
  });
34
35
  useEffect(() => {
36
+ // The server snapshot cannot see the browser query string, so a preview
37
+ // frame can briefly mount this active boundary during hydration. Avoid a
38
+ // same-origin config request before the parent switches to disabled mode.
39
+ if (isPreviewFrameRuntime()) {
40
+ return undefined;
41
+ }
35
42
  let active = true;
36
43
  let refreshTimer = null;
37
44
  const refresh = () => loadFunnelRuntimeConfig(funnelId, { versionId: funnelVersionId })
@@ -59,7 +59,6 @@ export type EmailCaptureResult = {
59
59
  activeSubscription: boolean;
60
60
  };
61
61
  declare class ApiService {
62
- private readFileAsDataUrl;
63
62
  getOrCreateClientUserId(): string;
64
63
  persistCanonicalUserId(userId: string): string;
65
64
  getSubscriptionManagementUserId(): string | null;
@@ -194,20 +194,6 @@ const toAppUser = (input) => {
194
194
  };
195
195
  };
196
196
  class ApiService {
197
- readFileAsDataUrl(file) {
198
- return new Promise((resolve, reject) => {
199
- const reader = new FileReader();
200
- reader.onload = () => {
201
- if (typeof reader.result !== 'string' || !reader.result.trim()) {
202
- reject(new Error('Failed to read image'));
203
- return;
204
- }
205
- resolve(reader.result);
206
- };
207
- reader.onerror = () => reject(new Error('Failed to read image'));
208
- reader.readAsDataURL(file);
209
- });
210
- }
211
197
  getOrCreateClientUserId() {
212
198
  const existing = readStoredUserId(FUNNEL_ID);
213
199
  if (existing) {
@@ -465,8 +451,8 @@ class ApiService {
465
451
  async uploadTempPhoto(input) {
466
452
  const userId = persistUserId(input.userId || this.getOrCreateClientUserId(), FUNNEL_ID);
467
453
  const publishableKey = getFunnelSdkPublishableKey();
468
- const imageDataUrl = await this.readFileAsDataUrl(input.file);
469
454
  if (!publishableKey) {
455
+ const imageDataUrl = URL.createObjectURL(input.file);
470
456
  return {
471
457
  key: `local_${userId}_${Date.now()}`,
472
458
  publicUrl: imageDataUrl,
@@ -475,11 +461,11 @@ class ApiService {
475
461
  uploadedAt: new Date().toISOString(),
476
462
  };
477
463
  }
478
- const payload = await funnelSdkService.uploadPhoto({
464
+ const payload = await funnelSdkService.uploadPhotoFile({
479
465
  userId,
480
466
  fileName: input.file.name || 'capture.jpg',
481
- mimeType: input.file.type || undefined,
482
- imageDataUrl,
467
+ contentType: input.file.type || undefined,
468
+ file: input.file,
483
469
  source: input.source || 'upload',
484
470
  });
485
471
  const publicUrl = asString(payload.publicUrl);
@@ -77,6 +77,25 @@ export type FunnelSdkUploadPhotoInput = {
77
77
  userId?: string | null;
78
78
  runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
79
79
  };
80
+ export type FunnelSdkUploadPhotoFileInput = {
81
+ file: Blob;
82
+ fileName: string;
83
+ contentType?: string | null;
84
+ source?: 'camera' | 'upload' | string | null;
85
+ userId?: string | null;
86
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
87
+ };
88
+ export type FunnelSdkPreparedPhotoUpload = {
89
+ key: string;
90
+ publicUrl: string;
91
+ uploadUrl: string;
92
+ uploadHeaders: Record<string, string>;
93
+ contentType: string;
94
+ sizeBytes: number;
95
+ };
96
+ export type FunnelSdkUploadedPhoto = Omit<FunnelSdkPreparedPhotoUpload, 'uploadUrl' | 'uploadHeaders'> & {
97
+ uploadedAt: string;
98
+ };
80
99
  export type FunnelSdkCreateHostedCheckoutSessionInput = {
81
100
  planId: string;
82
101
  offerSetId?: string | null;
@@ -263,6 +282,10 @@ declare class FunnelSdkService {
263
282
  claimUserSubscription<ResponsePayload = FunnelSdkJsonObject>(input: FunnelSdkClaimUserSubscriptionInput): Promise<ResponsePayload>;
264
283
  updateFunnelUser<ResponsePayload = FunnelSdkJsonObject>(input: FunnelSdkUpdateFunnelUserInput): Promise<ResponsePayload>;
265
284
  uploadPhoto<ResponsePayload = FunnelSdkJsonObject>(input: FunnelSdkUploadPhotoInput): Promise<ResponsePayload>;
285
+ preparePhotoUpload(input: Omit<FunnelSdkUploadPhotoFileInput, 'file'> & {
286
+ sizeBytes: number;
287
+ }): Promise<FunnelSdkPreparedPhotoUpload>;
288
+ uploadPhotoFile(input: FunnelSdkUploadPhotoFileInput): Promise<FunnelSdkUploadedPhoto>;
266
289
  listPaymentPlans(input?: {
267
290
  environment?: FunnelSdkRuntimeMode;
268
291
  offerSetId?: string | null;
@@ -186,6 +186,48 @@ class FunnelSdkService {
186
186
  errorMessage: 'Failed to upload photo',
187
187
  });
188
188
  }
189
+ async preparePhotoUpload(input) {
190
+ const { funnelId } = resolveRuntimeConfig(input.runtimeConfig);
191
+ return this.postJson('/sdk/public/uploads/photos/prepare', {
192
+ runtimeConfig: input.runtimeConfig,
193
+ body: {
194
+ funnelId,
195
+ userId: input.userId,
196
+ fileName: input.fileName,
197
+ contentType: input.contentType,
198
+ sizeBytes: input.sizeBytes,
199
+ source: input.source,
200
+ },
201
+ includeRuntimeFields: false,
202
+ includePublishableKey: false,
203
+ errorMessage: 'Failed to prepare photo upload',
204
+ });
205
+ }
206
+ async uploadPhotoFile(input) {
207
+ const prepared = await this.preparePhotoUpload({
208
+ fileName: input.fileName,
209
+ contentType: input.contentType,
210
+ sizeBytes: input.file.size,
211
+ source: input.source,
212
+ userId: input.userId,
213
+ runtimeConfig: input.runtimeConfig,
214
+ });
215
+ const response = await fetch(prepared.uploadUrl, {
216
+ method: 'PUT',
217
+ headers: prepared.uploadHeaders,
218
+ body: input.file,
219
+ });
220
+ if (!response.ok) {
221
+ throw new Error(`Failed to upload photo (${response.status})`);
222
+ }
223
+ return {
224
+ key: prepared.key,
225
+ publicUrl: prepared.publicUrl,
226
+ contentType: prepared.contentType,
227
+ sizeBytes: prepared.sizeBytes,
228
+ uploadedAt: new Date().toISOString(),
229
+ };
230
+ }
189
231
  async listPaymentPlans(input = {}) {
190
232
  const { funnelId } = resolveRuntimeConfig(input.runtimeConfig);
191
233
  return this.getJson('/sdk/public/payments/plans', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/runtime",
3
- "version": "0.7.26",
3
+ "version": "0.7.28",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",