@mission_sciences/provider-sdk 0.3.0 → 0.4.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 +24 -5
- package/dist/adapters/react/index.js +11 -10
- package/dist/adapters/react/index.js.map +1 -1
- package/dist/adapters/react/useMarketplaceSession.d.ts.map +1 -1
- package/dist/adapters/vue/index.js +10 -9
- package/dist/adapters/vue/index.js.map +1 -1
- package/dist/adapters/vue/useMarketplaceSession.d.ts +4 -0
- package/dist/adapters/vue/useMarketplaceSession.d.ts.map +1 -1
- package/dist/core/MarketplaceSDK.d.ts +12 -0
- package/dist/core/MarketplaceSDK.d.ts.map +1 -1
- package/dist/core/PurchaseStateManager.d.ts +28 -0
- package/dist/core/PurchaseStateManager.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/marketplace-sdk.es.js +316 -2
- package/dist/marketplace-sdk.es.js.map +1 -1
- package/dist/marketplace-sdk.umd.js +1 -1
- package/dist/marketplace-sdk.umd.js.map +1 -1
- package/dist/types/index.d.ts +81 -16
- package/dist/types/index.d.ts.map +1 -1
- package/dist/ui/PurchaseModal.d.ts +30 -0
- package/dist/ui/PurchaseModal.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -20,6 +20,19 @@ class SDKError extends Error {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
class PurchaseError extends SDKError {
|
|
24
|
+
constructor(message2, code, itemId, statusCode) {
|
|
25
|
+
super(message2, code, statusCode);
|
|
26
|
+
this.itemId = itemId;
|
|
27
|
+
this.name = "PurchaseError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
var PurchaseState = /* @__PURE__ */ ((PurchaseState2) => {
|
|
31
|
+
PurchaseState2["AVAILABLE"] = "AVAILABLE";
|
|
32
|
+
PurchaseState2["PRIVY_REQUIRED"] = "PRIVY_REQUIRED";
|
|
33
|
+
PurchaseState2["INSUFFICIENT_FUNDS"] = "INSUFFICIENT_FUNDS";
|
|
34
|
+
return PurchaseState2;
|
|
35
|
+
})(PurchaseState || {});
|
|
23
36
|
class JWTParser {
|
|
24
37
|
/**
|
|
25
38
|
* Decode JWT payload without verification
|
|
@@ -2017,6 +2030,55 @@ class TabSyncManager {
|
|
|
2017
2030
|
this.logger.log("Tab sync destroyed");
|
|
2018
2031
|
}
|
|
2019
2032
|
}
|
|
2033
|
+
class PurchaseStateManager {
|
|
2034
|
+
constructor(config) {
|
|
2035
|
+
this.config = {
|
|
2036
|
+
timeout: 5e3,
|
|
2037
|
+
...config
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
/**
|
|
2041
|
+
* Check purchase state for a specific item
|
|
2042
|
+
* Filters raw API response to expose only safe data to publisher apps
|
|
2043
|
+
*/
|
|
2044
|
+
async checkItemPurchaseState(itemId) {
|
|
2045
|
+
try {
|
|
2046
|
+
const response = await fetch(`${this.config.apiEndpoint}/items/${itemId}/purchase-state`, {
|
|
2047
|
+
method: "GET",
|
|
2048
|
+
headers: {
|
|
2049
|
+
"Content-Type": "application/json"
|
|
2050
|
+
},
|
|
2051
|
+
signal: AbortSignal.timeout(this.config.timeout)
|
|
2052
|
+
});
|
|
2053
|
+
if (!response.ok) {
|
|
2054
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
|
2055
|
+
}
|
|
2056
|
+
const rawData = await response.json();
|
|
2057
|
+
return this.filterPurchaseStateResponse(rawData);
|
|
2058
|
+
} catch (error) {
|
|
2059
|
+
if (error instanceof Error) {
|
|
2060
|
+
throw error;
|
|
2061
|
+
}
|
|
2062
|
+
throw new Error("Unknown error occurred while checking purchase state");
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Filters raw API response to expose only safe data to publisher apps
|
|
2067
|
+
* CRITICAL: Raw privyEligibility map must NEVER be exposed to apps
|
|
2068
|
+
*/
|
|
2069
|
+
filterPurchaseStateResponse(rawResponse) {
|
|
2070
|
+
const filteredResponse = {
|
|
2071
|
+
state: rawResponse.state
|
|
2072
|
+
};
|
|
2073
|
+
if (rawResponse.state === PurchaseState.PRIVY_REQUIRED && rawResponse.privyEligibility) {
|
|
2074
|
+
const requiredLevels = Object.values(rawResponse.privyEligibility).filter((details) => details.requiresUpgrade).map((details) => details.requiredLevel);
|
|
2075
|
+
if (requiredLevels.length > 0) {
|
|
2076
|
+
filteredResponse.requiredLevel = Math.max(...requiredLevels);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
return filteredResponse;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2020
2082
|
const lightTheme = {
|
|
2021
2083
|
colors: {
|
|
2022
2084
|
// Background
|
|
@@ -2503,6 +2565,166 @@ function extractTokenFromURL(paramName = "gwSession", url) {
|
|
|
2503
2565
|
function isBrowser() {
|
|
2504
2566
|
return typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
2505
2567
|
}
|
|
2568
|
+
class PurchaseModal {
|
|
2569
|
+
constructor(themeMode = "light", customStyles) {
|
|
2570
|
+
this.modal = null;
|
|
2571
|
+
this.legacyStyles = null;
|
|
2572
|
+
const prefersDark = themeMode === "dark" || themeMode === "auto" && this.detectDarkMode();
|
|
2573
|
+
this.theme = getTheme(prefersDark);
|
|
2574
|
+
if (customStyles) {
|
|
2575
|
+
this.legacyStyles = {
|
|
2576
|
+
backgroundColor: customStyles.backgroundColor || "#ffffff",
|
|
2577
|
+
textColor: customStyles.textColor || "#333333",
|
|
2578
|
+
primaryColor: customStyles.primaryColor || "#007bff",
|
|
2579
|
+
borderRadius: customStyles.borderRadius || "8px",
|
|
2580
|
+
fontFamily: customStyles.fontFamily || '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
detectDarkMode() {
|
|
2585
|
+
if (typeof window !== "undefined" && window.matchMedia) {
|
|
2586
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2587
|
+
}
|
|
2588
|
+
return false;
|
|
2589
|
+
}
|
|
2590
|
+
escapeHtml(text) {
|
|
2591
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Show purchase confirmation modal
|
|
2595
|
+
*/
|
|
2596
|
+
show(options) {
|
|
2597
|
+
this.hide();
|
|
2598
|
+
this.modal = document.createElement("div");
|
|
2599
|
+
this.modal.id = "gw-purchase-modal";
|
|
2600
|
+
this.modal.style.cssText = `
|
|
2601
|
+
position: fixed;
|
|
2602
|
+
top: 0;
|
|
2603
|
+
left: 0;
|
|
2604
|
+
width: 100%;
|
|
2605
|
+
height: 100%;
|
|
2606
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
2607
|
+
display: flex;
|
|
2608
|
+
align-items: center;
|
|
2609
|
+
justify-content: center;
|
|
2610
|
+
z-index: 99999;
|
|
2611
|
+
font-family: ${this.legacyStyles?.fontFamily || this.theme.typography.fontFamily};
|
|
2612
|
+
`;
|
|
2613
|
+
const content = document.createElement("div");
|
|
2614
|
+
const bgColor = this.legacyStyles?.backgroundColor || this.theme.colors.card;
|
|
2615
|
+
const textColor = this.legacyStyles?.textColor || this.theme.colors.cardForeground;
|
|
2616
|
+
const borderRadius = this.legacyStyles?.borderRadius || this.theme.spacing.borderRadius.lg;
|
|
2617
|
+
content.style.cssText = `
|
|
2618
|
+
background-color: ${bgColor};
|
|
2619
|
+
color: ${textColor};
|
|
2620
|
+
border-radius: ${borderRadius};
|
|
2621
|
+
padding: ${this.theme.spacing.padding.lg};
|
|
2622
|
+
max-width: 400px;
|
|
2623
|
+
width: 90%;
|
|
2624
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
2625
|
+
border: 1px solid ${this.theme.colors.border};
|
|
2626
|
+
`;
|
|
2627
|
+
content.innerHTML = `
|
|
2628
|
+
<h2 style="margin: 0 0 ${this.theme.spacing.padding.md} 0; font-size: ${this.theme.typography.fontSize.xl}; font-weight: ${this.theme.typography.fontWeight.semibold}; color: ${textColor};">
|
|
2629
|
+
🛒 Confirm Purchase
|
|
2630
|
+
</h2>
|
|
2631
|
+
<p style="margin: 0 0 ${this.theme.spacing.padding.lg} 0; font-size: ${this.theme.typography.fontSize.base}; line-height: ${this.theme.typography.lineHeight.normal}; color: ${this.theme.colors.mutedForeground};">
|
|
2632
|
+
Are you sure you want to purchase item <strong style="color: ${textColor};">${this.escapeHtml(options.itemId)}</strong>?
|
|
2633
|
+
</p>
|
|
2634
|
+
<div style="display: flex; gap: ${this.theme.spacing.gap.md}; justify-content: flex-end;">
|
|
2635
|
+
<button id="gw-cancel-btn" style="
|
|
2636
|
+
padding: 10px 20px;
|
|
2637
|
+
background-color: ${this.theme.colors.secondary};
|
|
2638
|
+
color: ${this.theme.colors.secondaryForeground};
|
|
2639
|
+
border: 1px solid ${this.theme.colors.border};
|
|
2640
|
+
border-radius: ${this.theme.spacing.borderRadius.sm};
|
|
2641
|
+
font-size: ${this.theme.typography.fontSize.sm};
|
|
2642
|
+
font-weight: ${this.theme.typography.fontWeight.medium};
|
|
2643
|
+
cursor: pointer;
|
|
2644
|
+
transition: all 0.2s;
|
|
2645
|
+
font-family: ${this.theme.typography.fontFamily};
|
|
2646
|
+
">
|
|
2647
|
+
Cancel
|
|
2648
|
+
</button>
|
|
2649
|
+
<button id="gw-confirm-btn" style="
|
|
2650
|
+
padding: 10px 20px;
|
|
2651
|
+
background-color: ${this.theme.colors.primary};
|
|
2652
|
+
color: ${this.theme.colors.primaryForeground};
|
|
2653
|
+
border: none;
|
|
2654
|
+
border-radius: ${this.theme.spacing.borderRadius.sm};
|
|
2655
|
+
font-size: ${this.theme.typography.fontSize.sm};
|
|
2656
|
+
font-weight: ${this.theme.typography.fontWeight.medium};
|
|
2657
|
+
cursor: pointer;
|
|
2658
|
+
transition: all 0.2s;
|
|
2659
|
+
font-family: ${this.theme.typography.fontFamily};
|
|
2660
|
+
">
|
|
2661
|
+
Confirm Purchase
|
|
2662
|
+
</button>
|
|
2663
|
+
</div>
|
|
2664
|
+
`;
|
|
2665
|
+
this.modal.appendChild(content);
|
|
2666
|
+
document.body.appendChild(this.modal);
|
|
2667
|
+
const confirmBtn = document.getElementById("gw-confirm-btn");
|
|
2668
|
+
if (confirmBtn && options.onConfirm) {
|
|
2669
|
+
confirmBtn.addEventListener("click", () => {
|
|
2670
|
+
options.onConfirm?.();
|
|
2671
|
+
this.hide();
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
const cancelBtn = document.getElementById("gw-cancel-btn");
|
|
2675
|
+
if (cancelBtn) {
|
|
2676
|
+
cancelBtn.addEventListener("click", () => {
|
|
2677
|
+
options.onCancel?.();
|
|
2678
|
+
this.hide();
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
const buttons = content.querySelectorAll("button");
|
|
2682
|
+
buttons.forEach((button) => {
|
|
2683
|
+
button.addEventListener("mouseenter", () => {
|
|
2684
|
+
button.style.opacity = "0.9";
|
|
2685
|
+
});
|
|
2686
|
+
button.addEventListener("mouseleave", () => {
|
|
2687
|
+
button.style.opacity = "1";
|
|
2688
|
+
});
|
|
2689
|
+
button.addEventListener("focus", () => {
|
|
2690
|
+
button.style.outline = `2px solid ${this.theme.colors.ring}`;
|
|
2691
|
+
button.style.outlineOffset = "2px";
|
|
2692
|
+
});
|
|
2693
|
+
button.addEventListener("blur", () => {
|
|
2694
|
+
button.style.outline = "none";
|
|
2695
|
+
});
|
|
2696
|
+
});
|
|
2697
|
+
const handleKeyDown = (e) => {
|
|
2698
|
+
if (e.key === "Escape") {
|
|
2699
|
+
options.onCancel?.();
|
|
2700
|
+
this.hide();
|
|
2701
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
2702
|
+
}
|
|
2703
|
+
};
|
|
2704
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
2705
|
+
this.modal.addEventListener("click", (e) => {
|
|
2706
|
+
if (e.target === this.modal) {
|
|
2707
|
+
options.onCancel?.();
|
|
2708
|
+
this.hide();
|
|
2709
|
+
}
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
/**
|
|
2713
|
+
* Hide and remove modal
|
|
2714
|
+
*/
|
|
2715
|
+
hide() {
|
|
2716
|
+
if (this.modal && this.modal.parentNode) {
|
|
2717
|
+
this.modal.parentNode.removeChild(this.modal);
|
|
2718
|
+
this.modal = null;
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
/**
|
|
2722
|
+
* Check if modal is currently shown
|
|
2723
|
+
*/
|
|
2724
|
+
isShown() {
|
|
2725
|
+
return this.modal !== null;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2506
2728
|
class MarketplaceSDK {
|
|
2507
2729
|
constructor(config) {
|
|
2508
2730
|
this.timer = null;
|
|
@@ -2513,8 +2735,21 @@ class MarketplaceSDK {
|
|
|
2513
2735
|
this.sessionData = null;
|
|
2514
2736
|
this.jwtToken = null;
|
|
2515
2737
|
this.endReason = "manual";
|
|
2738
|
+
this.purchaseModal = null;
|
|
2739
|
+
if (!config.jwksUri) {
|
|
2740
|
+
throw new SDKError(
|
|
2741
|
+
"jwksUri is required — pass an environment-aware JWKS URL (e.g., https://api.dev.generalwisdom.com/.well-known/jwks.json). See README for environment-specific URLs.",
|
|
2742
|
+
"CONFIG_MISSING_JWKS_URI"
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
if (!config.marketplaceUrl) {
|
|
2746
|
+
throw new SDKError(
|
|
2747
|
+
"marketplaceUrl is required — pass an environment-aware marketplace URL (e.g., https://dev.generalwisdom.com/). See README for environment-specific URLs.",
|
|
2748
|
+
"CONFIG_MISSING_MARKETPLACE_URL"
|
|
2749
|
+
);
|
|
2750
|
+
}
|
|
2516
2751
|
this.config = {
|
|
2517
|
-
jwksUri: config.jwksUri
|
|
2752
|
+
jwksUri: config.jwksUri,
|
|
2518
2753
|
jwtParamName: config.jwtParamName || "gwSession",
|
|
2519
2754
|
apiEndpoint: config.apiEndpoint || "https://api.platform.generalwisdom.com",
|
|
2520
2755
|
jwtIssuer: config.jwtIssuer || "generalwisdom.com",
|
|
@@ -2524,7 +2759,7 @@ class MarketplaceSDK {
|
|
|
2524
2759
|
customStyles: config.customStyles ?? {},
|
|
2525
2760
|
themeMode: config.themeMode ?? "light",
|
|
2526
2761
|
applicationId: config.applicationId ?? "",
|
|
2527
|
-
marketplaceUrl: config.marketplaceUrl
|
|
2762
|
+
marketplaceUrl: config.marketplaceUrl,
|
|
2528
2763
|
// Phase 2 options
|
|
2529
2764
|
enableHeartbeat: config.enableHeartbeat ?? false,
|
|
2530
2765
|
heartbeatIntervalSeconds: config.heartbeatIntervalSeconds ?? 30,
|
|
@@ -2537,6 +2772,9 @@ class MarketplaceSDK {
|
|
|
2537
2772
|
};
|
|
2538
2773
|
this.validator = new JWKSValidator(this.config.jwksUri, this.config.debug);
|
|
2539
2774
|
this.logger = new Logger(this.config.debug, "[MarketplaceSDK]");
|
|
2775
|
+
this.purchaseStateManager = new PurchaseStateManager({
|
|
2776
|
+
apiEndpoint: this.config.apiEndpoint
|
|
2777
|
+
});
|
|
2540
2778
|
this.logger.info("SDK initialized with config:", {
|
|
2541
2779
|
jwksUri: this.config.jwksUri,
|
|
2542
2780
|
jwtParamName: this.config.jwtParamName,
|
|
@@ -3046,6 +3284,79 @@ class MarketplaceSDK {
|
|
|
3046
3284
|
isTimerRunning() {
|
|
3047
3285
|
return this.timer?.isRunning() ?? false;
|
|
3048
3286
|
}
|
|
3287
|
+
/**
|
|
3288
|
+
* Request purchase of an item — opens PurchaseModal directly
|
|
3289
|
+
* Bypasses the add-ons panel; works for any active item (visible or hidden)
|
|
3290
|
+
*/
|
|
3291
|
+
requestPurchase(itemId) {
|
|
3292
|
+
if (!this.sessionData || !this.jwtToken) {
|
|
3293
|
+
throw new SDKError("No active session", "NO_SESSION");
|
|
3294
|
+
}
|
|
3295
|
+
this.logger.info("Requesting purchase for item:", itemId);
|
|
3296
|
+
this.events.onPurchaseStart?.({ itemId, quantity: 1 });
|
|
3297
|
+
if (!this.purchaseModal) {
|
|
3298
|
+
this.purchaseModal = new PurchaseModal(
|
|
3299
|
+
this.config.themeMode || "light",
|
|
3300
|
+
this.config.customStyles
|
|
3301
|
+
);
|
|
3302
|
+
}
|
|
3303
|
+
this.purchaseModal.show({
|
|
3304
|
+
itemId,
|
|
3305
|
+
onConfirm: async () => {
|
|
3306
|
+
try {
|
|
3307
|
+
const response = await fetch(
|
|
3308
|
+
`${this.config.apiEndpoint}/items/${itemId}/purchase`,
|
|
3309
|
+
{
|
|
3310
|
+
method: "POST",
|
|
3311
|
+
headers: {
|
|
3312
|
+
"Authorization": `Bearer ${this.jwtToken}`,
|
|
3313
|
+
"Content-Type": "application/json"
|
|
3314
|
+
},
|
|
3315
|
+
body: JSON.stringify({ item_id: itemId, quantity: 1 })
|
|
3316
|
+
}
|
|
3317
|
+
);
|
|
3318
|
+
if (!response.ok) {
|
|
3319
|
+
throw new PurchaseError(
|
|
3320
|
+
"Purchase failed",
|
|
3321
|
+
"PURCHASE_FAILED",
|
|
3322
|
+
itemId,
|
|
3323
|
+
response.status
|
|
3324
|
+
);
|
|
3325
|
+
}
|
|
3326
|
+
const data = await response.json();
|
|
3327
|
+
const result = {
|
|
3328
|
+
itemId,
|
|
3329
|
+
transactionId: data.transactionId,
|
|
3330
|
+
amount: data.amount
|
|
3331
|
+
};
|
|
3332
|
+
this.events.onPurchaseSuccess?.(result);
|
|
3333
|
+
this.events.onPurchaseComplete?.(itemId);
|
|
3334
|
+
if (data.newBalance !== void 0) {
|
|
3335
|
+
this.events.onBalanceUpdate?.(data.newBalance);
|
|
3336
|
+
}
|
|
3337
|
+
this.logger.info("Purchase successful:", result);
|
|
3338
|
+
} catch (error) {
|
|
3339
|
+
const purchaseError = error instanceof PurchaseError ? error : new PurchaseError(
|
|
3340
|
+
error instanceof Error ? error.message : "Purchase failed",
|
|
3341
|
+
"PURCHASE_ERROR",
|
|
3342
|
+
itemId
|
|
3343
|
+
);
|
|
3344
|
+
this.events.onPurchaseError?.(purchaseError);
|
|
3345
|
+
this.logger.error("Purchase failed:", purchaseError);
|
|
3346
|
+
}
|
|
3347
|
+
},
|
|
3348
|
+
onCancel: () => {
|
|
3349
|
+
this.events.onPurchaseCancelled?.(itemId);
|
|
3350
|
+
this.logger.info("Purchase cancelled for item:", itemId);
|
|
3351
|
+
}
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
/**
|
|
3355
|
+
* Get purchase state manager for item purchase state checking
|
|
3356
|
+
*/
|
|
3357
|
+
getPurchaseStateManager() {
|
|
3358
|
+
return this.purchaseStateManager;
|
|
3359
|
+
}
|
|
3049
3360
|
/**
|
|
3050
3361
|
* Cleanup and destroy SDK instance
|
|
3051
3362
|
*/
|
|
@@ -3055,6 +3366,7 @@ class MarketplaceSDK {
|
|
|
3055
3366
|
this.heartbeat?.stop();
|
|
3056
3367
|
this.tabSync?.destroy();
|
|
3057
3368
|
this.modal?.hide();
|
|
3369
|
+
this.purchaseModal?.hide();
|
|
3058
3370
|
if (typeof sessionStorage !== "undefined") {
|
|
3059
3371
|
sessionStorage.removeItem("gw_marketplace_jwt");
|
|
3060
3372
|
this.logger.log("JWT token cleared from storage");
|
|
@@ -3265,6 +3577,8 @@ export {
|
|
|
3265
3577
|
JWTParser,
|
|
3266
3578
|
Logger,
|
|
3267
3579
|
MarketplaceSDK,
|
|
3580
|
+
PurchaseError,
|
|
3581
|
+
PurchaseModal,
|
|
3268
3582
|
SDKError,
|
|
3269
3583
|
SessionHeader,
|
|
3270
3584
|
TabSyncManager,
|