@salla.sa/embedded-sdk 0.2.3 → 0.2.5
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 +3 -3
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +331 -245
- package/dist/esm/index.js.map +1 -1
- package/dist/system/index.js +2 -2
- package/dist/system/index.js.map +1 -1
- package/dist/types/index.d.ts +118 -41
- package/dist/umd/index.js +2 -2
- package/dist/umd/index.js.map +1 -1
- package/package.json +18 -24
package/dist/types/index.d.ts
CHANGED
|
@@ -52,6 +52,20 @@ export declare interface AuthModule {
|
|
|
52
52
|
introspect(options?: IntrospectOptions): Promise<IntrospectResponse>;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Breadcrumbs sub-module interface.
|
|
57
|
+
*/
|
|
58
|
+
declare interface BreadcrumbsSubModule {
|
|
59
|
+
/**
|
|
60
|
+
* Hide host breadcrumbs container.
|
|
61
|
+
*/
|
|
62
|
+
hide(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Show host breadcrumbs container.
|
|
65
|
+
*/
|
|
66
|
+
show(): void;
|
|
67
|
+
}
|
|
68
|
+
|
|
55
69
|
/**
|
|
56
70
|
* Optional configuration for checkout creation.
|
|
57
71
|
*/
|
|
@@ -146,6 +160,11 @@ export declare interface CheckoutModule {
|
|
|
146
160
|
* Called automatically by EmbeddedApp.destroy().
|
|
147
161
|
*/
|
|
148
162
|
destroy(): void;
|
|
163
|
+
/**
|
|
164
|
+
* Reset the checkout cache on the host side.
|
|
165
|
+
* Useful when page is refreshed or app state changes.
|
|
166
|
+
*/
|
|
167
|
+
resetCache(): void;
|
|
149
168
|
}
|
|
150
169
|
|
|
151
170
|
/**
|
|
@@ -218,7 +237,7 @@ export declare class EmbeddedApp {
|
|
|
218
237
|
private postInitHooks;
|
|
219
238
|
/** Auth module for token management */
|
|
220
239
|
auth: AuthModule;
|
|
221
|
-
/** Page module for navigation
|
|
240
|
+
/** Page module for navigation */
|
|
222
241
|
page: PageModule;
|
|
223
242
|
/** Nav module for primary actions */
|
|
224
243
|
nav: NavModule;
|
|
@@ -368,6 +387,8 @@ declare interface IntrospectResponseData {
|
|
|
368
387
|
declare interface LayoutInfo {
|
|
369
388
|
/** Current theme */
|
|
370
389
|
theme: Theme;
|
|
390
|
+
/** Text direction */
|
|
391
|
+
dir: "ltr" | "rtl";
|
|
371
392
|
/** Parent window width */
|
|
372
393
|
width: number;
|
|
373
394
|
/** Current locale */
|
|
@@ -402,6 +423,38 @@ declare interface LoadingSubModule {
|
|
|
402
423
|
hide(): void;
|
|
403
424
|
}
|
|
404
425
|
|
|
426
|
+
/** Result of addItem promise */
|
|
427
|
+
declare interface NavItemAddResult {
|
|
428
|
+
id: string;
|
|
429
|
+
value: string;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Callback when injected nav item is clicked */
|
|
433
|
+
declare type NavItemClickCallback = (payload: {
|
|
434
|
+
id: string;
|
|
435
|
+
value: string;
|
|
436
|
+
url: string;
|
|
437
|
+
}) => void;
|
|
438
|
+
|
|
439
|
+
/** Input for injecting one host sub-navigation item */
|
|
440
|
+
declare interface NavItemInput {
|
|
441
|
+
title: string;
|
|
442
|
+
value: string;
|
|
443
|
+
url: string;
|
|
444
|
+
disabled?: boolean;
|
|
445
|
+
active?: boolean;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** Patch for an injected item (identified by opaque id from addItem response) */
|
|
449
|
+
declare interface NavItemPatchInput {
|
|
450
|
+
id: string;
|
|
451
|
+
title?: string;
|
|
452
|
+
value?: string;
|
|
453
|
+
url?: string;
|
|
454
|
+
disabled?: boolean;
|
|
455
|
+
active?: boolean;
|
|
456
|
+
}
|
|
457
|
+
|
|
405
458
|
/**
|
|
406
459
|
* Nav module interface.
|
|
407
460
|
*/
|
|
@@ -429,15 +482,6 @@ export declare interface NavModule {
|
|
|
429
482
|
* { title: 'Export', value: 'export' },
|
|
430
483
|
* ]
|
|
431
484
|
* });
|
|
432
|
-
*
|
|
433
|
-
* // Handle clicks via onActionClick
|
|
434
|
-
* embedded.nav.onActionClick((value) => {
|
|
435
|
-
* if (value === 'create-product') {
|
|
436
|
-
* openCreateForm();
|
|
437
|
-
* } else if (value === 'import') {
|
|
438
|
-
* embedded.page.navigate('/import');
|
|
439
|
-
* }
|
|
440
|
-
* });
|
|
441
485
|
* ```
|
|
442
486
|
*/
|
|
443
487
|
setAction(config: PrimaryActionConfig): void;
|
|
@@ -460,20 +504,65 @@ export declare interface NavModule {
|
|
|
460
504
|
* ```typescript
|
|
461
505
|
* const unsubscribe = embedded.nav.onActionClick((value) => {
|
|
462
506
|
* switch (value) {
|
|
463
|
-
* case 'save':
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
* case 'export':
|
|
467
|
-
* handleExport();
|
|
468
|
-
* break;
|
|
469
|
-
* case 'settings':
|
|
470
|
-
* embedded.page.navigate('/settings');
|
|
471
|
-
* break;
|
|
507
|
+
* case 'save': handleSave(); break;
|
|
508
|
+
* case 'export': handleExport(); break;
|
|
509
|
+
* case 'settings': embedded.page.navigate('/settings'); break;
|
|
472
510
|
* }
|
|
473
511
|
* });
|
|
474
512
|
* ```
|
|
475
513
|
*/
|
|
476
514
|
onActionClick(callback: ActionClickCallback): Unsubscribe;
|
|
515
|
+
/**
|
|
516
|
+
* Inject one sub-navigation item beside API-driven items. Resolves with an
|
|
517
|
+
* opaque `id` you must keep if you intend to update or remove the item later.
|
|
518
|
+
* Rejects after 2s if the host does not acknowledge.
|
|
519
|
+
*
|
|
520
|
+
* @example
|
|
521
|
+
* ```typescript
|
|
522
|
+
* const tab = await embedded.nav.addNavItem({
|
|
523
|
+
* title: 'Reports',
|
|
524
|
+
* value: 'reports',
|
|
525
|
+
* url: '/apps/installed/my-app/reports',
|
|
526
|
+
* });
|
|
527
|
+
* console.log(tab.id); // opaque id, store it
|
|
528
|
+
* ```
|
|
529
|
+
*/
|
|
530
|
+
addNavItem(item: NavItemInput): Promise<NavItemAddResult>;
|
|
531
|
+
/**
|
|
532
|
+
* Patch a previously injected item — disable, rename, mark active, etc.
|
|
533
|
+
* Unknown ids are silently ignored by the host.
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```typescript
|
|
537
|
+
* embedded.nav.updateNavItem({ id: tab.id, disabled: true });
|
|
538
|
+
* embedded.nav.updateNavItem({ id: tab.id, title: 'Updated title' });
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
updateNavItem(item: NavItemPatchInput): void;
|
|
542
|
+
/**
|
|
543
|
+
* Remove an injected item by its opaque id (unknown ids are ignored).
|
|
544
|
+
*
|
|
545
|
+
* @example
|
|
546
|
+
* ```typescript
|
|
547
|
+
* embedded.nav.removeNavItem(tab.id);
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
removeNavItem(id: string): void;
|
|
551
|
+
/**
|
|
552
|
+
* Subscribe to clicks on injected sub-nav items from the merchant dashboard chrome.
|
|
553
|
+
*
|
|
554
|
+
* @example
|
|
555
|
+
* ```typescript
|
|
556
|
+
* const unsubscribe = embedded.nav.onNavItemClick(({ id, value, url }) => {
|
|
557
|
+
* console.log('clicked tab', value, '->', url);
|
|
558
|
+
* });
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
onNavItemClick(callback: NavItemClickCallback): Unsubscribe;
|
|
562
|
+
/**
|
|
563
|
+
* Tear down listeners (called by EmbeddedApp.destroy).
|
|
564
|
+
*/
|
|
565
|
+
destroy(): void;
|
|
477
566
|
}
|
|
478
567
|
|
|
479
568
|
/**
|
|
@@ -533,28 +622,6 @@ export declare interface PageModule {
|
|
|
533
622
|
* ```
|
|
534
623
|
*/
|
|
535
624
|
navTo(path: string, options?: NavToOptions): void;
|
|
536
|
-
/**
|
|
537
|
-
* Update the iframe height.
|
|
538
|
-
*
|
|
539
|
-
* @param height - Height in pixels
|
|
540
|
-
*
|
|
541
|
-
* @example
|
|
542
|
-
* ```typescript
|
|
543
|
-
* embedded.page.resize(800);
|
|
544
|
-
* ```
|
|
545
|
-
*/
|
|
546
|
-
resize(height: number): void;
|
|
547
|
-
/**
|
|
548
|
-
* Auto-resize iframe to content height.
|
|
549
|
-
* Measures document.documentElement.scrollHeight and sends resize.
|
|
550
|
-
*
|
|
551
|
-
* @example
|
|
552
|
-
* ```typescript
|
|
553
|
-
* // After content changes
|
|
554
|
-
* embedded.page.autoResize();
|
|
555
|
-
* ```
|
|
556
|
-
*/
|
|
557
|
-
autoResize(): void;
|
|
558
625
|
/**
|
|
559
626
|
* Set the page title in the host document.
|
|
560
627
|
*
|
|
@@ -566,6 +633,12 @@ export declare interface PageModule {
|
|
|
566
633
|
* ```
|
|
567
634
|
*/
|
|
568
635
|
setTitle(title: string): void;
|
|
636
|
+
/** @deprecated No effect - host manages iframe height automatically. */
|
|
637
|
+
resize(height: number): void;
|
|
638
|
+
/** @deprecated No effect - host manages iframe height automatically. */
|
|
639
|
+
autoResize(): void;
|
|
640
|
+
/** @deprecated No effect - host manages iframe height automatically. */
|
|
641
|
+
stopAutoResize(): void;
|
|
569
642
|
}
|
|
570
643
|
|
|
571
644
|
/**
|
|
@@ -670,6 +743,10 @@ export declare interface UIModule {
|
|
|
670
743
|
* Loading state control.
|
|
671
744
|
*/
|
|
672
745
|
loading: LoadingSubModule;
|
|
746
|
+
/**
|
|
747
|
+
* Breadcrumbs visibility control.
|
|
748
|
+
*/
|
|
749
|
+
breadcrumbs: BreadcrumbsSubModule;
|
|
673
750
|
/**
|
|
674
751
|
* Toast notifications.
|
|
675
752
|
*/
|
package/dist/umd/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(l,o){typeof exports=="object"&&typeof module<"u"?o(exports):typeof define=="function"&&define.amd?define(["exports"],o):(l=typeof globalThis<"u"?globalThis:l||self,o(l.SallaEmbeddedSDK={}))})(this,function(l){"use strict";const o="embedded::",T={INIT:`${o}iframe.ready`,RESIZE:`${o}iframe.resize`,READY:`${o}ready`,DESTROY:`${o}destroy`},L={PROVIDE:`${o}context.provide`,THEME_CHANGE:`${o}theme.change`},g={LOADING:`${o}ui.loading`,TOAST:`${o}ui.toast`,CONFIRM:`${o}ui.confirm`,CONFIRM_RESPONSE:`${o}ui.confirm.response`},W={},k={REFRESH:`${o}auth.refresh`},p={NAVIGATE:`${o}page.navigate`,REDIRECT:`${o}page.redirect`,SET_TITLE:`${o}page.setTitle`},S={SET_ACTION:`${o}nav.setAction`,CLEAR_ACTION:`${o}nav.clearAction`,ACTION_CLICK:`${o}nav.actionClick`},E={CREATE:`${o}checkout.create`,RESPONSE:`${o}checkout.response`,GET_ADDONS:`${o}checkout.getAddons`,GET_ADDONS_RESPONSE:`${o}checkout.getAddons.response`},A=W.version||"",Y=1e4,B=["localhost","merchants.workers.dev","s.salla.sa",".salla.group",".salla.sa"];let R={showVersion:!0,debug:!1};function Z(t){R={...R,...t}}function q(t){return`%c${t}`}function M(t,e="#fff"){return`background-color: ${t}; color: ${e}; padding: 2px 6px; border-radius: 3px; font-weight: 500; font-size: 11px;`}function v(t,...e){if(t==="debug"&&!R.debug)return;const r=[],i=[];r.push(q("EmbeddedSDK")),i.push(M("#10b981","#fff")),R.showVersion&&(r.push(q(`v${A}`)),i.push(M("#6b7280","#fff")));const n=r.join("").trim();(console[t]||console.log)(n,...i,...e)}const a={log:(...t)=>{v("log",...t)},warn:(...t)=>{v("warn",...t)},error:(...t)=>{v("error",...t)},info:(...t)=>{v("info",...t)},debug:(...t)=>{v("debug",...t)}};function Q(t){try{const r=new URL(t).hostname;return B.some(i=>i.startsWith(".")?r.endsWith(i)||r===i.slice(1):r===i||r.startsWith(`${i}:`))}catch{return!1}}function J(){return typeof window>"u"||window.parent===window?null:window.parent}function u(t,e,r="*",i,n){const s=J();if(!s){a.warn("Not running in an iframe, cannot post to host");return}const c={event:t,payload:e||{},timestamp:Date.now(),source:"embedded-app",...i&&{requestId:i},metadata:{version:A}};s.postMessage(c,r)}const f=new Map;let I=!1;function z(t){if(process.env.NODE_ENV==="production"&&!Q(t.origin))return;const e=t.data;if(!e||typeof e.event!="string"||!e.payload||typeof e.timestamp!="number"||!e.source){a.warn("Invalid message structure received:",e);return}const r=f.get(e.event);r&&r.forEach(n=>{try{n(e)}catch(s){a.error("Error in message handler:",s)}});const i=f.get("*");i&&i.forEach(n=>{try{n(e)}catch(s){a.error("Error in wildcard handler:",s)}})}function ee(){I||typeof window>"u"||(window.addEventListener("message",z),I=!0)}function y(t,e){ee(),f.has(t)||f.set(t,new Set);const r=f.get(t);return r.add(e),()=>{r.delete(e),r.size===0&&f.delete(t)}}function te(t,e=Y){return new Promise((r,i)=>{const n=setTimeout(()=>{s(),i(new Error(`[EmbeddedSDK] Timeout waiting for "${t}" message`))},e),s=y(t,c=>{clearTimeout(n),s(),r(c)})})}function re(){f.clear(),I&&typeof window<"u"&&(window.removeEventListener("message",z),I=!1)}function ie(){return typeof window>"u"?!1:window.parent!==window}function ne(t,e,r){const i={event:t,payload:e,timestamp:Date.now(),source:"merchant-dashboard",...r,metadata:{version:A,synthetic:!0}},n=f.get(t);n==null||n.forEach(s=>{try{s(i)}catch(c){a.error("Error in message handler:",c)}})}const m=new Map,se=3e4;function P(){const t=Date.now(),e=Math.random().toString(36).slice(2,9);return`req_${t}_${e}`}function U(t,e={},r=se){const i=P();return new Promise((n,s)=>{const c=setTimeout(()=>{m.get(i)&&(m.delete(i),s(new Error(`[EmbeddedSDK] Request "${t}" timed out after ${r}ms`)))},r);m.set(i,{resolve:n,reject:s,timeout:c,event:t}),u(t,e,"*",i)})}function V(t,e,r){const i=m.get(t);if(!i){a.warn(`Received response for unknown request: ${t}`);return}clearTimeout(i.timeout),m.delete(t),i.resolve(e)}function ae(t="SDK cleanup"){m.forEach((e,r)=>{clearTimeout(e.timeout),e.reject(new Error(`[EmbeddedSDK] Request ${r} cancelled: ${t}`))}),m.clear()}function N(){const t=new Set;return{subscribe(e){return t.add(e),()=>{t.delete(e)}},notify(...e){t.forEach(r=>{try{r(...e)}catch(i){a.error("Error in subscription callback:",i)}})},clear(){t.clear()},size(){return t.size}}}const oe="https://api.salla.dev";class w extends Error{constructor(e,r,i){super(e),this.status=r,this.response=i,this.name="ApiError"}}async function ue(t,e={}){const{method:r="GET",headers:i={},body:n,timeout:s=3e4}=e,c=`${oe}${t}`,D=new AbortController,X=setTimeout(()=>{D.abort()},s);try{const d=await fetch(c,{method:r,headers:{"Content-Type":"application/json",...i},body:n?JSON.stringify(n):void 0,signal:D.signal});clearTimeout(X);let C;const x=d.headers.get("content-type");if(x!=null&&x.includes("application/json")?C=await d.json():C=await d.text(),!d.ok)throw new w(`API request failed: ${d.statusText}`,d.status,C);return C}catch(d){throw clearTimeout(X),d instanceof w?d:d instanceof Error?d.name==="AbortError"?new w(`Request timeout after ${s}ms`):new w(`Request failed: ${d.message}`):new w("Unknown error occurred")}}function $(t){return{isVerified:!1,isError:!0,error:t,data:null}}async function ce(t){const{token:e,appId:r,refreshOnError:i=!0}=t;if(!e){const n="Token is required. Provide it as a parameter or in URL as ?token=XXX";return a.error("Error in introspect:",n),$(n)}if(!r){const n="App ID is required. Provide it as a parameter or in URL as ?app_id=XXX";return a.error("Error in introspect:",n),$(n)}try{const n=await ue("/exchange-authority/v1/introspect",{method:"POST",headers:{"S-Source":r,"Content-Type":"application/json"},body:{env:"prod",token:e,iss:"merchant-dashboard",subject:"embedded-page"}}),s=n.success;return{isVerified:s,isError:!s,error:s?void 0:"API request failed",data:s?n.data:null}}catch(n){i&&(O().ui.toast.error((n==null?void 0:n.toString())??"Introspect error"),u(k.REFRESH,{})),a.error("Error in introspect:",n);const s=n instanceof Error?n.message:n;return $(s)}}function de(t){return{getToken(){return new URLSearchParams(window.location.search).get("token")},getAppId(){return new URLSearchParams(window.location.search).get("app_id")},refresh(){u(k.REFRESH,{})},async introspect(e={}){const r=e.token??this.getToken()??"",i=e.appId??this.getAppId()??"";return ce({token:r,appId:i,refreshOnError:e.refreshOnError})}}}const j=["success","error","warning","info"];function le(t){const e=[];return t.type===void 0||t.type===null?e.push("Toast type is required"):(typeof t.type!="string"||!j.includes(t.type))&&e.push(`Invalid toast type "${t.type}". Expected: ${j.join(" | ")}`),t.message===void 0||t.message===null?e.push("Toast message is required"):typeof t.message!="string"?e.push("Toast message must be a string"):t.message.trim()===""&&e.push("Toast message cannot be empty"),t.duration!==void 0&&t.duration!==null&&(typeof t.duration!="number"?e.push("Toast duration must be a number"):t.duration<0&&e.push("Toast duration cannot be negative")),{valid:e.length===0,errors:e}}function H(t,e){const r=[];return typeof t!="object"||t===null?(r.push(`${e} must be an object`),r):((typeof t.type!="string"||t.type.trim()==="")&&r.push(`${e} must have a valid type`),(typeof t.slug!="string"||t.slug.trim()==="")&&r.push(`${e} must have a valid slug`),t.quantity!==void 0&&(typeof t.quantity!="number"||t.quantity<1)&&r.push(`${e} must have a quantity >= 1`),r)}function fe(t){const e=[];if(typeof t!="object"||t===null)return e.push("Checkout options must be an object"),{valid:!1,errors:e};const r="item"in t,i="items"in t;if(!r&&!i)return e.push("Checkout requires either 'item' or 'items'"),{valid:!1,errors:e};if(r){const n=H(t.item,"Item");return e.push(...n),{valid:e.length===0,errors:e}}return Array.isArray(t.items)?t.items.length===0?(e.push("At least one item is required"),{valid:!1,errors:e}):(t.items.forEach((n,s)=>{const c=H(n,`Item at index ${s}`);e.push(...c)}),{valid:e.length===0,errors:e}):(e.push("Checkout items must be an array"),{valid:!1,errors:e})}function he(t){const e=[];return t.path===void 0||t.path===null?e.push("Navigation path is required"):typeof t.path!="string"?e.push("Navigation path must be a string"):t.path.trim()===""&&e.push("Navigation path cannot be empty"),t.replace!==void 0&&typeof t.replace!="boolean"&&e.push("Navigation replace option must be a boolean"),{valid:e.length===0,errors:e}}function ge(t){const e=[];if(t.url===void 0||t.url===null)e.push("Redirect URL is required");else if(typeof t.url!="string")e.push("Redirect URL must be a string");else if(t.url.trim()==="")e.push("Redirect URL cannot be empty");else try{new URL(t.url)}catch{e.push(`Invalid redirect URL: "${t.url}"`)}return{valid:e.length===0,errors:e}}function me(t){const e=[];return t.title?typeof t.title!="string"&&e.push("Nav action title must be a string"):e.push("Nav action title is required"),t.value?typeof t.value!="string"&&e.push("Nav action value must be a string"):e.push("Nav action value is required"),t.subTitle!==void 0&&t.subTitle!==null&&typeof t.subTitle!="string"&&e.push("Nav action subTitle must be a string"),t.icon!==void 0&&t.icon!==null&&typeof t.icon!="string"&&e.push("Nav action icon must be a string"),t.disabled!==void 0&&t.disabled!==null&&typeof t.disabled!="boolean"&&e.push("Nav action disabled must be a boolean"),t.extendedActions!==void 0&&t.extendedActions!==null&&(Array.isArray(t.extendedActions)?t.extendedActions.forEach((r,i)=>{if(typeof r!="object"||r===null){e.push(`Extended action at index ${i} must be an object`);return}const n=r;(!n.title||typeof n.title!="string")&&e.push(`Extended action at index ${i} is missing required "title" property`),(!n.value||typeof n.value!="string")&&e.push(`Extended action at index ${i} is missing required "value" property`),n.subTitle!==void 0&&typeof n.subTitle!="string"&&e.push(`Extended action at index ${i} subTitle must be a string`),n.icon!==void 0&&typeof n.icon!="string"&&e.push(`Extended action at index ${i} icon must be a string`),n.disabled!==void 0&&typeof n.disabled!="boolean"&&e.push(`Extended action at index ${i} disabled must be a boolean`)}):e.push("Nav action extendedActions must be an array")),{valid:e.length===0,errors:e}}const F=["danger","warning","info"];function pe(t){const e=[];return t.title===void 0||t.title===null?e.push("Confirm dialog title is required"):typeof t.title!="string"?e.push("Confirm dialog title must be a string"):t.title.trim()===""&&e.push("Confirm dialog title cannot be empty"),t.message===void 0||t.message===null?e.push("Confirm dialog message is required"):typeof t.message!="string"?e.push("Confirm dialog message must be a string"):t.message.trim()===""&&e.push("Confirm dialog message cannot be empty"),t.confirmText!==void 0&&t.confirmText!==null&&typeof t.confirmText!="string"&&e.push("Confirm dialog confirmText must be a string"),t.cancelText!==void 0&&t.cancelText!==null&&typeof t.cancelText!="string"&&e.push("Confirm dialog cancelText must be a string"),t.variant!==void 0&&t.variant!==null&&(typeof t.variant!="string"||!F.includes(t.variant))&&e.push(`Invalid confirm variant "${t.variant}". Expected: ${F.join(" | ")}`),{valid:e.length===0,errors:e}}function h(t,e){a.error(`Validation failed for ${t}:
|
|
1
|
+
(function(f,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(f=typeof globalThis<"u"?globalThis:f||self,a(f.SallaEmbeddedSDK={}))})(this,(function(f){"use strict";const a="embedded::",C={INIT:`${a}iframe.ready`,READY:`${a}ready`,DESTROY:`${a}destroy`},x={PROVIDE:`${a}context.provide`,THEME_CHANGE:`${a}theme.change`},p={LOADING:`${a}ui.loading`,BREADCRUMBS:`${a}ui.breadcrumbs`,TOAST:`${a}ui.toast`,CONFIRM:`${a}ui.confirm`,CONFIRM_RESPONSE:`${a}ui.confirm.response`},B={},k={REFRESH:`${a}auth.refresh`},v={NAVIGATE:`${a}page.navigate`,REDIRECT:`${a}page.redirect`,SET_TITLE:`${a}page.setTitle`},c={SET_ACTION:`${a}nav.setAction`,CLEAR_ACTION:`${a}nav.clearAction`,ACTION_CLICK:`${a}nav.actionClick`,ADD_ITEM:`${a}nav.addItem`,ADD_ITEM_RESPONSE:`${a}nav.addItem.response`,UPDATE_ITEM:`${a}nav.updateItem`,REMOVE_ITEM:`${a}nav.removeItem`,ITEM_CLICK:`${a}nav.itemClick`},b={CREATE:`${a}checkout.create`,RESPONSE:`${a}checkout.response`,GET_ADDONS:`${a}checkout.getAddons`,GET_ADDONS_RESPONSE:`${a}checkout.getAddons.response`,RESET_CACHE:`${a}checkout.resetCache`},I=B.version||"",X=1e4,W=["localhost","merchants.workers.dev","s.salla.sa",".salla.group",".salla.sa"],Y=new Set(["ar","he","fa","ur","ps","sd","yi"]);function Q(t){const e=(t??"ar").toLowerCase().split(/[-_]/)[0];return Y.has(e)?"rtl":"ltr"}let A={showVersion:!0,debug:!1};function J(t){A={...A,...t}}function q(t){return`%c${t}`}function P(t,e="#fff"){return`background-color: ${t}; color: ${e}; padding: 2px 6px; border-radius: 3px; font-weight: 500; font-size: 11px;`}function T(t,...e){if(t==="debug"&&!A.debug)return;const r=[],i=[];r.push(q("EmbeddedSDK")),i.push(P("#10b981","#fff")),A.showVersion&&(r.push(q(`v${I}`)),i.push(P("#6b7280","#fff")));const n=r.join("").trim();(console[t]||console.log)(n,...i,...e)}const o={log:(...t)=>{T("log",...t)},warn:(...t)=>{T("warn",...t)},error:(...t)=>{T("error",...t)},info:(...t)=>{T("info",...t)},debug:(...t)=>{T("debug",...t)}};function Z(t){try{const r=new URL(t).hostname;return W.some(i=>i.startsWith(".")?r.endsWith(i)||r===i.slice(1):r===i||r.startsWith(`${i}:`))}catch{return!1}}function ee(){return typeof window>"u"||window.parent===window?null:window.parent}function u(t,e,r="*",i,n){const s=ee();if(!s){o.warn("Not running in an iframe, cannot post to host");return}const d={event:t,payload:e||{},timestamp:Date.now(),source:"embedded-app",...i&&{requestId:i},metadata:{version:I}};s.postMessage(d,r)}const m=new Map;let R=!1;function z(t){if(process.env.NODE_ENV==="production"&&!Z(t.origin))return;const e=t.data;if(!e||typeof e.event!="string"||!e.payload||typeof e.timestamp!="number"||!e.source){o.warn("Invalid message structure received:",e);return}const r=m.get(e.event);r&&r.forEach(n=>{try{n(e)}catch(s){o.error("Error in message handler:",s)}});const i=m.get("*");i&&i.forEach(n=>{try{n(e)}catch(s){o.error("Error in wildcard handler:",s)}})}function te(){R||typeof window>"u"||(window.addEventListener("message",z),R=!0)}function g(t,e){te(),m.has(t)||m.set(t,new Set);const r=m.get(t);return r.add(e),()=>{r.delete(e),r.size===0&&m.delete(t)}}function re(t,e=X){return new Promise((r,i)=>{const n=setTimeout(()=>{s(),i(new Error(`[EmbeddedSDK] Timeout waiting for "${t}" message`))},e),s=g(t,d=>{clearTimeout(n),s(),r(d)})})}function ie(){m.clear(),R&&typeof window<"u"&&(window.removeEventListener("message",z),R=!1)}function ne(){return typeof window>"u"?!1:window.parent!==window}function se(t,e,r){const i={event:t,payload:e,timestamp:Date.now(),source:"merchant-dashboard",...r,metadata:{version:I,synthetic:!0}};m.get(t)?.forEach(s=>{try{s(i)}catch(d){o.error("Error in message handler:",d)}})}const E=new Map,ae=3e4;function U(){const t=Date.now(),e=Math.random().toString(36).slice(2,9);return`req_${t}_${e}`}function _(t,e={},r=ae){const i=U();return new Promise((n,s)=>{const d=setTimeout(()=>{E.get(i)&&(E.delete(i),s(new Error(`[EmbeddedSDK] Request "${t}" timed out after ${r}ms`)))},r);E.set(i,{resolve:n,reject:s,timeout:d,event:t}),u(t,e,"*",i)})}function $(t,e,r){const i=E.get(t);if(!i){o.warn(`Received response for unknown request: ${t}`);return}clearTimeout(i.timeout),E.delete(t),i.resolve(e)}function oe(t="SDK cleanup"){E.forEach((e,r)=>{clearTimeout(e.timeout),e.reject(new Error(`[EmbeddedSDK] Request ${r} cancelled: ${t}`))}),E.clear()}function w(){const t=new Set;return{subscribe(e){return t.add(e),()=>{t.delete(e)}},notify(...e){t.forEach(r=>{try{r(...e)}catch(i){o.error("Error in subscription callback:",i)}})},clear(){t.clear()},size(){return t.size}}}const ue="https://api.salla.dev";class S extends Error{constructor(e,r,i){super(e),this.status=r,this.response=i,this.name="ApiError"}}async function de(t,e={}){const{method:r="GET",headers:i={},body:n,timeout:s=3e4}=e,d=`${ue}${t}`,L=new AbortController,K=setTimeout(()=>{L.abort()},s);try{const l=await fetch(d,{method:r,headers:{"Content-Type":"application/json",...i},body:n?JSON.stringify(n):void 0,signal:L.signal});clearTimeout(K);let N;if(l.headers.get("content-type")?.includes("application/json")?N=await l.json():N=await l.text(),!l.ok)throw new S(`API request failed: ${l.statusText}`,l.status,N);return N}catch(l){throw clearTimeout(K),l instanceof S?l:l instanceof Error?l.name==="AbortError"?new S(`Request timeout after ${s}ms`):new S(`Request failed: ${l.message}`):new S("Unknown error occurred")}}function D(t){return{isVerified:!1,isError:!0,error:t,data:null}}async function ce(t){const{token:e,appId:r,refreshOnError:i=!0}=t;if(!e){const n="Token is required. Provide it as a parameter or in URL as ?token=XXX";return o.error("Error in introspect:",n),D(n)}if(!r){const n="App ID is required. Provide it as a parameter or in URL as ?app_id=XXX";return o.error("Error in introspect:",n),D(n)}try{const n=await de("/exchange-authority/v1/introspect",{method:"POST",headers:{"S-Source":r,"Content-Type":"application/json"},body:{env:"prod",token:e,iss:"merchant-dashboard",subject:"embedded-page"}}),s=n.success;return{isVerified:s,isError:!s,error:s?void 0:"API request failed",data:s?n.data:null}}catch(n){i&&(M().ui.toast.error(n?.toString()??"Introspect error"),u(k.REFRESH,{})),o.error("Error in introspect:",n);const s=n instanceof Error?n.message:n;return D(s)}}function le(t){return{getToken(){return new URLSearchParams(window.location.search).get("token")},getAppId(){return new URLSearchParams(window.location.search).get("app_id")},refresh(){u(k.REFRESH,{})},async introspect(e={}){const r=e.token??this.getToken()??"",i=e.appId??this.getAppId()??"";return ce({token:r,appId:i,refreshOnError:e.refreshOnError})}}}const V=["success","error","warning","info"];function fe(t){const e=[];return t.type===void 0||t.type===null?e.push("Toast type is required"):(typeof t.type!="string"||!V.includes(t.type))&&e.push(`Invalid toast type "${t.type}". Expected: ${V.join(" | ")}`),t.message===void 0||t.message===null?e.push("Toast message is required"):typeof t.message!="string"?e.push("Toast message must be a string"):t.message.trim()===""&&e.push("Toast message cannot be empty"),t.duration!==void 0&&t.duration!==null&&(typeof t.duration!="number"?e.push("Toast duration must be a number"):t.duration<0&&e.push("Toast duration cannot be negative")),{valid:e.length===0,errors:e}}function j(t,e){const r=[];return typeof t!="object"||t===null?(r.push(`${e} must be an object`),r):((typeof t.type!="string"||t.type.trim()==="")&&r.push(`${e} must have a valid type`),(typeof t.slug!="string"||t.slug.trim()==="")&&r.push(`${e} must have a valid slug`),t.quantity!==void 0&&(typeof t.quantity!="number"||t.quantity<1)&&r.push(`${e} must have a quantity >= 1`),r)}function he(t){const e=[];if(typeof t!="object"||t===null)return e.push("Checkout options must be an object"),{valid:!1,errors:e};const r="item"in t,i="items"in t;if(!r&&!i)return e.push("Checkout requires either 'item' or 'items'"),{valid:!1,errors:e};if(r){const n=j(t.item,"Item");return e.push(...n),{valid:e.length===0,errors:e}}return Array.isArray(t.items)?t.items.length===0?(e.push("At least one item is required"),{valid:!1,errors:e}):(t.items.forEach((n,s)=>{const d=j(n,`Item at index ${s}`);e.push(...d)}),{valid:e.length===0,errors:e}):(e.push("Checkout items must be an array"),{valid:!1,errors:e})}function pe(t){const e=[];return t.path===void 0||t.path===null?e.push("Navigation path is required"):typeof t.path!="string"?e.push("Navigation path must be a string"):t.path.trim()===""&&e.push("Navigation path cannot be empty"),t.replace!==void 0&&typeof t.replace!="boolean"&&e.push("Navigation replace option must be a boolean"),{valid:e.length===0,errors:e}}function me(t){const e=[];if(t.url===void 0||t.url===null)e.push("Redirect URL is required");else if(typeof t.url!="string")e.push("Redirect URL must be a string");else if(t.url.trim()==="")e.push("Redirect URL cannot be empty");else try{new URL(t.url)}catch{e.push(`Invalid redirect URL: "${t.url}"`)}return{valid:e.length===0,errors:e}}function ge(t){const e=[];return t.title?typeof t.title!="string"&&e.push("Nav action title must be a string"):e.push("Nav action title is required"),t.value?typeof t.value!="string"&&e.push("Nav action value must be a string"):e.push("Nav action value is required"),t.subTitle!==void 0&&t.subTitle!==null&&typeof t.subTitle!="string"&&e.push("Nav action subTitle must be a string"),t.icon!==void 0&&t.icon!==null&&typeof t.icon!="string"&&e.push("Nav action icon must be a string"),t.disabled!==void 0&&t.disabled!==null&&typeof t.disabled!="boolean"&&e.push("Nav action disabled must be a boolean"),t.extendedActions!==void 0&&t.extendedActions!==null&&(Array.isArray(t.extendedActions)?t.extendedActions.forEach((r,i)=>{if(typeof r!="object"||r===null){e.push(`Extended action at index ${i} must be an object`);return}const n=r;(!n.title||typeof n.title!="string")&&e.push(`Extended action at index ${i} is missing required "title" property`),(!n.value||typeof n.value!="string")&&e.push(`Extended action at index ${i} is missing required "value" property`),n.subTitle!==void 0&&typeof n.subTitle!="string"&&e.push(`Extended action at index ${i} subTitle must be a string`),n.icon!==void 0&&typeof n.icon!="string"&&e.push(`Extended action at index ${i} icon must be a string`),n.disabled!==void 0&&typeof n.disabled!="boolean"&&e.push(`Extended action at index ${i} disabled must be a boolean`)}):e.push("Nav action extendedActions must be an array")),{valid:e.length===0,errors:e}}function be(t){const e=[];if(typeof t!="object"||t===null)return e.push("Nav addItem expects an object item"),{valid:!1,errors:e};const r=t;return["title","value","url"].forEach(i=>{(typeof r[i]!="string"||!String(r[i]).trim())&&e.push(`Sub-nav addItem "${i}" must be non-empty string`)}),r.disabled!==void 0&&typeof r.disabled!="boolean"&&e.push('Sub-nav addItem "disabled" must be boolean'),r.active!==void 0&&typeof r.active!="boolean"&&e.push('Sub-nav addItem "active" must be boolean'),{valid:e.length===0,errors:e}}function Ee(t){const e=[];if(typeof t!="object"||t===null)return e.push("Nav updateItem expects an object item"),{valid:!1,errors:e};const r=t;if(typeof r.id!="string"||!r.id.trim())return e.push('Sub-nav updateItem requires string "id"'),{valid:!1,errors:e};let i=0;return r.title!==void 0&&i++,r.value!==void 0&&i++,r.url!==void 0&&i++,r.disabled!==void 0&&i++,r.active!==void 0&&i++,i===0?(e.push("Sub-nav updateItem must include at least one of title, value, url, disabled, active"),{valid:!1,errors:e}):(["title","value","url"].forEach(n=>{r[n]!==void 0&&(typeof r[n]!="string"||!r[n].trim())&&e.push(`Sub-nav updateItem "${n}" must be non-empty`)}),r.disabled!==void 0&&typeof r.disabled!="boolean"&&e.push('Sub-nav updateItem "disabled" must be boolean'),r.active!==void 0&&typeof r.active!="boolean"&&e.push('Sub-nav updateItem "active" must be boolean'),{valid:e.length===0,errors:e})}function ve(t){const e=[];return(typeof t!="string"||!t.trim())&&e.push("Nav removeItem id must be non-empty string"),{valid:e.length===0,errors:e}}const F=["danger","warning","info"];function ye(t){const e=[];return t.title===void 0||t.title===null?e.push("Confirm dialog title is required"):typeof t.title!="string"?e.push("Confirm dialog title must be a string"):t.title.trim()===""&&e.push("Confirm dialog title cannot be empty"),t.message===void 0||t.message===null?e.push("Confirm dialog message is required"):typeof t.message!="string"?e.push("Confirm dialog message must be a string"):t.message.trim()===""&&e.push("Confirm dialog message cannot be empty"),t.confirmText!==void 0&&t.confirmText!==null&&typeof t.confirmText!="string"&&e.push("Confirm dialog confirmText must be a string"),t.cancelText!==void 0&&t.cancelText!==null&&typeof t.cancelText!="string"&&e.push("Confirm dialog cancelText must be a string"),t.variant!==void 0&&t.variant!==null&&(typeof t.variant!="string"||!F.includes(t.variant))&&e.push(`Invalid confirm variant "${t.variant}". Expected: ${F.join(" | ")}`),{valid:e.length===0,errors:e}}function h(t,e){o.error(`Validation failed for ${t}:
|
|
2
2
|
`+e.map(r=>` • ${r}`).join(`
|
|
3
|
-
`))}function
|
|
3
|
+
`))}function Te(){return{resize:i=>{},autoResize:()=>{},stopAutoResize:()=>{}}}function we(){const{resize:t,autoResize:e,stopAutoResize:r}=Te();return{navigate(i,n){const s=pe({path:i,...n});if(!s.valid){h(v.NAVIGATE,s.errors);return}u(v.NAVIGATE,{path:i,state:n?.state,replace:n?.replace})},redirect(i){const n=me({url:i});if(!n.valid){h(v.REDIRECT,n.errors);return}u(v.REDIRECT,{url:i})},navTo(i,n){if(i.startsWith("http://")||i.startsWith("https://")){this.redirect(i);return}this.navigate(i,n)},setTitle(i){if(typeof i!="string"||!i.trim()){h(v.SET_TITLE,["Title must be a non-empty string"]);return}u(v.SET_TITLE,{title:i})},resize:t,autoResize:e,stopAutoResize:r}}function Se(){const t=w(),e=w(),r=[];return r.push(g(c.ACTION_CLICK,i=>{t.notify(i.payload.value)})),r.push(g(c.ADD_ITEM_RESPONSE,i=>{const n=i.payload.item;i.requestId&&n&&typeof n.id=="string"&&typeof n.value=="string"&&$(i.requestId,n)})),r.push(g(c.ITEM_CLICK,i=>{e.notify(i.payload)})),{setAction(i){const n=ge(i);if(!n.valid){h(c.SET_ACTION,n.errors);return}u(c.SET_ACTION,{title:i.title,value:i.value,subTitle:i.subTitle,icon:i.icon,disabled:i.disabled,extendedActions:i.extendedActions?.map(s=>({title:s.title,value:s.value,subTitle:s.subTitle,icon:s.icon,disabled:s.disabled}))})},clearAction(){u(c.CLEAR_ACTION,{})},onActionClick(i){return t.subscribe(i)},addNavItem(i){const n=be(i);return n.valid?_(c.ADD_ITEM,{item:i},2e3):(h(c.ADD_ITEM,n.errors),Promise.reject(new Error(n.errors[0])))},updateNavItem(i){const n=Ee(i);if(!n.valid){h(c.UPDATE_ITEM,n.errors);return}u(c.UPDATE_ITEM,{item:i})},removeNavItem(i){const n=ve(i);if(!n.valid){h(c.REMOVE_ITEM,n.errors);return}u(c.REMOVE_ITEM,{id:i})},onNavItemClick(i){return e.subscribe(i)},destroy(){r.forEach(i=>i()),r.length=0,t.clear(),e.clear()}}}function Ie(){return{show(){u(p.LOADING,{action:"show"})},hide(){u(p.LOADING,{action:"hide"})}}}function Ae(){return{hide(){u(p.BREADCRUMBS,{action:"hide"})},show(){u(p.BREADCRUMBS,{action:"show"})}}}function Re(){const t=e=>{const r=fe(e);if(!r.valid){h(p.TOAST,r.errors);return}u(p.TOAST,{type:e.type,message:e.message,duration:e.duration})};return{show:t,success(e,r){t({type:"success",message:e,duration:r})},error(e,r){t({type:"error",message:e,duration:r})},warning(e,r){t({type:"warning",message:e,duration:r})},info(e,r){t({type:"info",message:e,duration:r})}}}function Ne(){return async t=>{const e=ye(t);return e.valid?_(p.CONFIRM,{title:t.title,message:t.message,confirmText:t.confirmText??"Confirm",cancelText:t.cancelText??"Cancel",variant:t.variant??"info"}):(h(p.CONFIRM,e.errors),Promise.reject(new Error(e.errors.join(", "))))}}function Ce(){return{loading:Ie(),breadcrumbs:Ae(),toast:Re(),confirm:Ne()}}function _e(){const t=w(),e=[];return e.push(g(b.RESPONSE,r=>{t.notify({success:r.payload.success,order_id:r.payload.order_id,status:r.payload.status,error:r.payload.error,context:r.payload.context})})),e.push(g(b.GET_ADDONS_RESPONSE,r=>{r.requestId&&$(r.requestId,{success:r.payload.success,addons:r.payload.addons,error:r.payload.error})})),{create(r,i){const n=Array.isArray(r)?r:[r],s=he({items:n});if(!s.valid)throw h(b.CREATE,s.errors),new Error(s.errors[0]);u(b.CREATE,{items:n.map(d=>({type:d.type,slug:d.slug,quantity:d.quantity??1})),...i?.context!==void 0&&{context:i.context}},"*",U())},onResult(r){return t.subscribe(r)},async getAddons(){try{return await _(b.GET_ADDONS,{},3e4)}catch(r){return{success:!1,error:{code:"REQUEST_FAILED",message:r instanceof Error?r.message:"Unknown error"}}}},resetCache(){u(b.RESET_CACHE,{},"*")},destroy(){e.forEach(r=>r()),e.length=0,t.clear()}}}const H={theme:"light",dir:"rtl",width:0,locale:"ar",currency:"SAR"};class G{constructor(){this.initialized=!1,this.initializing=!1,this.debugMode=!1,this.appReady=!1,this.layout={...H},this.postInitHooks=[],this.themeSubscription=w(),this.initSubscription=w(),this.auth=le(),this.page=we(),this.nav=Se(),this.ui=Ce(),this.checkout=_e(),this.registerPostInitHook(e=>{const r=e.payload.pendingCheckoutResult;r&&(o.debug("Dispatching pending checkout result:",r),queueMicrotask(()=>{se(b.RESPONSE,{success:r.success,status:r.status,error:r.error,context:r.context})}))}),this.setupListeners()}registerPostInitHook(e){this.postInitHooks.push(e)}setupListeners(){g(x.THEME_CHANGE,e=>{this.layout.theme=e.payload.theme,o.debug("Theme changed:",e.payload.theme),this.themeSubscription.notify(e.payload.theme)}),g(p.CONFIRM_RESPONSE,e=>{o.debug("Received confirm response:",e),e.requestId&&$(e.requestId,{confirmed:e.payload.confirmed})})}getState(){return{ready:this.initialized,initializing:this.initializing,layout:{...this.layout}}}isReady(){return this.initialized}onThemeChange(e){return this.themeSubscription.subscribe(e)}onInit(e){if(this.initialized)try{e(this.getState())}catch(r){o.error("Error in init callback:",r)}return this.initSubscription.subscribe(e)}ready(){if(this.appReady){o.debug("App already signaled as ready");return}if(!this.initialized){o.warn("Cannot signal ready before init() is called");return}this.appReady=!0,u(C.READY,{}),o.debug("Sent ready signal to host")}async init(e={}){if(this.initialized)return o.debug("Already initialized, returning current layout"),{layout:{...this.layout}};if(this.initializing)return o.warn("Initialization already in progress"),new Promise(r=>{const i=this.onInit(n=>{i(),r({layout:{...n.layout}})})});this.initializing=!0,this.debugMode=e.debug??!1,J({debug:this.debugMode}),ne()||o.warn("Not running in an iframe. Some features may not work."),o.debug("Initializing SDK...");try{u(C.INIT,{height:document.documentElement.scrollHeight}),o.debug("Sent iframe.ready message, waiting for context...");const r=await re(x.PROVIDE);o.debug("Received context from host:",r);const{layout:i}=r.payload;return this.layout={theme:i?.theme??"light",dir:i?.dir??Q(i?.locale),width:i?.width??0,locale:i?.locale??"ar",currency:i?.currency??"SAR"},this.postInitHooks.forEach(n=>{try{n(r)}catch(s){o.error("Error in post-init hook:",s)}}),this.initialized=!0,this.initializing=!1,o.debug("Initialization complete. Layout:",this.layout),this.initSubscription.notify(this.getState()),{layout:{...this.layout}}}catch(r){throw this.initializing=!1,r}}destroy(){o.debug("Destroying SDK instance"),this.initialized&&(u(C.DESTROY,{}),o.debug("Sent destroy event to host")),this.checkout.destroy(),this.nav.destroy(),oe("SDK destroyed"),ie(),this.themeSubscription.clear(),this.initSubscription.clear(),this.postInitHooks=[],this.initialized=!1,this.initializing=!1,this.appReady=!1,this.layout={...H}}}let y=null;function M(){return y||(y=new G),y}function $e(){y&&(y.destroy(),y=null)}const O=M(),De=I;typeof window<"u"&&(window.salla=window.salla||window.Salla||{},window.Salla=window.salla,window.salla.embedded||(window.salla.embedded=O),window.Salla.embedded||(window.Salla.embedded=O)),f.EmbeddedApp=G,f.embedded=O,f.getEmbeddedApp=M,f.resetEmbeddedApp=$e,f.version=De,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
|
|
4
4
|
//# sourceMappingURL=index.js.map
|