@proveanything/smartlinks 1.3.8 → 1.3.9

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.
@@ -0,0 +1,549 @@
1
+ // =============================================================================
2
+ // IframeResponder - Parent-side iframe communication handler
3
+ // =============================================================================
4
+ import * as cache from './cache';
5
+ import { collection } from './api/collection';
6
+ /**
7
+ * Parent-side iframe responder for SmartLinks microapp embedding.
8
+ *
9
+ * Handles all bidirectional communication with embedded iframes:
10
+ * - API proxy requests (with caching)
11
+ * - Authentication state synchronization
12
+ * - Deep linking / route changes
13
+ * - Resize management
14
+ * - Chunked file upload proxying
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const responder = new IframeResponder({
19
+ * collectionId: 'my-collection',
20
+ * appId: 'warranty',
21
+ * onAuthLogin: async (token, user) => {
22
+ * smartlinks.setBearerToken(token);
23
+ * },
24
+ * });
25
+ *
26
+ * const src = await responder.attach(iframeElement);
27
+ * iframeElement.src = src;
28
+ *
29
+ * // Cleanup
30
+ * responder.destroy();
31
+ * ```
32
+ */
33
+ export class IframeResponder {
34
+ constructor(options) {
35
+ this.iframe = null;
36
+ this.uploads = new Map();
37
+ this.isInitialLoad = true;
38
+ this.messageHandler = null;
39
+ this.resizeHandler = null;
40
+ this.appUrl = null;
41
+ this.resolveReady = null;
42
+ this.options = options;
43
+ this.cache = options.cache || {};
44
+ // Create ready promise
45
+ this.ready = new Promise((resolve) => {
46
+ this.resolveReady = resolve;
47
+ });
48
+ // Start resolving app URL
49
+ this.resolveAppUrl().then(() => {
50
+ var _a;
51
+ (_a = this.resolveReady) === null || _a === void 0 ? void 0 : _a.call(this);
52
+ });
53
+ }
54
+ /**
55
+ * Attach to an iframe element.
56
+ * Returns the src URL to set on the iframe.
57
+ */
58
+ async attach(iframe) {
59
+ await this.ready;
60
+ this.iframe = iframe;
61
+ // Set up message listener
62
+ this.messageHandler = this.handleMessage.bind(this);
63
+ window.addEventListener('message', this.messageHandler);
64
+ // Set up resize listener for viewport-based calculations
65
+ this.resizeHandler = this.calculateViewportHeight.bind(this);
66
+ window.addEventListener('resize', this.resizeHandler);
67
+ window.addEventListener('orientationchange', this.resizeHandler);
68
+ return this.buildIframeSrc();
69
+ }
70
+ /**
71
+ * Update cached data (e.g., after user logs in).
72
+ */
73
+ updateCache(data) {
74
+ this.cache = Object.assign(Object.assign({}, this.cache), data);
75
+ }
76
+ /**
77
+ * Cleanup - remove event listeners and clear state.
78
+ */
79
+ destroy() {
80
+ if (this.messageHandler) {
81
+ window.removeEventListener('message', this.messageHandler);
82
+ this.messageHandler = null;
83
+ }
84
+ if (this.resizeHandler) {
85
+ window.removeEventListener('resize', this.resizeHandler);
86
+ window.removeEventListener('orientationchange', this.resizeHandler);
87
+ this.resizeHandler = null;
88
+ }
89
+ this.uploads.clear();
90
+ this.iframe = null;
91
+ }
92
+ // ===========================================================================
93
+ // URL Resolution
94
+ // ===========================================================================
95
+ async resolveAppUrl() {
96
+ var _a, _b;
97
+ // Use explicit override if provided
98
+ if (this.options.appUrl) {
99
+ this.appUrl = this.options.appUrl;
100
+ return;
101
+ }
102
+ // Check pre-populated cache
103
+ const cachedApps = this.cache.apps;
104
+ if (cachedApps) {
105
+ const app = cachedApps.find(a => a.id === this.options.appId);
106
+ if (app) {
107
+ this.appUrl = this.getVersionUrl(app);
108
+ return;
109
+ }
110
+ }
111
+ // Fetch from API with caching
112
+ try {
113
+ const appsConfig = await cache.getOrFetch(`apps:${this.options.collectionId}`, () => collection.getAppsConfig(this.options.collectionId), { ttl: 5 * 60 * 1000, storage: 'session' });
114
+ const apps = appsConfig.apps;
115
+ const app = apps.find(a => a.id === this.options.appId);
116
+ if (!app) {
117
+ throw new Error(`App "${this.options.appId}" not found in collection "${this.options.collectionId}"`);
118
+ }
119
+ this.appUrl = this.getVersionUrl(app);
120
+ }
121
+ catch (err) {
122
+ (_b = (_a = this.options).onError) === null || _b === void 0 ? void 0 : _b.call(_a, err);
123
+ throw err;
124
+ }
125
+ }
126
+ getVersionUrl(app) {
127
+ // Use publicIframeUrl from AppConfig
128
+ if (!app.publicIframeUrl) {
129
+ throw new Error(`App "${app.id}" does not have a publicIframeUrl configured`);
130
+ }
131
+ return app.publicIframeUrl;
132
+ }
133
+ // ===========================================================================
134
+ // URL Building
135
+ // ===========================================================================
136
+ buildIframeSrc() {
137
+ var _a, _b;
138
+ if (!this.appUrl) {
139
+ throw new Error('App URL not resolved');
140
+ }
141
+ const params = new URLSearchParams();
142
+ // Required context
143
+ params.set('collectionId', this.options.collectionId);
144
+ params.set('appId', this.options.appId);
145
+ // Optional context
146
+ if (this.options.productId)
147
+ params.set('productId', this.options.productId);
148
+ if (this.options.proofId)
149
+ params.set('proofId', this.options.proofId);
150
+ if (this.options.isAdmin)
151
+ params.set('isAdmin', 'true');
152
+ // Dark mode from collection
153
+ const isDark = (_b = (_a = this.cache.collection) === null || _a === void 0 ? void 0 : _a.dark) !== null && _b !== void 0 ? _b : false;
154
+ params.set('dark', isDark ? '1' : '0');
155
+ // Parent URL for redirects
156
+ try {
157
+ params.set('parentUrl', window.location.href);
158
+ }
159
+ catch (_c) {
160
+ // Ignore if can't access location
161
+ }
162
+ // Encode theme from collection
163
+ if (this.cache.collection) {
164
+ try {
165
+ const themeData = {
166
+ p: this.cache.collection.primaryColor,
167
+ s: this.cache.collection.secondaryColor,
168
+ m: this.cache.collection.dark ? 'd' : 'l',
169
+ };
170
+ if (themeData.p || themeData.s) {
171
+ params.set('theme', btoa(JSON.stringify(themeData)));
172
+ }
173
+ }
174
+ catch (_d) {
175
+ // Ignore encoding errors
176
+ }
177
+ }
178
+ // Build URL
179
+ let base = this.appUrl.replace(/#\/?$/, '');
180
+ if (base.endsWith('/')) {
181
+ base = base.slice(0, -1);
182
+ }
183
+ // Build hash path
184
+ let hashPath = this.options.initialPath || '';
185
+ if (hashPath && !hashPath.startsWith('/')) {
186
+ hashPath = '/' + hashPath;
187
+ }
188
+ if (hashPath === '/') {
189
+ hashPath = '';
190
+ }
191
+ return `${base}/#/${hashPath}?${params.toString()}`.replace('/#//', '/#/');
192
+ }
193
+ // ===========================================================================
194
+ // Viewport Resize Calculation
195
+ // ===========================================================================
196
+ calculateViewportHeight() {
197
+ var _a, _b;
198
+ if (!this.iframe)
199
+ return;
200
+ const container = this.iframe.parentElement;
201
+ if (!container)
202
+ return;
203
+ const rect = container.getBoundingClientRect();
204
+ const viewportHeight = window.innerHeight;
205
+ const calculatedHeight = Math.max(0, viewportHeight - rect.top);
206
+ (_b = (_a = this.options).onResize) === null || _b === void 0 ? void 0 : _b.call(_a, calculatedHeight);
207
+ }
208
+ // ===========================================================================
209
+ // Message Handling
210
+ // ===========================================================================
211
+ async handleMessage(event) {
212
+ // Validate source is our iframe
213
+ if (!this.iframe || event.source !== this.iframe.contentWindow) {
214
+ return;
215
+ }
216
+ const data = event.data;
217
+ if (!data || typeof data !== 'object')
218
+ return;
219
+ // Route changes (deep linking)
220
+ if (data.type === 'smartlinks-route-change') {
221
+ this.handleRouteChange(data);
222
+ return;
223
+ }
224
+ // Standardized iframe messages
225
+ if (data._smartlinksIframeMessage) {
226
+ await this.handleStandardMessage(data, event);
227
+ return;
228
+ }
229
+ // File upload proxy
230
+ if (data._smartlinksProxyUpload) {
231
+ await this.handleUpload(data, event);
232
+ return;
233
+ }
234
+ // API proxy requests
235
+ if (data._smartlinksProxyRequest) {
236
+ await this.handleProxyRequest(data, event);
237
+ return;
238
+ }
239
+ }
240
+ // ===========================================================================
241
+ // Route Changes (Deep Linking)
242
+ // ===========================================================================
243
+ handleRouteChange(data) {
244
+ var _a, _b;
245
+ // Skip initial load to prevent duplicating path
246
+ if (this.isInitialLoad) {
247
+ this.isInitialLoad = false;
248
+ return;
249
+ }
250
+ const { context = {}, state = {}, path = '' } = data;
251
+ // Filter out known iframe params, keep only app-specific state
252
+ const KNOWN_PARAMS = new Set([
253
+ 'collectionId', 'appId', 'productId', 'proofId',
254
+ 'isAdmin', 'dark', 'parentUrl', 'theme', 'lang',
255
+ ]);
256
+ const filteredState = {};
257
+ Object.entries(context).forEach(([key, value]) => {
258
+ if (value != null && !KNOWN_PARAMS.has(key)) {
259
+ filteredState[key] = value;
260
+ }
261
+ });
262
+ Object.entries(state).forEach(([key, value]) => {
263
+ if (value != null && !KNOWN_PARAMS.has(key)) {
264
+ filteredState[key] = value;
265
+ }
266
+ });
267
+ (_b = (_a = this.options).onRouteChange) === null || _b === void 0 ? void 0 : _b.call(_a, path, filteredState);
268
+ }
269
+ // ===========================================================================
270
+ // Standard Iframe Messages
271
+ // ===========================================================================
272
+ async handleStandardMessage(data, event) {
273
+ var _a, _b, _c, _d, _e, _f, _g;
274
+ switch (data.type) {
275
+ case 'smartlinks:resize': {
276
+ const contentHeight = (_a = data.payload) === null || _a === void 0 ? void 0 : _a.height;
277
+ if (typeof contentHeight === 'number' && contentHeight > 0) {
278
+ (_c = (_b = this.options).onResize) === null || _c === void 0 ? void 0 : _c.call(_b, contentHeight);
279
+ }
280
+ break;
281
+ }
282
+ case 'smartlinks:redirect': {
283
+ const url = (_d = data.payload) === null || _d === void 0 ? void 0 : _d.url;
284
+ if (url && typeof url === 'string') {
285
+ window.location.href = url;
286
+ }
287
+ break;
288
+ }
289
+ case 'smartlinks:authkit:login': {
290
+ const { token, user, accountData, messageId } = data.payload || {};
291
+ if (!this.options.onAuthLogin) {
292
+ this.sendResponse(event, {
293
+ type: 'smartlinks:authkit:login-acknowledged',
294
+ messageId,
295
+ success: false,
296
+ error: 'No auth handler available',
297
+ });
298
+ break;
299
+ }
300
+ try {
301
+ // TODO: Validate token using SDK auth utilities when available
302
+ // await auth.verifyToken(token);
303
+ await this.options.onAuthLogin(token, user, accountData);
304
+ // Update cache with new user
305
+ this.cache.user = user;
306
+ this.sendResponse(event, {
307
+ type: 'smartlinks:authkit:login-acknowledged',
308
+ messageId,
309
+ success: true,
310
+ });
311
+ }
312
+ catch (err) {
313
+ this.sendResponse(event, {
314
+ type: 'smartlinks:authkit:login-acknowledged',
315
+ messageId,
316
+ success: false,
317
+ error: (err === null || err === void 0 ? void 0 : err.message) || 'Auth failed',
318
+ });
319
+ (_f = (_e = this.options).onError) === null || _f === void 0 ? void 0 : _f.call(_e, err);
320
+ }
321
+ break;
322
+ }
323
+ case 'smartlinks:authkit:logout': {
324
+ if (this.options.onAuthLogout) {
325
+ await this.options.onAuthLogout();
326
+ this.cache.user = null;
327
+ }
328
+ break;
329
+ }
330
+ case 'smartlinks:authkit:redirect': {
331
+ const url = (_g = data.payload) === null || _g === void 0 ? void 0 : _g.url;
332
+ if (url && typeof url === 'string') {
333
+ window.location.href = url;
334
+ }
335
+ break;
336
+ }
337
+ }
338
+ }
339
+ // ===========================================================================
340
+ // API Proxy
341
+ // ===========================================================================
342
+ async handleProxyRequest(data, event) {
343
+ var _a, _b, _c;
344
+ const response = {
345
+ _smartlinksProxyResponse: true,
346
+ id: data.id,
347
+ };
348
+ // Handle custom proxy requests (redirects, etc.)
349
+ if ('_smartlinksCustomProxyRequest' in data && data._smartlinksCustomProxyRequest) {
350
+ if (data.request === 'REDIRECT') {
351
+ const url = (_a = data.params) === null || _a === void 0 ? void 0 : _a.url;
352
+ if (url) {
353
+ window.location.href = url;
354
+ }
355
+ response.data = { success: true };
356
+ this.sendResponse(event, response);
357
+ return;
358
+ }
359
+ }
360
+ const proxyData = data;
361
+ try {
362
+ const path = proxyData.path.startsWith('/') ? proxyData.path.slice(1) : proxyData.path;
363
+ // Check for cached data matches on GET requests
364
+ if (proxyData.method === 'GET') {
365
+ const cachedResponse = this.getCachedResponse(path);
366
+ if (cachedResponse !== null) {
367
+ response.data = cachedResponse;
368
+ this.sendResponse(event, response);
369
+ return;
370
+ }
371
+ }
372
+ // Forward to actual API using SDK's http utilities
373
+ // Build full URL and use fetch for now (can be replaced with SDK request when needed)
374
+ const baseUrl = '/api/v1';
375
+ const fetchOptions = {
376
+ method: proxyData.method,
377
+ headers: proxyData.headers,
378
+ };
379
+ if (proxyData.body && proxyData.method !== 'GET') {
380
+ fetchOptions.body = JSON.stringify(proxyData.body);
381
+ fetchOptions.headers = Object.assign(Object.assign({}, fetchOptions.headers), { 'Content-Type': 'application/json' });
382
+ }
383
+ const fetchResponse = await fetch(`${baseUrl}/${path}`, fetchOptions);
384
+ response.data = await fetchResponse.json();
385
+ }
386
+ catch (err) {
387
+ response.error = (err === null || err === void 0 ? void 0 : err.message) || 'Unknown error';
388
+ (_c = (_b = this.options).onError) === null || _c === void 0 ? void 0 : _c.call(_b, err);
389
+ }
390
+ this.sendResponse(event, response);
391
+ }
392
+ getCachedResponse(path) {
393
+ // Collection request
394
+ if (path.includes('/collection/') && this.cache.collection) {
395
+ const match = path.match(/collection\/([^/]+)/);
396
+ if (match && match[1] === this.options.collectionId) {
397
+ return JSON.parse(JSON.stringify(this.cache.collection));
398
+ }
399
+ }
400
+ // Product request
401
+ if (path.includes('/product/') && this.cache.product && this.options.productId) {
402
+ const match = path.match(/product\/([^/]+)/);
403
+ if (match && match[1] === this.options.productId) {
404
+ return JSON.parse(JSON.stringify(this.cache.product));
405
+ }
406
+ }
407
+ // Proof request
408
+ if (path.includes('/proof/') && this.cache.proof && this.options.proofId) {
409
+ const match = path.match(/proof\/([^/]+)/);
410
+ if (match && match[1] === this.options.proofId) {
411
+ return JSON.parse(JSON.stringify(this.cache.proof));
412
+ }
413
+ }
414
+ // Account request
415
+ if (path.includes('/account') && this.cache.user) {
416
+ return JSON.parse(JSON.stringify(Object.assign(Object.assign({}, this.cache.user.accountData), { uid: this.cache.user.uid, email: this.cache.user.email, displayName: this.cache.user.displayName })));
417
+ }
418
+ return null;
419
+ }
420
+ // ===========================================================================
421
+ // Chunked File Uploads
422
+ // ===========================================================================
423
+ async handleUpload(data, event) {
424
+ var _a, _b;
425
+ switch (data.phase) {
426
+ case 'start': {
427
+ const startData = data;
428
+ this.uploads.set(startData.id, {
429
+ chunks: [],
430
+ fields: startData.fields,
431
+ fileInfo: startData.fileInfo,
432
+ path: startData.path,
433
+ });
434
+ break;
435
+ }
436
+ case 'chunk': {
437
+ const chunkData = data;
438
+ const upload = this.uploads.get(chunkData.id);
439
+ if (upload) {
440
+ const uint8Array = new Uint8Array(chunkData.chunk);
441
+ upload.chunks.push(uint8Array);
442
+ this.sendResponse(event, {
443
+ _smartlinksProxyUpload: true,
444
+ phase: 'ack',
445
+ id: chunkData.id,
446
+ seq: chunkData.seq,
447
+ });
448
+ }
449
+ break;
450
+ }
451
+ case 'end': {
452
+ const endData = data;
453
+ const upload = this.uploads.get(endData.id);
454
+ if (!upload)
455
+ break;
456
+ try {
457
+ const blobParts = upload.chunks.map(chunk => chunk.buffer.slice(0));
458
+ const blob = new Blob(blobParts, {
459
+ type: upload.fileInfo.type || 'application/octet-stream'
460
+ });
461
+ const formData = new FormData();
462
+ upload.fields.forEach(([key, value]) => formData.append(key, value));
463
+ formData.append(upload.fileInfo.key || 'file', blob, upload.fileInfo.name || 'upload.bin');
464
+ const path = upload.path.startsWith('/') ? upload.path.slice(1) : upload.path;
465
+ const baseUrl = '/api/v1';
466
+ const response = await fetch(`${baseUrl}/${path}`, {
467
+ method: 'POST',
468
+ body: formData,
469
+ });
470
+ if (!response.ok) {
471
+ throw new Error(`Upload failed: ${response.status}`);
472
+ }
473
+ const result = await response.json();
474
+ this.sendResponse(event, {
475
+ _smartlinksProxyUpload: true,
476
+ phase: 'done',
477
+ id: endData.id,
478
+ ok: true,
479
+ data: result,
480
+ });
481
+ }
482
+ catch (err) {
483
+ this.sendResponse(event, {
484
+ _smartlinksProxyUpload: true,
485
+ phase: 'done',
486
+ id: endData.id,
487
+ ok: false,
488
+ error: (err === null || err === void 0 ? void 0 : err.message) || 'Upload failed',
489
+ });
490
+ (_b = (_a = this.options).onError) === null || _b === void 0 ? void 0 : _b.call(_a, err);
491
+ }
492
+ this.uploads.delete(endData.id);
493
+ break;
494
+ }
495
+ }
496
+ }
497
+ // ===========================================================================
498
+ // Utilities
499
+ // ===========================================================================
500
+ sendResponse(event, message) {
501
+ if (event.source && 'postMessage' in event.source) {
502
+ event.source.postMessage(message, event.origin);
503
+ }
504
+ }
505
+ }
506
+ // =============================================================================
507
+ // Helper Functions
508
+ // =============================================================================
509
+ /**
510
+ * Determine admin status from collection/proof roles.
511
+ */
512
+ export function isAdminFromRoles(user, collection, proof) {
513
+ var _a, _b;
514
+ if (!(user === null || user === void 0 ? void 0 : user.uid))
515
+ return false;
516
+ return !!(((_a = collection === null || collection === void 0 ? void 0 : collection.roles) === null || _a === void 0 ? void 0 : _a[user.uid]) || ((_b = proof === null || proof === void 0 ? void 0 : proof.roles) === null || _b === void 0 ? void 0 : _b[user.uid]));
517
+ }
518
+ /**
519
+ * Build iframe src URL with all context params.
520
+ * Standalone helper for cases where you don't need the full IframeResponder.
521
+ */
522
+ export function buildIframeSrc(options) {
523
+ var _a, _b;
524
+ const params = new URLSearchParams();
525
+ params.set('collectionId', options.collectionId);
526
+ params.set('appId', options.appId);
527
+ if (options.productId)
528
+ params.set('productId', options.productId);
529
+ if (options.proofId)
530
+ params.set('proofId', options.proofId);
531
+ if (options.isAdmin)
532
+ params.set('isAdmin', 'true');
533
+ params.set('dark', options.dark ? '1' : '0');
534
+ if (((_a = options.theme) === null || _a === void 0 ? void 0 : _a.primary) || ((_b = options.theme) === null || _b === void 0 ? void 0 : _b.secondary)) {
535
+ const themeData = { p: options.theme.primary, s: options.theme.secondary };
536
+ params.set('theme', btoa(JSON.stringify(themeData)));
537
+ }
538
+ try {
539
+ params.set('parentUrl', window.location.href);
540
+ }
541
+ catch (_c) {
542
+ // Ignore
543
+ }
544
+ let base = options.appUrl.replace(/#\/?$/, '').replace(/\/$/, '');
545
+ let path = options.initialPath || '';
546
+ if (path && !path.startsWith('/'))
547
+ path = '/' + path;
548
+ return `${base}/#${path}?${params.toString()}`.replace('/#/?', '/#?').replace('/#/', '/#/');
549
+ }
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { initializeApi, request, sendCustomProxyMessage } from "./http";
2
2
  export * from "./api";
3
3
  export * from "./types";
4
4
  export { iframe } from "./iframe";
5
+ export * as cache from './cache';
6
+ export { IframeResponder, isAdminFromRoles, buildIframeSrc, } from './iframeResponder';
5
7
  export type { LoginResponse, VerifyTokenResponse, AccountInfoResponse, } from "./api/auth";
6
8
  export type { UserAccountRegistrationRequest, } from "./types/auth";
7
9
  export type { CommunicationEvent, CommsQueryByUser, CommsRecipientIdsQuery, CommsRecipientsWithoutActionQuery, CommsRecipientsWithActionQuery, RecipientId, RecipientWithOutcome, LogCommunicationEventBody, LogBulkCommunicationEventsBody, AppendResult, AppendBulkResult, CommsSettings, TopicConfig, CommsSettingsGetResponse, CommsSettingsPatchBody, CommsPublicTopicsResponse, UnsubscribeQuery, UnsubscribeResponse, CommsConsentUpsertRequest, CommsPreferencesUpsertRequest, CommsSubscribeRequest, CommsSubscribeResponse, CommsSubscriptionCheckQuery, CommsSubscriptionCheckResponse, CommsListMethodsQuery, CommsListMethodsResponse, RegisterEmailMethodRequest, RegisterSmsMethodRequest, RegisterMethodResponse, SubscriptionsResolveRequest, SubscriptionsResolveResponse, } from "./types/comms";
package/dist/index.js CHANGED
@@ -5,3 +5,7 @@ export * from "./api";
5
5
  export * from "./types";
6
6
  // Iframe namespace
7
7
  export { iframe } from "./iframe";
8
+ import * as cache_1 from './cache';
9
+ export { cache_1 as cache };
10
+ // IframeResponder (also exported via iframe namespace)
11
+ export { IframeResponder, isAdminFromRoles, buildIframeSrc, } from './iframeResponder';
@@ -0,0 +1,112 @@
1
+ export type { AuthKitUser } from './authKit';
2
+ export type { AppConfig as CollectionApp } from './collection';
3
+ import type { AppConfig } from './collection';
4
+ export interface CachedData {
5
+ collection?: Record<string, any>;
6
+ product?: Record<string, any>;
7
+ proof?: Record<string, any>;
8
+ user?: {
9
+ uid: string;
10
+ email?: string;
11
+ displayName?: string;
12
+ accountData?: Record<string, any>;
13
+ } | null;
14
+ apps?: AppConfig[];
15
+ }
16
+ export interface IframeResponderOptions {
17
+ collectionId: string;
18
+ appId: string;
19
+ productId?: string;
20
+ proofId?: string;
21
+ /** Version to load: 'stable' | 'development' | specific version */
22
+ version?: string;
23
+ /** Override auto-resolved URL (for local development) */
24
+ appUrl?: string;
25
+ /** Initial hash path (e.g., '/settings') */
26
+ initialPath?: string;
27
+ /** Is user an admin of this collection */
28
+ isAdmin?: boolean;
29
+ cache?: CachedData;
30
+ onAuthLogin?: (token: string, user: any, accountData?: Record<string, any>) => Promise<void>;
31
+ onAuthLogout?: () => Promise<void>;
32
+ onRouteChange?: (path: string, state: Record<string, string>) => void;
33
+ onResize?: (height: number) => void;
34
+ onError?: (error: Error) => void;
35
+ onReady?: () => void;
36
+ }
37
+ export interface RouteChangeMessage {
38
+ type: 'smartlinks-route-change';
39
+ path: string;
40
+ context: Record<string, string>;
41
+ state: Record<string, string>;
42
+ appId?: string;
43
+ }
44
+ export interface SmartlinksIframeMessage {
45
+ _smartlinksIframeMessage: true;
46
+ type: 'smartlinks:resize' | 'smartlinks:redirect' | 'smartlinks:authkit:login' | 'smartlinks:authkit:logout' | 'smartlinks:authkit:redirect';
47
+ payload: Record<string, any>;
48
+ messageId?: string;
49
+ }
50
+ export interface ProxyRequest {
51
+ _smartlinksProxyRequest: true;
52
+ id: string;
53
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
54
+ path: string;
55
+ body?: any;
56
+ headers?: Record<string, string>;
57
+ }
58
+ export interface CustomProxyRequest {
59
+ _smartlinksProxyRequest: true;
60
+ _smartlinksCustomProxyRequest: true;
61
+ id: string;
62
+ request: 'REDIRECT' | string;
63
+ params: Record<string, any>;
64
+ }
65
+ export interface ProxyResponse {
66
+ _smartlinksProxyResponse: true;
67
+ id: string;
68
+ data?: any;
69
+ error?: string;
70
+ }
71
+ export interface UploadStartMessage {
72
+ _smartlinksProxyUpload: true;
73
+ phase: 'start';
74
+ id: string;
75
+ fields: [string, string][];
76
+ fileInfo: {
77
+ type?: string;
78
+ name?: string;
79
+ key?: string;
80
+ };
81
+ path: string;
82
+ headers?: Record<string, string>;
83
+ }
84
+ export interface UploadChunkMessage {
85
+ _smartlinksProxyUpload: true;
86
+ phase: 'chunk';
87
+ id: string;
88
+ seq: number;
89
+ chunk: ArrayBuffer;
90
+ }
91
+ export interface UploadEndMessage {
92
+ _smartlinksProxyUpload: true;
93
+ phase: 'end';
94
+ id: string;
95
+ }
96
+ export interface UploadAckMessage {
97
+ _smartlinksProxyUpload: true;
98
+ phase: 'ack';
99
+ id: string;
100
+ seq: number;
101
+ }
102
+ export interface UploadDoneMessage {
103
+ _smartlinksProxyUpload: true;
104
+ phase: 'done';
105
+ id: string;
106
+ ok: boolean;
107
+ data?: any;
108
+ error?: string;
109
+ }
110
+ export type UploadMessage = UploadStartMessage | UploadChunkMessage | UploadEndMessage | UploadAckMessage | UploadDoneMessage;
111
+ /** Reserved iframe context parameters (not app state) */
112
+ export declare const KNOWN_IFRAME_PARAMS: Set<string>;