@altazion/commerce-sdk-core 1.0.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/assets/cache.worker-BX4nA-m-.js +131 -0
- package/dist/assets/cache.worker-BX4nA-m-.js.map +1 -0
- package/dist/index.cjs +861 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +998 -0
- package/dist/index.iife.js +864 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.js +860 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
6
|
+
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
7
|
+
class CommerceContext {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
__publicField(this, "baseUrl");
|
|
10
|
+
__publicField(this, "siteUrl");
|
|
11
|
+
__publicField(this, "sitePk");
|
|
12
|
+
__publicField(this, "rjsId");
|
|
13
|
+
__publicField(this, "locale");
|
|
14
|
+
__publicField(this, "currency");
|
|
15
|
+
__publicField(this, "mode");
|
|
16
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
17
|
+
this.siteUrl = options.siteUrl;
|
|
18
|
+
this.sitePk = options.sitePk;
|
|
19
|
+
this.rjsId = options.rjsId;
|
|
20
|
+
this.locale = options.locale ?? "fr-FR";
|
|
21
|
+
this.currency = options.currency ?? "EUR";
|
|
22
|
+
this.mode = CommerceContext.detectMode();
|
|
23
|
+
}
|
|
24
|
+
static detectMode() {
|
|
25
|
+
if (typeof navigator !== "undefined" && navigator.userAgent.includes("Altazion Device Shell")) {
|
|
26
|
+
return "kiosk";
|
|
27
|
+
}
|
|
28
|
+
return "browser";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
class AltazionApiError extends Error {
|
|
32
|
+
constructor(status, problem) {
|
|
33
|
+
super(problem.detail ?? problem.title ?? `HTTP ${status}`);
|
|
34
|
+
__publicField(this, "status");
|
|
35
|
+
__publicField(this, "problem");
|
|
36
|
+
this.name = "AltazionApiError";
|
|
37
|
+
this.status = status;
|
|
38
|
+
this.problem = problem;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
class OfflineError extends Error {
|
|
42
|
+
constructor(message = "Le SDK est hors-ligne et aucun cache n'est disponible pour cette ressource") {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "OfflineError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
class CacheError extends Error {
|
|
48
|
+
constructor(message, cause) {
|
|
49
|
+
super(message);
|
|
50
|
+
__publicField(this, "cause");
|
|
51
|
+
this.name = "CacheError";
|
|
52
|
+
this.cause = cause;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
class QueueFlushError extends Error {
|
|
56
|
+
constructor(operationId, apiError) {
|
|
57
|
+
super(`Échec du rejeu de l'opération ${operationId} : ${apiError.message}`);
|
|
58
|
+
__publicField(this, "failedOperationId");
|
|
59
|
+
__publicField(this, "apiError");
|
|
60
|
+
this.name = "QueueFlushError";
|
|
61
|
+
this.failedOperationId = operationId;
|
|
62
|
+
this.apiError = apiError;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
class CommerceHttpAdapter {
|
|
66
|
+
constructor(context) {
|
|
67
|
+
__publicField(this, "context");
|
|
68
|
+
this.context = context;
|
|
69
|
+
}
|
|
70
|
+
async request(path, options = {}) {
|
|
71
|
+
const { method = "GET", body, headers = {}, maxRetries = 2 } = options;
|
|
72
|
+
const url = `${this.context.baseUrl}${path}`;
|
|
73
|
+
const requestHeaders = {
|
|
74
|
+
Accept: "application/json",
|
|
75
|
+
"Accept-Language": this.context.locale,
|
|
76
|
+
...headers
|
|
77
|
+
};
|
|
78
|
+
if (body !== void 0) {
|
|
79
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
80
|
+
}
|
|
81
|
+
const init = {
|
|
82
|
+
method,
|
|
83
|
+
headers: requestHeaders,
|
|
84
|
+
credentials: "include",
|
|
85
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
86
|
+
};
|
|
87
|
+
let lastNetworkError;
|
|
88
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
89
|
+
try {
|
|
90
|
+
const response = await fetch(url, init);
|
|
91
|
+
return await this.handleResponse(response);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (err instanceof AltazionApiError) throw err;
|
|
94
|
+
lastNetworkError = err;
|
|
95
|
+
if (attempt < maxRetries) {
|
|
96
|
+
await delay(150 * Math.pow(2, attempt));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
throw lastNetworkError;
|
|
101
|
+
}
|
|
102
|
+
async handleResponse(response) {
|
|
103
|
+
if (response.ok) {
|
|
104
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
105
|
+
if (response.status === 204 || !contentType.includes("application/json")) {
|
|
106
|
+
return void 0;
|
|
107
|
+
}
|
|
108
|
+
return response.json();
|
|
109
|
+
}
|
|
110
|
+
let problem = { status: response.status };
|
|
111
|
+
try {
|
|
112
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
113
|
+
if (contentType.includes("application/json") || contentType.includes("application/problem+json")) {
|
|
114
|
+
problem = await response.json();
|
|
115
|
+
problem.status ?? (problem.status = response.status);
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
throw new AltazionApiError(response.status, problem);
|
|
120
|
+
}
|
|
121
|
+
/** Effectue un GET et retourne T */
|
|
122
|
+
get(path, headers) {
|
|
123
|
+
return this.request(path, { method: "GET", headers });
|
|
124
|
+
}
|
|
125
|
+
/** Effectue un POST avec un body JSON et retourne T */
|
|
126
|
+
post(path, body, headers) {
|
|
127
|
+
return this.request(path, { method: "POST", body, headers });
|
|
128
|
+
}
|
|
129
|
+
/** Effectue un PUT avec un body JSON et retourne T */
|
|
130
|
+
put(path, body, headers) {
|
|
131
|
+
return this.request(path, { method: "PUT", body, headers });
|
|
132
|
+
}
|
|
133
|
+
/** Effectue un PATCH avec un body JSON et retourne T */
|
|
134
|
+
patch(path, body, headers) {
|
|
135
|
+
return this.request(path, { method: "PATCH", body, headers });
|
|
136
|
+
}
|
|
137
|
+
/** Effectue un DELETE et retourne T */
|
|
138
|
+
delete(path, headers) {
|
|
139
|
+
return this.request(path, { method: "DELETE", headers });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function delay(ms) {
|
|
143
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
144
|
+
}
|
|
145
|
+
class ConnectivityManager {
|
|
146
|
+
constructor() {
|
|
147
|
+
__publicField(this, "status");
|
|
148
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
149
|
+
__publicField(this, "handleOnline", () => {
|
|
150
|
+
this.status = "online";
|
|
151
|
+
this.emit("online");
|
|
152
|
+
});
|
|
153
|
+
__publicField(this, "handleOffline", () => {
|
|
154
|
+
this.status = "offline";
|
|
155
|
+
this.emit("offline");
|
|
156
|
+
});
|
|
157
|
+
this.status = this.readOnlineStatus();
|
|
158
|
+
if (typeof window !== "undefined") {
|
|
159
|
+
window.addEventListener("online", this.handleOnline);
|
|
160
|
+
window.addEventListener("offline", this.handleOffline);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
get isOnline() {
|
|
164
|
+
return this.status === "online";
|
|
165
|
+
}
|
|
166
|
+
get isOffline() {
|
|
167
|
+
return this.status === "offline";
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Abonne un listener aux changements de connectivité.
|
|
171
|
+
* Retourne une fonction de désabonnement.
|
|
172
|
+
*/
|
|
173
|
+
subscribe(listener) {
|
|
174
|
+
this.listeners.add(listener);
|
|
175
|
+
return () => this.listeners.delete(listener);
|
|
176
|
+
}
|
|
177
|
+
/** Libère les event listeners sur window. */
|
|
178
|
+
dispose() {
|
|
179
|
+
if (typeof window !== "undefined") {
|
|
180
|
+
window.removeEventListener("online", this.handleOnline);
|
|
181
|
+
window.removeEventListener("offline", this.handleOffline);
|
|
182
|
+
}
|
|
183
|
+
this.listeners.clear();
|
|
184
|
+
}
|
|
185
|
+
readOnlineStatus() {
|
|
186
|
+
if (typeof navigator === "undefined" || navigator.onLine === void 0) {
|
|
187
|
+
return "online";
|
|
188
|
+
}
|
|
189
|
+
return navigator.onLine ? "online" : "offline";
|
|
190
|
+
}
|
|
191
|
+
emit(status) {
|
|
192
|
+
for (const listener of this.listeners) {
|
|
193
|
+
try {
|
|
194
|
+
listener(status);
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
class WorkerBridge {
|
|
201
|
+
constructor() {
|
|
202
|
+
__publicField(this, "port", null);
|
|
203
|
+
__publicField(this, "pending", /* @__PURE__ */ new Map());
|
|
204
|
+
__publicField(this, "requestCounter", 0);
|
|
205
|
+
__publicField(this, "fallback", /* @__PURE__ */ new Map());
|
|
206
|
+
__publicField(this, "usingFallback");
|
|
207
|
+
__publicField(this, "handleMessage", (event) => {
|
|
208
|
+
const { id, result, error } = event.data;
|
|
209
|
+
const pending = this.pending.get(id);
|
|
210
|
+
if (!pending) return;
|
|
211
|
+
this.pending.delete(id);
|
|
212
|
+
if (error !== void 0) {
|
|
213
|
+
pending.reject(new CacheError(error));
|
|
214
|
+
} else {
|
|
215
|
+
pending.resolve(result);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
this.usingFallback = !this.tryStartWorker();
|
|
219
|
+
}
|
|
220
|
+
tryStartWorker() {
|
|
221
|
+
if (typeof SharedWorker === "undefined") return false;
|
|
222
|
+
try {
|
|
223
|
+
const worker = new SharedWorker(
|
|
224
|
+
new URL(
|
|
225
|
+
/* @vite-ignore */
|
|
226
|
+
"/assets/cache.worker-BX4nA-m-.js",
|
|
227
|
+
typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href
|
|
228
|
+
),
|
|
229
|
+
{ type: "module", name: "altazion-cache-worker" }
|
|
230
|
+
);
|
|
231
|
+
this.port = worker.port;
|
|
232
|
+
this.port.addEventListener("message", this.handleMessage);
|
|
233
|
+
this.port.start();
|
|
234
|
+
return true;
|
|
235
|
+
} catch {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
nextId() {
|
|
240
|
+
return String(++this.requestCounter);
|
|
241
|
+
}
|
|
242
|
+
send(message) {
|
|
243
|
+
return new Promise((resolve, reject) => {
|
|
244
|
+
if (!this.port) {
|
|
245
|
+
reject(new CacheError("Worker non initialisé"));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const id = this.nextId();
|
|
249
|
+
this.pending.set(id, { resolve, reject });
|
|
250
|
+
this.port.postMessage({ ...message, id });
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
async get(key) {
|
|
254
|
+
if (this.usingFallback) {
|
|
255
|
+
const entry = this.fallback.get(key);
|
|
256
|
+
if (!entry || Date.now() > entry.expiresAt) return null;
|
|
257
|
+
return entry.data;
|
|
258
|
+
}
|
|
259
|
+
return this.send({ type: "GET", key });
|
|
260
|
+
}
|
|
261
|
+
async set(key, data, ttlMs) {
|
|
262
|
+
if (this.usingFallback) {
|
|
263
|
+
this.fallback.set(key, { data, expiresAt: Date.now() + ttlMs });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
await this.send({ type: "SET", key, data, ttl: ttlMs });
|
|
267
|
+
}
|
|
268
|
+
async invalidate(pattern) {
|
|
269
|
+
if (this.usingFallback) {
|
|
270
|
+
const regex = new RegExp(pattern);
|
|
271
|
+
for (const key of this.fallback.keys()) {
|
|
272
|
+
if (regex.test(key)) this.fallback.delete(key);
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
await this.send({ type: "INVALIDATE", pattern });
|
|
277
|
+
}
|
|
278
|
+
async clearAll() {
|
|
279
|
+
if (this.usingFallback) {
|
|
280
|
+
this.fallback.clear();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
await this.send({ type: "CLEAR_ALL" });
|
|
284
|
+
}
|
|
285
|
+
dispose() {
|
|
286
|
+
var _a;
|
|
287
|
+
(_a = this.port) == null ? void 0 : _a.removeEventListener("message", this.handleMessage);
|
|
288
|
+
this.pending.clear();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const DEFAULT_TTL = {
|
|
292
|
+
"network-first": 3e4,
|
|
293
|
+
// 30 secondes (session, panier)
|
|
294
|
+
"cache-first": 5 * 6e4,
|
|
295
|
+
// 5 minutes (recherche)
|
|
296
|
+
"stale-while-revalidate": 60 * 6e4
|
|
297
|
+
// 1 heure (catalogue)
|
|
298
|
+
};
|
|
299
|
+
class CacheStrategy {
|
|
300
|
+
constructor(bridge, http) {
|
|
301
|
+
this.bridge = bridge;
|
|
302
|
+
this.http = http;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Exécute la requête selon la stratégie choisie.
|
|
306
|
+
*
|
|
307
|
+
* @param key Clé de cache (généralement le path + params)
|
|
308
|
+
* @param fetcher Fonction qui effectue la requête HTTP
|
|
309
|
+
* @param strategy Stratégie à appliquer
|
|
310
|
+
* @param ttlMs TTL du cache (override la valeur par défaut)
|
|
311
|
+
*/
|
|
312
|
+
async execute(key, fetcher, strategy, ttlMs) {
|
|
313
|
+
switch (strategy) {
|
|
314
|
+
case "network-only":
|
|
315
|
+
return fetcher();
|
|
316
|
+
case "network-first":
|
|
317
|
+
return this.networkFirst(key, fetcher, ttlMs ?? DEFAULT_TTL["network-first"]);
|
|
318
|
+
case "cache-first":
|
|
319
|
+
return this.cacheFirst(key, fetcher, ttlMs ?? DEFAULT_TTL["cache-first"]);
|
|
320
|
+
case "stale-while-revalidate":
|
|
321
|
+
return this.staleWhileRevalidate(key, fetcher, ttlMs ?? DEFAULT_TTL["stale-while-revalidate"]);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async networkFirst(key, fetcher, ttlMs) {
|
|
325
|
+
try {
|
|
326
|
+
const data = await fetcher();
|
|
327
|
+
await this.bridge.set(key, data, ttlMs);
|
|
328
|
+
return data;
|
|
329
|
+
} catch (err) {
|
|
330
|
+
if (err instanceof TypeError) {
|
|
331
|
+
const cached = await this.bridge.get(key);
|
|
332
|
+
if (cached !== null) return cached;
|
|
333
|
+
}
|
|
334
|
+
throw err;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async cacheFirst(key, fetcher, ttlMs) {
|
|
338
|
+
const cached = await this.bridge.get(key);
|
|
339
|
+
if (cached !== null) return cached;
|
|
340
|
+
const data = await fetcher();
|
|
341
|
+
await this.bridge.set(key, data, ttlMs);
|
|
342
|
+
return data;
|
|
343
|
+
}
|
|
344
|
+
async staleWhileRevalidate(key, fetcher, ttlMs) {
|
|
345
|
+
const cached = await this.bridge.get(key);
|
|
346
|
+
fetcher().then((data2) => this.bridge.set(key, data2, ttlMs)).catch(() => {
|
|
347
|
+
});
|
|
348
|
+
if (cached !== null) {
|
|
349
|
+
return cached;
|
|
350
|
+
}
|
|
351
|
+
const data = await fetcher();
|
|
352
|
+
await this.bridge.set(key, data, ttlMs);
|
|
353
|
+
return data;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const DB_NAME = "altazion-queue";
|
|
357
|
+
const DB_VERSION = 1;
|
|
358
|
+
const STORE_NAME = "operations";
|
|
359
|
+
class OfflineQueue {
|
|
360
|
+
constructor() {
|
|
361
|
+
__publicField(this, "db", null);
|
|
362
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
363
|
+
__publicField(this, "counter", 0);
|
|
364
|
+
}
|
|
365
|
+
/** Abonne un listener aux événements de file. Retourne une fonction de désabonnement. */
|
|
366
|
+
subscribe(listener) {
|
|
367
|
+
this.listeners.add(listener);
|
|
368
|
+
return () => this.listeners.delete(listener);
|
|
369
|
+
}
|
|
370
|
+
get pendingCount() {
|
|
371
|
+
return this.openDb().then(
|
|
372
|
+
(db) => new Promise((resolve, reject) => {
|
|
373
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
374
|
+
const req = tx.objectStore(STORE_NAME).count();
|
|
375
|
+
req.onsuccess = () => resolve(req.result);
|
|
376
|
+
req.onerror = () => reject(req.error);
|
|
377
|
+
})
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
async enqueue(method, path, body, headers) {
|
|
381
|
+
const operation = {
|
|
382
|
+
id: `${Date.now()}-${++this.counter}`,
|
|
383
|
+
enqueuedAt: Date.now(),
|
|
384
|
+
method,
|
|
385
|
+
path,
|
|
386
|
+
body,
|
|
387
|
+
headers,
|
|
388
|
+
attempts: 0
|
|
389
|
+
};
|
|
390
|
+
const db = await this.openDb();
|
|
391
|
+
await this.put(db, operation);
|
|
392
|
+
this.emit({ type: "enqueued", operation });
|
|
393
|
+
return operation;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Rejoue toutes les opérations en attente dans l'ordre d'enfilage.
|
|
397
|
+
* S'arrête sur un conflit (4xx) et émet un événement `conflict`.
|
|
398
|
+
*/
|
|
399
|
+
async flush(http) {
|
|
400
|
+
const operations = await this.getAll();
|
|
401
|
+
if (operations.length === 0) return;
|
|
402
|
+
for (const op of operations) {
|
|
403
|
+
try {
|
|
404
|
+
op.attempts++;
|
|
405
|
+
await http.request(op.path, {
|
|
406
|
+
method: op.method,
|
|
407
|
+
body: op.body,
|
|
408
|
+
headers: op.headers
|
|
409
|
+
});
|
|
410
|
+
await this.remove(op.id);
|
|
411
|
+
} catch (err) {
|
|
412
|
+
if (err instanceof AltazionApiError && err.status >= 400 && err.status < 500) {
|
|
413
|
+
const flushError = new QueueFlushError(op.id, err);
|
|
414
|
+
this.emit({ type: "conflict", operation: op, error: flushError });
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
await this.put(await this.openDb(), op);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
this.emit({ type: "flushed" });
|
|
422
|
+
const remaining = await this.pendingCount;
|
|
423
|
+
if (remaining === 0) {
|
|
424
|
+
this.emit({ type: "emptied" });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
emit(event) {
|
|
428
|
+
for (const listener of this.listeners) {
|
|
429
|
+
try {
|
|
430
|
+
listener(event);
|
|
431
|
+
} catch {
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
openDb() {
|
|
436
|
+
return new Promise((resolve, reject) => {
|
|
437
|
+
if (this.db) {
|
|
438
|
+
resolve(this.db);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
442
|
+
request.onupgradeneeded = (event) => {
|
|
443
|
+
const database = event.target.result;
|
|
444
|
+
if (!database.objectStoreNames.contains(STORE_NAME)) {
|
|
445
|
+
database.createObjectStore(STORE_NAME, { keyPath: "id" });
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
request.onsuccess = (event) => {
|
|
449
|
+
this.db = event.target.result;
|
|
450
|
+
resolve(this.db);
|
|
451
|
+
};
|
|
452
|
+
request.onerror = () => reject(request.error);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
put(db, operation) {
|
|
456
|
+
return new Promise((resolve, reject) => {
|
|
457
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
458
|
+
const req = tx.objectStore(STORE_NAME).put(operation);
|
|
459
|
+
req.onsuccess = () => resolve();
|
|
460
|
+
req.onerror = () => reject(req.error);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
remove(id) {
|
|
464
|
+
return this.openDb().then(
|
|
465
|
+
(db) => new Promise((resolve, reject) => {
|
|
466
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
467
|
+
const req = tx.objectStore(STORE_NAME).delete(id);
|
|
468
|
+
req.onsuccess = () => resolve();
|
|
469
|
+
req.onerror = () => reject(req.error);
|
|
470
|
+
})
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
getAll() {
|
|
474
|
+
return this.openDb().then(
|
|
475
|
+
(db) => new Promise((resolve, reject) => {
|
|
476
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
477
|
+
const req = tx.objectStore(STORE_NAME).getAll();
|
|
478
|
+
req.onsuccess = () => {
|
|
479
|
+
const ops = req.result.sort(
|
|
480
|
+
(a, b) => a.enqueuedAt - b.enqueuedAt
|
|
481
|
+
);
|
|
482
|
+
resolve(ops);
|
|
483
|
+
};
|
|
484
|
+
req.onerror = () => reject(req.error);
|
|
485
|
+
})
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
class BrowserSessionManager {
|
|
490
|
+
constructor(http, context) {
|
|
491
|
+
__publicField(this, "http");
|
|
492
|
+
__publicField(this, "context");
|
|
493
|
+
this.http = http;
|
|
494
|
+
this.context = context;
|
|
495
|
+
}
|
|
496
|
+
async initialize() {
|
|
497
|
+
try {
|
|
498
|
+
const existing = await this.http.get("/commerce/api/sessions/raw");
|
|
499
|
+
return existing;
|
|
500
|
+
} catch (err) {
|
|
501
|
+
if (err instanceof AltazionApiError && err.status === 404) {
|
|
502
|
+
return this.createSession();
|
|
503
|
+
}
|
|
504
|
+
throw err;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async onSessionExpired() {
|
|
508
|
+
await this.createSession();
|
|
509
|
+
}
|
|
510
|
+
async createSession() {
|
|
511
|
+
const body = this.buildCreationRequest();
|
|
512
|
+
return this.http.post("/commerce/api/sessions/create", body);
|
|
513
|
+
}
|
|
514
|
+
buildCreationRequest() {
|
|
515
|
+
if (this.context.sitePk !== void 0 && this.context.rjsId !== void 0) {
|
|
516
|
+
return { sitePk: this.context.sitePk, rjsId: this.context.rjsId };
|
|
517
|
+
}
|
|
518
|
+
if (this.context.siteUrl !== void 0) {
|
|
519
|
+
return { siteUrl: this.context.siteUrl };
|
|
520
|
+
}
|
|
521
|
+
const currentUrl = typeof window !== "undefined" ? window.location.href : void 0;
|
|
522
|
+
return { siteUrl: currentUrl };
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
class KioskSessionManager {
|
|
526
|
+
async initialize() {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
async onSessionExpired() {
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
class SessionManagerFactory {
|
|
533
|
+
static create(http, context) {
|
|
534
|
+
if (context.mode === "kiosk") {
|
|
535
|
+
return new KioskSessionManager();
|
|
536
|
+
}
|
|
537
|
+
return new BrowserSessionManager(http, context);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
class SessionModule {
|
|
541
|
+
constructor(http, cache) {
|
|
542
|
+
this.http = http;
|
|
543
|
+
this.cache = cache;
|
|
544
|
+
}
|
|
545
|
+
/** Récupère les informations de la session en cours */
|
|
546
|
+
getSession() {
|
|
547
|
+
return this.cache.execute(
|
|
548
|
+
"session:raw",
|
|
549
|
+
() => this.http.get("/commerce/api/sessions/raw"),
|
|
550
|
+
"network-first"
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
/** Associe un magasin à la session */
|
|
554
|
+
async associateStore(storeGuid) {
|
|
555
|
+
const result = await this.http.post(
|
|
556
|
+
`/commerce/api/sessions/associate/store/${storeGuid}`
|
|
557
|
+
);
|
|
558
|
+
await this.cache.execute("session:raw", () => Promise.resolve(result), "network-first");
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
class CartModule {
|
|
563
|
+
constructor(http, cache, queue, connectivity) {
|
|
564
|
+
this.http = http;
|
|
565
|
+
this.cache = cache;
|
|
566
|
+
this.queue = queue;
|
|
567
|
+
this.connectivity = connectivity;
|
|
568
|
+
}
|
|
569
|
+
/** Récupère le panier en cours */
|
|
570
|
+
getCart() {
|
|
571
|
+
return this.cache.execute(
|
|
572
|
+
"cart:current",
|
|
573
|
+
() => this.http.get("/commerce/api/process/cart"),
|
|
574
|
+
"network-first"
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
/** Récupère le statut de validation du panier */
|
|
578
|
+
getValidationStatus() {
|
|
579
|
+
return this.http.get("/commerce/api/process/cart/status/check");
|
|
580
|
+
}
|
|
581
|
+
/** Ajoute un article au panier */
|
|
582
|
+
async addItem(reference, quantity, options) {
|
|
583
|
+
const body = { reference, quantity, ...options };
|
|
584
|
+
if (this.connectivity.isOffline) {
|
|
585
|
+
await this.queue.enqueue("POST", "/commerce/api/process/cart/items", body);
|
|
586
|
+
return this.cache.execute("cart:current", () => this.http.get("/commerce/api/process/cart"), "network-first");
|
|
587
|
+
}
|
|
588
|
+
const cart = await this.http.post("/commerce/api/process/cart/items", body);
|
|
589
|
+
await this.cache.execute("cart:current", () => Promise.resolve(cart), "network-first");
|
|
590
|
+
return cart;
|
|
591
|
+
}
|
|
592
|
+
/** Met à jour la quantité d'une ligne */
|
|
593
|
+
async updateItem(lineId, quantity) {
|
|
594
|
+
const body = { quantity };
|
|
595
|
+
if (this.connectivity.isOffline) {
|
|
596
|
+
await this.queue.enqueue("PUT", `/commerce/api/process/cart/items/${lineId}`, body);
|
|
597
|
+
return this.cache.execute("cart:current", () => this.http.get("/commerce/api/process/cart"), "network-first");
|
|
598
|
+
}
|
|
599
|
+
const cart = await this.http.put(`/commerce/api/process/cart/items/${lineId}`, body);
|
|
600
|
+
await this.cache.execute("cart:current", () => Promise.resolve(cart), "network-first");
|
|
601
|
+
return cart;
|
|
602
|
+
}
|
|
603
|
+
/** Supprime une ligne du panier */
|
|
604
|
+
async removeItem(lineId) {
|
|
605
|
+
if (this.connectivity.isOffline) {
|
|
606
|
+
await this.queue.enqueue("DELETE", `/commerce/api/process/cart/items/${lineId}`);
|
|
607
|
+
return this.cache.execute("cart:current", () => this.http.get("/commerce/api/process/cart"), "network-first");
|
|
608
|
+
}
|
|
609
|
+
const cart = await this.http.delete(`/commerce/api/process/cart/items/${lineId}`);
|
|
610
|
+
await this.cache.execute("cart:current", () => Promise.resolve(cart), "network-first");
|
|
611
|
+
return cart;
|
|
612
|
+
}
|
|
613
|
+
/** Applique un coupon */
|
|
614
|
+
async applyCoupon(code) {
|
|
615
|
+
const cart = await this.http.post("/commerce/api/process/cart/coupons", { code });
|
|
616
|
+
await this.cache.execute("cart:current", () => Promise.resolve(cart), "network-first");
|
|
617
|
+
return cart;
|
|
618
|
+
}
|
|
619
|
+
/** Retire un coupon */
|
|
620
|
+
async removeCoupon(code) {
|
|
621
|
+
const cart = await this.http.delete(`/commerce/api/process/cart/coupons/${encodeURIComponent(code)}`);
|
|
622
|
+
await this.cache.execute("cart:current", () => Promise.resolve(cart), "network-first");
|
|
623
|
+
return cart;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
class CatalogModule {
|
|
627
|
+
constructor(http, cache) {
|
|
628
|
+
this.http = http;
|
|
629
|
+
this.cache = cache;
|
|
630
|
+
}
|
|
631
|
+
/** Récupère le détail d'un produit par sa référence */
|
|
632
|
+
getProduct(reference) {
|
|
633
|
+
return this.cache.execute(
|
|
634
|
+
`catalog:product:${reference}`,
|
|
635
|
+
() => this.http.get(`/commerce/api/pim/products/${encodeURIComponent(reference)}`),
|
|
636
|
+
"stale-while-revalidate"
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
/** Récupère une liste de produits par leurs références */
|
|
640
|
+
getProducts(references) {
|
|
641
|
+
const key = `catalog:products:${references.sort().join(",")}`;
|
|
642
|
+
return this.cache.execute(
|
|
643
|
+
key,
|
|
644
|
+
() => this.http.post("/commerce/api/pim/products/batch", { references }),
|
|
645
|
+
"stale-while-revalidate"
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
/** Récupère les produits d'une catégorie */
|
|
649
|
+
getCategory(categoryCode, pageIndex = 0, pageSize = 20) {
|
|
650
|
+
const key = `catalog:category:${categoryCode}:${pageIndex}:${pageSize}`;
|
|
651
|
+
return this.cache.execute(
|
|
652
|
+
key,
|
|
653
|
+
() => this.http.get(
|
|
654
|
+
`/commerce/api/pim/categories/${encodeURIComponent(categoryCode)}/products?pageIndex=${pageIndex}&pageSize=${pageSize}`
|
|
655
|
+
),
|
|
656
|
+
"stale-while-revalidate"
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
/** Effectue une recherche full-text */
|
|
660
|
+
search(request) {
|
|
661
|
+
const key = `catalog:search:${JSON.stringify(request)}`;
|
|
662
|
+
return this.cache.execute(
|
|
663
|
+
key,
|
|
664
|
+
() => this.http.post("/commerce/api/pim/search", request),
|
|
665
|
+
"cache-first"
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
/** Récupère les suggestions de recherche (autocomplete) */
|
|
669
|
+
suggest(query) {
|
|
670
|
+
return this.cache.execute(
|
|
671
|
+
`catalog:suggest:${query}`,
|
|
672
|
+
() => this.http.get(`/commerce/api/pim/search/suggest?q=${encodeURIComponent(query)}`),
|
|
673
|
+
"cache-first",
|
|
674
|
+
2 * 6e4
|
|
675
|
+
// 2 minutes pour les suggestions
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
class ShippingModule {
|
|
680
|
+
constructor(http, cache) {
|
|
681
|
+
this.http = http;
|
|
682
|
+
this.cache = cache;
|
|
683
|
+
}
|
|
684
|
+
/** Récupère les groupes de modes de livraison disponibles pour le panier en cours */
|
|
685
|
+
getAvailableModes() {
|
|
686
|
+
return this.cache.execute(
|
|
687
|
+
"shipping:modes",
|
|
688
|
+
() => this.http.get("/commerce/api/process/cart/shipping/modes"),
|
|
689
|
+
"network-first"
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
/** Sélectionne un mode de livraison */
|
|
693
|
+
selectMode(shippingModeGuid) {
|
|
694
|
+
return this.http.post("/commerce/api/process/cart/shipping/mode", { shippingModeGuid });
|
|
695
|
+
}
|
|
696
|
+
/** Met à jour l'adresse de livraison */
|
|
697
|
+
setAddress(address) {
|
|
698
|
+
return this.http.put("/commerce/api/process/cart/address/shipping", address);
|
|
699
|
+
}
|
|
700
|
+
/** Recherche des points relais à proximité */
|
|
701
|
+
getRelayPoints(postalCode, countryCode = "FR", shippingModeGuid) {
|
|
702
|
+
const params = new URLSearchParams({ postalCode, countryCode });
|
|
703
|
+
if (shippingModeGuid) params.set("shippingModeGuid", shippingModeGuid);
|
|
704
|
+
const key = `shipping:relays:${postalCode}:${countryCode}:${shippingModeGuid ?? ""}`;
|
|
705
|
+
return this.cache.execute(
|
|
706
|
+
key,
|
|
707
|
+
() => this.http.get(`/commerce/api/process/shipping/relays?${params.toString()}`),
|
|
708
|
+
"cache-first",
|
|
709
|
+
10 * 6e4
|
|
710
|
+
// 10 minutes
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
class MarketingModule {
|
|
715
|
+
constructor(http, cache) {
|
|
716
|
+
this.http = http;
|
|
717
|
+
this.cache = cache;
|
|
718
|
+
}
|
|
719
|
+
/** Récupère un item marketing par son code */
|
|
720
|
+
getItem(code) {
|
|
721
|
+
return this.cache.execute(
|
|
722
|
+
`marketing:item:${code}`,
|
|
723
|
+
() => this.http.get(`/commerce/api/marketing/items/${encodeURIComponent(code)}`),
|
|
724
|
+
"stale-while-revalidate"
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
/** Récupère plusieurs items marketing par leurs codes */
|
|
728
|
+
getItems(codes) {
|
|
729
|
+
const key = `marketing:items:${codes.sort().join(",")}`;
|
|
730
|
+
return this.cache.execute(
|
|
731
|
+
key,
|
|
732
|
+
() => this.http.post("/commerce/api/marketing/items/batch", { codes }),
|
|
733
|
+
"stale-while-revalidate"
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
class StoresModule {
|
|
738
|
+
constructor(http, cache) {
|
|
739
|
+
this.http = http;
|
|
740
|
+
this.cache = cache;
|
|
741
|
+
}
|
|
742
|
+
/** Recherche des magasins par coordonnées géographiques */
|
|
743
|
+
findByLocation(latitude, longitude, radiusKm = 50) {
|
|
744
|
+
const key = `stores:geo:${latitude.toFixed(4)}:${longitude.toFixed(4)}:${radiusKm}`;
|
|
745
|
+
return this.cache.execute(
|
|
746
|
+
key,
|
|
747
|
+
() => this.http.get(
|
|
748
|
+
`/commerce/api/stores/locator?lat=${latitude}&lon=${longitude}&radius=${radiusKm}`
|
|
749
|
+
),
|
|
750
|
+
"cache-first",
|
|
751
|
+
15 * 6e4
|
|
752
|
+
// 15 minutes
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
/** Recherche des magasins par code postal */
|
|
756
|
+
findByPostalCode(postalCode, countryCode = "FR") {
|
|
757
|
+
const key = `stores:postal:${postalCode}:${countryCode}`;
|
|
758
|
+
return this.cache.execute(
|
|
759
|
+
key,
|
|
760
|
+
() => this.http.get(
|
|
761
|
+
`/commerce/api/stores/locator?postalCode=${encodeURIComponent(postalCode)}&countryCode=${encodeURIComponent(countryCode)}`
|
|
762
|
+
),
|
|
763
|
+
"cache-first",
|
|
764
|
+
15 * 6e4
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
/** Récupère le détail d'un magasin par son GUID */
|
|
768
|
+
getStore(storeGuid) {
|
|
769
|
+
return this.cache.execute(
|
|
770
|
+
`stores:detail:${storeGuid}`,
|
|
771
|
+
() => this.http.get(`/commerce/api/stores/${storeGuid}`),
|
|
772
|
+
"stale-while-revalidate"
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
class CommerceClient {
|
|
777
|
+
constructor(options) {
|
|
778
|
+
__publicField(this, "context");
|
|
779
|
+
__publicField(this, "connectivity");
|
|
780
|
+
__publicField(this, "session");
|
|
781
|
+
__publicField(this, "cart");
|
|
782
|
+
__publicField(this, "catalog");
|
|
783
|
+
__publicField(this, "shipping");
|
|
784
|
+
__publicField(this, "marketing");
|
|
785
|
+
__publicField(this, "stores");
|
|
786
|
+
__publicField(this, "http");
|
|
787
|
+
__publicField(this, "workerBridge");
|
|
788
|
+
__publicField(this, "cacheStrategy");
|
|
789
|
+
__publicField(this, "offlineQueue");
|
|
790
|
+
__publicField(this, "sessionManager");
|
|
791
|
+
__publicField(this, "unsubscribeOnline", null);
|
|
792
|
+
this.context = new CommerceContext(options);
|
|
793
|
+
this.http = new CommerceHttpAdapter(this.context);
|
|
794
|
+
this.connectivity = new ConnectivityManager();
|
|
795
|
+
this.workerBridge = new WorkerBridge();
|
|
796
|
+
this.cacheStrategy = new CacheStrategy(this.workerBridge, this.http);
|
|
797
|
+
this.offlineQueue = new OfflineQueue();
|
|
798
|
+
this.sessionManager = SessionManagerFactory.create(this.http, this.context);
|
|
799
|
+
this.session = new SessionModule(this.http, this.cacheStrategy);
|
|
800
|
+
this.cart = new CartModule(this.http, this.cacheStrategy, this.offlineQueue, this.connectivity);
|
|
801
|
+
this.catalog = new CatalogModule(this.http, this.cacheStrategy);
|
|
802
|
+
this.shipping = new ShippingModule(this.http, this.cacheStrategy);
|
|
803
|
+
this.marketing = new MarketingModule(this.http, this.cacheStrategy);
|
|
804
|
+
this.stores = new StoresModule(this.http, this.cacheStrategy);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Initialise le SDK :
|
|
808
|
+
* - Crée la session si nécessaire (mode browser uniquement)
|
|
809
|
+
* - Abonne le flush automatique de la file hors-ligne au retour en ligne
|
|
810
|
+
*/
|
|
811
|
+
async initialize() {
|
|
812
|
+
this.unsubscribeOnline = this.connectivity.subscribe(async (status) => {
|
|
813
|
+
if (status === "online") {
|
|
814
|
+
await this.offlineQueue.flush(this.http);
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
return this.sessionManager.initialize();
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Retourne vrai si le SDK est hors-ligne.
|
|
821
|
+
*/
|
|
822
|
+
get isOffline() {
|
|
823
|
+
return this.connectivity.isOffline;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Retourne le nombre d'opérations en attente dans la file hors-ligne.
|
|
827
|
+
*/
|
|
828
|
+
get pendingOperationsCount() {
|
|
829
|
+
return this.offlineQueue.pendingCount;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Abonne un listener aux événements de la file hors-ligne.
|
|
833
|
+
* Retourne une fonction de désabonnement.
|
|
834
|
+
*/
|
|
835
|
+
onQueueEvent(listener) {
|
|
836
|
+
return this.offlineQueue.subscribe(listener);
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Force le vidage du cache.
|
|
840
|
+
*/
|
|
841
|
+
clearCache() {
|
|
842
|
+
return this.workerBridge.clearAll();
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Libère les ressources (listeners, worker).
|
|
846
|
+
*/
|
|
847
|
+
dispose() {
|
|
848
|
+
var _a;
|
|
849
|
+
(_a = this.unsubscribeOnline) == null ? void 0 : _a.call(this);
|
|
850
|
+
this.connectivity.dispose();
|
|
851
|
+
this.workerBridge.dispose();
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
exports.AltazionApiError = AltazionApiError;
|
|
855
|
+
exports.CacheError = CacheError;
|
|
856
|
+
exports.CommerceClient = CommerceClient;
|
|
857
|
+
exports.CommerceContext = CommerceContext;
|
|
858
|
+
exports.ConnectivityManager = ConnectivityManager;
|
|
859
|
+
exports.OfflineError = OfflineError;
|
|
860
|
+
exports.QueueFlushError = QueueFlushError;
|
|
861
|
+
//# sourceMappingURL=index.cjs.map
|