@commercengine/js 0.1.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/README.md +367 -0
- package/dist/index.cjs +514 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +341 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +341 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +518 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +510 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +47 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
|
|
3
|
+
//#region src/events.ts
|
|
4
|
+
var EventEmitter = class {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Subscribe to an event
|
|
10
|
+
*/
|
|
11
|
+
on(event, listener) {
|
|
12
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
13
|
+
this.listeners.get(event)?.add(listener);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Unsubscribe from an event
|
|
17
|
+
*/
|
|
18
|
+
off(event, listener) {
|
|
19
|
+
const eventListeners = this.listeners.get(event);
|
|
20
|
+
if (eventListeners) eventListeners.delete(listener);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Subscribe to an event (once)
|
|
24
|
+
*/
|
|
25
|
+
once(event, listener) {
|
|
26
|
+
const onceListener = (data) => {
|
|
27
|
+
this.off(event, onceListener);
|
|
28
|
+
listener(data);
|
|
29
|
+
};
|
|
30
|
+
this.on(event, onceListener);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Emit an event to all subscribers
|
|
34
|
+
*/
|
|
35
|
+
emit(event, data) {
|
|
36
|
+
const eventListeners = this.listeners.get(event);
|
|
37
|
+
if (eventListeners) for (const listener of eventListeners) try {
|
|
38
|
+
listener(data);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error(`[Commercengine] Error in ${event} listener:`, error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Remove all listeners
|
|
45
|
+
*/
|
|
46
|
+
removeAllListeners() {
|
|
47
|
+
this.listeners.clear();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/iframe-manager.ts
|
|
53
|
+
var IframeManager = class {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.iframe = null;
|
|
56
|
+
this.container = null;
|
|
57
|
+
this.checkoutOrigin = null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create and mount the iframe (hidden initially)
|
|
61
|
+
*/
|
|
62
|
+
create(url, zIndex) {
|
|
63
|
+
if (this.container) {
|
|
64
|
+
console.warn("[Commercengine] Iframe already created");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.checkoutOrigin = new URL(url).origin;
|
|
68
|
+
this.container = document.createElement("div");
|
|
69
|
+
this.container.id = "commercengine-checkout";
|
|
70
|
+
this.container.style.cssText = `
|
|
71
|
+
position: fixed;
|
|
72
|
+
inset: 0;
|
|
73
|
+
z-index: ${zIndex};
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
`;
|
|
76
|
+
this.iframe = document.createElement("iframe");
|
|
77
|
+
this.iframe.id = "commercengine-checkout-iframe";
|
|
78
|
+
this.iframe.src = url;
|
|
79
|
+
this.iframe.allow = "payment";
|
|
80
|
+
this.iframe.style.cssText = `
|
|
81
|
+
width: 100%;
|
|
82
|
+
height: 100%;
|
|
83
|
+
border: none;
|
|
84
|
+
pointer-events: inherit;
|
|
85
|
+
background-color: transparent;
|
|
86
|
+
`;
|
|
87
|
+
this.container.appendChild(this.iframe);
|
|
88
|
+
document.body.appendChild(this.container);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Show the checkout overlay (enable pointer events)
|
|
92
|
+
*/
|
|
93
|
+
show() {
|
|
94
|
+
if (this.container) {
|
|
95
|
+
this.container.style.pointerEvents = "auto";
|
|
96
|
+
document.body.style.overflow = "hidden";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Hide the checkout overlay (disable pointer events)
|
|
101
|
+
*/
|
|
102
|
+
hide() {
|
|
103
|
+
if (this.container) {
|
|
104
|
+
this.container.style.pointerEvents = "none";
|
|
105
|
+
document.body.style.overflow = "";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if iframe is visible
|
|
110
|
+
*/
|
|
111
|
+
isVisible() {
|
|
112
|
+
return this.container?.style.pointerEvents === "auto";
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Send a message to the checkout iframe
|
|
116
|
+
*/
|
|
117
|
+
postMessage(type, data) {
|
|
118
|
+
if (!this.iframe?.contentWindow) {
|
|
119
|
+
console.warn("[Commercengine] Cannot send message - iframe not ready");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!this.checkoutOrigin) {
|
|
123
|
+
console.warn("[Commercengine] Cannot send message - checkout origin not established");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.iframe.contentWindow.postMessage({
|
|
127
|
+
type,
|
|
128
|
+
data
|
|
129
|
+
}, this.checkoutOrigin);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get the checkout origin for message validation
|
|
133
|
+
*/
|
|
134
|
+
getCheckoutOrigin() {
|
|
135
|
+
return this.checkoutOrigin;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Destroy the iframe and clean up
|
|
139
|
+
*/
|
|
140
|
+
destroy() {
|
|
141
|
+
if (this.container) {
|
|
142
|
+
this.container.remove();
|
|
143
|
+
this.container = null;
|
|
144
|
+
}
|
|
145
|
+
this.iframe = null;
|
|
146
|
+
this.checkoutOrigin = null;
|
|
147
|
+
document.body.style.overflow = "";
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/checkout.ts
|
|
153
|
+
/**
|
|
154
|
+
* Checkout Class
|
|
155
|
+
*
|
|
156
|
+
* Main entry point for Commerce Engine Checkout integration.
|
|
157
|
+
* Manages iframe lifecycle, event handling, and provides public API.
|
|
158
|
+
*/
|
|
159
|
+
/**
|
|
160
|
+
* Resolve checkout URL based on current environment
|
|
161
|
+
*
|
|
162
|
+
* The environment is determined by where the script was loaded from:
|
|
163
|
+
* - js.commercengine.com → production checkout
|
|
164
|
+
* - js.staging.commercengine.com → staging checkout
|
|
165
|
+
* - localhost → local development
|
|
166
|
+
*/
|
|
167
|
+
function resolveCheckoutUrl(storeId, apiKey) {
|
|
168
|
+
const scripts = document.querySelectorAll("script[src*=\"commercengine\"]");
|
|
169
|
+
let isStaging = false;
|
|
170
|
+
for (const script of scripts) if ((script.getAttribute("src") || "").includes("staging.commercengine.com")) {
|
|
171
|
+
isStaging = true;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
if (typeof window !== "undefined" && window.location.hostname === "localhost") return `http://localhost:8080?store_id=${storeId}&api_key=${apiKey}&mode=iframe`;
|
|
175
|
+
return `${isStaging ? "https://checkout.staging.commercengine.com" : "https://checkout.commercengine.com"}?store_id=${storeId}&api_key=${apiKey}&mode=iframe`;
|
|
176
|
+
}
|
|
177
|
+
var Checkout = class extends EventEmitter {
|
|
178
|
+
constructor(config) {
|
|
179
|
+
super();
|
|
180
|
+
this.isReady = false;
|
|
181
|
+
this.isOpen = false;
|
|
182
|
+
this.cartState = {
|
|
183
|
+
count: 0,
|
|
184
|
+
total: 0,
|
|
185
|
+
currency: "INR"
|
|
186
|
+
};
|
|
187
|
+
this.hasQuickBuyParams = false;
|
|
188
|
+
this.config = config;
|
|
189
|
+
this.iframe = new IframeManager();
|
|
190
|
+
this.boundMessageHandler = this.handleMessage.bind(this);
|
|
191
|
+
this.initialize();
|
|
192
|
+
}
|
|
193
|
+
initialize() {
|
|
194
|
+
let baseUrl;
|
|
195
|
+
if (this.config.url) baseUrl = this.config.url;
|
|
196
|
+
else if (this.config.storeId && this.config.apiKey) baseUrl = resolveCheckoutUrl(this.config.storeId, this.config.apiKey);
|
|
197
|
+
else throw new Error("[Commercengine] Either 'url' or both 'storeId' and 'apiKey' must be provided.");
|
|
198
|
+
const url = new URL(baseUrl);
|
|
199
|
+
url.searchParams.set("mode", "iframe");
|
|
200
|
+
if (this.config.storeId && !url.searchParams.has("store_id")) url.searchParams.set("store_id", this.config.storeId);
|
|
201
|
+
if (this.config.apiKey && !url.searchParams.has("api_key")) url.searchParams.set("api_key", this.config.apiKey);
|
|
202
|
+
if (this.config.theme) url.searchParams.set("theme", this.config.theme);
|
|
203
|
+
if (this.config.authMode) url.searchParams.set("auth_mode", this.config.authMode);
|
|
204
|
+
if (this.config.accessToken) url.searchParams.set("token", this.config.accessToken);
|
|
205
|
+
if (this.config.refreshToken) url.searchParams.set("refresh_token", this.config.refreshToken);
|
|
206
|
+
let quickBuy = this.config.quickBuy;
|
|
207
|
+
let sessionMode = this.config.sessionMode;
|
|
208
|
+
if (this.config.autoDetectQuickBuy && !quickBuy) {
|
|
209
|
+
const parentParams = new URLSearchParams(window.location.search);
|
|
210
|
+
const productId = parentParams.get("product_id");
|
|
211
|
+
const variantId = parentParams.get("variant_id");
|
|
212
|
+
if (productId) {
|
|
213
|
+
quickBuy = {
|
|
214
|
+
productId,
|
|
215
|
+
variantId: variantId ?? null,
|
|
216
|
+
quantity: parseInt(parentParams.get("qty") || parentParams.get("quantity") || "1", 10)
|
|
217
|
+
};
|
|
218
|
+
const parentSessionMode = parentParams.get("session_mode");
|
|
219
|
+
if (parentSessionMode === "force-new" || parentSessionMode === "continue-existing") sessionMode = parentSessionMode;
|
|
220
|
+
setTimeout(() => this.cleanQuickBuyParamsFromUrl(), 0);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (quickBuy) {
|
|
224
|
+
this.hasQuickBuyParams = true;
|
|
225
|
+
url.searchParams.set("product_id", quickBuy.productId);
|
|
226
|
+
if (quickBuy.variantId) url.searchParams.set("variant_id", quickBuy.variantId);
|
|
227
|
+
if (quickBuy.quantity && quickBuy.quantity !== 1) url.searchParams.set("qty", String(quickBuy.quantity));
|
|
228
|
+
}
|
|
229
|
+
if (sessionMode) url.searchParams.set("session_mode", sessionMode);
|
|
230
|
+
if (typeof window !== "undefined") url.searchParams.set("parent_origin", window.location.origin);
|
|
231
|
+
const zIndex = this.config.appearance?.zIndex ?? 99999;
|
|
232
|
+
this.iframe.create(url.toString(), zIndex);
|
|
233
|
+
window.addEventListener("message", this.boundMessageHandler);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Remove quick buy params from the parent URL to prevent duplicate adds on refresh
|
|
237
|
+
* Uses replaceState to avoid adding to browser history
|
|
238
|
+
*/
|
|
239
|
+
cleanQuickBuyParamsFromUrl() {
|
|
240
|
+
if (typeof window === "undefined") return;
|
|
241
|
+
const url = new URL(window.location.href);
|
|
242
|
+
const paramsToRemove = [
|
|
243
|
+
"product_id",
|
|
244
|
+
"variant_id",
|
|
245
|
+
"qty",
|
|
246
|
+
"quantity",
|
|
247
|
+
"session_mode"
|
|
248
|
+
];
|
|
249
|
+
let hasChanges = false;
|
|
250
|
+
for (const param of paramsToRemove) if (url.searchParams.has(param)) {
|
|
251
|
+
url.searchParams.delete(param);
|
|
252
|
+
hasChanges = true;
|
|
253
|
+
}
|
|
254
|
+
if (hasChanges) window.history.replaceState({}, "", url.toString());
|
|
255
|
+
}
|
|
256
|
+
handleMessage(event) {
|
|
257
|
+
const expectedOrigin = this.iframe.getCheckoutOrigin();
|
|
258
|
+
if (!expectedOrigin || event.origin !== expectedOrigin) return;
|
|
259
|
+
const { type, data } = event.data || {};
|
|
260
|
+
if (!type) return;
|
|
261
|
+
switch (type) {
|
|
262
|
+
case "checkout:ready":
|
|
263
|
+
this.handleReady();
|
|
264
|
+
break;
|
|
265
|
+
case "checkout:open":
|
|
266
|
+
this.handleOpen();
|
|
267
|
+
break;
|
|
268
|
+
case "checkout:close":
|
|
269
|
+
this.handleClose();
|
|
270
|
+
break;
|
|
271
|
+
case "checkout:complete":
|
|
272
|
+
this.handleComplete(data);
|
|
273
|
+
break;
|
|
274
|
+
case "checkout:error":
|
|
275
|
+
this.handleError(data);
|
|
276
|
+
break;
|
|
277
|
+
case "cart:updated":
|
|
278
|
+
this.handleCartUpdate(data);
|
|
279
|
+
break;
|
|
280
|
+
case "auth:login":
|
|
281
|
+
this.handleAuthChange({
|
|
282
|
+
type: "login",
|
|
283
|
+
...data
|
|
284
|
+
});
|
|
285
|
+
break;
|
|
286
|
+
case "auth:logout":
|
|
287
|
+
this.handleAuthChange({ type: "logout" });
|
|
288
|
+
break;
|
|
289
|
+
case "auth:refresh":
|
|
290
|
+
this.handleAuthChange({
|
|
291
|
+
type: "refresh",
|
|
292
|
+
...data
|
|
293
|
+
});
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
handleReady() {
|
|
298
|
+
this.isReady = true;
|
|
299
|
+
this.config.onReady?.();
|
|
300
|
+
this.emit("ready");
|
|
301
|
+
if (this.hasQuickBuyParams) this.openCart();
|
|
302
|
+
}
|
|
303
|
+
handleError(data) {
|
|
304
|
+
this.isReady = true;
|
|
305
|
+
this.config.onError?.(data);
|
|
306
|
+
this.emit("error", data);
|
|
307
|
+
}
|
|
308
|
+
handleOpen() {
|
|
309
|
+
this.iframe.show();
|
|
310
|
+
this.isOpen = true;
|
|
311
|
+
this.config.onOpen?.();
|
|
312
|
+
this.emit("open");
|
|
313
|
+
}
|
|
314
|
+
handleClose() {
|
|
315
|
+
this.iframe.hide();
|
|
316
|
+
this.isOpen = false;
|
|
317
|
+
this.config.onClose?.();
|
|
318
|
+
this.emit("close");
|
|
319
|
+
}
|
|
320
|
+
handleComplete(data) {
|
|
321
|
+
this.config.onComplete?.(data);
|
|
322
|
+
this.emit("complete", data);
|
|
323
|
+
}
|
|
324
|
+
handleCartUpdate(data) {
|
|
325
|
+
this.cartState = data;
|
|
326
|
+
this.config.onCartUpdate?.(data);
|
|
327
|
+
this.emit("cart:updated", data);
|
|
328
|
+
}
|
|
329
|
+
handleAuthChange(data) {
|
|
330
|
+
this.config.onAuthChange?.(data);
|
|
331
|
+
this.emit("auth:change", data);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Open the cart drawer
|
|
335
|
+
*/
|
|
336
|
+
openCart() {
|
|
337
|
+
if (!this.isReady) {
|
|
338
|
+
console.warn("[Commercengine] Not ready. Wait for onReady callback or 'ready' event.");
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
this.iframe.show();
|
|
342
|
+
this.isOpen = true;
|
|
343
|
+
this.iframe.postMessage("checkout:open-cart");
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Open the checkout drawer directly (e.g., for Buy Now flow)
|
|
347
|
+
*/
|
|
348
|
+
openCheckout() {
|
|
349
|
+
if (!this.isReady) {
|
|
350
|
+
console.warn("[Commercengine] Not ready. Wait for onReady callback or 'ready' event.");
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
this.iframe.show();
|
|
354
|
+
this.isOpen = true;
|
|
355
|
+
this.iframe.postMessage("checkout:open-checkout");
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Close the checkout overlay
|
|
359
|
+
*/
|
|
360
|
+
close() {
|
|
361
|
+
this.iframe.hide();
|
|
362
|
+
this.isOpen = false;
|
|
363
|
+
this.iframe.postMessage("checkout:close");
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Update authentication tokens
|
|
367
|
+
* Use this when user logs in/out on the parent site
|
|
368
|
+
*/
|
|
369
|
+
updateTokens(accessToken, refreshToken) {
|
|
370
|
+
if (!this.isReady) {
|
|
371
|
+
this.config.accessToken = accessToken;
|
|
372
|
+
this.config.refreshToken = refreshToken;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
this.iframe.postMessage("checkout:update-tokens", {
|
|
376
|
+
accessToken,
|
|
377
|
+
refreshToken
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Add an item to cart and open the cart drawer
|
|
382
|
+
*
|
|
383
|
+
* @param productId - Product ID (required)
|
|
384
|
+
* @param variantId - Variant ID (required, null for non-variant products)
|
|
385
|
+
* @param quantity - Quantity to add (default: 1)
|
|
386
|
+
*/
|
|
387
|
+
addToCart(productId, variantId, quantity = 1) {
|
|
388
|
+
if (!this.isReady) {
|
|
389
|
+
console.warn("[Commercengine] Not ready. Wait for onReady callback or 'ready' event.");
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
this.iframe.postMessage("checkout:add-item", {
|
|
393
|
+
productId,
|
|
394
|
+
variantId,
|
|
395
|
+
quantity
|
|
396
|
+
});
|
|
397
|
+
this.openCart();
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get current cart state
|
|
401
|
+
*/
|
|
402
|
+
getCart() {
|
|
403
|
+
return { ...this.cartState };
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Check if checkout is ready
|
|
407
|
+
*/
|
|
408
|
+
get ready() {
|
|
409
|
+
return this.isReady;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Check if checkout overlay is currently open
|
|
413
|
+
*/
|
|
414
|
+
get open() {
|
|
415
|
+
return this.isOpen;
|
|
416
|
+
}
|
|
417
|
+
on(event, listener) {
|
|
418
|
+
super.on(event, listener);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Destroy the checkout instance
|
|
422
|
+
* Removes iframe and cleans up event listeners
|
|
423
|
+
*/
|
|
424
|
+
destroy() {
|
|
425
|
+
window.removeEventListener("message", this.boundMessageHandler);
|
|
426
|
+
this.iframe.destroy();
|
|
427
|
+
this.removeAllListeners();
|
|
428
|
+
this.isReady = false;
|
|
429
|
+
this.isOpen = false;
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region src/index.ts
|
|
435
|
+
/**
|
|
436
|
+
* Commerce Engine Checkout
|
|
437
|
+
*
|
|
438
|
+
* @commercengine/js - Embeddable checkout SDK
|
|
439
|
+
*
|
|
440
|
+
* @example CDN Usage (Vanilla JS):
|
|
441
|
+
* ```html
|
|
442
|
+
* <script src="https://js.commercengine.com/v1.js" async><\/script>
|
|
443
|
+
* <script>
|
|
444
|
+
* window.Commercengine.onLoad = async () => {
|
|
445
|
+
* const checkout = await Commercengine.init({
|
|
446
|
+
* storeId: "store_xxx",
|
|
447
|
+
* apiKey: "ak_xxx",
|
|
448
|
+
* theme: "dark",
|
|
449
|
+
* onComplete: (order) => console.log("Order:", order.orderNumber),
|
|
450
|
+
* });
|
|
451
|
+
*
|
|
452
|
+
* document.getElementById("cart-btn").onclick = () => checkout.openCart();
|
|
453
|
+
* };
|
|
454
|
+
* <\/script>
|
|
455
|
+
* ```
|
|
456
|
+
*
|
|
457
|
+
* @example ESM Import:
|
|
458
|
+
* ```typescript
|
|
459
|
+
* import { Commercengine } from "@commercengine/js";
|
|
460
|
+
*
|
|
461
|
+
* const checkout = await Commercengine.init({
|
|
462
|
+
* storeId: "store_xxx",
|
|
463
|
+
* apiKey: "ak_xxx",
|
|
464
|
+
* });
|
|
465
|
+
*
|
|
466
|
+
* checkout.on("cart:updated", (cart) => updateBadge(cart.count));
|
|
467
|
+
* checkout.openCart();
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
/**
|
|
471
|
+
* Global Commercengine namespace
|
|
472
|
+
*/
|
|
473
|
+
const Commercengine = { init: async (config) => {
|
|
474
|
+
if (!config.url && (!config.storeId || !config.apiKey)) throw new Error("[Commercengine] Either 'url' or both 'storeId' and 'apiKey' must be provided");
|
|
475
|
+
if (Commercengine.instance) Commercengine.instance.destroy();
|
|
476
|
+
const checkout = new Checkout(config);
|
|
477
|
+
Commercengine.instance = checkout;
|
|
478
|
+
return new Promise((resolve, reject) => {
|
|
479
|
+
if (checkout.ready) {
|
|
480
|
+
resolve(checkout);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const timeout = setTimeout(() => {
|
|
484
|
+
reject(/* @__PURE__ */ new Error("Checkout initialization timed out"));
|
|
485
|
+
}, 3e4);
|
|
486
|
+
checkout.once("ready", () => {
|
|
487
|
+
clearTimeout(timeout);
|
|
488
|
+
resolve(checkout);
|
|
489
|
+
});
|
|
490
|
+
checkout.once("error", () => {
|
|
491
|
+
clearTimeout(timeout);
|
|
492
|
+
resolve(checkout);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
} };
|
|
496
|
+
if (typeof window !== "undefined") {
|
|
497
|
+
const existingOnLoad = window.Commercengine?.onLoad;
|
|
498
|
+
window.Commercengine = Commercengine;
|
|
499
|
+
if (existingOnLoad) {
|
|
500
|
+
Commercengine.onLoad = existingOnLoad;
|
|
501
|
+
try {
|
|
502
|
+
existingOnLoad();
|
|
503
|
+
} catch (error) {
|
|
504
|
+
console.error("[Commercengine] Error in onLoad callback:", error);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
var src_default = Commercengine;
|
|
509
|
+
|
|
510
|
+
//#endregion
|
|
511
|
+
exports.Checkout = Checkout;
|
|
512
|
+
exports.Commercengine = Commercengine;
|
|
513
|
+
exports.default = src_default;
|
|
514
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["onceListener: EventListener<T>","baseUrl: string","Commercengine: CommercengineGlobal"],"sources":["../src/events.ts","../src/iframe-manager.ts","../src/checkout.ts","../src/index.ts"],"sourcesContent":["/**\n * Simple Event Emitter\n *\n * Provides pub/sub functionality for checkout events.\n */\n\nimport type { CheckoutEventType, EventListener } from \"./types\";\n\nexport class EventEmitter {\n private listeners: Map<CheckoutEventType, Set<EventListener>> = new Map();\n\n /**\n * Subscribe to an event\n */\n on<T = unknown>(event: CheckoutEventType, listener: EventListener<T>): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)?.add(listener as EventListener);\n }\n\n /**\n * Unsubscribe from an event\n */\n off<T = unknown>(event: CheckoutEventType, listener: EventListener<T>): void {\n const eventListeners = this.listeners.get(event);\n if (eventListeners) {\n eventListeners.delete(listener as EventListener);\n }\n }\n\n /**\n * Subscribe to an event (once)\n */\n once<T = unknown>(event: CheckoutEventType, listener: EventListener<T>): void {\n const onceListener: EventListener<T> = (data) => {\n this.off(event, onceListener);\n listener(data);\n };\n this.on(event, onceListener);\n }\n\n /**\n * Emit an event to all subscribers\n */\n protected emit<T = unknown>(event: CheckoutEventType, data?: T): void {\n const eventListeners = this.listeners.get(event);\n if (eventListeners) {\n for (const listener of eventListeners) {\n try {\n listener(data);\n } catch (error) {\n // biome-ignore lint/suspicious/noConsole: Intentional error logging\n console.error(`[Commercengine] Error in ${event} listener:`, error);\n }\n }\n }\n }\n\n /**\n * Remove all listeners\n */\n protected removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","/**\n * Iframe Manager\n *\n * Handles iframe creation, lifecycle, and visibility management.\n * The iframe is pre-mounted (hidden) on init and shown on demand.\n */\n\nimport type { OutgoingEventType } from \"./types\";\n\nexport class IframeManager {\n private iframe: HTMLIFrameElement | null = null;\n private container: HTMLDivElement | null = null;\n private checkoutOrigin: string | null = null;\n\n /**\n * Create and mount the iframe (hidden initially)\n */\n create(url: string, zIndex: number): void {\n if (this.container) {\n // biome-ignore lint/suspicious/noConsole: Intentional warning\n console.warn(\"[Commercengine] Iframe already created\");\n return;\n }\n\n // Store origin for postMessage security\n this.checkoutOrigin = new URL(url).origin;\n\n // Create container (covers viewport, initially non-interactive)\n this.container = document.createElement(\"div\");\n this.container.id = \"commercengine-checkout\";\n this.container.style.cssText = `\n position: fixed;\n inset: 0;\n z-index: ${zIndex};\n pointer-events: none;\n `;\n\n // Create iframe\n this.iframe = document.createElement(\"iframe\");\n this.iframe.id = \"commercengine-checkout-iframe\";\n this.iframe.src = url;\n this.iframe.allow = \"payment\";\n this.iframe.style.cssText = `\n width: 100%;\n height: 100%;\n border: none;\n pointer-events: inherit;\n background-color: transparent;\n `;\n\n this.container.appendChild(this.iframe);\n document.body.appendChild(this.container);\n }\n\n /**\n * Show the checkout overlay (enable pointer events)\n */\n show(): void {\n if (this.container) {\n this.container.style.pointerEvents = \"auto\";\n document.body.style.overflow = \"hidden\";\n }\n }\n\n /**\n * Hide the checkout overlay (disable pointer events)\n */\n hide(): void {\n if (this.container) {\n this.container.style.pointerEvents = \"none\";\n document.body.style.overflow = \"\";\n }\n }\n\n /**\n * Check if iframe is visible\n */\n isVisible(): boolean {\n return this.container?.style.pointerEvents === \"auto\";\n }\n\n /**\n * Send a message to the checkout iframe\n */\n postMessage(type: OutgoingEventType, data?: unknown): void {\n if (!this.iframe?.contentWindow) {\n // biome-ignore lint/suspicious/noConsole: Intentional warning\n console.warn(\"[Commercengine] Cannot send message - iframe not ready\");\n return;\n }\n\n // Use specific origin for security - never use \"*\"\n if (!this.checkoutOrigin) {\n // biome-ignore lint/suspicious/noConsole: Intentional warning for security\n console.warn(\"[Commercengine] Cannot send message - checkout origin not established\");\n return;\n }\n this.iframe.contentWindow.postMessage({ type, data }, this.checkoutOrigin);\n }\n\n /**\n * Get the checkout origin for message validation\n */\n getCheckoutOrigin(): string | null {\n return this.checkoutOrigin;\n }\n\n /**\n * Destroy the iframe and clean up\n */\n destroy(): void {\n if (this.container) {\n this.container.remove();\n this.container = null;\n }\n this.iframe = null;\n this.checkoutOrigin = null;\n\n // Restore body scroll\n document.body.style.overflow = \"\";\n }\n}\n","/**\n * Checkout Class\n *\n * Main entry point for Commerce Engine Checkout integration.\n * Manages iframe lifecycle, event handling, and provides public API.\n */\n\nimport { EventEmitter } from \"./events\";\nimport { IframeManager } from \"./iframe-manager\";\nimport type {\n AuthChangeData,\n CartData,\n CheckoutConfig,\n CheckoutEventType,\n EventListener,\n IncomingEventType,\n OrderData,\n} from \"./types\";\n\n// =============================================================================\n// URL RESOLUTION\n// =============================================================================\n\n/**\n * Resolve checkout URL based on current environment\n *\n * The environment is determined by where the script was loaded from:\n * - js.commercengine.com → production checkout\n * - js.staging.commercengine.com → staging checkout\n * - localhost → local development\n */\nfunction resolveCheckoutUrl(storeId: string, apiKey: string): string {\n // Detect environment from script source\n const scripts = document.querySelectorAll('script[src*=\"commercengine\"]');\n let isStaging = false;\n\n for (const script of scripts) {\n const src = script.getAttribute(\"src\") || \"\";\n if (src.includes(\"staging.commercengine.com\")) {\n isStaging = true;\n break;\n }\n }\n\n // For local development, use localhost\n if (typeof window !== \"undefined\" && window.location.hostname === \"localhost\") {\n return `http://localhost:8080?store_id=${storeId}&api_key=${apiKey}&mode=iframe`;\n }\n\n // Determine checkout URL based on environment\n const baseUrl = isStaging\n ? \"https://checkout.staging.commercengine.com\"\n : \"https://checkout.commercengine.com\";\n\n return `${baseUrl}?store_id=${storeId}&api_key=${apiKey}&mode=iframe`;\n}\n\n// =============================================================================\n// CHECKOUT CLASS\n// =============================================================================\n\nexport class Checkout extends EventEmitter {\n private config: CheckoutConfig;\n private iframe: IframeManager;\n private isReady = false;\n private isOpen = false;\n private cartState: CartData = { count: 0, total: 0, currency: \"INR\" };\n private boundMessageHandler: (event: MessageEvent) => void;\n private hasQuickBuyParams = false; // Track if we detected quick buy params\n\n constructor(config: CheckoutConfig) {\n super();\n this.config = config;\n this.iframe = new IframeManager();\n this.boundMessageHandler = this.handleMessage.bind(this);\n this.initialize();\n }\n\n // ===========================================================================\n // INITIALIZATION\n // ===========================================================================\n\n private initialize(): void {\n // Build checkout URL - use direct URL if provided, otherwise resolve from credentials\n let baseUrl: string;\n if (this.config.url) {\n // Direct URL provided (local development)\n baseUrl = this.config.url;\n } else if (this.config.storeId && this.config.apiKey) {\n // Resolve from credentials\n baseUrl = resolveCheckoutUrl(this.config.storeId, this.config.apiKey);\n } else {\n throw new Error(\n \"[Commercengine] Either 'url' or both 'storeId' and 'apiKey' must be provided.\"\n );\n }\n\n const url = new URL(baseUrl);\n\n // Always set iframe mode\n url.searchParams.set(\"mode\", \"iframe\");\n\n // Add store credentials if provided and not already in URL\n if (this.config.storeId && !url.searchParams.has(\"store_id\")) {\n url.searchParams.set(\"store_id\", this.config.storeId);\n }\n if (this.config.apiKey && !url.searchParams.has(\"api_key\")) {\n url.searchParams.set(\"api_key\", this.config.apiKey);\n }\n\n // Add theme preference\n if (this.config.theme) {\n url.searchParams.set(\"theme\", this.config.theme);\n }\n\n // Add auth config\n if (this.config.authMode) {\n url.searchParams.set(\"auth_mode\", this.config.authMode);\n }\n if (this.config.accessToken) {\n url.searchParams.set(\"token\", this.config.accessToken);\n }\n if (this.config.refreshToken) {\n url.searchParams.set(\"refresh_token\", this.config.refreshToken);\n }\n\n // Auto-detect quick buy params from parent URL (if enabled and no explicit quickBuy)\n let quickBuy = this.config.quickBuy;\n let sessionMode = this.config.sessionMode;\n\n if (this.config.autoDetectQuickBuy && !quickBuy) {\n const parentParams = new URLSearchParams(window.location.search);\n const productId = parentParams.get(\"product_id\");\n const variantId = parentParams.get(\"variant_id\");\n\n if (productId) {\n quickBuy = {\n productId,\n variantId: variantId ?? null,\n quantity: parseInt(parentParams.get(\"qty\") || parentParams.get(\"quantity\") || \"1\", 10),\n };\n\n // Also check for session_mode in parent URL\n const parentSessionMode = parentParams.get(\"session_mode\");\n if (parentSessionMode === \"force-new\" || parentSessionMode === \"continue-existing\") {\n sessionMode = parentSessionMode;\n }\n\n // Clean quick buy params from URL to prevent duplicate adds on refresh\n // Deferred to next tick to handle React StrictMode double-mounting\n setTimeout(() => this.cleanQuickBuyParamsFromUrl(), 0);\n }\n }\n\n // Add quick buy params\n if (quickBuy) {\n this.hasQuickBuyParams = true;\n url.searchParams.set(\"product_id\", quickBuy.productId);\n if (quickBuy.variantId) {\n url.searchParams.set(\"variant_id\", quickBuy.variantId);\n }\n if (quickBuy.quantity && quickBuy.quantity !== 1) {\n url.searchParams.set(\"qty\", String(quickBuy.quantity));\n }\n }\n\n // Add session mode\n if (sessionMode) {\n url.searchParams.set(\"session_mode\", sessionMode);\n }\n\n // Pass parent origin for postMessage security\n // This allows the iframe to validate incoming messages and send responses to the correct origin\n if (typeof window !== \"undefined\") {\n url.searchParams.set(\"parent_origin\", window.location.origin);\n }\n\n // Create iframe (hidden initially, will show when ready + opened)\n const zIndex = this.config.appearance?.zIndex ?? 99999;\n this.iframe.create(url.toString(), zIndex);\n\n // Listen for messages from checkout iframe\n window.addEventListener(\"message\", this.boundMessageHandler);\n }\n\n // ===========================================================================\n // URL CLEANUP\n // ===========================================================================\n\n /**\n * Remove quick buy params from the parent URL to prevent duplicate adds on refresh\n * Uses replaceState to avoid adding to browser history\n */\n private cleanQuickBuyParamsFromUrl(): void {\n if (typeof window === \"undefined\") return;\n\n const url = new URL(window.location.href);\n const paramsToRemove = [\"product_id\", \"variant_id\", \"qty\", \"quantity\", \"session_mode\"];\n\n let hasChanges = false;\n for (const param of paramsToRemove) {\n if (url.searchParams.has(param)) {\n url.searchParams.delete(param);\n hasChanges = true;\n }\n }\n\n if (hasChanges) {\n window.history.replaceState({}, \"\", url.toString());\n }\n }\n\n // ===========================================================================\n // MESSAGE HANDLING\n // ===========================================================================\n\n private handleMessage(event: MessageEvent): void {\n // Validate origin for security - reject if no expected origin or mismatch\n const expectedOrigin = this.iframe.getCheckoutOrigin();\n if (!expectedOrigin || event.origin !== expectedOrigin) {\n return;\n }\n\n const { type, data } = event.data || {};\n if (!type) return;\n\n switch (type as IncomingEventType) {\n case \"checkout:ready\":\n this.handleReady();\n break;\n\n case \"checkout:open\":\n this.handleOpen();\n break;\n\n case \"checkout:close\":\n this.handleClose();\n break;\n\n case \"checkout:complete\":\n this.handleComplete(data as OrderData);\n break;\n\n case \"checkout:error\":\n this.handleError(data as { message: string });\n break;\n\n case \"cart:updated\":\n this.handleCartUpdate(data as CartData);\n break;\n\n case \"auth:login\":\n this.handleAuthChange({ type: \"login\", ...data });\n break;\n\n case \"auth:logout\":\n this.handleAuthChange({ type: \"logout\" });\n break;\n\n case \"auth:refresh\":\n this.handleAuthChange({ type: \"refresh\", ...data });\n break;\n }\n }\n\n private handleReady(): void {\n this.isReady = true;\n this.config.onReady?.();\n this.emit(\"ready\");\n\n // Auto-open cart if we have quick buy params (from config or auto-detected URL)\n if (this.hasQuickBuyParams) {\n this.openCart();\n }\n }\n\n private handleError(data: { message: string }): void {\n // Set ready so user can still interact (they'll see the error drawer)\n this.isReady = true;\n this.config.onError?.(data);\n this.emit(\"error\", data);\n }\n\n private handleOpen(): void {\n this.iframe.show();\n this.isOpen = true;\n this.config.onOpen?.();\n this.emit(\"open\");\n }\n\n private handleClose(): void {\n this.iframe.hide();\n this.isOpen = false;\n this.config.onClose?.();\n this.emit(\"close\");\n }\n\n private handleComplete(data: OrderData): void {\n this.config.onComplete?.(data);\n this.emit(\"complete\", data);\n }\n\n private handleCartUpdate(data: CartData): void {\n this.cartState = data;\n this.config.onCartUpdate?.(data);\n this.emit(\"cart:updated\", data);\n }\n\n private handleAuthChange(data: AuthChangeData): void {\n this.config.onAuthChange?.(data);\n this.emit(\"auth:change\", data);\n }\n\n // ===========================================================================\n // PUBLIC API\n // ===========================================================================\n\n /**\n * Open the cart drawer\n */\n openCart(): void {\n if (!this.isReady) {\n // biome-ignore lint/suspicious/noConsole: Intentional warning\n console.warn(\"[Commercengine] Not ready. Wait for onReady callback or 'ready' event.\");\n return;\n }\n\n this.iframe.show();\n this.isOpen = true;\n this.iframe.postMessage(\"checkout:open-cart\");\n }\n\n /**\n * Open the checkout drawer directly (e.g., for Buy Now flow)\n */\n openCheckout(): void {\n if (!this.isReady) {\n // biome-ignore lint/suspicious/noConsole: Intentional warning\n console.warn(\"[Commercengine] Not ready. Wait for onReady callback or 'ready' event.\");\n return;\n }\n\n this.iframe.show();\n this.isOpen = true;\n this.iframe.postMessage(\"checkout:open-checkout\");\n }\n\n /**\n * Close the checkout overlay\n */\n close(): void {\n this.iframe.hide();\n this.isOpen = false;\n this.iframe.postMessage(\"checkout:close\");\n }\n\n /**\n * Update authentication tokens\n * Use this when user logs in/out on the parent site\n */\n updateTokens(accessToken: string, refreshToken?: string): void {\n if (!this.isReady) {\n // Store for when ready\n this.config.accessToken = accessToken;\n this.config.refreshToken = refreshToken;\n return;\n }\n\n this.iframe.postMessage(\"checkout:update-tokens\", { accessToken, refreshToken });\n }\n\n /**\n * Add an item to cart and open the cart drawer\n *\n * @param productId - Product ID (required)\n * @param variantId - Variant ID (required, null for non-variant products)\n * @param quantity - Quantity to add (default: 1)\n */\n addToCart(productId: string, variantId: string | null, quantity = 1): void {\n if (!this.isReady) {\n // biome-ignore lint/suspicious/noConsole: Intentional warning\n console.warn(\"[Commercengine] Not ready. Wait for onReady callback or 'ready' event.\");\n return;\n }\n\n this.iframe.postMessage(\"checkout:add-item\", {\n productId,\n variantId,\n quantity,\n });\n\n // Auto-open cart to show the added item\n this.openCart();\n }\n\n /**\n * Get current cart state\n */\n getCart(): CartData {\n return { ...this.cartState };\n }\n\n /**\n * Check if checkout is ready\n */\n get ready(): boolean {\n return this.isReady;\n }\n\n /**\n * Check if checkout overlay is currently open\n */\n get open(): boolean {\n return this.isOpen;\n }\n\n /**\n * Subscribe to an event\n * @override EventEmitter.on with typed overloads\n */\n on(event: \"ready\", listener: EventListener<void>): void;\n on(event: \"open\", listener: EventListener<void>): void;\n on(event: \"close\", listener: EventListener<void>): void;\n on(event: \"complete\", listener: EventListener<OrderData>): void;\n on(event: \"cart:updated\", listener: EventListener<CartData>): void;\n on(event: \"auth:change\", listener: EventListener<AuthChangeData>): void;\n on<T = unknown>(event: CheckoutEventType, listener: EventListener<T>): void {\n super.on(event, listener);\n }\n\n /**\n * Destroy the checkout instance\n * Removes iframe and cleans up event listeners\n */\n destroy(): void {\n window.removeEventListener(\"message\", this.boundMessageHandler);\n this.iframe.destroy();\n this.removeAllListeners();\n this.isReady = false;\n this.isOpen = false;\n }\n}\n","/**\n * Commerce Engine Checkout\n *\n * @commercengine/js - Embeddable checkout SDK\n *\n * @example CDN Usage (Vanilla JS):\n * ```html\n * <script src=\"https://js.commercengine.com/v1.js\" async></script>\n * <script>\n * window.Commercengine.onLoad = async () => {\n * const checkout = await Commercengine.init({\n * storeId: \"store_xxx\",\n * apiKey: \"ak_xxx\",\n * theme: \"dark\",\n * onComplete: (order) => console.log(\"Order:\", order.orderNumber),\n * });\n *\n * document.getElementById(\"cart-btn\").onclick = () => checkout.openCart();\n * };\n * </script>\n * ```\n *\n * @example ESM Import:\n * ```typescript\n * import { Commercengine } from \"@commercengine/js\";\n *\n * const checkout = await Commercengine.init({\n * storeId: \"store_xxx\",\n * apiKey: \"ak_xxx\",\n * });\n *\n * checkout.on(\"cart:updated\", (cart) => updateBadge(cart.count));\n * checkout.openCart();\n * ```\n */\n\nimport { Checkout } from \"./checkout\";\nimport type { CheckoutConfig } from \"./types\";\n\n// Re-export types for consumers\nexport type {\n AddToCartItem,\n AuthChangeData,\n AuthMode,\n CartData,\n CheckoutConfig,\n CheckoutEventType,\n ErrorData,\n OrderData,\n QuickBuyConfig,\n SessionMode,\n} from \"./types\";\n\nexport { Checkout };\n\n// =============================================================================\n// GLOBAL API\n// =============================================================================\n\n/**\n * Global Commercengine object interface\n */\ninterface CommercengineGlobal {\n /**\n * Initialize checkout with configuration\n * @returns Promise that resolves to Checkout instance\n */\n init: (config: CheckoutConfig) => Promise<Checkout>;\n\n /**\n * Callback invoked when script has loaded\n * Set this BEFORE including the script to be notified when ready\n */\n onLoad?: () => void;\n\n /**\n * Current checkout instance (if initialized)\n */\n instance?: Checkout;\n}\n\n/**\n * Global Commercengine namespace\n */\nconst Commercengine: CommercengineGlobal = {\n /**\n * Initialize checkout\n *\n * @param config - Checkout configuration\n * @returns Checkout instance\n *\n * @example\n * const checkout = await Commercengine.init({\n * storeId: \"store_xxx\",\n * apiKey: \"ak_xxx\",\n * theme: \"system\",\n * });\n */\n init: async (config: CheckoutConfig): Promise<Checkout> => {\n // Validate required fields - either url (for dev) or storeId+apiKey (for prod)\n if (!config.url && (!config.storeId || !config.apiKey)) {\n throw new Error(\n \"[Commercengine] Either 'url' or both 'storeId' and 'apiKey' must be provided\"\n );\n }\n\n // Destroy existing instance if any\n if (Commercengine.instance) {\n Commercengine.instance.destroy();\n }\n\n // Create new instance\n const checkout = new Checkout(config);\n Commercengine.instance = checkout;\n\n // Wait for ready or error with timeout\n return new Promise((resolve, reject) => {\n if (checkout.ready) {\n resolve(checkout);\n return;\n }\n\n const timeoutMs = 30000; // 30 second timeout\n const timeout = setTimeout(() => {\n reject(new Error(\"Checkout initialization timed out\"));\n }, timeoutMs);\n\n // Resolve on ready\n checkout.once(\"ready\", () => {\n clearTimeout(timeout);\n resolve(checkout);\n });\n\n // Also resolve on error (checkout is still usable, will show error drawer)\n checkout.once(\"error\", () => {\n clearTimeout(timeout);\n resolve(checkout);\n });\n });\n },\n};\n\n// =============================================================================\n// GLOBAL SETUP\n// =============================================================================\n\n// Expose on window for CDN usage\nif (typeof window !== \"undefined\") {\n // Preserve any existing onLoad callback set before script loaded\n const existingOnLoad = (window as unknown as { Commercengine?: CommercengineGlobal })\n .Commercengine?.onLoad;\n\n // Set global\n (window as unknown as { Commercengine: CommercengineGlobal }).Commercengine = Commercengine;\n\n // Restore and call onLoad if it was set\n if (existingOnLoad) {\n Commercengine.onLoad = existingOnLoad;\n try {\n existingOnLoad();\n } catch (error) {\n // biome-ignore lint/suspicious/noConsole: Intentional error logging\n console.error(\"[Commercengine] Error in onLoad callback:\", error);\n }\n }\n}\n\nexport { Commercengine };\nexport default Commercengine;\n"],"mappings":"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"}
|