arky-sdk 0.9.20 → 0.10.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.
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { atom, computed, map } from 'nanostores';
2
+
1
3
  // src/types/index.ts
2
4
  var PaymentMethodType = /* @__PURE__ */ ((PaymentMethodType2) => {
3
5
  PaymentMethodType2["Cash"] = "cash";
@@ -45,70 +47,50 @@ var COMMON_ACTION_KEYS = [
45
47
  "share",
46
48
  "wishlist.added"
47
49
  ];
48
- var createActionApi = (apiConfig) => ({
50
+ var createActionApi = (apiConfig, lifecycle) => ({
49
51
  COMMON_ACTION_KEYS,
50
52
  async track(params) {
53
+ await lifecycle.ensureVisitorSession();
51
54
  try {
52
- await apiConfig.httpClient.post(
53
- `/v1/storefront/${apiConfig.storeId}/actions/track`,
54
- { key: params.key, payload: params.payload }
55
- );
55
+ await apiConfig.httpClient.post("/v1/storefront/actions/track", {
56
+ key: params.key,
57
+ payload: params.payload
58
+ });
56
59
  } catch {
57
60
  }
58
61
  }
59
62
  });
60
- var createStorefrontApi = (apiConfig, updateContactSession) => {
61
- const base = (storeId = apiConfig.storeId) => `/v1/storefront/${storeId}`;
62
- const pendingVerifications = /* @__PURE__ */ new Map();
63
+ var createStorefrontApi = (apiConfig, updateContactSession, lifecycle) => {
64
+ const base = "/v1/storefront";
63
65
  function persistIdentification(result) {
64
- const issued = result.token;
65
- if (issued?.token) {
66
+ const sessionToken = result.token?.token;
67
+ if (sessionToken) {
66
68
  updateContactSession(() => ({
67
- access_token: issued.token,
68
- contact: result.contact,
69
- store: result.store,
70
- market: result.market
69
+ sessionToken,
70
+ contact: result.contact
71
71
  }));
72
72
  } else {
73
- if (result.verification_challenge) {
74
- pendingVerifications.set(result.verification_challenge.challenge_id, {
75
- store: result.store,
76
- market: result.market
77
- });
78
- }
79
73
  updateContactSession(
80
- (current) => current ? {
81
- ...current,
82
- contact: result.contact,
83
- store: result.store,
84
- market: result.market
85
- } : null
74
+ (current) => current ? { ...current, contact: result.contact } : null
86
75
  );
87
76
  }
88
77
  return result;
89
78
  }
90
79
  async function submitIdentification(path, params, options) {
91
- const store_id = apiConfig.storeId;
92
80
  const result = await apiConfig.httpClient.post(
93
- `${base(store_id)}/account/${path}`,
94
- {
95
- store_id,
96
- market: params?.market || apiConfig.market || null,
97
- email: params?.email
98
- },
81
+ `${base}/account/${path}`,
82
+ { email: params?.email },
99
83
  options
100
84
  );
101
85
  return persistIdentification(result);
102
86
  }
103
87
  return {
104
88
  store: {
105
- getStore(options) {
106
- return apiConfig.httpClient.get(base(), options);
107
- },
89
+ getSetup: lifecycle.getSetup,
108
90
  location: {
109
91
  getCountries(options) {
110
92
  return apiConfig.httpClient.get(
111
- `/v1/platform/countries`,
93
+ "/v1/platform/countries",
112
94
  options
113
95
  );
114
96
  },
@@ -120,13 +102,13 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
120
102
  },
121
103
  list(options) {
122
104
  return apiConfig.httpClient.get(
123
- `${base()}/locations`,
105
+ `${base}/locations`,
124
106
  options
125
107
  );
126
108
  },
127
109
  get(id, options) {
128
110
  return apiConfig.httpClient.get(
129
- `${base()}/locations/${id}`,
111
+ `${base}/locations/${id}`,
130
112
  options
131
113
  );
132
114
  }
@@ -134,13 +116,13 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
134
116
  market: {
135
117
  list(options) {
136
118
  return apiConfig.httpClient.get(
137
- `${base()}/markets`,
119
+ `${base}/markets`,
138
120
  options
139
121
  );
140
122
  },
141
123
  get(id, options) {
142
124
  return apiConfig.httpClient.get(
143
- `${base()}/markets/${id}`,
125
+ `${base}/markets/${id}`,
144
126
  options
145
127
  );
146
128
  }
@@ -149,85 +131,59 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
149
131
  cms: {
150
132
  collection: {
151
133
  get(params, options) {
152
- const store_id = params.store_id || apiConfig.storeId;
153
- const identifier = params.id !== void 0 ? params.id : `${store_id}:${params.key}`;
134
+ const identifier = params.id ?? params.key;
154
135
  return apiConfig.httpClient.get(
155
- `${base(store_id)}/collections/${identifier}`,
136
+ `${base}/collections/${identifier}`,
156
137
  options
157
138
  );
158
139
  }
159
140
  },
160
141
  entry: {
161
142
  get(params, options) {
162
- const store_id = params.store_id || apiConfig.storeId;
163
- if (!params.id) {
164
- throw new Error("GetEntryParams requires id");
165
- }
166
143
  return apiConfig.httpClient.get(
167
- `${base(store_id)}/entries/${params.id}`,
144
+ `${base}/entries/${params.id}`,
168
145
  options
169
146
  );
170
147
  },
171
148
  find(params, options) {
172
- const { store_id, ...queryParams } = params;
173
149
  return apiConfig.httpClient.get(
174
- `${base(store_id)}/entries`,
175
- {
176
- ...options,
177
- params: queryParams
178
- }
150
+ `${base}/entries`,
151
+ { ...options, params }
179
152
  );
180
153
  }
181
154
  },
182
155
  form: {
183
156
  get(params, options) {
184
- const store_id = params.store_id || apiConfig.storeId;
185
- let identifier;
186
- if (params.id) {
187
- identifier = params.id;
188
- } else if (params.key) {
189
- identifier = `${store_id}:${params.key}`;
190
- } else {
191
- throw new Error("GetFormParams requires id or key");
192
- }
157
+ const identifier = params.id ?? params.key;
158
+ if (!identifier) throw new Error("GetFormParams requires id or key");
193
159
  return apiConfig.httpClient.get(
194
- `${base(store_id)}/forms/${identifier}`,
160
+ `${base}/forms/${identifier}`,
195
161
  options
196
162
  );
197
163
  },
198
- submit(params, options) {
199
- const { store_id, form_id, ...payload } = params;
200
- const target_store_id = store_id || apiConfig.storeId;
201
- if (!form_id) {
202
- throw new Error("SubmitFormParams requires form_id");
203
- }
164
+ async submit(params, options) {
165
+ await lifecycle.ensureVisitorSession();
166
+ const { form_id, ...payload } = params;
167
+ if (!form_id) throw new Error("SubmitFormParams requires form_id");
204
168
  return apiConfig.httpClient.post(
205
- `${base(target_store_id)}/forms/${form_id}/submissions`,
206
- { ...payload, form_id, store_id: target_store_id },
169
+ `${base}/forms/${form_id}/submissions`,
170
+ { ...payload, form_id },
207
171
  options
208
172
  );
209
173
  }
210
174
  },
211
175
  taxonomy: {
212
176
  get(params, options) {
213
- const store_id = params.store_id || apiConfig.storeId;
214
- let identifier;
215
- if (params.id) {
216
- identifier = params.id;
217
- } else if (params.key) {
218
- identifier = `${store_id}:${params.key}`;
219
- } else {
220
- throw new Error("GetTaxonomyParams requires id or key");
221
- }
177
+ const identifier = params.id ?? params.key;
178
+ if (!identifier) throw new Error("GetTaxonomyParams requires id or key");
222
179
  return apiConfig.httpClient.get(
223
- `${base(store_id)}/taxonomies/${identifier}`,
180
+ `${base}/taxonomies/${identifier}`,
224
181
  options
225
182
  );
226
183
  },
227
184
  getChildren(params, options) {
228
- const store_id = params.store_id || apiConfig.storeId;
229
185
  return apiConfig.httpClient.get(
230
- `${base(store_id)}/taxonomies/${params.id}/children`,
186
+ `${base}/taxonomies/${params.id}/children`,
231
187
  options
232
188
  );
233
189
  }
@@ -236,113 +192,90 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
236
192
  eshop: {
237
193
  product: {
238
194
  get(params, options) {
239
- const store_id = params.store_id || apiConfig.storeId;
240
- let identifier;
241
- if (params.id) {
242
- identifier = params.id;
243
- } else if (params.slug) {
244
- identifier = `${store_id}:${apiConfig.locale}:${params.slug}`;
245
- } else {
246
- throw new Error("GetProductParams requires id or slug");
247
- }
195
+ const identifier = params.id ?? params.slug;
196
+ if (!identifier) throw new Error("GetProductParams requires id or slug");
248
197
  return apiConfig.httpClient.get(
249
- `${base(store_id)}/products/${identifier}`,
198
+ `${base}/products/${identifier}`,
250
199
  options
251
200
  );
252
201
  },
253
202
  find(params, options) {
254
- const { store_id, ...queryParams } = params;
255
203
  return apiConfig.httpClient.get(
256
- `${base(store_id)}/products`,
257
- {
258
- ...options,
259
- params: queryParams
260
- }
204
+ `${base}/products`,
205
+ { ...options, params }
261
206
  );
262
207
  }
263
208
  },
264
209
  cart: {
265
- current(params = {}, options) {
266
- const store_id = params.store_id || apiConfig.storeId;
267
- const { store_id: _store_id, ...payload } = params;
210
+ async current(options) {
211
+ await lifecycle.ensureVisitorSession();
268
212
  return apiConfig.httpClient.post(
269
- `${base(store_id)}/carts/current`,
270
- {
271
- ...payload,
272
- store_id,
273
- market: payload.market || apiConfig.market
274
- },
213
+ `${base}/carts/current`,
214
+ {},
275
215
  options
276
216
  );
277
217
  },
278
- get(params, options) {
279
- const store_id = params.store_id || apiConfig.storeId;
218
+ async get(params, options) {
219
+ await lifecycle.ensureVisitorSession();
280
220
  return apiConfig.httpClient.get(
281
- `${base(store_id)}/carts/${params.id}`,
221
+ `${base}/carts/${params.id}`,
282
222
  {
283
223
  ...options,
284
224
  params: params.token ? { token: params.token } : options?.params
285
225
  }
286
226
  );
287
227
  },
288
- update(params, options) {
289
- const { store_id, items, ...payload } = params;
290
- const target = store_id || apiConfig.storeId;
228
+ async update(params, options) {
229
+ await lifecycle.ensureVisitorSession();
230
+ const { items, ...payload } = params;
291
231
  return apiConfig.httpClient.put(
292
- `${base(target)}/carts/${params.id}`,
232
+ `${base}/carts/${params.id}`,
293
233
  {
294
234
  ...payload,
295
- store_id: target,
296
235
  ...items ? { items: sanitizePublicCheckoutItems(items) } : {}
297
236
  },
298
237
  options
299
238
  );
300
239
  },
301
- addItem(params, options) {
302
- const { store_id, item, ...payload } = params;
303
- const target = store_id || apiConfig.storeId;
240
+ async addItem(params, options) {
241
+ await lifecycle.ensureVisitorSession();
242
+ const { item, ...payload } = params;
304
243
  return apiConfig.httpClient.post(
305
- `${base(target)}/carts/${params.id}/items`,
306
- {
307
- ...payload,
308
- store_id: target,
309
- item: sanitizePublicCheckoutItems([item])[0]
310
- },
244
+ `${base}/carts/${params.id}/items`,
245
+ { ...payload, item: sanitizePublicCheckoutItems([item])[0] },
311
246
  options
312
247
  );
313
248
  },
314
- removeItem(params, options) {
315
- const { store_id, ...payload } = params;
316
- const target = store_id || apiConfig.storeId;
249
+ async removeItem(params, options) {
250
+ await lifecycle.ensureVisitorSession();
317
251
  return apiConfig.httpClient.post(
318
- `${base(target)}/carts/${params.id}/items/remove`,
319
- { ...payload, store_id: target },
252
+ `${base}/carts/${params.id}/items/remove`,
253
+ params,
320
254
  options
321
255
  );
322
256
  },
323
- clear(params, options) {
324
- const store_id = params.store_id || apiConfig.storeId;
257
+ async clear(params, options) {
258
+ await lifecycle.ensureVisitorSession();
325
259
  return apiConfig.httpClient.post(
326
- `${base(store_id)}/carts/${params.id}/clear`,
327
- { id: params.id, store_id },
260
+ `${base}/carts/${params.id}/clear`,
261
+ { id: params.id },
328
262
  options
329
263
  );
330
264
  },
331
- quote(params, options) {
332
- const store_id = params.store_id || apiConfig.storeId;
265
+ async quote(params, options) {
266
+ await lifecycle.ensureVisitorSession();
333
267
  return apiConfig.httpClient.post(
334
- `${base(store_id)}/carts/${params.id}/quote`,
335
- { id: params.id, store_id },
268
+ `${base}/carts/${params.id}/quote`,
269
+ { id: params.id },
336
270
  options
337
271
  );
338
272
  },
339
- checkout(params, options) {
340
- const store_id = params.store_id || apiConfig.storeId;
273
+ async checkout(params, options) {
274
+ await lifecycle.ensureVisitorSession();
341
275
  return apiConfig.httpClient.post(
342
- `${base(store_id)}/carts/${params.id}/checkout`,
276
+ `${base}/carts/${params.id}/checkout`,
343
277
  {
344
278
  id: params.id,
345
- store_id,
346
279
  payment_method_key: params.payment_method_key,
347
280
  confirmation_token_id: params.confirmation_token_id,
348
281
  return_url: params.return_url
@@ -352,115 +285,85 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
352
285
  }
353
286
  },
354
287
  order: {
355
- get(params, options) {
356
- const store_id = params.store_id || apiConfig.storeId;
288
+ async get(params, options) {
289
+ await lifecycle.ensureVisitorSession();
357
290
  return apiConfig.httpClient.get(
358
- `${base(store_id)}/orders/${params.id}`,
291
+ `${base}/orders/${params.id}`,
359
292
  options
360
293
  );
361
294
  },
362
- find(params, options) {
363
- const { store_id, ...queryParams } = params;
295
+ async find(params, options) {
296
+ await lifecycle.ensureVisitorSession();
364
297
  return apiConfig.httpClient.get(
365
- `${base(store_id)}/orders`,
366
- {
367
- ...options,
368
- params: queryParams
369
- }
298
+ `${base}/orders`,
299
+ { ...options, params }
370
300
  );
371
301
  },
372
- downloadDigitalAccess(params, options) {
373
- const store_id = params.store_id || apiConfig.storeId;
302
+ async downloadDigitalAccess(params, options) {
303
+ await lifecycle.ensureVisitorSession();
374
304
  return apiConfig.httpClient.post(
375
- `${base(store_id)}/orders/${params.order_id}/digital-access/${params.grant_id}/download`,
305
+ `${base}/orders/${params.order_id}/digital-access/${params.grant_id}/download`,
376
306
  {},
377
307
  options
378
308
  );
379
309
  },
380
- findDigitalAccess(params, options) {
381
- const { order_id, store_id, ...queryParams } = params;
310
+ async findDigitalAccess(params, options) {
311
+ await lifecycle.ensureVisitorSession();
312
+ const { order_id, ...queryParams } = params;
382
313
  return apiConfig.httpClient.get(
383
- `${base(store_id)}/orders/${order_id}/digital-access`,
314
+ `${base}/orders/${order_id}/digital-access`,
384
315
  { ...options, params: queryParams }
385
316
  );
386
317
  },
387
- getDigitalAccess(params, options) {
388
- const store_id = params.store_id || apiConfig.storeId;
318
+ async getDigitalAccess(params, options) {
319
+ await lifecycle.ensureVisitorSession();
389
320
  return apiConfig.httpClient.get(
390
- `${base(store_id)}/orders/${params.order_id}/digital-access/${params.grant_id}`,
321
+ `${base}/orders/${params.order_id}/digital-access/${params.grant_id}`,
391
322
  options
392
323
  );
393
324
  }
394
325
  },
395
326
  service: {
396
327
  get(params, options) {
397
- const store_id = params.store_id || apiConfig.storeId;
398
- let identifier;
399
- if (params.id) {
400
- identifier = params.id;
401
- } else if (params.slug) {
402
- identifier = `${store_id}:${apiConfig.locale}:${params.slug}`;
403
- } else {
404
- throw new Error("GetServiceParams requires id or slug");
405
- }
328
+ const identifier = params.id ?? params.slug;
329
+ if (!identifier) throw new Error("GetServiceParams requires id or slug");
406
330
  return apiConfig.httpClient.get(
407
- `${base(store_id)}/services/${identifier}`,
331
+ `${base}/services/${identifier}`,
408
332
  options
409
333
  );
410
334
  },
411
335
  find(params, options) {
412
- const { store_id, ...queryParams } = params;
413
336
  return apiConfig.httpClient.get(
414
- `${base(store_id)}/services`,
415
- {
416
- ...options,
417
- params: queryParams
418
- }
337
+ `${base}/services`,
338
+ { ...options, params }
419
339
  );
420
340
  },
421
341
  findProviders(params, options) {
422
- const { store_id, ...queryParams } = params;
423
342
  return apiConfig.httpClient.get(
424
- `${base(store_id)}/service-providers`,
425
- {
426
- ...options,
427
- params: queryParams
428
- }
343
+ `${base}/service-providers`,
344
+ { ...options, params }
429
345
  );
430
346
  },
431
347
  getAvailability(params, options) {
432
- const { store_id, ...queryParams } = params;
433
- const target_store_id = store_id || apiConfig.storeId;
434
348
  return apiConfig.httpClient.get(
435
- `${base(target_store_id)}/services/availability`,
436
- { ...options, params: queryParams }
349
+ `${base}/services/availability`,
350
+ { ...options, params }
437
351
  );
438
352
  }
439
353
  },
440
354
  provider: {
441
355
  get(params, options) {
442
- const store_id = params.store_id || apiConfig.storeId;
443
- let identifier;
444
- if (params.id) {
445
- identifier = params.id;
446
- } else if (params.slug) {
447
- identifier = `${store_id}:${apiConfig.locale}:${params.slug}`;
448
- } else {
449
- throw new Error("GetProviderParams requires id or slug");
450
- }
356
+ const identifier = params.id ?? params.slug;
357
+ if (!identifier) throw new Error("GetProviderParams requires id or slug");
451
358
  return apiConfig.httpClient.get(
452
- `${base(store_id)}/providers/${identifier}`,
359
+ `${base}/providers/${identifier}`,
453
360
  options
454
361
  );
455
362
  },
456
363
  find(params, options) {
457
- const { store_id, ...queryParams } = params;
458
364
  return apiConfig.httpClient.get(
459
- `${base(store_id)}/providers`,
460
- {
461
- ...options,
462
- params: queryParams
463
- }
365
+ `${base}/providers`,
366
+ { ...options, params }
464
367
  );
465
368
  }
466
369
  }
@@ -474,36 +377,23 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
474
377
  return submitIdentification("code", params, options);
475
378
  },
476
379
  async verify(params, options) {
477
- const store_id = apiConfig.storeId;
478
380
  const result = await apiConfig.httpClient.post(
479
- `${base(store_id)}/account/verify`,
480
- { store_id, challenge_id: params.challenge_id, code: params.code },
381
+ `${base}/account/verify`,
382
+ params,
481
383
  options
482
384
  );
483
- if (result?.token?.token) {
484
- const pending = pendingVerifications.get(params.challenge_id);
485
- const identifiedStore = pending?.store || await apiConfig.httpClient.get(base(store_id), options);
486
- updateContactSession(
487
- (prev) => prev ? {
488
- ...prev,
489
- access_token: result.token.token,
490
- contact: result.contact
491
- } : {
492
- access_token: result.token.token,
493
- contact: result.contact,
494
- store: identifiedStore,
495
- market: pending?.market || null
496
- }
497
- );
498
- pendingVerifications.delete(params.challenge_id);
385
+ if (result.token?.token) {
386
+ updateContactSession(() => ({
387
+ sessionToken: result.token.token,
388
+ contact: result.contact
389
+ }));
499
390
  }
500
391
  return result;
501
392
  },
502
393
  async logout(options) {
503
- const store_id = apiConfig.storeId;
504
394
  try {
505
395
  await apiConfig.httpClient.post(
506
- `${base(store_id)}/account/logout`,
396
+ `${base}/account/logout`,
507
397
  {},
508
398
  options
509
399
  );
@@ -513,110 +403,74 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
513
403
  },
514
404
  getMe(options) {
515
405
  return apiConfig.httpClient.get(
516
- `${base()}/account/me`,
406
+ `${base}/account/me`,
517
407
  options
518
408
  );
519
409
  }
520
410
  },
521
411
  contactList: {
522
412
  get(params, options) {
523
- const store_id = params.store_id || apiConfig.storeId;
524
413
  return apiConfig.httpClient.get(
525
- `${base(store_id)}/contact-lists/${params.id}`,
414
+ `${base}/contact-lists/${params.id}`,
526
415
  options
527
416
  );
528
417
  },
529
- find(params, options) {
530
- const { store_id, ...queryParams } = params || {};
418
+ find(params = {}, options) {
531
419
  return apiConfig.httpClient.get(
532
- `${base(store_id)}/contact-lists`,
533
- {
534
- ...options,
535
- params: queryParams
536
- }
420
+ `${base}/contact-lists`,
421
+ { ...options, params }
537
422
  );
538
423
  },
539
424
  plans: {
540
425
  find(params, options) {
541
- const { store_id, contact_list_id, ...queryParams } = params;
426
+ const { contact_list_id, ...queryParams } = params;
542
427
  return apiConfig.httpClient.get(
543
- `${base(store_id)}/contact-lists/${contact_list_id}/plans`,
428
+ `${base}/contact-lists/${contact_list_id}/plans`,
544
429
  { ...options, params: queryParams }
545
430
  );
546
431
  }
547
432
  },
548
433
  memberships: {
549
- find(params, options) {
550
- const { store_id, ...queryParams } = params || {};
551
- return apiConfig.httpClient.get(`${base(store_id)}/contact-lists/memberships`, {
434
+ async find(params = {}, options) {
435
+ await lifecycle.ensureVisitorSession();
436
+ return apiConfig.httpClient.get(`${base}/contact-lists/memberships`, {
552
437
  ...options,
553
- params: queryParams
438
+ params
554
439
  });
555
440
  }
556
441
  },
557
- subscribe(params, options) {
558
- const { store_id, id, ...payload } = params;
442
+ async subscribe(params, options) {
443
+ await lifecycle.ensureVisitorSession();
444
+ const { id, ...payload } = params;
559
445
  return apiConfig.httpClient.post(
560
- `${base(store_id)}/contact-lists/${id}/subscribe`,
446
+ `${base}/contact-lists/${id}/subscribe`,
561
447
  payload,
562
448
  options
563
449
  );
564
450
  },
565
- checkAccess(params, options) {
566
- const store_id = params.store_id || apiConfig.storeId;
451
+ async checkAccess(params, options) {
452
+ await lifecycle.ensureVisitorSession();
567
453
  return apiConfig.httpClient.get(
568
- `${base(store_id)}/contact-lists/${params.id}/access`,
569
- options
570
- );
571
- },
572
- checkContentAccess(params, options) {
573
- const { store_id, ...payload } = params;
574
- return apiConfig.httpClient.post(
575
- `${base(store_id)}/contact-lists/access`,
576
- payload,
577
- options
578
- );
579
- },
580
- manage(token, options) {
581
- return apiConfig.httpClient.post(
582
- `${base()}/contact-lists/manage`,
583
- { token },
454
+ `${base}/contact-lists/${params.id}/access`,
584
455
  options
585
456
  );
586
457
  },
587
- unsubscribe(token, options) {
588
- const headers = { ...options?.headers };
589
- for (const name of Object.keys(headers)) {
590
- if (name.toLowerCase() === "content-type") delete headers[name];
591
- }
592
- return apiConfig.httpClient.post(
593
- `${base()}/contact-lists/unsubscribe`,
594
- new URLSearchParams({ "List-Unsubscribe": "One-Click" }),
595
- {
596
- ...options,
597
- params: { ...options?.params || {}, token },
598
- headers: {
599
- ...headers,
600
- "Content-Type": "application/x-www-form-urlencoded"
601
- }
602
- }
603
- );
604
- },
605
- confirm(token, options) {
458
+ async checkContentAccess(params, options) {
459
+ await lifecycle.ensureVisitorSession();
606
460
  return apiConfig.httpClient.post(
607
- `${base()}/contact-lists/confirm`,
608
- { token },
461
+ `${base}/contact-lists/access`,
462
+ params,
609
463
  options
610
464
  );
611
465
  }
612
466
  }
613
467
  },
614
- action: createActionApi(apiConfig),
468
+ action: createActionApi(apiConfig, lifecycle),
615
469
  experiments: {
616
- use(params, options) {
617
- const store_id = params.store_id || apiConfig.storeId;
470
+ async use(params, options) {
471
+ await lifecycle.ensureVisitorSession();
618
472
  return apiConfig.httpClient.post(
619
- `${base(store_id)}/experiments/use`,
473
+ `${base}/experiments/use`,
620
474
  { key: params.key },
621
475
  options
622
476
  );
@@ -776,6 +630,15 @@ function requestError(name, message, details = {}) {
776
630
  function isRecord(value) {
777
631
  return typeof value === "object" && value !== null;
778
632
  }
633
+ function omitStorefrontRouting(value) {
634
+ if (!isRecord(value) || Array.isArray(value)) return value;
635
+ const prototype = Object.getPrototypeOf(value);
636
+ if (prototype !== Object.prototype && prototype !== null) return value;
637
+ const result = { ...value };
638
+ delete result.store_id;
639
+ delete result.market;
640
+ return result;
641
+ }
779
642
  function isTokenSet(value) {
780
643
  return isRecord(value) && typeof value.access_token === "string" && (value.refresh_token === void 0 || typeof value.refresh_token === "string") && (value.access_expires_at === void 0 || typeof value.access_expires_at === "number");
781
644
  }
@@ -851,10 +714,25 @@ function createHttpClient(cfg) {
851
714
  if (!retried && options?.transformRequest) {
852
715
  body = options.transformRequest(body);
853
716
  }
717
+ if (cfg.storefrontMode) {
718
+ body = omitStorefrontRouting(body);
719
+ }
720
+ const forcedHeaders = typeof cfg.forcedHeaders === "function" ? cfg.forcedHeaders() : cfg.forcedHeaders || {};
721
+ const callerHeaders = { ...options?.headers || {} };
722
+ const protectedHeaderNames = new Set(
723
+ Object.keys(forcedHeaders).map((name) => name.toLowerCase())
724
+ );
725
+ if (cfg.storefrontMode) protectedHeaderNames.add("authorization");
726
+ for (const name of Object.keys(callerHeaders)) {
727
+ if (protectedHeaderNames.has(name.toLowerCase())) {
728
+ delete callerHeaders[name];
729
+ }
730
+ }
854
731
  const headers = {
855
732
  Accept: "application/json",
856
733
  "Content-Type": "application/json",
857
- ...options?.headers || {}
734
+ ...callerHeaders,
735
+ ...forcedHeaders
858
736
  };
859
737
  let tokens = authStorage.getTokens();
860
738
  const nowSec = Date.now() / 1e3;
@@ -865,7 +743,8 @@ function createHttpClient(cfg) {
865
743
  if (tokens?.access_token) {
866
744
  headers["Authorization"] = `Bearer ${tokens.access_token}`;
867
745
  }
868
- const finalPath = options?.params ? path + buildQueryString(options.params) : path;
746
+ const requestParams = cfg.storefrontMode ? omitStorefrontRouting(options?.params) : options?.params;
747
+ const finalPath = requestParams ? path + buildQueryString(requestParams) : path;
869
748
  const fetchOptions = {
870
749
  method,
871
750
  headers,
@@ -896,8 +775,19 @@ function createHttpClient(cfg) {
896
775
  throw err;
897
776
  }
898
777
  if (res.status === 401 && !retried) {
899
- await ensureFreshToken();
900
- return request(method, path, body, options, true);
778
+ if (cfg.onUnauthorized) {
779
+ const recovered = await cfg.onUnauthorized({
780
+ hadAuthorization: Boolean(tokens?.access_token),
781
+ authorizationToken: tokens?.access_token || null,
782
+ path
783
+ });
784
+ if (recovered) {
785
+ return request(method, path, body, options, true);
786
+ }
787
+ } else {
788
+ await ensureFreshToken();
789
+ return request(method, path, body, options, true);
790
+ }
901
791
  }
902
792
  try {
903
793
  const contentLength = res.headers.get("content-length");
@@ -1113,6 +1003,14 @@ var createStoreApi = (apiConfig, _updateSession) => {
1113
1003
  params
1114
1004
  });
1115
1005
  },
1006
+ async regeneratePublishableKey(params = {}, options) {
1007
+ const store_id = params.store_id || apiConfig.storeId;
1008
+ return apiConfig.httpClient.post(
1009
+ `/v1/stores/${store_id}/publishable-key/regenerate`,
1010
+ {},
1011
+ options
1012
+ );
1013
+ },
1116
1014
  async getSubscriptionPlans(_params, options) {
1117
1015
  return apiConfig.httpClient.get("/v1/stores/plans", options);
1118
1016
  },
@@ -2011,7 +1909,10 @@ var createMarketApi = (apiConfig) => {
2011
1909
  async delete(params, options) {
2012
1910
  return apiConfig.httpClient.delete(
2013
1911
  `/v1/stores/${apiConfig.storeId}/markets/${params.id}`,
2014
- options
1912
+ {
1913
+ ...options,
1914
+ params: params.replacement_default_market_id ? { replacement_default_market_id: params.replacement_default_market_id } : options?.params
1915
+ }
2015
1916
  );
2016
1917
  }
2017
1918
  };
@@ -2637,32 +2538,38 @@ function storefrontSupportOptions(supportToken, options) {
2637
2538
  }
2638
2539
  };
2639
2540
  }
2640
- function createStorefrontSupportApi(config) {
2541
+ function createStorefrontSupportApi(config, ensureVisitorSession) {
2641
2542
  const { httpClient } = config;
2642
2543
  return {
2643
2544
  async startConversation(params = {}, opts) {
2644
- const storeId = config.storeId;
2545
+ await ensureVisitorSession();
2645
2546
  return httpClient.post(
2646
- `/v1/storefront/${storeId}/support/conversations`,
2647
- { ...params, store_id: storeId },
2547
+ "/v1/storefront/support/conversations",
2548
+ params,
2648
2549
  opts
2649
2550
  );
2650
2551
  },
2651
2552
  async sendMessage(params, opts) {
2553
+ await ensureVisitorSession();
2652
2554
  const { support_token, ...request } = params;
2653
- const storeId = config.storeId;
2654
2555
  return httpClient.post(
2655
- `/v1/storefront/${storeId}/support/conversations/${request.conversation_id}/messages`,
2656
- { ...request, store_id: storeId },
2556
+ `/v1/storefront/support/conversations/${request.conversation_id}/messages`,
2557
+ request,
2657
2558
  storefrontSupportOptions(support_token, opts)
2658
2559
  );
2659
2560
  },
2660
2561
  async getConversation(params, opts) {
2562
+ await ensureVisitorSession();
2661
2563
  const { support_token, ...request } = params;
2662
- const storeId = config.storeId;
2663
- const qs = supportConversationQuery({ ...request, store_id: storeId });
2564
+ const query = new URLSearchParams();
2565
+ if (request.message_limit) query.set("message_limit", String(request.message_limit));
2566
+ if (typeof request.after_created_at === "number") {
2567
+ query.set("after_created_at", String(request.after_created_at));
2568
+ }
2569
+ if (request.after_id) query.set("after_id", request.after_id);
2570
+ const suffix = query.size ? `?${query}` : "";
2664
2571
  return httpClient.get(
2665
- `/v1/storefront/${storeId}/support/conversations/${request.conversation_id}?${qs}`,
2572
+ `/v1/storefront/support/conversations/${request.conversation_id}${suffix}`,
2666
2573
  storefrontSupportOptions(support_token, opts)
2667
2574
  );
2668
2575
  }
@@ -4113,145 +4020,1918 @@ function getFirstAvailableFCId(variant, quantity = 1) {
4113
4020
  return inv?.location_id;
4114
4021
  }
4115
4022
 
4116
- // src/index.ts
4117
- var SDK_VERSION = "0.9.20";
4118
- var SUPPORTED_FRAMEWORKS = [
4119
- "astro",
4120
- "react",
4121
- "vue",
4122
- "svelte",
4123
- "vanilla"
4124
- ];
4125
- function createUtilitySurface(apiConfig) {
4126
- return {
4127
- getImageUrl: (imageBlock, isBlock2 = true) => getImageUrl(imageBlock, isBlock2),
4128
- getBlockValue,
4129
- getBlockTextValue,
4130
- getBlockContentValue,
4131
- getBlockValues,
4132
- getBlockLabel,
4133
- getBlockObjectValues,
4134
- getBlockFromArray,
4135
- formatBlockValue,
4136
- prepareBlocksForSubmission,
4137
- extractBlockValues,
4138
- formatPrice: (prices) => formatPrice(prices, apiConfig.market),
4139
- getPriceAmount: (prices) => getPriceAmount(prices, apiConfig.market),
4140
- formatPayment,
4141
- formatMinor,
4142
- getCurrencySymbol,
4143
- getCurrencyName,
4144
- validatePhoneNumber,
4145
- tzGroups,
4146
- findTimeZone,
4147
- slugify,
4148
- humanize,
4149
- categorify,
4150
- formatDate,
4151
- getSvgContentForAstro,
4152
- fetchSvgContent,
4153
- injectSvgIntoElement,
4154
- isValidKey,
4155
- validateKey,
4156
- toKey,
4157
- nameToKey,
4158
- getAvailableStock,
4159
- getReservedStock,
4160
- hasStock,
4161
- getInventoryAt,
4162
- getFirstAvailableFCId
4163
- };
4023
+ // src/payments/stripe.ts
4024
+ async function defaultStripeLoader(publishableKey, options) {
4025
+ const { loadStripe } = await import('@stripe/stripe-js');
4026
+ return loadStripe(publishableKey, options);
4164
4027
  }
4165
- var ADMIN_STORAGE_KEY = "arky_admin_session";
4166
- function readAdminSession() {
4167
- if (typeof window === "undefined") return null;
4168
- try {
4169
- const raw = localStorage.getItem(ADMIN_STORAGE_KEY);
4170
- return raw ? JSON.parse(raw) : null;
4171
- } catch {
4172
- return null;
4173
- }
4174
- }
4175
- function writeAdminSession(s) {
4176
- if (typeof window === "undefined") return;
4177
- if (s) {
4178
- localStorage.setItem(ADMIN_STORAGE_KEY, JSON.stringify(s));
4179
- } else {
4180
- localStorage.removeItem(ADMIN_STORAGE_KEY);
4181
- }
4028
+ function normalizeCurrency(currency) {
4029
+ return currency.trim().toLowerCase();
4182
4030
  }
4183
- function createAdmin(config) {
4184
- const locale = config.locale || "en";
4185
- const listeners = /* @__PURE__ */ new Set();
4186
- function toPublic(s) {
4187
- return s ? { email: s.email } : null;
4188
- }
4189
- function emit() {
4190
- const pub = toPublic(readAdminSession());
4191
- for (const l of listeners) {
4192
- Promise.resolve().then(() => l(pub)).catch(() => {
4031
+ async function createStripeConfirmationTokenController(config, stripeLoader = defaultStripeLoader) {
4032
+ const stripe = await stripeLoader(
4033
+ config.publishableKey,
4034
+ config.connectedAccountId ? { stripeAccount: config.connectedAccountId } : void 0
4035
+ );
4036
+ if (!stripe) throw new Error("Stripe failed to initialize");
4037
+ let elements = createElements(stripe, config);
4038
+ let paymentElement = elements.create("payment", {
4039
+ layout: "tabs"
4040
+ });
4041
+ return {
4042
+ mount(target) {
4043
+ if (!paymentElement) {
4044
+ paymentElement = elements.create("payment", { layout: "tabs" });
4045
+ }
4046
+ paymentElement.mount(target);
4047
+ },
4048
+ update(input) {
4049
+ elements.update({
4050
+ ...input.amount !== void 0 ? { amount: input.amount } : {},
4051
+ ...input.currency ? { currency: normalizeCurrency(input.currency) } : {}
4193
4052
  });
4194
- }
4195
- }
4196
- const updateSession = (updater) => {
4197
- if (config.apiToken) return;
4198
- const prev = readAdminSession();
4199
- const next = updater(prev);
4200
- writeAdminSession(next);
4201
- emit();
4202
- };
4203
- const authStorage = config.apiToken ? {
4204
- getTokens: () => ({ access_token: config.apiToken }),
4205
- onTokensRefreshed: () => {
4206
4053
  },
4207
- onForcedLogout: () => {
4208
- }
4209
- } : {
4210
- getTokens() {
4211
- const s = readAdminSession();
4212
- if (!s) return null;
4054
+ async createConfirmationToken(options = {}) {
4055
+ const submitResult = await elements.submit();
4056
+ if (submitResult.error) {
4057
+ throw new Error(submitResult.error.message || "Payment details are incomplete");
4058
+ }
4059
+ const tokenInput = {
4060
+ elements,
4061
+ params: {
4062
+ ...options.return_url ? { return_url: options.return_url } : {},
4063
+ ...options.billing_details ? { payment_method_data: { billing_details: options.billing_details } } : {}
4064
+ }
4065
+ };
4066
+ const result = await stripe.createConfirmationToken(tokenInput);
4067
+ if (result.error) {
4068
+ throw new Error(result.error.message || "Payment confirmation token failed");
4069
+ }
4070
+ if (!result.confirmationToken?.id) {
4071
+ throw new Error("Stripe did not return a confirmation token");
4072
+ }
4213
4073
  return {
4214
- access_token: s.access_token,
4215
- refresh_token: s.refresh_token,
4216
- access_expires_at: s.access_expires_at
4074
+ confirmation_token_id: result.confirmationToken.id,
4075
+ return_url: options.return_url
4217
4076
  };
4218
4077
  },
4219
- onTokensRefreshed(tokens) {
4220
- updateSession(
4221
- (prev) => prev ? {
4222
- ...prev,
4223
- access_token: tokens.access_token,
4224
- refresh_token: tokens.refresh_token ?? prev.refresh_token,
4225
- access_expires_at: tokens.access_expires_at ?? prev.access_expires_at
4226
- } : null
4227
- );
4078
+ async handleNextAction(clientSecret) {
4079
+ const result = await stripe.handleNextAction({ clientSecret });
4080
+ if (result.error) {
4081
+ throw new Error(result.error.message || "Payment authentication failed");
4082
+ }
4228
4083
  },
4229
- onForcedLogout() {
4230
- updateSession(() => null);
4084
+ destroy() {
4085
+ paymentElement?.destroy();
4086
+ paymentElement = null;
4231
4087
  }
4232
4088
  };
4233
- const httpClient = createHttpClient({
4234
- baseUrl: config.baseUrl,
4235
- storeId: config.storeId,
4236
- refreshPath: config.refreshPath,
4237
- navigate: config.navigate,
4238
- loginFallbackPath: config.loginFallbackPath,
4239
- authStorage
4089
+ }
4090
+ function createElements(stripe, config) {
4091
+ return stripe.elements({
4092
+ mode: "payment",
4093
+ amount: config.amount,
4094
+ currency: normalizeCurrency(config.currency),
4095
+ paymentMethodCreation: "manual",
4096
+ ...config.appearance ? { appearance: config.appearance } : {}
4240
4097
  });
4241
- const apiConfig = {
4242
- httpClient,
4243
- storeId: config.storeId,
4244
- baseUrl: config.baseUrl,
4245
- market: config.market,
4246
- locale,
4247
- authStorage
4098
+ }
4099
+
4100
+ // src/storefrontStore/utils.ts
4101
+ function readErrorMessage(error, fallback) {
4102
+ if (error instanceof Error && error.message) return error.message;
4103
+ if (typeof error === "string" && error.length > 0) return error;
4104
+ return fallback;
4105
+ }
4106
+ function createId(prefix) {
4107
+ const cryptoValue = globalThis.crypto;
4108
+ if (cryptoValue && "randomUUID" in cryptoValue) return cryptoValue.randomUUID();
4109
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
4110
+ }
4111
+ function firstLocalized(value, locale) {
4112
+ if (typeof value === "string") return value;
4113
+ if (!value || typeof value !== "object" || Array.isArray(value)) return "";
4114
+ const record = value;
4115
+ const localeValue = record[locale];
4116
+ if (typeof localeValue === "string") return localeValue;
4117
+ const englishValue = record.en;
4118
+ if (typeof englishValue === "string") return englishValue;
4119
+ for (const entry of Object.values(record)) {
4120
+ if (typeof entry === "string") return entry;
4121
+ }
4122
+ return "";
4123
+ }
4124
+ function findBlock2(blocks, keys) {
4125
+ return (blocks || []).find((block) => keys.includes(block.key)) || null;
4126
+ }
4127
+ function blockText(blocks, keys, locale) {
4128
+ const block = findBlock2(blocks, keys);
4129
+ if (!block) return "";
4130
+ return firstLocalized(block.value, locale);
4131
+ }
4132
+ function productName(product, locale) {
4133
+ return blockText(product.blocks, ["name", "title"], locale) || product.key || product.id;
4134
+ }
4135
+ function serviceName(service, locale) {
4136
+ return blockText(service.blocks, ["name", "title"], locale) || service.key || service.id;
4137
+ }
4138
+ function providerName(provider, locale) {
4139
+ return blockText(provider.blocks, ["name", "title"], locale) || provider.key || provider.id;
4140
+ }
4141
+ function entitySlug(entity, locale) {
4142
+ return entity.slug?.[locale] || entity.slug?.en || Object.values(entity.slug || {})[0] || entity.id;
4143
+ }
4144
+ function priceForMarket(prices, market, marketCurrency) {
4145
+ const marketKey = market.trim();
4146
+ if (!marketKey) throw new Error("A market is required to select a product price");
4147
+ const currency = marketCurrency?.trim().toUpperCase();
4148
+ if (!currency) throw new Error(`Market ${marketKey} does not have an authoritative currency`);
4149
+ const marketPrices = prices.filter((candidate) => candidate.market === marketKey);
4150
+ if (marketPrices.length === 0) {
4151
+ throw new Error(`Product is not priced for market ${marketKey}`);
4152
+ }
4153
+ if (marketPrices.some(
4154
+ (candidate) => !Number.isSafeInteger(candidate.amount) || candidate.amount < 0 || candidate.currency.trim().toUpperCase() !== currency
4155
+ )) {
4156
+ throw new Error(`Product has an invalid price for market ${marketKey}`);
4157
+ }
4158
+ const authorizedPrices = marketPrices.filter((candidate) => candidate.contact_list_id);
4159
+ if (authorizedPrices.length > 0) {
4160
+ return authorizedPrices.reduce(
4161
+ (lowest, candidate) => candidate.amount < lowest.amount ? candidate : lowest
4162
+ );
4163
+ }
4164
+ const basePrices = marketPrices.filter((candidate) => !candidate.contact_list_id);
4165
+ if (basePrices.length !== 1) {
4166
+ throw new Error(`Product does not have one base price for market ${marketKey}`);
4167
+ }
4168
+ const price = basePrices[0];
4169
+ return price;
4170
+ }
4171
+ function availableStock(client, variant) {
4172
+ const fromUtility = client.utils.getAvailableStock(variant);
4173
+ if (Number.isFinite(fromUtility)) return fromUtility;
4174
+ const stock = (variant.inventory || []).reduce((total, row) => total + (row.available || 0), 0);
4175
+ return stock > 0 ? stock : void 0;
4176
+ }
4177
+ function locationToAddress(location) {
4178
+ return {
4179
+ country: location.country || "",
4180
+ state: location.state || "",
4181
+ city: location.city || "",
4182
+ postal_code: location.postal_code || "",
4183
+ name: "",
4184
+ street1: "",
4185
+ street2: null
4248
4186
  };
4249
- const accountApi = createAccountApi(apiConfig);
4250
- const authApi = createAuthApi(apiConfig, updateSession);
4251
- const storeApi = createStoreApi(apiConfig);
4252
- const platformApi = createPlatformApi(apiConfig);
4253
- const cmsApi = createCmsApi(apiConfig);
4254
- const eshopApi = createEshopApi(apiConfig);
4187
+ }
4188
+ function createFormEntry(formId, fields) {
4189
+ if (!formId.trim()) throw new Error("formId is required");
4190
+ return { form_id: formId, fields };
4191
+ }
4192
+ function toProductCheckoutItems(items) {
4193
+ return items.map((item) => ({
4194
+ type: "product",
4195
+ id: item.id,
4196
+ product_id: item.product_id,
4197
+ variant_id: item.variant_id,
4198
+ quantity: item.quantity
4199
+ }));
4200
+ }
4201
+ function toServiceCheckoutItems(items) {
4202
+ return items.map((item) => ({
4203
+ type: "service",
4204
+ id: item.id,
4205
+ service_id: item.service_id,
4206
+ provider_id: item.provider_id,
4207
+ slots: [...item.slots].sort((a, b) => a.from - b.from),
4208
+ forms: item.forms
4209
+ }));
4210
+ }
4211
+ function formValueError(field, message) {
4212
+ return new Error(`Invalid value for form field '${field.key}': ${message}`);
4213
+ }
4214
+ function isValidGeoLocation(value) {
4215
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
4216
+ const location = value;
4217
+ if (location.label !== void 0 && location.label !== null && typeof location.label !== "string") {
4218
+ return false;
4219
+ }
4220
+ const coordinates = location.coordinates;
4221
+ if (!coordinates || typeof coordinates !== "object") return false;
4222
+ return Number.isFinite(coordinates.lat) && Number.isFinite(coordinates.lon) && coordinates.lat >= -90 && coordinates.lat <= 90 && coordinates.lon >= -180 && coordinates.lon <= 180;
4223
+ }
4224
+ function buildFormField(field, value) {
4225
+ const common = { id: field.id, key: field.key };
4226
+ switch (field.type) {
4227
+ case "text":
4228
+ if (typeof value !== "string") throw formValueError(field, "expected text");
4229
+ if (field.required && value.trim().length === 0) throw formValueError(field, "required text is blank");
4230
+ return { ...common, type: "text", value };
4231
+ case "number":
4232
+ if (typeof value !== "number" || !Number.isFinite(value)) {
4233
+ throw formValueError(field, "expected a finite number");
4234
+ }
4235
+ if (field.min !== null && field.min !== void 0 && value < field.min) {
4236
+ throw formValueError(field, `must be at least ${field.min}`);
4237
+ }
4238
+ if (field.max !== null && field.max !== void 0 && value > field.max) {
4239
+ throw formValueError(field, `must be at most ${field.max}`);
4240
+ }
4241
+ return { ...common, type: "number", value };
4242
+ case "boolean":
4243
+ if (typeof value !== "boolean") throw formValueError(field, "expected a boolean");
4244
+ return { ...common, type: "boolean", value };
4245
+ case "date":
4246
+ if (typeof value !== "number" || !Number.isSafeInteger(value)) {
4247
+ throw formValueError(field, "expected an integer timestamp");
4248
+ }
4249
+ return { ...common, type: "date", value };
4250
+ case "geo_location":
4251
+ if (!isValidGeoLocation(value)) throw formValueError(field, "expected valid coordinates");
4252
+ return { ...common, type: "geo_location", value };
4253
+ case "select": {
4254
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
4255
+ throw formValueError(field, "expected a list of options");
4256
+ }
4257
+ const selected = value;
4258
+ if (field.required && selected.length === 0) throw formValueError(field, "at least one option is required");
4259
+ if (new Set(selected).size !== selected.length || selected.some((item) => !field.options.includes(item))) {
4260
+ throw formValueError(field, "contains an unknown or duplicate option");
4261
+ }
4262
+ return { ...common, type: "select", value: selected };
4263
+ }
4264
+ }
4265
+ }
4266
+ function isEmptyOptionalValue(field, value) {
4267
+ if (field.required) return false;
4268
+ if (field.type === "text") return value === "";
4269
+ if (field.type === "select") return Array.isArray(value) && value.length === 0;
4270
+ if (field.type === "geo_location") {
4271
+ return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0);
4272
+ }
4273
+ return false;
4274
+ }
4275
+ function buildFormFields(schema, values) {
4276
+ const knownKeys = new Set(schema.map((field) => field.key));
4277
+ const unknownKey = Object.keys(values).find((key) => !knownKeys.has(key));
4278
+ if (unknownKey) throw new Error(`Form field '${unknownKey}' is not defined by the form schema`);
4279
+ const fields = [];
4280
+ for (const field of schema) {
4281
+ const value = values[field.key];
4282
+ if (value === void 0) {
4283
+ if (field.required) throw formValueError(field, "required value is missing");
4284
+ continue;
4285
+ }
4286
+ if (isEmptyOptionalValue(field, value)) continue;
4287
+ fields.push(buildFormField(field, value));
4288
+ }
4289
+ return fields;
4290
+ }
4291
+ function createFormEntryFromValues(form, values) {
4292
+ return createFormEntry(form.id, buildFormFields(form.schema, values));
4293
+ }
4294
+ function getFormBlockType(field) {
4295
+ if (field.key === "email") return "email";
4296
+ if (field.key === "phone") return "phone";
4297
+ if (field.type === "geo_location") return "address";
4298
+ return field.type;
4299
+ }
4300
+ function getFormBlockValue(field) {
4301
+ if (field.type === "boolean") return false;
4302
+ if (field.type === "select") return [];
4303
+ if (field.type === "geo_location") return {};
4304
+ if (field.type === "number" || field.type === "date") return void 0;
4305
+ return "";
4306
+ }
4307
+ function formSchemaToBlock(field) {
4308
+ const min = field.type === "number" ? field.min : void 0;
4309
+ const max = field.type === "number" ? field.max : void 0;
4310
+ const options = field.type === "select" ? field.options : void 0;
4311
+ return {
4312
+ id: field.id,
4313
+ key: field.key,
4314
+ type: getFormBlockType(field),
4315
+ properties: {
4316
+ isRequired: field.required,
4317
+ minValues: field.required ? 1 : 0,
4318
+ min,
4319
+ max,
4320
+ options,
4321
+ pattern: field.key === "email" ? "^.+@.+\\..+$" : field.key === "phone" ? "^.{6,20}$" : void 0
4322
+ },
4323
+ value: getFormBlockValue(field)
4324
+ };
4325
+ }
4326
+ function formatServiceTime(ts, tz) {
4327
+ return new Date(ts * 1e3).toLocaleTimeString([], {
4328
+ hour: "2-digit",
4329
+ minute: "2-digit",
4330
+ timeZone: tz
4331
+ });
4332
+ }
4333
+ function formatServiceSlotTime(from, to, tz) {
4334
+ return `${formatServiceTime(from, tz)} - ${formatServiceTime(to, tz)}`;
4335
+ }
4336
+ function getSlotsForDate(availability, dateStr, providerId) {
4337
+ if (!availability) return [];
4338
+ const slots = [];
4339
+ for (const provider of availability.providers) {
4340
+ if (providerId && provider.provider_id !== providerId) continue;
4341
+ const day = provider.days.find((candidate) => candidate.date === dateStr);
4342
+ if (!day) continue;
4343
+ for (const slot of day.slots) {
4344
+ if (slot.spots > 0) slots.push({ from: slot.from, to: slot.to, providerId: provider.provider_id });
4345
+ }
4346
+ }
4347
+ return slots.sort((a, b) => a.from - b.from);
4348
+ }
4349
+ function hasAvailableSlotsForDate(availability, dateStr, providerId) {
4350
+ if (!availability) return false;
4351
+ return availability.providers.some((provider) => {
4352
+ if (providerId && provider.provider_id !== providerId) return false;
4353
+ const day = provider.days.find((candidate) => candidate.date === dateStr);
4354
+ return !!day?.slots.some((slot) => slot.spots > 0);
4355
+ });
4356
+ }
4357
+ var SERVICE_WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
4358
+ function createServiceInitialState() {
4359
+ return {
4360
+ service: null,
4361
+ availability: null,
4362
+ providers: [],
4363
+ serviceProviders: [],
4364
+ selectedProviderId: null,
4365
+ currentMonth: new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1),
4366
+ calendar: [],
4367
+ selectedDate: null,
4368
+ slots: [],
4369
+ selectedSlot: null,
4370
+ timezone: typeof window !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : "UTC",
4371
+ tzGroups: {},
4372
+ loading: false,
4373
+ weekdays: SERVICE_WEEKDAYS,
4374
+ quote: null,
4375
+ fetchingQuote: false,
4376
+ quoteError: null,
4377
+ currency: null,
4378
+ dateTimeConfirmed: false,
4379
+ availablePaymentMethods: [],
4380
+ cartId: null,
4381
+ promoCode: null
4382
+ };
4383
+ }
4384
+ function normalizeTimezoneGroups(groups) {
4385
+ const normalized = {};
4386
+ for (const group of groups) {
4387
+ normalized[group.label] = group.zones.map((zone) => ({
4388
+ zone: zone.value,
4389
+ name: zone.label
4390
+ }));
4391
+ }
4392
+ return normalized;
4393
+ }
4394
+
4395
+ // src/storefrontStore/initialize.ts
4396
+ function firstFiniteNumber(...values) {
4397
+ return values.find((value) => typeof value === "number" && Number.isFinite(value));
4398
+ }
4399
+ function initializeStoreCore(publishableKey, config, scopedClient) {
4400
+ const client = scopedClient || createStorefront(publishableKey, config);
4401
+ const session = atom(client.session);
4402
+ const setup = atom(null);
4403
+ const locale = atom(config.locale || client.getLocale());
4404
+ const market_key = atom(config.market || client.getMarket());
4405
+ const market = computed([setup, market_key], (setupValue, marketKey) => {
4406
+ const resolvedKey = marketKey || setupValue?.markets.default;
4407
+ return setupValue?.markets.available.find((candidate) => candidate.key === resolvedKey) || null;
4408
+ });
4409
+ const currency = computed(market, (value) => value?.currency || null);
4410
+ const allowed_payment_methods = computed(market, (value) => value?.payment_methods || []);
4411
+ const payment_config = computed([setup, market], (setupValue, marketValue) => {
4412
+ const methods = marketValue?.payment_methods || [];
4413
+ const hasCreditCard = methods.some((method) => method.type === "credit_card");
4414
+ return {
4415
+ provider: setupValue?.payment || null,
4416
+ enabled: hasCreditCard && Boolean(setupValue?.readiness.payment)
4417
+ };
4418
+ });
4419
+ const cart = atom(null);
4420
+ const product_items = atom([]);
4421
+ const service_items = atom([]);
4422
+ const quote = atom(null);
4423
+ const promo_code = atom(null);
4424
+ const last_order = atom(null);
4425
+ const payment_controller = atom(null);
4426
+ const payment_ready = computed(payment_controller, (value) => value !== null);
4427
+ const cart_status = map({
4428
+ loading: false,
4429
+ syncing: false,
4430
+ fetching_quote: false,
4431
+ processing_checkout: false,
4432
+ error: null,
4433
+ quote_error: null,
4434
+ selected_shipping_method_id: null,
4435
+ user_token: null
4436
+ });
4437
+ function rawProductItemCount(value) {
4438
+ return (value?.items || []).reduce((total, item) => {
4439
+ if (item.type !== "product") return total;
4440
+ return total + (item.quantity || 0);
4441
+ }, 0);
4442
+ }
4443
+ function rawServiceItemCount(value) {
4444
+ return (value?.items || []).reduce((total, item) => {
4445
+ if (item.type !== "service") return total;
4446
+ return total + Math.max(1, item.slots?.length || 0);
4447
+ }, 0);
4448
+ }
4449
+ const product_item_count = computed(
4450
+ [cart, product_items],
4451
+ (cartValue, items) => Math.max(
4452
+ rawProductItemCount(cartValue),
4453
+ items.reduce((total, item) => total + (item.quantity || 0), 0)
4454
+ )
4455
+ );
4456
+ const service_item_count = computed(
4457
+ [cart, service_items],
4458
+ (cartValue, items) => Math.max(
4459
+ rawServiceItemCount(cartValue),
4460
+ items.reduce((total, item) => total + Math.max(1, item.slots.length), 0)
4461
+ )
4462
+ );
4463
+ const item_count = computed(
4464
+ [cart, product_item_count, service_item_count],
4465
+ (cartValue, products, services) => Math.max(cartValue?.item_count || 0, products + services)
4466
+ );
4467
+ const snapshot = computed([cart, product_items, service_items, item_count], (cartValue, products, services, count) => ({
4468
+ cart: cartValue,
4469
+ product_items: products,
4470
+ service_items: services,
4471
+ item_count: count
4472
+ }));
4473
+ let cartWriteRevision = 0;
4474
+ let sessionRequest = null;
4475
+ let cartRequest = null;
4476
+ function nextCartWriteRevision() {
4477
+ cartWriteRevision += 1;
4478
+ return cartWriteRevision;
4479
+ }
4480
+ const cms_state = map({
4481
+ entries: {},
4482
+ forms: {},
4483
+ loading: false,
4484
+ error: null
4485
+ });
4486
+ const eshop_state = map({
4487
+ products: [],
4488
+ services: [],
4489
+ providers: [],
4490
+ product_cursor: null,
4491
+ service_cursor: null,
4492
+ provider_cursor: null,
4493
+ availability: null,
4494
+ loading_products: false,
4495
+ loading_services: false,
4496
+ loading_providers: false,
4497
+ loading_availability: false,
4498
+ error: null
4499
+ });
4500
+ const service_state = map(createServiceInitialState());
4501
+ const service_form_definitions = /* @__PURE__ */ new Map();
4502
+ const service_form_state = map({
4503
+ provider_id: null,
4504
+ groups: [],
4505
+ loading: false,
4506
+ error: null
4507
+ });
4508
+ const service_form_groups = computed(service_form_state, (state) => state.groups);
4509
+ const service_form_blocks = computed(service_form_groups, (groups) => groups.flatMap((group) => group.blocks));
4510
+ client.onAuthStateChanged((value) => session.set(value));
4511
+ currency.subscribe((value) => service_state.setKey("currency", value));
4512
+ market.subscribe((value) => {
4513
+ const methods = value?.payment_methods || [];
4514
+ if (methods.length && service_state.get().availablePaymentMethods.length === 0) {
4515
+ service_state.setKey("availablePaymentMethods", methods);
4516
+ }
4517
+ });
4518
+ function currentMarketKey() {
4519
+ return market_key.get() || client.getMarket() || setup.get()?.markets.default || "";
4520
+ }
4521
+ function currentLocale() {
4522
+ return locale.get() || client.getLocale() || setup.get()?.languages.default || "en";
4523
+ }
4524
+ function currentStripePublishableKey() {
4525
+ const provider = payment_config.get()?.provider;
4526
+ return provider?.publishable_key || null;
4527
+ }
4528
+ function currentStripeConnectedAccountId() {
4529
+ const provider = payment_config.get()?.provider;
4530
+ return provider?.connected_account_id || null;
4531
+ }
4532
+ function currentPaymentAmount() {
4533
+ return Math.max(
4534
+ 0,
4535
+ firstFiniteNumber(quote.get()?.charge_amount, cart.get()?.quote_snapshot?.charge_amount, cart.get()?.quote_snapshot?.total) ?? 0
4536
+ );
4537
+ }
4538
+ function currentPaymentCurrency() {
4539
+ return quote.get()?.money?.currency?.trim() || cart.get()?.quote_snapshot?.money?.currency?.trim() || null;
4540
+ }
4541
+ function setPaymentController(controller) {
4542
+ const current = payment_controller.get();
4543
+ if (current && current !== controller) {
4544
+ current.destroy();
4545
+ }
4546
+ payment_controller.set(controller);
4547
+ return controller;
4548
+ }
4549
+ function destroyPaymentController() {
4550
+ setPaymentController(null);
4551
+ }
4552
+ async function loadSetup() {
4553
+ const current = setup.get();
4554
+ if (current) return current;
4555
+ const result = await client.getSetup();
4556
+ setup.set(result);
4557
+ if (!market_key.get() && result.markets.default) {
4558
+ market_key.set(result.markets.default);
4559
+ }
4560
+ if (!locale.get() && result.languages.default) {
4561
+ locale.set(result.languages.default);
4562
+ }
4563
+ return result;
4564
+ }
4565
+ async function mountStripePayment(target, options = {}) {
4566
+ await loadSetup();
4567
+ const publishableKey2 = currentStripePublishableKey();
4568
+ if (!publishableKey2) {
4569
+ throw new Error("Stripe card payment is not configured for this Store");
4570
+ }
4571
+ const hasExplicitAmount = options.amount !== void 0;
4572
+ const hasExplicitCurrency = Boolean(options.currency?.trim());
4573
+ if (hasExplicitAmount !== hasExplicitCurrency) {
4574
+ throw new Error("Stripe amount and currency must be supplied together");
4575
+ }
4576
+ const amount = hasExplicitAmount ? options.amount : currentPaymentAmount();
4577
+ if (!Number.isSafeInteger(amount) || amount <= 0) {
4578
+ throw new Error("A positive minor-unit payment amount is required to mount card payment");
4579
+ }
4580
+ const paymentCurrency = hasExplicitCurrency ? options.currency.trim() : currentPaymentCurrency();
4581
+ if (!paymentCurrency || !/^[a-z]{3}$/i.test(paymentCurrency)) {
4582
+ throw new Error("An explicit three-letter payment currency is required to mount card payment");
4583
+ }
4584
+ const controller = await createStripeConfirmationTokenController({
4585
+ publishableKey: publishableKey2,
4586
+ connectedAccountId: currentStripeConnectedAccountId() || void 0,
4587
+ amount,
4588
+ currency: paymentCurrency,
4589
+ ...options.appearance ? { appearance: options.appearance } : {}
4590
+ });
4591
+ controller.mount(target);
4592
+ setPaymentController(controller);
4593
+ return controller;
4594
+ }
4595
+ function updatePaymentController(input) {
4596
+ payment_controller.get()?.update(input);
4597
+ }
4598
+ async function ensureSession() {
4599
+ const current = session.get();
4600
+ if (client.isAuthenticated) return current;
4601
+ if (!sessionRequest) {
4602
+ sessionRequest = identify().finally(() => {
4603
+ sessionRequest = null;
4604
+ });
4605
+ }
4606
+ return sessionRequest;
4607
+ }
4608
+ async function identify(params = {}) {
4609
+ if (params.market) setMarket(params.market);
4610
+ const result = await client.identify({
4611
+ ...params,
4612
+ market: params.market || currentMarketKey()
4613
+ });
4614
+ session.set({
4615
+ contact: result.contact
4616
+ });
4617
+ return result;
4618
+ }
4619
+ async function identifyContactEmailIfMissing(email) {
4620
+ const normalizedEmail = email.trim().toLowerCase();
4621
+ if (!normalizedEmail) throw new Error("Contact email is required");
4622
+ const current = session.get();
4623
+ if (current?.contact.email === normalizedEmail) return current;
4624
+ return identify({ email: normalizedEmail });
4625
+ }
4626
+ function setMarket(key) {
4627
+ const next = key.trim();
4628
+ const current = currentMarketKey();
4629
+ if (next && current && next !== current && (cart.get()?.item_count || item_count.get()) > 0) {
4630
+ throw Object.assign(
4631
+ new Error("Market cannot change while the cart contains items"),
4632
+ { code: "CART_MARKET_LOCKED" }
4633
+ );
4634
+ }
4635
+ market_key.set(next);
4636
+ client.setMarket(next);
4637
+ }
4638
+ function setLocale(value) {
4639
+ locale.set(value);
4640
+ client.setLocale(value);
4641
+ }
4642
+ function setContext(context) {
4643
+ if (context.market !== void 0) setMarket(context.market);
4644
+ if (context.locale !== void 0) setLocale(context.locale);
4645
+ }
4646
+ async function ensureCart() {
4647
+ if (cartRequest) return cartRequest;
4648
+ cart_status.setKey("loading", true);
4649
+ cart_status.setKey("error", null);
4650
+ const refreshRevision = cartWriteRevision;
4651
+ cartRequest = (async () => {
4652
+ await ensureSession();
4653
+ const response = await client.eshop.cart.current();
4654
+ await applyCartResponse(response, { ifRevision: refreshRevision });
4655
+ return response;
4656
+ })();
4657
+ try {
4658
+ return await cartRequest;
4659
+ } catch (error) {
4660
+ cart_status.setKey("error", readErrorMessage(error, "Failed to load cart."));
4661
+ throw error;
4662
+ } finally {
4663
+ cartRequest = null;
4664
+ cart_status.setKey("loading", false);
4665
+ }
4666
+ }
4667
+ async function buildProductCartItem(item, source, productHint) {
4668
+ try {
4669
+ const product = productHint?.id === item.product_id ? productHint : await client.eshop.product.get({ id: item.product_id });
4670
+ const variant = product.variants.find((candidate) => candidate.id === item.variant_id);
4671
+ if (!variant) return null;
4672
+ return {
4673
+ id: item.id || createId("product"),
4674
+ product_id: product.id,
4675
+ variant_id: variant.id,
4676
+ product_name: productName(product, currentLocale()),
4677
+ product_slug: entitySlug(product, currentLocale()),
4678
+ variant_attributes: variant.attributes,
4679
+ requires_shipping: variant.requires_shipping !== false,
4680
+ price: priceForMarket(variant.prices, currentMarketKey(), market.get()?.currency),
4681
+ quantity: item.quantity,
4682
+ added_at: source.created_at ? source.created_at * 1e3 : Date.now(),
4683
+ max_stock: availableStock(client, variant)
4684
+ };
4685
+ } catch {
4686
+ return null;
4687
+ }
4688
+ }
4689
+ async function buildServiceCartItems(items) {
4690
+ const rows = [];
4691
+ for (const item of items) {
4692
+ let service = null;
4693
+ let provider = null;
4694
+ try {
4695
+ service = await client.eshop.service.get({ id: item.service_id });
4696
+ } catch {
4697
+ }
4698
+ try {
4699
+ provider = await client.eshop.provider.get({ id: item.provider_id });
4700
+ } catch {
4701
+ }
4702
+ rows.push({
4703
+ id: item.id || createId("service"),
4704
+ service_id: item.service_id,
4705
+ provider_id: item.provider_id,
4706
+ slots: item.slots,
4707
+ forms: item.forms || [],
4708
+ service_name: service ? serviceName(service, currentLocale()) : item.service_id,
4709
+ provider_name: provider ? providerName(provider, currentLocale()) : item.provider_id
4710
+ });
4711
+ }
4712
+ return rows;
4713
+ }
4714
+ async function applyCartResponse(response, options = {}) {
4715
+ if (options.ifRevision !== void 0 && options.ifRevision !== cartWriteRevision) {
4716
+ return cart.get() || response;
4717
+ }
4718
+ cart.set(response);
4719
+ cart_status.setKey("user_token", response.token || null);
4720
+ cart_status.setKey("selected_shipping_method_id", response.shipping_method_id || null);
4721
+ promo_code.set(response.promo_code || null);
4722
+ quote.set(response.quote_snapshot || null);
4723
+ const items = response.items || [];
4724
+ const products = await Promise.all(
4725
+ items.filter((item) => item.type === "product").map((item) => buildProductCartItem(item, response, options.productHint))
4726
+ );
4727
+ const services = await buildServiceCartItems(items.filter((item) => item.type === "service"));
4728
+ product_items.set(products.filter((item) => item !== null));
4729
+ service_items.set(services);
4730
+ return response;
4731
+ }
4732
+ function checkoutItems(input = {}) {
4733
+ return [
4734
+ ...toProductCheckoutItems(input.product_items || product_items.get()),
4735
+ ...toServiceCheckoutItems(input.service_items || service_items.get())
4736
+ ];
4737
+ }
4738
+ async function syncCart(input = {}, writeRevision = nextCartWriteRevision()) {
4739
+ cart_status.setKey("syncing", true);
4740
+ cart_status.setKey("error", null);
4741
+ try {
4742
+ const current = cart.get() || await ensureCart();
4743
+ const response = await client.eshop.cart.update({
4744
+ id: current.id,
4745
+ items: checkoutItems(input),
4746
+ shipping_address: input.shipping_address,
4747
+ billing_address: input.billing_address,
4748
+ forms: input.forms,
4749
+ promo_code: input.promo_code === null ? "" : input.promo_code === void 0 ? promo_code.get() || void 0 : input.promo_code,
4750
+ payment_method_key: input.payment_method_key === null ? "" : input.payment_method_key,
4751
+ shipping_method_id: input.shipping_method_id === null ? "" : input.shipping_method_id === void 0 ? cart_status.get().selected_shipping_method_id || void 0 : input.shipping_method_id
4752
+ });
4753
+ await applyCartResponse(response, { ifRevision: writeRevision });
4754
+ return response;
4755
+ } catch (error) {
4756
+ cart_status.setKey("error", readErrorMessage(error, "Failed to sync cart."));
4757
+ throw error;
4758
+ } finally {
4759
+ cart_status.setKey("syncing", false);
4760
+ }
4761
+ }
4762
+ async function addProduct(product, variant, quantity = 1) {
4763
+ cart_status.setKey("error", null);
4764
+ const writeRevision = nextCartWriteRevision();
4765
+ try {
4766
+ const current = cart.get() || await ensureCart();
4767
+ const response = await client.eshop.cart.addItem({
4768
+ id: current.id,
4769
+ item: {
4770
+ type: "product",
4771
+ product_id: product.id,
4772
+ variant_id: variant.id,
4773
+ quantity
4774
+ }
4775
+ });
4776
+ await applyCartResponse(response, { ifRevision: writeRevision, productHint: product });
4777
+ return response;
4778
+ } catch (error) {
4779
+ cart_status.setKey("error", readErrorMessage(error, "Failed to add product to cart."));
4780
+ throw error;
4781
+ }
4782
+ }
4783
+ async function setProductQuantity(itemId, quantity) {
4784
+ const writeRevision = nextCartWriteRevision();
4785
+ const next = product_items.get().map((item) => {
4786
+ if (item.id !== itemId) return item;
4787
+ const bounded = item.max_stock ? Math.min(Math.max(1, quantity), item.max_stock) : Math.max(1, quantity);
4788
+ return { ...item, quantity: bounded };
4789
+ });
4790
+ product_items.set(next);
4791
+ return syncCart({ product_items: next }, writeRevision);
4792
+ }
4793
+ async function removeProduct(itemId) {
4794
+ const writeRevision = nextCartWriteRevision();
4795
+ const item = product_items.get().find((candidate) => candidate.id === itemId);
4796
+ product_items.set(product_items.get().filter((candidate) => candidate.id !== itemId));
4797
+ const current = cart.get();
4798
+ if (!current || !item) return null;
4799
+ const response = await client.eshop.cart.removeItem({
4800
+ id: current.id,
4801
+ item_id: item.id
4802
+ });
4803
+ await applyCartResponse(response, { ifRevision: writeRevision });
4804
+ return response;
4805
+ }
4806
+ async function addServiceItem(item) {
4807
+ const writeRevision = nextCartWriteRevision();
4808
+ const next = [...service_items.get(), item];
4809
+ service_items.set(next);
4810
+ return syncCart({ service_items: next }, writeRevision);
4811
+ }
4812
+ async function removeServiceItem(itemId) {
4813
+ const writeRevision = nextCartWriteRevision();
4814
+ const next = service_items.get().filter((item) => item.id !== itemId);
4815
+ service_items.set(next);
4816
+ return syncCart({ service_items: next }, writeRevision);
4817
+ }
4818
+ async function clearCart() {
4819
+ const writeRevision = nextCartWriteRevision();
4820
+ const current = cart.get();
4821
+ clearLocalCart();
4822
+ if (!current) return null;
4823
+ const response = await client.eshop.cart.clear({ id: current.id });
4824
+ await applyCartResponse(response, { ifRevision: writeRevision });
4825
+ return response;
4826
+ }
4827
+ function clearLocalCart() {
4828
+ product_items.set([]);
4829
+ service_items.set([]);
4830
+ cart.set(null);
4831
+ quote.set(null);
4832
+ promo_code.set(null);
4833
+ cart_status.setKey("selected_shipping_method_id", null);
4834
+ }
4835
+ async function fetchQuote(input = {}) {
4836
+ if (checkoutItems(input).length === 0) {
4837
+ quote.set(null);
4838
+ return null;
4839
+ }
4840
+ cart_status.setKey("fetching_quote", true);
4841
+ cart_status.setKey("quote_error", null);
4842
+ try {
4843
+ const current = await syncCart(input);
4844
+ const response = await client.eshop.cart.quote({ id: current.id });
4845
+ quote.set(response);
4846
+ return response;
4847
+ } catch (error) {
4848
+ quote.set(null);
4849
+ cart_status.setKey("quote_error", readErrorMessage(error, "Failed to fetch quote."));
4850
+ throw error;
4851
+ } finally {
4852
+ cart_status.setKey("fetching_quote", false);
4853
+ }
4854
+ }
4855
+ async function checkout(input = {}) {
4856
+ if (checkoutItems(input).length === 0) throw new Error("Cart is empty");
4857
+ cart_status.setKey("processing_checkout", true);
4858
+ cart_status.setKey("error", null);
4859
+ try {
4860
+ const current = await syncCart(input);
4861
+ const quoteValue = quote.get();
4862
+ const paymentMethodKey = input.payment_method_key || current.payment_method_key || quoteValue?.money?.payment_method_key || void 0;
4863
+ let chargeAmount = firstFiniteNumber(quoteValue?.charge_amount, current.quote_snapshot?.charge_amount);
4864
+ if (paymentMethodKey === "credit_card" && chargeAmount === void 0) {
4865
+ const latestQuote = await client.eshop.cart.quote({ id: current.id });
4866
+ quote.set(latestQuote);
4867
+ chargeAmount = firstFiniteNumber(latestQuote.charge_amount, latestQuote.total);
4868
+ }
4869
+ if (paymentMethodKey === "credit_card" && (typeof chargeAmount !== "number" || !Number.isSafeInteger(chargeAmount) || chargeAmount < 0)) {
4870
+ throw new Error("Card checkout requires a non-negative integer charge amount in minor units");
4871
+ }
4872
+ const needsConfirmationToken = paymentMethodKey === "credit_card" && typeof chargeAmount === "number" && chargeAmount > 0;
4873
+ let confirmationTokenId;
4874
+ let returnUrl = input.return_url;
4875
+ const paymentController = input.payment ?? payment_controller.get();
4876
+ if (needsConfirmationToken) {
4877
+ if (!paymentController) throw new Error("Payment controller is required for card checkout");
4878
+ returnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : void 0);
4879
+ const token = await paymentController.createConfirmationToken({
4880
+ return_url: returnUrl,
4881
+ billing_details: input.billing_details
4882
+ });
4883
+ confirmationTokenId = token.confirmation_token_id;
4884
+ returnUrl = token.return_url || returnUrl;
4885
+ }
4886
+ const response = await client.eshop.cart.checkout({
4887
+ id: current.id,
4888
+ payment_method_key: paymentMethodKey,
4889
+ confirmation_token_id: confirmationTokenId,
4890
+ return_url: returnUrl
4891
+ });
4892
+ if (response.payment_action.type === "handle_next_action") {
4893
+ if (!paymentController) throw new Error("Payment controller is required for card authentication");
4894
+ await paymentController.handleNextAction(response.payment_action.client_secret);
4895
+ }
4896
+ const stored = {
4897
+ order_id: response.order_id,
4898
+ number: response.number,
4899
+ payment_action: response.payment_action,
4900
+ payment: response.payment,
4901
+ product_items: input.product_items || product_items.get(),
4902
+ service_items: input.service_items || service_items.get(),
4903
+ shipping_address: input.shipping_address || null,
4904
+ billing_address: input.billing_address || null,
4905
+ total: response.payment.amount,
4906
+ currency: response.payment.currency,
4907
+ payment_method_key: paymentMethodKey || null,
4908
+ created_at: Date.now()
4909
+ };
4910
+ last_order.set(stored);
4911
+ if (input.clear_after_checkout !== false) {
4912
+ clearLocalCart();
4913
+ }
4914
+ return response;
4915
+ } catch (error) {
4916
+ cart_status.setKey("error", readErrorMessage(error, "Checkout failed."));
4917
+ throw error;
4918
+ } finally {
4919
+ cart_status.setKey("processing_checkout", false);
4920
+ }
4921
+ }
4922
+ function serviceCalendar() {
4923
+ const state = service_state.get();
4924
+ const { currentMonth, selectedDate, availability, selectedProviderId } = state;
4925
+ const year = currentMonth.getFullYear();
4926
+ const monthIndex = currentMonth.getMonth();
4927
+ const first = new Date(year, monthIndex, 1);
4928
+ const last = new Date(year, monthIndex + 1, 0);
4929
+ const today = /* @__PURE__ */ new Date();
4930
+ today.setHours(0, 0, 0, 0);
4931
+ const cells = [];
4932
+ const pad = (first.getDay() + 6) % 7;
4933
+ for (let i = 0; i < pad; i++) {
4934
+ cells.push({
4935
+ date: /* @__PURE__ */ new Date(0),
4936
+ iso: "",
4937
+ available: false,
4938
+ isSelected: false,
4939
+ isInRange: false,
4940
+ isToday: false,
4941
+ blank: true
4942
+ });
4943
+ }
4944
+ for (let day = 1; day <= last.getDate(); day++) {
4945
+ const date = new Date(year, monthIndex, day);
4946
+ const iso = `${year}-${String(monthIndex + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
4947
+ cells.push({
4948
+ date,
4949
+ iso,
4950
+ available: hasAvailableSlotsForDate(availability, iso, selectedProviderId),
4951
+ isSelected: iso === selectedDate,
4952
+ isInRange: false,
4953
+ isToday: date.getTime() === today.getTime(),
4954
+ blank: false
4955
+ });
4956
+ }
4957
+ const suffix = (7 - cells.length % 7) % 7;
4958
+ for (let i = 0; i < suffix; i++) {
4959
+ cells.push({
4960
+ date: /* @__PURE__ */ new Date(0),
4961
+ iso: "",
4962
+ available: false,
4963
+ isSelected: false,
4964
+ isInRange: false,
4965
+ isToday: false,
4966
+ blank: true
4967
+ });
4968
+ }
4969
+ return cells;
4970
+ }
4971
+ function computeServiceSlots(dateStr) {
4972
+ const state = service_state.get();
4973
+ const { availability, selectedProviderId, timezone, service } = state;
4974
+ return getSlotsForDate(availability, dateStr, selectedProviderId).map((slot, index) => ({
4975
+ id: `${service?.id || "service"}-${slot.from}-${index}`,
4976
+ serviceId: service?.id || "",
4977
+ providerId: slot.providerId,
4978
+ from: slot.from,
4979
+ to: slot.to,
4980
+ timeText: formatServiceSlotTime(slot.from, slot.to, timezone),
4981
+ dateText: new Date(slot.from * 1e3).toLocaleDateString([], {
4982
+ weekday: "short",
4983
+ month: "short",
4984
+ day: "numeric",
4985
+ timeZone: timezone
4986
+ })
4987
+ }));
4988
+ }
4989
+ function toServiceCartItem(slots, forms = []) {
4990
+ const orderedSlots = [...slots].sort((left, right) => left.from - right.from || left.to - right.to);
4991
+ const [first] = orderedSlots;
4992
+ if (!first) throw new Error("At least one service slot is required");
4993
+ if (orderedSlots.some((slot) => slot.serviceId !== first.serviceId || slot.providerId !== first.providerId)) {
4994
+ throw new Error("A booking can only contain slots for one service and provider");
4995
+ }
4996
+ if (orderedSlots.some((slot) => !Number.isSafeInteger(slot.from) || !Number.isSafeInteger(slot.to) || slot.from >= slot.to)) {
4997
+ throw new Error("A booking contains an invalid service slot");
4998
+ }
4999
+ for (let index = 1; index < orderedSlots.length; index += 1) {
5000
+ if (orderedSlots[index - 1].to !== orderedSlots[index].from) {
5001
+ throw new Error("A multi-slot booking must contain adjacent slots");
5002
+ }
5003
+ }
5004
+ return {
5005
+ id: createId("booking"),
5006
+ service_id: first.serviceId,
5007
+ provider_id: first.providerId,
5008
+ slots: orderedSlots.map(({ from, to }) => ({ from, to })),
5009
+ forms,
5010
+ service_name: first.serviceName,
5011
+ date_text: first.dateText,
5012
+ time_text: first.timeText,
5013
+ is_multi_day: orderedSlots.length > 1
5014
+ };
5015
+ }
5016
+ async function syncServiceCart(items) {
5017
+ try {
5018
+ return await syncCart({
5019
+ product_items: product_items.get(),
5020
+ service_items: items
5021
+ });
5022
+ } catch (error) {
5023
+ service_state.setKey("quoteError", readErrorMessage(error, "Failed to sync service cart."));
5024
+ throw error;
5025
+ }
5026
+ }
5027
+ function serviceCurrentStepName() {
5028
+ const state = service_state.get();
5029
+ if (!state.service) return "";
5030
+ if (!state.selectedSlot || !state.dateTimeConfirmed) return "datetime";
5031
+ return "review";
5032
+ }
5033
+ const service_current_step_name = computed(service_state, serviceCurrentStepName);
5034
+ const service_can_proceed = computed(service_state, (state) => {
5035
+ const step = serviceCurrentStepName();
5036
+ if (step === "datetime") {
5037
+ return !!(state.selectedDate && state.selectedSlot);
5038
+ }
5039
+ if (step === "review") return true;
5040
+ return false;
5041
+ });
5042
+ const service_month_year = computed(
5043
+ service_state,
5044
+ (state) => state.currentMonth.toLocaleString(void 0, {
5045
+ month: "long",
5046
+ year: "numeric"
5047
+ })
5048
+ );
5049
+ const service_chain_start = computed(service_items, (items) => {
5050
+ const slots = items.flatMap((item) => item.slots);
5051
+ if (!slots.length) return null;
5052
+ return Math.max(...slots.map((slot) => slot.to));
5053
+ });
5054
+ const service_total_steps = computed(service_state, (state) => state.service ? 2 : 0);
5055
+ const service_steps = computed(service_state, () => ({
5056
+ 1: { name: "datetime" },
5057
+ 2: { name: "review" }
5058
+ }));
5059
+ const service_current_step = computed([service_current_step_name, service_steps], (name, steps) => {
5060
+ for (const [idx, step] of Object.entries(steps)) {
5061
+ if (step.name === name) return Number(idx);
5062
+ }
5063
+ return 1;
5064
+ });
5065
+ function formatServiceDateDisplay(value) {
5066
+ if (!value) return "";
5067
+ return new Date(value).toLocaleDateString(void 0, {
5068
+ month: "short",
5069
+ day: "numeric"
5070
+ });
5071
+ }
5072
+ function configuredServiceFormIds(relationship) {
5073
+ const formIds = relationship.forms.map((entry) => entry.form_id.trim());
5074
+ if (formIds.some((formId) => !formId) || new Set(formIds).size !== formIds.length) {
5075
+ throw new Error(`Service provider ${relationship.provider_id} has blank or duplicate configured form IDs`);
5076
+ }
5077
+ return formIds;
5078
+ }
5079
+ function resolveServiceProvider(state, providerId) {
5080
+ const serviceId = state.service?.id;
5081
+ if (!serviceId) return null;
5082
+ const relationships = state.serviceProviders.filter((relationship) => relationship.service_id === serviceId);
5083
+ const targetProviderId = providerId ?? state.selectedSlot?.providerId ?? state.selectedProviderId;
5084
+ if (targetProviderId) {
5085
+ return relationships.find((relationship) => relationship.provider_id === targetProviderId) || null;
5086
+ }
5087
+ return relationships.length === 1 ? relationships[0] : null;
5088
+ }
5089
+ function clearServiceFormState(error = null, loading = false) {
5090
+ service_form_state.set({
5091
+ provider_id: null,
5092
+ groups: [],
5093
+ loading,
5094
+ error
5095
+ });
5096
+ }
5097
+ function activateServiceProviderForms(providerId, reset = false) {
5098
+ const relationship = resolveServiceProvider(service_state.get(), providerId);
5099
+ if (!relationship) {
5100
+ clearServiceFormState();
5101
+ return null;
5102
+ }
5103
+ const formIds = configuredServiceFormIds(relationship);
5104
+ const current = service_form_state.get();
5105
+ const currentFormIds = current.groups.map((group) => group.form.id);
5106
+ if (!reset && current.provider_id === relationship.provider_id && currentFormIds.length === formIds.length && currentFormIds.every((formId, index) => formId === formIds[index])) {
5107
+ if (current.error) service_form_state.setKey("error", null);
5108
+ return relationship;
5109
+ }
5110
+ const groups = formIds.map((formId) => {
5111
+ const form = service_form_definitions.get(formId);
5112
+ if (!form) throw new Error(`Configured booking form '${formId}' was not loaded`);
5113
+ return {
5114
+ form,
5115
+ blocks: form.schema.map(formSchemaToBlock)
5116
+ };
5117
+ });
5118
+ service_form_state.set({
5119
+ provider_id: relationship.provider_id,
5120
+ groups,
5121
+ loading: false,
5122
+ error: null
5123
+ });
5124
+ return relationship;
5125
+ }
5126
+ async function loadServiceFormDefinitions(relationships) {
5127
+ const formIds = [
5128
+ ...new Set(relationships.flatMap((relationship) => configuredServiceFormIds(relationship)))
5129
+ ];
5130
+ if (formIds.length === 0) return;
5131
+ const forms = await Promise.all(formIds.map((formId) => loadForm({ id: formId })));
5132
+ for (let index = 0; index < forms.length; index += 1) {
5133
+ const form = forms[index];
5134
+ const formId = formIds[index];
5135
+ if (form.id !== formId) {
5136
+ throw new Error(`Configured booking form '${formId}' resolved to '${form.id}'`);
5137
+ }
5138
+ }
5139
+ for (const form of forms) service_form_definitions.set(form.id, form);
5140
+ }
5141
+ function configuredServiceFormEntries(relationship) {
5142
+ const formIds = configuredServiceFormIds(relationship);
5143
+ if (formIds.length === 0) return [];
5144
+ activateServiceProviderForms(relationship.provider_id);
5145
+ const state = service_form_state.get();
5146
+ if (state.provider_id !== relationship.provider_id || state.groups.length !== formIds.length || state.groups.some((group, index) => group.form.id !== formIds[index])) {
5147
+ throw new Error(`Booking forms are not ready for provider ${relationship.provider_id}`);
5148
+ }
5149
+ return state.groups.map(
5150
+ (group) => createFormEntryFromValues(
5151
+ group.form,
5152
+ Object.fromEntries(group.blocks.map((block) => [block.key, block.value]))
5153
+ )
5154
+ );
5155
+ }
5156
+ const service_controller = {
5157
+ async initialize() {
5158
+ service_state.setKey("tzGroups", normalizeTimezoneGroups(client.utils.tzGroups));
5159
+ await ensureCart();
5160
+ const methods = market.get()?.payment_methods || [];
5161
+ if (methods.length) service_state.setKey("availablePaymentMethods", methods);
5162
+ },
5163
+ setTimezone(tz) {
5164
+ service_state.setKey("timezone", tz);
5165
+ service_state.setKey("calendar", serviceCalendar());
5166
+ const state = service_state.get();
5167
+ if (state.selectedDate) {
5168
+ service_state.setKey("slots", computeServiceSlots(state.selectedDate));
5169
+ service_state.setKey("selectedSlot", null);
5170
+ service_state.setKey("quote", null);
5171
+ service_state.setKey("quoteError", null);
5172
+ activateServiceProviderForms();
5173
+ }
5174
+ },
5175
+ async select(service) {
5176
+ service_form_definitions.clear();
5177
+ clearServiceFormState(null, true);
5178
+ service_state.set({
5179
+ ...service_state.get(),
5180
+ service: null,
5181
+ serviceProviders: [],
5182
+ providers: [],
5183
+ selectedProviderId: null,
5184
+ availability: null,
5185
+ selectedDate: null,
5186
+ slots: [],
5187
+ selectedSlot: null,
5188
+ dateTimeConfirmed: false,
5189
+ quote: null,
5190
+ quoteError: null,
5191
+ loading: true
5192
+ });
5193
+ try {
5194
+ const [fullService, serviceProviders] = await Promise.all([
5195
+ client.eshop.service.get({ id: service.id }),
5196
+ client.eshop.service.findProviders({
5197
+ service_id: service.id
5198
+ })
5199
+ ]);
5200
+ const providerIds = [...new Set(serviceProviders.map((relationship) => relationship.provider_id))];
5201
+ const [providerResults] = await Promise.all([
5202
+ Promise.all(providerIds.map((id) => client.eshop.provider.get({ id }).catch(() => null))),
5203
+ loadServiceFormDefinitions(serviceProviders)
5204
+ ]);
5205
+ service_state.set({
5206
+ ...service_state.get(),
5207
+ service: fullService,
5208
+ serviceProviders,
5209
+ providers: providerResults.filter(
5210
+ (provider) => provider !== null
5211
+ ),
5212
+ selectedProviderId: null,
5213
+ availability: null,
5214
+ selectedDate: null,
5215
+ slots: [],
5216
+ selectedSlot: null,
5217
+ currentMonth: new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1),
5218
+ loading: false,
5219
+ dateTimeConfirmed: false,
5220
+ quote: null,
5221
+ quoteError: null
5222
+ });
5223
+ activateServiceProviderForms();
5224
+ await service_controller.loadMonth();
5225
+ } catch (error) {
5226
+ service_form_definitions.clear();
5227
+ clearServiceFormState(readErrorMessage(error, "Failed to load booking forms."));
5228
+ service_state.setKey("loading", false);
5229
+ throw error;
5230
+ }
5231
+ },
5232
+ async loadMonth() {
5233
+ const state = service_state.get();
5234
+ if (!state.service) return;
5235
+ service_state.setKey("loading", true);
5236
+ try {
5237
+ const chainedStart = service_chain_start.get();
5238
+ let from;
5239
+ let to;
5240
+ if (chainedStart) {
5241
+ from = chainedStart;
5242
+ to = chainedStart;
5243
+ } else {
5244
+ const month = state.currentMonth;
5245
+ from = Math.floor(Date.UTC(month.getFullYear(), month.getMonth(), 1) / 1e3);
5246
+ to = Math.floor(Date.UTC(month.getFullYear(), month.getMonth() + 1, 1) / 1e3);
5247
+ }
5248
+ const availability = await loadAvailability({
5249
+ service_id: state.service.id,
5250
+ from,
5251
+ to
5252
+ });
5253
+ service_state.setKey("availability", availability);
5254
+ service_state.setKey("calendar", serviceCalendar());
5255
+ } finally {
5256
+ service_state.setKey("loading", false);
5257
+ }
5258
+ },
5259
+ prevMonth() {
5260
+ const { currentMonth } = service_state.get();
5261
+ service_state.setKey("currentMonth", new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1));
5262
+ void service_controller.loadMonth();
5263
+ },
5264
+ nextMonth() {
5265
+ const { currentMonth } = service_state.get();
5266
+ service_state.setKey("currentMonth", new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1));
5267
+ void service_controller.loadMonth();
5268
+ },
5269
+ selectProvider(providerId) {
5270
+ const state = service_state.get();
5271
+ if (providerId && !resolveServiceProvider(state, providerId)) {
5272
+ throw new Error(`Provider ${providerId} is not configured for the selected service`);
5273
+ }
5274
+ service_state.set({
5275
+ ...state,
5276
+ selectedProviderId: providerId,
5277
+ selectedDate: null,
5278
+ slots: [],
5279
+ selectedSlot: null,
5280
+ dateTimeConfirmed: false,
5281
+ quote: null,
5282
+ quoteError: null
5283
+ });
5284
+ activateServiceProviderForms(providerId);
5285
+ void service_controller.loadMonth();
5286
+ },
5287
+ selectDate(cell) {
5288
+ if (cell.blank || !cell.available) return;
5289
+ const state = service_state.get();
5290
+ service_state.set({
5291
+ ...state,
5292
+ selectedDate: cell.iso,
5293
+ slots: computeServiceSlots(cell.iso),
5294
+ selectedSlot: null,
5295
+ dateTimeConfirmed: false,
5296
+ quote: null,
5297
+ quoteError: null
5298
+ });
5299
+ activateServiceProviderForms();
5300
+ service_state.setKey("calendar", serviceCalendar());
5301
+ },
5302
+ selectTimeSlot(slot) {
5303
+ const state = service_state.get();
5304
+ if (slot) {
5305
+ if (!state.service || slot.serviceId !== state.service.id) {
5306
+ throw new Error("The selected slot does not belong to the selected service");
5307
+ }
5308
+ if (state.selectedProviderId && slot.providerId !== state.selectedProviderId) {
5309
+ throw new Error("The selected slot does not belong to the selected provider");
5310
+ }
5311
+ if (!resolveServiceProvider(state, slot.providerId)) {
5312
+ throw new Error(`Provider ${slot.providerId} is not configured for the selected service`);
5313
+ }
5314
+ }
5315
+ service_state.set({
5316
+ ...state,
5317
+ selectedSlot: slot,
5318
+ dateTimeConfirmed: false,
5319
+ quote: null,
5320
+ quoteError: null
5321
+ });
5322
+ activateServiceProviderForms(slot?.providerId);
5323
+ },
5324
+ resetDateSelection() {
5325
+ service_state.set({
5326
+ ...service_state.get(),
5327
+ selectedDate: null,
5328
+ slots: [],
5329
+ selectedSlot: null,
5330
+ dateTimeConfirmed: false,
5331
+ quote: null,
5332
+ quoteError: null
5333
+ });
5334
+ activateServiceProviderForms();
5335
+ },
5336
+ updateCalendar() {
5337
+ service_state.setKey("calendar", serviceCalendar());
5338
+ },
5339
+ findFirstAvailable() {
5340
+ for (const day of service_state.get().calendar) {
5341
+ if (!day.blank && day.available) {
5342
+ service_controller.selectDate(day);
5343
+ return;
5344
+ }
5345
+ }
5346
+ },
5347
+ async addToCart(explicitSlots) {
5348
+ const state = service_state.get();
5349
+ const slots = explicitSlots || (state.selectedSlot ? [state.selectedSlot] : []);
5350
+ if (slots.length === 0) return;
5351
+ const first = slots[0];
5352
+ if (!state.service || first.serviceId !== state.service.id) {
5353
+ throw new Error("The booking slots do not belong to the selected service");
5354
+ }
5355
+ const relationship = resolveServiceProvider(state, first.providerId);
5356
+ if (!relationship) {
5357
+ throw new Error(`Provider ${first.providerId} is not configured for the selected service`);
5358
+ }
5359
+ let forms;
5360
+ try {
5361
+ forms = configuredServiceFormEntries(relationship);
5362
+ } catch (error) {
5363
+ service_form_state.setKey("error", readErrorMessage(error, "Booking forms are invalid."));
5364
+ throw error;
5365
+ }
5366
+ const displayName = serviceName(state.service, currentLocale());
5367
+ const enriched = slots.map((slot) => ({
5368
+ ...slot,
5369
+ serviceName: displayName,
5370
+ date: slot.dateText
5371
+ }));
5372
+ const nextItems = [...service_items.get(), toServiceCartItem(enriched, forms)];
5373
+ await syncServiceCart(nextItems);
5374
+ service_state.set({
5375
+ ...service_state.get(),
5376
+ selectedDate: null,
5377
+ slots: [],
5378
+ selectedSlot: null,
5379
+ dateTimeConfirmed: false,
5380
+ quote: null,
5381
+ quoteError: null
5382
+ });
5383
+ activateServiceProviderForms(service_state.get().selectedProviderId, true);
5384
+ service_state.setKey("calendar", serviceCalendar());
5385
+ },
5386
+ async removeFromCart(bookingId) {
5387
+ await syncServiceCart(service_items.get().filter((item) => item.id !== bookingId));
5388
+ },
5389
+ async clearCart() {
5390
+ await syncServiceCart([]);
5391
+ },
5392
+ async checkout(paymentMethodId, forms = []) {
5393
+ const state = service_state.get();
5394
+ const items = service_items.get();
5395
+ if (!items.length) throw new Error("Cart is empty");
5396
+ service_state.setKey("loading", true);
5397
+ try {
5398
+ const result = await checkout({
5399
+ service_items: items,
5400
+ payment_method_key: paymentMethodId,
5401
+ promo_code: state.promoCode || void 0,
5402
+ forms
5403
+ });
5404
+ service_state.setKey("cartId", cart.get()?.id || null);
5405
+ return result;
5406
+ } finally {
5407
+ service_state.setKey("loading", false);
5408
+ }
5409
+ },
5410
+ async fetchQuote(paymentMethodId, promoCode) {
5411
+ service_state.get();
5412
+ const items = service_items.get();
5413
+ if (!items.length) return null;
5414
+ service_state.setKey("fetchingQuote", true);
5415
+ service_state.setKey("quoteError", null);
5416
+ try {
5417
+ service_state.setKey("promoCode", promoCode || null);
5418
+ const response = await fetchQuote({
5419
+ service_items: items,
5420
+ payment_method_key: paymentMethodId,
5421
+ promo_code: promoCode || void 0
5422
+ });
5423
+ service_state.setKey("cartId", cart.get()?.id || null);
5424
+ service_state.setKey("quote", response);
5425
+ const methods = response?.payment_methods || market.get()?.payment_methods || [];
5426
+ if (methods.length) service_state.setKey("availablePaymentMethods", methods);
5427
+ return response;
5428
+ } catch (error) {
5429
+ service_state.setKey("quoteError", readErrorMessage(error, "Failed to fetch quote."));
5430
+ return null;
5431
+ } finally {
5432
+ service_state.setKey("fetchingQuote", false);
5433
+ }
5434
+ },
5435
+ getProvidersList() {
5436
+ return service_state.get().providers;
5437
+ },
5438
+ prevStep() {
5439
+ const current = serviceCurrentStepName();
5440
+ if (current === "review") {
5441
+ service_state.setKey("dateTimeConfirmed", false);
5442
+ return;
5443
+ }
5444
+ if (current === "datetime") {
5445
+ service_state.setKey("selectedSlot", null);
5446
+ service_state.setKey("dateTimeConfirmed", false);
5447
+ service_state.setKey("quote", null);
5448
+ service_state.setKey("quoteError", null);
5449
+ activateServiceProviderForms();
5450
+ }
5451
+ },
5452
+ nextStep() {
5453
+ if (serviceCurrentStepName() === "datetime" && service_can_proceed.get()) {
5454
+ service_state.setKey("dateTimeConfirmed", true);
5455
+ }
5456
+ },
5457
+ getServicePrice() {
5458
+ const state = service_state.get();
5459
+ const relationship = resolveServiceProvider(state);
5460
+ if (!relationship) return "";
5461
+ try {
5462
+ const price = priceForMarket(relationship.prices, currentMarketKey(), market.get()?.currency);
5463
+ return client.utils.formatPrice([price]);
5464
+ } catch {
5465
+ return "";
5466
+ }
5467
+ },
5468
+ formatDateDisplay: formatServiceDateDisplay,
5469
+ serviceItemsFromSlots(slots, forms = []) {
5470
+ return slots.length ? [toServiceCartItem(slots, forms)] : [];
5471
+ }
5472
+ };
5473
+ async function loadEntry(params, options) {
5474
+ cms_state.setKey("loading", true);
5475
+ cms_state.setKey("error", null);
5476
+ try {
5477
+ const { locale: nextLocale, market: nextMarket, ...entryParams } = params;
5478
+ setContext({ locale: nextLocale, market: nextMarket });
5479
+ if (entryParams.id) {
5480
+ const entry2 = await client.cms.entry.get(entryParams, options);
5481
+ const cacheKey = entryParams.key || entryParams.id || entry2.id;
5482
+ cms_state.setKey("entries", {
5483
+ ...cms_state.get().entries,
5484
+ [cacheKey]: entry2
5485
+ });
5486
+ return entry2;
5487
+ }
5488
+ if (!entryParams.collection_id || !entryParams.key) {
5489
+ throw new Error("ArkyCmsEntryParams requires id, or collection_id and key");
5490
+ }
5491
+ const result = await client.cms.entry.find(
5492
+ {
5493
+ ...entryParams,
5494
+ collection_id: entryParams.collection_id,
5495
+ key: entryParams.key,
5496
+ limit: 1
5497
+ },
5498
+ options
5499
+ );
5500
+ const entry = result.items?.[0];
5501
+ if (!entry) {
5502
+ throw new Error("CMS entry not found");
5503
+ }
5504
+ cms_state.setKey("entries", {
5505
+ ...cms_state.get().entries,
5506
+ [entryParams.key]: entry
5507
+ });
5508
+ return entry;
5509
+ } catch (error) {
5510
+ cms_state.setKey("error", readErrorMessage(error, "Failed to load CMS entry."));
5511
+ throw error;
5512
+ } finally {
5513
+ cms_state.setKey("loading", false);
5514
+ }
5515
+ }
5516
+ function formCacheKey(params) {
5517
+ const identifier = params.id ? `id:${params.id}` : params.key ? `key:${params.key}` : "missing";
5518
+ return identifier;
5519
+ }
5520
+ async function loadForm(params, options) {
5521
+ cms_state.setKey("loading", true);
5522
+ cms_state.setKey("error", null);
5523
+ try {
5524
+ const form = await client.cms.form.get(params, options);
5525
+ const forms = { ...cms_state.get().forms };
5526
+ forms[formCacheKey({ id: form.id })] = form;
5527
+ forms[formCacheKey({ key: form.key })] = form;
5528
+ cms_state.setKey("forms", forms);
5529
+ return form;
5530
+ } catch (error) {
5531
+ cms_state.setKey("error", readErrorMessage(error, "Failed to load CMS form."));
5532
+ throw error;
5533
+ } finally {
5534
+ cms_state.setKey("loading", false);
5535
+ }
5536
+ }
5537
+ async function submitForm(params, options) {
5538
+ await ensureSession();
5539
+ return client.cms.form.submit(params, options);
5540
+ }
5541
+ async function submitFormByKey(params, options) {
5542
+ const form = await loadForm({ key: params.key }, options);
5543
+ const entry = createFormEntryFromValues(form, params.values);
5544
+ return submitForm({ form_id: form.id, fields: entry.fields }, options);
5545
+ }
5546
+ async function loadProducts(params = {}, options) {
5547
+ eshop_state.setKey("loading_products", true);
5548
+ eshop_state.setKey("error", null);
5549
+ try {
5550
+ const response = await client.eshop.product.find(params, options);
5551
+ eshop_state.setKey("products", response.items || []);
5552
+ eshop_state.setKey("product_cursor", response.cursor || null);
5553
+ return response;
5554
+ } catch (error) {
5555
+ eshop_state.setKey("error", readErrorMessage(error, "Failed to load products."));
5556
+ throw error;
5557
+ } finally {
5558
+ eshop_state.setKey("loading_products", false);
5559
+ }
5560
+ }
5561
+ async function loadServices(params = {}, options) {
5562
+ eshop_state.setKey("loading_services", true);
5563
+ eshop_state.setKey("error", null);
5564
+ try {
5565
+ const response = await client.eshop.service.find(params, options);
5566
+ eshop_state.setKey("services", response.items || []);
5567
+ eshop_state.setKey("service_cursor", response.cursor || null);
5568
+ return response;
5569
+ } catch (error) {
5570
+ eshop_state.setKey("error", readErrorMessage(error, "Failed to load services."));
5571
+ throw error;
5572
+ } finally {
5573
+ eshop_state.setKey("loading_services", false);
5574
+ }
5575
+ }
5576
+ async function loadProviders(params = {}, options) {
5577
+ eshop_state.setKey("loading_providers", true);
5578
+ eshop_state.setKey("error", null);
5579
+ try {
5580
+ const response = await client.eshop.provider.find(params, options);
5581
+ eshop_state.setKey("providers", response.items || []);
5582
+ eshop_state.setKey("provider_cursor", response.cursor || null);
5583
+ return response;
5584
+ } catch (error) {
5585
+ eshop_state.setKey("error", readErrorMessage(error, "Failed to load providers."));
5586
+ throw error;
5587
+ } finally {
5588
+ eshop_state.setKey("loading_providers", false);
5589
+ }
5590
+ }
5591
+ async function loadAvailability(params, options) {
5592
+ eshop_state.setKey("loading_availability", true);
5593
+ eshop_state.setKey("error", null);
5594
+ try {
5595
+ const response = await client.eshop.service.getAvailability(params, options);
5596
+ eshop_state.setKey("availability", response);
5597
+ return response;
5598
+ } catch (error) {
5599
+ eshop_state.setKey("error", readErrorMessage(error, "Failed to load availability."));
5600
+ throw error;
5601
+ } finally {
5602
+ eshop_state.setKey("loading_availability", false);
5603
+ }
5604
+ }
5605
+ async function useExperiment(params) {
5606
+ await ensureSession();
5607
+ const input = typeof params === "string" ? { key: params } : params;
5608
+ return client.experiments.use(input);
5609
+ }
5610
+ async function trackAction(params) {
5611
+ await ensureSession();
5612
+ return client.action.track(params);
5613
+ }
5614
+ const cart_store = {
5615
+ cart,
5616
+ product_items,
5617
+ service_items,
5618
+ quote_result: quote,
5619
+ promo_code,
5620
+ last_order,
5621
+ status: cart_status,
5622
+ product_item_count,
5623
+ service_item_count,
5624
+ item_count,
5625
+ snapshot,
5626
+ load: ensureCart,
5627
+ refresh: syncCart,
5628
+ addProduct,
5629
+ setProductQuantity,
5630
+ removeProduct,
5631
+ addServiceItem,
5632
+ removeServiceItem,
5633
+ clear: clearCart,
5634
+ clearLocal: clearLocalCart,
5635
+ quote: fetchQuote,
5636
+ checkout,
5637
+ payment: {
5638
+ controller: payment_controller,
5639
+ ready: payment_ready,
5640
+ setController: setPaymentController,
5641
+ getController: () => payment_controller.get(),
5642
+ mountStripe: mountStripePayment,
5643
+ update: updatePaymentController,
5644
+ destroy: destroyPaymentController
5645
+ },
5646
+ applyPromoCode(code, input = {}) {
5647
+ return fetchQuote({ ...input, promo_code: code });
5648
+ },
5649
+ removePromoCode(input = {}) {
5650
+ return fetchQuote({ ...input, promo_code: null });
5651
+ },
5652
+ selectShippingMethod(id) {
5653
+ cart_status.setKey("selected_shipping_method_id", id);
5654
+ },
5655
+ locationToAddress,
5656
+ createFormEntry,
5657
+ buildItems: checkoutItems,
5658
+ buildProductItems: toProductCheckoutItems,
5659
+ buildServiceItems: toServiceCheckoutItems
5660
+ };
5661
+ const product_store = {
5662
+ get: (params, options) => client.eshop.product.get(params, options),
5663
+ list: loadProducts
5664
+ };
5665
+ const service_store = {
5666
+ get: (params, options) => client.eshop.service.get(params, options),
5667
+ list: loadServices,
5668
+ listProviders: (params, options) => client.eshop.service.findProviders(params, options),
5669
+ getAvailability: loadAvailability,
5670
+ state: service_state,
5671
+ form_state: service_form_state,
5672
+ form_groups: service_form_groups,
5673
+ form_blocks: service_form_blocks,
5674
+ current_step_name: service_current_step_name,
5675
+ can_proceed: service_can_proceed,
5676
+ month_year: service_month_year,
5677
+ chain_start: service_chain_start,
5678
+ total_steps: service_total_steps,
5679
+ steps: service_steps,
5680
+ current_step: service_current_step,
5681
+ initialize: service_controller.initialize,
5682
+ select: service_controller.select,
5683
+ setTimezone: service_controller.setTimezone,
5684
+ loadMonth: service_controller.loadMonth,
5685
+ prevMonth: service_controller.prevMonth,
5686
+ nextMonth: service_controller.nextMonth,
5687
+ selectProvider: service_controller.selectProvider,
5688
+ selectDate: service_controller.selectDate,
5689
+ selectTimeSlot: service_controller.selectTimeSlot,
5690
+ resetDateSelection: service_controller.resetDateSelection,
5691
+ updateCalendar: service_controller.updateCalendar,
5692
+ findFirstAvailable: service_controller.findFirstAvailable,
5693
+ addToCart: service_controller.addToCart,
5694
+ removeFromCart: service_controller.removeFromCart,
5695
+ clearCart: service_controller.clearCart,
5696
+ getProvidersList: service_controller.getProvidersList,
5697
+ prevStep: service_controller.prevStep,
5698
+ nextStep: service_controller.nextStep,
5699
+ getServicePrice: service_controller.getServicePrice,
5700
+ formatDateDisplay: service_controller.formatDateDisplay,
5701
+ serviceItemsFromSlots: service_controller.serviceItemsFromSlots
5702
+ };
5703
+ return {
5704
+ client,
5705
+ session,
5706
+ setup,
5707
+ market,
5708
+ market_key,
5709
+ locale,
5710
+ currency,
5711
+ allowed_payment_methods,
5712
+ payment_config,
5713
+ identify,
5714
+ identifyContactEmailIfMissing,
5715
+ verify: client.verify,
5716
+ me: client.me,
5717
+ logout: client.logout,
5718
+ onAuthStateChanged: client.onAuthStateChanged,
5719
+ get isAuthenticated() {
5720
+ return client.isAuthenticated;
5721
+ },
5722
+ setMarket,
5723
+ setLocale,
5724
+ setContext,
5725
+ getMarket: currentMarketKey,
5726
+ getLocale: currentLocale,
5727
+ cms: {
5728
+ state: cms_state,
5729
+ collection: {
5730
+ get: (params, options) => client.cms.collection.get(params, options)
5731
+ },
5732
+ entry: {
5733
+ get: loadEntry,
5734
+ find: (params, options) => client.cms.entry.find(params, options)
5735
+ },
5736
+ form: {
5737
+ get: loadForm,
5738
+ submit: submitForm,
5739
+ submitByKey: submitFormByKey
5740
+ },
5741
+ taxonomy: client.cms.taxonomy
5742
+ },
5743
+ eshop: {
5744
+ state: eshop_state,
5745
+ product: product_store,
5746
+ service: service_store,
5747
+ provider: {
5748
+ get: (params, options) => client.eshop.provider.get(params, options),
5749
+ list: loadProviders
5750
+ },
5751
+ order: client.eshop.order,
5752
+ cart: cart_store
5753
+ },
5754
+ crm: client.crm,
5755
+ action: {
5756
+ track(params) {
5757
+ return trackAction(params);
5758
+ },
5759
+ pageView(payload = {}) {
5760
+ return trackAction({ key: "page.view", payload });
5761
+ },
5762
+ state: atom(null)
5763
+ },
5764
+ experiments: {
5765
+ use: useExperiment
5766
+ },
5767
+ support: client.support,
5768
+ store: {
5769
+ ...client.store,
5770
+ setup,
5771
+ load: loadSetup
5772
+ },
5773
+ utils: client.utils
5774
+ };
5775
+ }
5776
+ function initializeStore(publishableKey, config, scopedClient) {
5777
+ const store = initializeStoreCore(publishableKey, config, scopedClient);
5778
+ return Object.assign(store, {
5779
+ withContext(context) {
5780
+ return initializeStore(
5781
+ publishableKey,
5782
+ {
5783
+ ...config,
5784
+ locale: context.locale ?? store.getLocale(),
5785
+ market: context.market ?? store.getMarket()
5786
+ },
5787
+ store.client.withContext(context)
5788
+ );
5789
+ }
5790
+ });
5791
+ }
5792
+ function initialize(publishableKey, options = {}) {
5793
+ return initializeStore(publishableKey, options);
5794
+ }
5795
+
5796
+ // src/index.ts
5797
+ var SDK_VERSION = "0.10.0";
5798
+ var SUPPORTED_FRAMEWORKS = [
5799
+ "astro",
5800
+ "react",
5801
+ "vue",
5802
+ "svelte",
5803
+ "vanilla"
5804
+ ];
5805
+ function createUtilitySurface(apiConfig) {
5806
+ return {
5807
+ getImageUrl: (imageBlock, isBlock2 = true) => getImageUrl(imageBlock, isBlock2),
5808
+ getBlockValue,
5809
+ getBlockTextValue,
5810
+ getBlockContentValue,
5811
+ getBlockValues,
5812
+ getBlockLabel,
5813
+ getBlockObjectValues,
5814
+ getBlockFromArray,
5815
+ formatBlockValue,
5816
+ prepareBlocksForSubmission,
5817
+ extractBlockValues,
5818
+ formatPrice: (prices) => formatPrice(prices, apiConfig.market),
5819
+ getPriceAmount: (prices) => getPriceAmount(prices, apiConfig.market),
5820
+ formatPayment,
5821
+ formatMinor,
5822
+ getCurrencySymbol,
5823
+ getCurrencyName,
5824
+ validatePhoneNumber,
5825
+ tzGroups,
5826
+ findTimeZone,
5827
+ slugify,
5828
+ humanize,
5829
+ categorify,
5830
+ formatDate,
5831
+ getSvgContentForAstro,
5832
+ fetchSvgContent,
5833
+ injectSvgIntoElement,
5834
+ isValidKey,
5835
+ validateKey,
5836
+ toKey,
5837
+ nameToKey,
5838
+ getAvailableStock,
5839
+ getReservedStock,
5840
+ hasStock,
5841
+ getInventoryAt,
5842
+ getFirstAvailableFCId
5843
+ };
5844
+ }
5845
+ var ADMIN_STORAGE_KEY = "arky_admin_session";
5846
+ function readAdminSession() {
5847
+ if (typeof window === "undefined") return null;
5848
+ try {
5849
+ const raw = localStorage.getItem(ADMIN_STORAGE_KEY);
5850
+ return raw ? JSON.parse(raw) : null;
5851
+ } catch {
5852
+ return null;
5853
+ }
5854
+ }
5855
+ function writeAdminSession(s) {
5856
+ if (typeof window === "undefined") return;
5857
+ if (s) {
5858
+ localStorage.setItem(ADMIN_STORAGE_KEY, JSON.stringify(s));
5859
+ } else {
5860
+ localStorage.removeItem(ADMIN_STORAGE_KEY);
5861
+ }
5862
+ }
5863
+ function createAdmin(config) {
5864
+ const locale = config.locale || "en";
5865
+ const listeners = /* @__PURE__ */ new Set();
5866
+ function toPublic(s) {
5867
+ return s ? { email: s.email } : null;
5868
+ }
5869
+ function emit() {
5870
+ const pub = toPublic(readAdminSession());
5871
+ for (const l of listeners) {
5872
+ Promise.resolve().then(() => l(pub)).catch(() => {
5873
+ });
5874
+ }
5875
+ }
5876
+ const updateSession = (updater) => {
5877
+ if (config.apiToken) return;
5878
+ const prev = readAdminSession();
5879
+ const next = updater(prev);
5880
+ writeAdminSession(next);
5881
+ emit();
5882
+ };
5883
+ const authStorage = config.apiToken ? {
5884
+ getTokens: () => ({ access_token: config.apiToken }),
5885
+ onTokensRefreshed: () => {
5886
+ },
5887
+ onForcedLogout: () => {
5888
+ }
5889
+ } : {
5890
+ getTokens() {
5891
+ const s = readAdminSession();
5892
+ if (!s) return null;
5893
+ return {
5894
+ access_token: s.access_token,
5895
+ refresh_token: s.refresh_token,
5896
+ access_expires_at: s.access_expires_at
5897
+ };
5898
+ },
5899
+ onTokensRefreshed(tokens) {
5900
+ updateSession(
5901
+ (prev) => prev ? {
5902
+ ...prev,
5903
+ access_token: tokens.access_token,
5904
+ refresh_token: tokens.refresh_token ?? prev.refresh_token,
5905
+ access_expires_at: tokens.access_expires_at ?? prev.access_expires_at
5906
+ } : null
5907
+ );
5908
+ },
5909
+ onForcedLogout() {
5910
+ updateSession(() => null);
5911
+ }
5912
+ };
5913
+ const httpClient = createHttpClient({
5914
+ baseUrl: config.baseUrl,
5915
+ storeId: config.storeId,
5916
+ refreshPath: config.refreshPath,
5917
+ navigate: config.navigate,
5918
+ loginFallbackPath: config.loginFallbackPath,
5919
+ authStorage
5920
+ });
5921
+ const apiConfig = {
5922
+ httpClient,
5923
+ storeId: config.storeId,
5924
+ baseUrl: config.baseUrl,
5925
+ market: config.market,
5926
+ locale,
5927
+ authStorage
5928
+ };
5929
+ const accountApi = createAccountApi(apiConfig);
5930
+ const authApi = createAuthApi(apiConfig, updateSession);
5931
+ const storeApi = createStoreApi(apiConfig);
5932
+ const platformApi = createPlatformApi(apiConfig);
5933
+ const cmsApi = createCmsApi(apiConfig);
5934
+ const eshopApi = createEshopApi(apiConfig);
4255
5935
  const promoCodeApi = createPromoCodeApi(apiConfig);
4256
5936
  const crmApi = createContactApi(apiConfig);
4257
5937
  const supportApi = createAdminSupportApi(apiConfig);
@@ -4312,6 +5992,7 @@ function createAdmin(config) {
4312
5992
  update: storeApi.updateStore,
4313
5993
  get: storeApi.getStore,
4314
5994
  find: storeApi.getStores,
5995
+ regeneratePublishableKey: storeApi.regeneratePublishableKey,
4315
5996
  subscription: {
4316
5997
  get: storeApi.getSubscription,
4317
5998
  getPlans: storeApi.getSubscriptionPlans,
@@ -4630,6 +6311,8 @@ function createAdmin(config) {
4630
6311
  };
4631
6312
  return sdk;
4632
6313
  }
6314
+ var DEFAULT_STOREFRONT_API_URL = "https://api.arky.io";
6315
+ var storefrontScopeSequence = 0;
4633
6316
  function defaultStorefrontSessionStorage() {
4634
6317
  if (typeof window === "undefined") return null;
4635
6318
  try {
@@ -4638,132 +6321,177 @@ function defaultStorefrontSessionStorage() {
4638
6321
  return null;
4639
6322
  }
4640
6323
  }
4641
- function storefrontSessionStorageKey(baseUrl, storeId) {
4642
- const normalizedBaseUrl = baseUrl.trim().replace(/\/+$/, "").toLowerCase();
4643
- return `arky_contact_session:${encodeURIComponent(normalizedBaseUrl)}:${encodeURIComponent(storeId)}`;
6324
+ function normalizeStorefrontApiUrl(value) {
6325
+ const input = value?.trim() || DEFAULT_STOREFRONT_API_URL;
6326
+ let parsed;
6327
+ try {
6328
+ parsed = new URL(input);
6329
+ } catch {
6330
+ throw new Error("Storefront apiUrl must be a valid HTTP(S) URL");
6331
+ }
6332
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
6333
+ throw new Error("Storefront apiUrl must use HTTP or HTTPS");
6334
+ }
6335
+ return input.replace(/\/+$/, "");
4644
6336
  }
4645
- function createStorefrontClient(config) {
4646
- const locale = config.locale || "en";
4647
- const initialMarket = config.market || "";
6337
+ function validatePublishableKey(publishableKey) {
6338
+ if (typeof publishableKey !== "string") {
6339
+ throw new Error(
6340
+ "A valid Arky publishable key is required (arky_pk_ followed by 43 URL-safe characters)"
6341
+ );
6342
+ }
6343
+ const key = publishableKey.trim();
6344
+ if (!/^arky_pk_[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/.test(key)) {
6345
+ throw new Error(
6346
+ "A valid Arky publishable key is required (arky_pk_ followed by 43 URL-safe characters)"
6347
+ );
6348
+ }
6349
+ return key;
6350
+ }
6351
+ function publishableKeyFingerprint(publishableKey) {
6352
+ let hashA = 2166136261;
6353
+ let hashB = 2654435769;
6354
+ for (let index = 0; index < publishableKey.length; index += 1) {
6355
+ const code = publishableKey.charCodeAt(index);
6356
+ hashA = Math.imul(hashA ^ code, 16777619);
6357
+ hashB = Math.imul(hashB ^ code, 2246822507);
6358
+ }
6359
+ return `${(hashA >>> 0).toString(36)}${(hashB >>> 0).toString(36)}`;
6360
+ }
6361
+ function storefrontSessionStorageKey(apiUrl, publishableKey) {
6362
+ return `arky_visitor_session:${encodeURIComponent(apiUrl.toLowerCase())}:${publishableKeyFingerprint(publishableKey)}`;
6363
+ }
6364
+ function isVisitorSessionToken(value) {
6365
+ return Boolean(value && /^arky_vst_[0-9a-f]{64}$/.test(value));
6366
+ }
6367
+ function createStorefrontClientCore(publishableKeyInput, options = {}, isolatedSession = false) {
6368
+ const publishableKey = validatePublishableKey(publishableKeyInput);
6369
+ const apiUrl = normalizeStorefrontApiUrl(options.apiUrl);
6370
+ let locale = options.locale?.trim() || "";
6371
+ let market = options.market?.trim() || "";
4648
6372
  const listeners = /* @__PURE__ */ new Set();
4649
- let bareIdentifyPromise = null;
6373
+ let identifyPromise = null;
4650
6374
  let identityTail = Promise.resolve();
4651
- const sessionStorage = config.sessionStorage || defaultStorefrontSessionStorage();
6375
+ let setupPromise = null;
6376
+ let setupValue = null;
6377
+ const explicitSessionStorage = options.sessionStorage;
6378
+ const sessionStorage = isolatedSession ? explicitSessionStorage || null : explicitSessionStorage || defaultStorefrontSessionStorage();
6379
+ const canCreateVisitorSession = typeof window !== "undefined" || Boolean(explicitSessionStorage);
6380
+ const storageKey = `${storefrontSessionStorageKey(apiUrl, publishableKey)}${isolatedSession ? `:scope:${++storefrontScopeSequence}` : ""}`;
4652
6381
  let memorySession = null;
4653
- function readContactSession() {
4654
- if (!sessionStorage) return memorySession;
6382
+ let memorySessionToken = null;
6383
+ function readSessionToken() {
6384
+ if (!sessionStorage) return memorySessionToken;
4655
6385
  try {
4656
- const raw = sessionStorage.getItem(
4657
- storefrontSessionStorageKey(config.baseUrl, config.storeId)
4658
- );
4659
- return raw ? JSON.parse(raw) : null;
6386
+ const stored = sessionStorage.getItem(storageKey);
6387
+ return isVisitorSessionToken(stored) ? stored : memorySessionToken;
4660
6388
  } catch {
4661
- return memorySession;
6389
+ return memorySessionToken;
4662
6390
  }
4663
6391
  }
4664
6392
  function writeContactSession(session) {
4665
6393
  memorySession = session;
6394
+ memorySessionToken = session?.sessionToken || null;
4666
6395
  if (!sessionStorage) return;
4667
- const key = storefrontSessionStorageKey(config.baseUrl, config.storeId);
4668
6396
  try {
4669
6397
  if (session) {
4670
- sessionStorage.setItem(key, JSON.stringify(session));
6398
+ sessionStorage.setItem(storageKey, session.sessionToken);
4671
6399
  } else {
4672
- sessionStorage.removeItem(key);
6400
+ sessionStorage.removeItem(storageKey);
4673
6401
  }
4674
6402
  } catch {
4675
6403
  }
4676
6404
  }
4677
6405
  function toPublic(s) {
4678
- return s ? { contact: s.contact, store: s.store, market: s.market } : null;
6406
+ return s ? { contact: s.contact } : null;
4679
6407
  }
4680
6408
  function emit() {
4681
- const pub = toPublic(readContactSession());
6409
+ const pub = toPublic(memorySession);
4682
6410
  for (const l of listeners) {
4683
6411
  Promise.resolve().then(() => l(pub)).catch(() => {
4684
6412
  });
4685
6413
  }
4686
6414
  }
4687
6415
  const updateSession = (updater) => {
4688
- if (config.apiToken) return;
4689
- const prev = readContactSession();
4690
- const next = updater(prev);
6416
+ const next = updater(memorySession);
4691
6417
  writeContactSession(next);
4692
6418
  emit();
4693
6419
  };
4694
- const authStorage = config.apiToken ? {
4695
- getTokens: () => ({ access_token: config.apiToken }),
4696
- onTokensRefreshed: () => {
4697
- },
4698
- onForcedLogout: () => {
4699
- }
4700
- } : {
6420
+ const authStorage = {
4701
6421
  getTokens() {
4702
- const s = readContactSession();
4703
- return s ? { access_token: s.access_token } : null;
6422
+ const sessionToken = readSessionToken();
6423
+ return sessionToken ? { access_token: sessionToken } : null;
4704
6424
  },
4705
6425
  onTokensRefreshed() {
4706
6426
  },
4707
6427
  onForcedLogout() {
4708
- bareIdentifyPromise = null;
6428
+ identifyPromise = null;
4709
6429
  updateSession(() => null);
4710
6430
  }
4711
6431
  };
6432
+ let recoverUnauthorized = async () => false;
6433
+ let visitorRecoveryPromise = null;
4712
6434
  const httpClient = createHttpClient({
4713
- baseUrl: config.baseUrl,
4714
- storeId: config.storeId,
4715
- refreshPath: config.refreshPath,
4716
- navigate: config.navigate,
4717
- loginFallbackPath: config.loginFallbackPath,
4718
- authStorage
6435
+ baseUrl: apiUrl,
6436
+ authStorage,
6437
+ storefrontMode: true,
6438
+ forcedHeaders: () => ({
6439
+ "X-Arky-Publishable-Key": publishableKey,
6440
+ ...locale ? { "X-Arky-Locale": locale } : {},
6441
+ ...market ? { "X-Arky-Market": market } : {}
6442
+ }),
6443
+ onUnauthorized: ({ authorizationToken, path }) => recoverUnauthorized(authorizationToken, path)
4719
6444
  });
4720
6445
  const apiConfig = {
4721
6446
  httpClient,
4722
- storeId: config.storeId,
4723
- baseUrl: config.baseUrl,
4724
- market: initialMarket,
6447
+ apiUrl,
6448
+ publishableKey,
6449
+ market,
4725
6450
  locale,
4726
6451
  authStorage
4727
6452
  };
4728
- const storefrontApi = createStorefrontApi(apiConfig, updateSession);
6453
+ function requireVisitorSessionCapability() {
6454
+ if (!canCreateVisitorSession) {
6455
+ throw new Error(
6456
+ "Stateful storefront operations during SSR require an explicit request-local sessionStorage adapter"
6457
+ );
6458
+ }
6459
+ }
6460
+ async function getSetup(requestOptions) {
6461
+ if (setupValue) return setupValue;
6462
+ if (setupPromise) return setupPromise;
6463
+ setupPromise = httpClient.get(
6464
+ "/v1/storefront",
6465
+ requestOptions
6466
+ ).then((setup) => {
6467
+ setupValue = setup;
6468
+ return setup;
6469
+ }).finally(() => {
6470
+ setupPromise = null;
6471
+ });
6472
+ return setupPromise;
6473
+ }
6474
+ async function ensureVisitorSession() {
6475
+ if (readSessionToken()) return;
6476
+ requireVisitorSessionCapability();
6477
+ await identify();
6478
+ }
6479
+ const storefrontApi = createStorefrontApi(apiConfig, updateSession, {
6480
+ ensureVisitorSession,
6481
+ getSetup
6482
+ });
4729
6483
  const contactApi = storefrontApi.crm.contact;
4730
6484
  function identify(params) {
4731
- if (params?.market !== void 0) apiConfig.market = params.market;
4732
- const market = apiConfig.market;
6485
+ requireVisitorSessionCapability();
6486
+ if (params?.market !== void 0) setMarket(params.market);
4733
6487
  const isBareCall = !params?.email && !params?.verify;
4734
- if (isBareCall && bareIdentifyPromise) return bareIdentifyPromise;
6488
+ if (isBareCall && identifyPromise) return identifyPromise;
4735
6489
  const run = async () => {
4736
- try {
4737
- const result = await (params?.verify ? contactApi.requestCode({
4738
- market,
4739
- email: params.email
4740
- }) : contactApi.identify({
4741
- market,
4742
- email: params?.email
4743
- }));
4744
- return {
4745
- contact: result.contact,
4746
- store: result.store,
4747
- market: result.market,
4748
- verification_challenge: result.verification_challenge
4749
- };
4750
- } catch (err) {
4751
- const e = err;
4752
- const status = e?.statusCode || e?.status || e?.response?.status;
4753
- if (isBareCall && status === 401) {
4754
- updateSession(() => null);
4755
- const result = await contactApi.identify({
4756
- market
4757
- });
4758
- return {
4759
- contact: result.contact,
4760
- store: result.store,
4761
- market: result.market,
4762
- verification_challenge: result.verification_challenge
4763
- };
4764
- }
4765
- throw err;
4766
- }
6490
+ const result = await (params?.verify ? contactApi.requestCode({ email: params.email }) : contactApi.identify({ email: params?.email }));
6491
+ return {
6492
+ contact: result.contact,
6493
+ verification_challenge: result.verification_challenge
6494
+ };
4767
6495
  };
4768
6496
  const promise = identityTail.then(run);
4769
6497
  identityTail = promise.then(
@@ -4771,49 +6499,88 @@ function createStorefrontClient(config) {
4771
6499
  () => void 0
4772
6500
  );
4773
6501
  if (isBareCall) {
4774
- bareIdentifyPromise = promise;
6502
+ identifyPromise = promise;
4775
6503
  void promise.then(
4776
6504
  () => {
4777
- if (bareIdentifyPromise === promise) bareIdentifyPromise = null;
6505
+ if (identifyPromise === promise) identifyPromise = null;
4778
6506
  },
4779
6507
  () => {
4780
- if (bareIdentifyPromise === promise) bareIdentifyPromise = null;
6508
+ if (identifyPromise === promise) identifyPromise = null;
4781
6509
  }
4782
6510
  );
4783
6511
  }
4784
6512
  return promise;
4785
6513
  }
4786
6514
  async function verify(params) {
6515
+ requireVisitorSessionCapability();
4787
6516
  const result = await contactApi.verify(params);
4788
- bareIdentifyPromise = null;
4789
- return result;
6517
+ identifyPromise = null;
6518
+ return { contact: result.contact };
6519
+ }
6520
+ async function me() {
6521
+ await ensureVisitorSession();
6522
+ return contactApi.getMe();
4790
6523
  }
4791
6524
  async function logout() {
4792
- if (config.apiToken) return;
4793
- bareIdentifyPromise = null;
6525
+ identifyPromise = null;
6526
+ if (!readSessionToken()) {
6527
+ updateSession(() => null);
6528
+ return;
6529
+ }
4794
6530
  try {
4795
6531
  await contactApi.logout();
4796
6532
  } catch {
4797
6533
  updateSession(() => null);
4798
6534
  }
4799
6535
  }
6536
+ function setMarket(value) {
6537
+ market = value.trim();
6538
+ apiConfig.market = market;
6539
+ identifyPromise = null;
6540
+ }
6541
+ function setLocale(value) {
6542
+ locale = value.trim();
6543
+ apiConfig.locale = locale;
6544
+ }
6545
+ function setContext(context) {
6546
+ if (context.locale !== void 0) setLocale(context.locale);
6547
+ if (context.market !== void 0) setMarket(context.market);
6548
+ }
6549
+ recoverUnauthorized = async (authorizationToken, path) => {
6550
+ if (!authorizationToken) return false;
6551
+ const currentToken = readSessionToken();
6552
+ if (currentToken !== authorizationToken) {
6553
+ if (currentToken) return true;
6554
+ if (!visitorRecoveryPromise) return false;
6555
+ await visitorRecoveryPromise;
6556
+ return Boolean(readSessionToken());
6557
+ }
6558
+ updateSession(() => null);
6559
+ if (/\/account\/(identify|code)$/.test(path)) return true;
6560
+ const recovery = ensureVisitorSession();
6561
+ const trackedRecovery = recovery.finally(() => {
6562
+ if (visitorRecoveryPromise === trackedRecovery) {
6563
+ visitorRecoveryPromise = null;
6564
+ }
6565
+ });
6566
+ visitorRecoveryPromise = trackedRecovery;
6567
+ await trackedRecovery;
6568
+ return true;
6569
+ };
4800
6570
  return {
4801
6571
  identify,
4802
6572
  verify,
4803
6573
  logout,
4804
- me: () => contactApi.getMe(),
6574
+ me,
4805
6575
  get session() {
4806
- if (config.apiToken) return null;
4807
- return toPublic(readContactSession());
6576
+ return toPublic(memorySession);
4808
6577
  },
4809
6578
  get isAuthenticated() {
4810
- if (config.apiToken) return true;
4811
- const s = readContactSession();
4812
- return s !== null && !!s.access_token;
6579
+ return Boolean(readSessionToken());
4813
6580
  },
4814
6581
  onAuthStateChanged(listener) {
4815
6582
  listeners.add(listener);
4816
- const current = toPublic(readContactSession());
6583
+ const current = toPublic(memorySession);
4817
6584
  if (current) {
4818
6585
  Promise.resolve().then(() => listener(current)).catch(() => {
4819
6586
  });
@@ -4825,44 +6592,52 @@ function createStorefrontClient(config) {
4825
6592
  store: storefrontApi.store,
4826
6593
  cms: storefrontApi.cms,
4827
6594
  eshop: storefrontApi.eshop,
4828
- crm: storefrontApi.crm,
6595
+ crm: {
6596
+ ...storefrontApi.crm,
6597
+ contact: {
6598
+ identify: (params) => identify(params),
6599
+ requestCode: (params) => identify({ ...params, verify: true }),
6600
+ verify,
6601
+ logout,
6602
+ getMe: me
6603
+ }
6604
+ },
4829
6605
  action: storefrontApi.action,
4830
6606
  experiments: storefrontApi.experiments,
4831
- support: createStorefrontSupportApi(apiConfig),
4832
- getStoreId: () => apiConfig.storeId,
4833
- setMarket: (key) => {
4834
- apiConfig.market = key;
4835
- bareIdentifyPromise = null;
4836
- },
4837
- getMarket: () => apiConfig.market,
4838
- setLocale: (l) => {
4839
- apiConfig.locale = l;
4840
- },
4841
- getLocale: () => apiConfig.locale,
6607
+ support: createStorefrontSupportApi(apiConfig, ensureVisitorSession),
6608
+ getSetup,
6609
+ setContext,
6610
+ setMarket,
6611
+ getMarket: () => market,
6612
+ setLocale,
6613
+ getLocale: () => locale,
4842
6614
  utils: createUtilitySurface(apiConfig)
4843
6615
  };
4844
6616
  }
4845
- function createStorefront(config) {
4846
- const sessionStorage = config.sessionStorage || defaultStorefrontSessionStorage() || void 0;
4847
- const scopedConfig = {
4848
- ...config,
4849
- sessionStorage
4850
- };
4851
- const client = createStorefrontClient(scopedConfig);
4852
- Object.defineProperty(client, "forStore", {
4853
- enumerable: true,
4854
- configurable: false,
4855
- writable: false,
4856
- value: (storeId) => createStorefront({
4857
- ...scopedConfig,
4858
- storeId,
4859
- market: client.getMarket(),
4860
- locale: client.getLocale()
4861
- })
6617
+ function createStorefrontClient(publishableKey, options = {}, isolatedSession = false) {
6618
+ const client = createStorefrontClientCore(
6619
+ publishableKey,
6620
+ options,
6621
+ isolatedSession
6622
+ );
6623
+ return Object.assign(client, {
6624
+ withContext(context) {
6625
+ return createStorefrontClient(
6626
+ publishableKey,
6627
+ {
6628
+ ...options,
6629
+ locale: context.locale ?? client.getLocale(),
6630
+ market: context.market ?? client.getMarket()
6631
+ },
6632
+ true
6633
+ );
6634
+ }
4862
6635
  });
4863
- return client;
6636
+ }
6637
+ function createStorefront(publishableKey, options = {}) {
6638
+ return createStorefrontClient(publishableKey, options);
4864
6639
  }
4865
6640
 
4866
- export { COMMON_ACTION_KEYS, PaymentMethodType, SDK_VERSION, SUPPORTED_FRAMEWORKS, createAdmin, createCartController, createStorefront };
6641
+ export { COMMON_ACTION_KEYS, DEFAULT_STOREFRONT_API_URL, PaymentMethodType, SDK_VERSION, SUPPORTED_FRAMEWORKS, buildFormFields, createAdmin, createCartController, createFormEntry, createFormEntryFromValues, createStorefront, initialize };
4867
6642
  //# sourceMappingURL=index.js.map
4868
6643
  //# sourceMappingURL=index.js.map