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