UNPKG

spaps-sdk

Version:

Sweet Potato Authentication & Payment Service SDK - Zero-config client with built-in permission checking and role-based access control

1,050 lines (1,047 loc) 44.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/permissions.ts var permissions_exports = {}; __export(permissions_exports, { DEFAULT_ADMIN_ACCOUNTS: () => DEFAULT_ADMIN_ACCOUNTS, PermissionChecker: () => PermissionChecker, canAccessAdmin: () => canAccessAdmin, createPermissionChecker: () => createPermissionChecker, defaultPermissionChecker: () => defaultPermissionChecker, getRoleAwareErrorMessage: () => getRoleAwareErrorMessage, getUserDisplay: () => getUserDisplay, getUserRole: () => getUserRole, hasPermission: () => hasPermission, isAdminAccount: () => isAdminAccount }); function isAdminAccount(identifier, customAdmins = []) { if (!identifier) return false; const normalized = identifier.toLowerCase(); if (normalized === DEFAULT_ADMIN_ACCOUNTS.email.toLowerCase() || normalized === DEFAULT_ADMIN_ACCOUNTS.wallets.ethereum.toLowerCase() || normalized === DEFAULT_ADMIN_ACCOUNTS.wallets.solana.toLowerCase()) { return true; } return customAdmins.some((admin) => { if (typeof admin === "string") { return admin.toLowerCase() === normalized; } if ("email" in admin && admin.email.toLowerCase() === normalized) { return true; } if ("wallets" in admin) { return Object.values(admin.wallets).some( (wallet) => wallet.toLowerCase() === normalized ); } return false; }); } function getUserRole(identifier, customAdmins = []) { if (!identifier) return "guest"; if (isAdminAccount(identifier, customAdmins)) { return "admin"; } return "user"; } function hasPermission(user, requiredPermissions, customAdmins = []) { if (!user) return false; const identifier = user.email || user.wallet_address; const userRole = getUserRole(identifier, customAdmins); if (userRole === "admin") { return true; } if (Array.isArray(requiredPermissions)) { return requiredPermissions.every( (permission) => user.permissions?.includes(permission) ); } return user.permissions?.includes(requiredPermissions) || false; } function canAccessAdmin(user, customAdmins = []) { if (!user) { return { allowed: false, reason: "Authentication required", userRole: "guest", requiredRole: "admin" }; } const identifier = user.email || user.wallet_address; const userRole = getUserRole(identifier, customAdmins); const isAdmin = userRole === "admin"; return { allowed: isAdmin, reason: isAdmin ? void 0 : "Admin privileges required", userRole, requiredRole: "admin" }; } function getRoleAwareErrorMessage(requiredRole, userRole, action = "perform this action") { const messages = { admin: { user: `\u{1F512} Admin privileges required to ${action}. Please authenticate with an admin account.`, guest: `\u{1F510} Authentication required. Please sign in with an admin account to ${action}.` }, user: { guest: `\u{1F510} Authentication required. Please sign in to ${action}.` } }; return messages[requiredRole]?.[userRole] || `Access denied. Required role: ${requiredRole}, current role: ${userRole}`; } function getUserDisplay(user, customAdmins = []) { if (!user) { return { displayName: "Guest", role: "guest", badge: null, isAdmin: false }; } const identifier = user.email || user.wallet_address; const role = getUserRole(identifier, customAdmins); const isAdmin = role === "admin"; return { displayName: user.email || `${user.wallet_address?.slice(0, 6)}...${user.wallet_address?.slice(-4)}` || "User", role, badge: isAdmin ? "\u{1F451} Admin" : null, isAdmin }; } function createPermissionChecker(customAdmins = []) { return new PermissionChecker(customAdmins); } var DEFAULT_ADMIN_ACCOUNTS, PermissionChecker, defaultPermissionChecker; var init_permissions = __esm({ "src/permissions.ts"() { "use strict"; DEFAULT_ADMIN_ACCOUNTS = { email: "buildooor@gmail.com", wallets: { ethereum: "0xa72bb7CeF1e4B2Cc144373d8dE0Add7CCc8DF4Ba", solana: "HVEbdiYU3Rr34NHBSgKs7q8cvdTeZLqNL77Z1FB2vjLy" } }; PermissionChecker = class { customAdmins; constructor(customAdmins = []) { this.customAdmins = customAdmins; } isAdmin(identifier) { return isAdminAccount(identifier, this.customAdmins); } getRole(identifier) { return getUserRole(identifier, this.customAdmins); } hasPermission(user, permissions) { return hasPermission(user, permissions, this.customAdmins); } canAccessAdmin(user) { return canAccessAdmin(user, this.customAdmins); } getErrorMessage(requiredRole, userRole, action) { return getRoleAwareErrorMessage(requiredRole, userRole, action); } getUserDisplay(user) { return getUserDisplay(user, this.customAdmins); } // Convenience methods requiresAuth(user) { return !user; } requiresAdmin(user) { return !this.canAccessAdmin(user).allowed; } addCustomAdmin(admin) { this.customAdmins.push(admin); } removeCustomAdmin(admin) { this.customAdmins = this.customAdmins.filter((a) => a !== admin); } }; defaultPermissionChecker = new PermissionChecker(); } }); // src/index.ts var index_exports = {}; __export(index_exports, { DEFAULT_ADMIN_ACCOUNTS: () => DEFAULT_ADMIN_ACCOUNTS, PermissionChecker: () => PermissionChecker, SPAPS: () => SPAPSClient, SPAPSClient: () => SPAPSClient, TokenManager: () => TokenManager, WalletUtils: () => WalletUtils, canAccessAdmin: () => canAccessAdmin, createPermissionChecker: () => createPermissionChecker, createSecureMessageRequestSchema: () => import_spaps_types.createSecureMessageRequestSchema, default: () => index_default, defaultPermissionChecker: () => defaultPermissionChecker, getRoleAwareErrorMessage: () => getRoleAwareErrorMessage, getUserDisplay: () => getUserDisplay, getUserRole: () => getUserRole, hasPermission: () => hasPermission, isAdminAccount: () => isAdminAccount, secureMessageMetadataSchema: () => import_spaps_types.secureMessageMetadataSchema, secureMessageSchema: () => import_spaps_types.secureMessageSchema, verifyCryptoWebhookSignature: () => verifyCryptoWebhookSignature }); module.exports = __toCommonJS(index_exports); var import_crypto = __toESM(require("crypto")); var import_axios = __toESM(require("axios")); var import_spaps_types = require("spaps-types"); init_permissions(); if (typeof globalThis.fetch === "undefined") { require("cross-fetch/polyfill"); } var SPAPSClient = class { client; apiKey; accessToken; refreshToken; _isLocalMode = false; unwrapApiResponse(response, fallback) { if (!response) { throw new Error(fallback); } const payload = this.isAxiosResponse(response) ? response.data : this.isResponseLikeWithData(response) ? response.data : response; if (this.isApiResponse(payload)) { if (payload.success === false) { throw new Error(payload.error?.message || fallback); } if (payload.data !== void 0) { return payload.data; } return void 0; } return payload; } isAxiosResponse(value) { if (!value || typeof value !== "object") { return false; } const record = value; return "data" in record && "status" in record; } isResponseLikeWithData(value) { if (!value || typeof value !== "object") return false; const record = value; return "data" in record && !("success" in record); } isApiResponse(value) { if (!value || typeof value !== "object") { return false; } const record = value; return "success" in record && typeof record.success === "boolean"; } // Admin namespace for cleaner API admin = { createProduct: (productData) => this.createProduct(productData), updateProduct: (productId, updates) => this.updateProduct(productId, updates), deleteProduct: (productId) => this.deleteProduct(productId), createPrice: (priceData) => this.createPrice(priceData), syncProducts: () => this.syncProducts(), getProducts: () => this.getProducts(), triggerCryptoReconcile: (opts) => this.payments.crypto.reconcile(opts || {}) }; secureMessages = { create: (payload) => this.createSecureMessage(payload), list: () => this.listSecureMessages() }; constructor(config = {}) { const apiUrl = config.apiUrl || process.env.SPAPS_API_URL || process.env.NEXT_PUBLIC_SPAPS_API_URL; if (!apiUrl || apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1")) { this._isLocalMode = true; this.client = import_axios.default.create({ baseURL: apiUrl || "http://localhost:3300", timeout: config.timeout || 1e4, headers: { "Content-Type": "application/json" } }); } else { if (!config.apiKey && !process.env.SPAPS_API_KEY) { console.warn("\u26A0\uFE0F SPAPS: No API key provided. Some features may not work."); } this.apiKey = config.apiKey || process.env.SPAPS_API_KEY; this.client = import_axios.default.create({ baseURL: apiUrl, timeout: config.timeout || 1e4, headers: { "Content-Type": "application/json", ...this.apiKey && { "X-API-Key": this.apiKey } } }); } this.client.interceptors.request.use((config2) => { if (this.accessToken && !config2.headers.Authorization) { config2.headers.Authorization = `Bearer ${this.accessToken}`; } return config2; }); this.client.interceptors.response.use( (response) => response, async (error) => { if (error.response?.status === 401 && this.refreshToken) { try { const { data } = await this.refresh(this.refreshToken); this.accessToken = data.access_token; this.refreshToken = data.refresh_token; if (error.config) { error.config.headers.Authorization = `Bearer ${this.accessToken}`; return this.client.request(error.config); } } catch (refreshError) { this.accessToken = void 0; this.refreshToken = void 0; } } return Promise.reject(error); } ); } // -------- Helper methods (for tests and convenience) -------- /** Raw API request helper that returns an ApiResponse-like shape */ async request(method, url, data, requiresAuth = false) { try { const config = { method, url, data }; if (requiresAuth && this.accessToken) { config.headers = { ...config.headers || {}, Authorization: `Bearer ${this.accessToken}` }; } const res = await this.client.request(config); return { success: true, data: res.data }; } catch (err) { const message = err?.response?.data?.error?.message || err?.message || "Request failed"; return Promise.reject(new Error(message)); } } /** Health check helper that returns boolean */ async healthCheck() { try { const res = await this.client.get("/health"); return Boolean(res.data?.success); } catch { return false; } } // Authentication Methods async login(email, password) { const response = await this.client.post("/api/auth/login", { email, password }); this.accessToken = response.data.access_token; this.refreshToken = response.data.refresh_token; return response; } async register(email, password) { const response = await this.client.post("/api/auth/register", { email, password }); this.accessToken = response.data.access_token; this.refreshToken = response.data.refresh_token; return response; } async walletSignIn(walletAddress, signature, message, chainType = "solana") { const response = await this.client.post("/api/auth/wallet-sign-in", { wallet_address: walletAddress, signature, message, chain_type: chainType }); this.accessToken = response.data.access_token; this.refreshToken = response.data.refresh_token; return response; } async refresh(refreshToken) { const response = await this.client.post("/api/auth/refresh", { refresh_token: refreshToken || this.refreshToken }); this.accessToken = response.data.access_token; this.refreshToken = response.data.refresh_token; return response; } async logout() { await this.client.post("/api/auth/logout"); this.accessToken = void 0; this.refreshToken = void 0; } async getUser() { return this.client.get("/api/auth/user"); } // -------- Facade namespaces (compat with previous SDK shape) -------- auth = { /** * Verify magic link token without mutating token state. * Returns a simple success object from the API. */ getNonce: async (walletAddress) => { const res = await this.client.post("/api/auth/nonce", { wallet_address: walletAddress }); const body = res.data; if (body?.success === false) throw new Error(body?.error?.message || "Failed to generate nonce"); return body?.data ?? body; }, signInWithWallet: async (req) => { const res = await this.client.post("/api/auth/wallet-sign-in", req); const body = res.data; if (body?.success === false) throw new Error(body?.error?.message || "Wallet sign-in failed"); const data = body?.data ?? body; this.accessToken = data.access_token; this.refreshToken = data.refresh_token; return data; }, authenticateWallet: async (walletAddress, signFn, chainType, username) => { const nonce = await this.auth.getNonce(walletAddress); const signature = await signFn(nonce.message); return this.auth.signInWithWallet({ wallet_address: walletAddress, signature, message: nonce.message, chain_type: chainType, username }); }, signInWithPassword: async (payload) => { const res = await this.client.post("/api/auth/login", payload); const body = res.data; if (body?.success === false) throw new Error(body?.error?.message || "Login failed"); const data = body?.data ?? body; this.accessToken = data.access_token; this.refreshToken = data.refresh_token; return data; }, requestMagicLink: async (payload) => { await this.client.post("/api/auth/magic-link", payload); }, requestPasswordReset: async (payload) => { await this.client.post("/api/auth/password-reset", payload); }, confirmPasswordReset: async (payload) => { await this.client.post("/api/auth/reset-password-confirm", payload); }, register: async (payload) => { const res = await this.client.post("/api/auth/register", payload); const body = res.data; if (body?.success === false) throw new Error(body?.error?.message || "Register failed"); const data = body?.data ?? body; this.accessToken = data.access_token; this.refreshToken = data.refresh_token; return data; }, /** * Verify a magic link token. Does not set access/refresh tokens. * Consumers should redirect or prompt login based on the returned status. */ verifyMagicLink: async (payload) => { const res = await this.client.post("/api/auth/verify-magic-link", payload); return this.unwrapApiResponse(res, "Failed to verify magic link"); }, solana: { linkWallet: async (payload) => { const res = await this.client.post("/api/auth/solana/link-wallet", payload); return this.unwrapApiResponse(res, "Failed to link Solana wallet"); }, verifySignature: async (payload) => { const res = await this.client.post("/api/auth/solana/verify-signature", payload); return this.unwrapApiResponse(res, "Failed to verify Solana signature"); }, generateMessage: async (wallet_address) => { const res = await this.client.get(`/api/auth/solana/generate-message/${wallet_address}`); return this.unwrapApiResponse(res, "Failed to generate Solana auth message"); }, getWallets: async () => { const res = await this.client.get("/api/auth/solana/wallets"); return this.unwrapApiResponse(res, "Failed to fetch Solana wallets"); }, networkInfo: async () => { const res = await this.client.get("/api/auth/solana/network-info"); return this.unwrapApiResponse(res, "Failed to fetch Solana network info"); } }, ethereum: { linkWallet: async (payload) => { const res = await this.client.post("/api/auth/ethereum/link-wallet", payload); return this.unwrapApiResponse(res, "Failed to link Ethereum wallet"); }, verifySignature: async (payload) => { const res = await this.client.post("/api/auth/ethereum/verify-signature", payload); return this.unwrapApiResponse(res, "Failed to verify Ethereum signature"); }, verifyTypedData: async (payload) => { const res = await this.client.post("/api/auth/ethereum/verify-typed-data", payload); return this.unwrapApiResponse(res, "Failed to verify EIP-712 typed data"); }, generateMessage: async (wallet_address) => { const res = await this.client.get(`/api/auth/ethereum/generate-message/${wallet_address}`); return this.unwrapApiResponse(res, "Failed to generate Ethereum auth message"); }, generateTypedData: async (wallet_address) => { const res = await this.client.get(`/api/auth/ethereum/generate-typed-data/${wallet_address}`); return this.unwrapApiResponse(res, "Failed to generate Ethereum typed data"); }, getWallets: async () => { const res = await this.client.get("/api/auth/ethereum/wallets"); return this.unwrapApiResponse(res, "Failed to fetch Ethereum wallets"); }, networkInfo: async () => { const res = await this.client.get("/api/auth/ethereum/network-info"); return this.unwrapApiResponse(res, "Failed to fetch Ethereum network info"); }, balance: async (wallet_address) => { const res = await this.client.get(`/api/auth/ethereum/balance/${wallet_address}`); return this.unwrapApiResponse(res, "Failed to fetch Ethereum balance"); }, contractCheck: async (wallet_address, contract_address) => { const res = await this.client.get(`/api/auth/ethereum/contract-check/${wallet_address}/${contract_address}`); return this.unwrapApiResponse(res, "Failed to check contract"); } }, refreshToken: async (refreshToken) => { const res = await this.client.post("/api/auth/refresh", { refresh_token: refreshToken }); const body = res.data; if (body?.success === false) throw new Error(body?.error?.message || "Token refresh failed"); const data = body?.data ?? body; this.accessToken = data.access_token; this.refreshToken = data.refresh_token; return data; }, /** * Logout and clear tokens. Network errors are intentionally swallowed * to avoid trapping users in a bad state during sign‑out flows. */ logout: async () => { try { await this.client.post("/api/auth/logout"); } catch { } finally { this.accessToken = void 0; this.refreshToken = void 0; } }, getCurrentUser: async () => { const res = await this.client.get("/api/auth/user"); const body = res.data; if (body?.success === false) throw new Error(body?.error?.message || "Failed to get user profile"); return body?.data?.user ?? body?.user; }, isAuthenticated: () => !!this.accessToken }; payments = { crypto: { createInvoice: async (payload) => { const body = { asset: payload.asset, network: payload.network, amount: payload.amount }; if (typeof payload.expiresInSeconds === "number") { body.expires_in_seconds = payload.expiresInSeconds; } if (payload.beneficiary) { body.beneficiary = payload.beneficiary; } if (payload.metadata) { body.metadata = payload.metadata; } const res = await this.client.post("/api/payments/crypto/invoices", body); const data = this.unwrapApiResponse(res, "Failed to create crypto invoice"); return data.invoice; }, getInvoice: async (invoiceId) => { const res = await this.client.get(`/api/payments/crypto/invoices/${invoiceId}`); const data = this.unwrapApiResponse(res, "Failed to fetch crypto invoice"); return data.invoice; }, getInvoiceStatus: async (invoiceId) => { const res = await this.client.get(`/api/payments/crypto/invoices/${invoiceId}/status`); return this.unwrapApiResponse(res, "Failed to fetch crypto invoice status"); }, reconcile: async (options = {}) => { const headers = {}; if (options.reconToken) { headers["X-Recon-Token"] = options.reconToken; } const payload = {}; if (options.cursor) { payload.cursor = options.cursor; } const res = await this.client.post("/api/payments/crypto/reconcile", payload, { headers }); return this.unwrapApiResponse(res, "Failed to trigger crypto reconciliation"); } }, createCheckoutSession: async (payload) => { const headers = {}; if (this.accessToken) headers["Authorization"] = `Bearer ${this.accessToken}`; const res = await this.client.post("/api/stripe/checkout-sessions", payload, { headers }); return this.unwrapApiResponse(res, "Failed to create checkout session"); }, createPaymentCheckout: async (params) => { const payload = { mode: "payment", line_items: [{ price_id: params.price_id, quantity: params.quantity ?? 1 }], success_url: params.success_url, cancel_url: params.cancel_url }; return this.payments.createCheckoutSession(payload); }, createSubscriptionCheckout: async (params) => { const payload = { mode: "subscription", line_items: [{ price_id: params.price_id, quantity: 1 }], success_url: params.success_url, cancel_url: params.cancel_url }; if (params.trial_period_days) payload.subscription_data = { trial_period_days: params.trial_period_days }; return this.payments.createCheckoutSession(payload); }, getCheckoutSession: async (sessionId) => { const res = await this.client.get(`/api/stripe/checkout-sessions/${sessionId}`); return this.unwrapApiResponse(res, "Failed to get checkout session"); }, listCheckoutSessions: async (query = {}) => { const q = new URLSearchParams(); if (query.limit) q.append("limit", String(query.limit)); if (query.starting_after) q.append("starting_after", query.starting_after); const res = await this.client.get(`/api/stripe/checkout-sessions${q.toString() ? `?${q.toString()}` : ""}`); return this.unwrapApiResponse(res, "Failed to list checkout sessions"); }, expireCheckoutSession: async (sessionId) => { const res = await this.client.post(`/api/stripe/checkout-sessions/${sessionId}/expire`); return this.unwrapApiResponse(res, "Failed to expire checkout session"); }, listProducts: async (query = {}) => { const q = new URLSearchParams(); if (query.category) q.append("category", query.category); if (typeof query.active === "boolean") q.append("active", String(query.active)); if (query.limit) q.append("limit", String(query.limit)); if (query.starting_after) q.append("starting_after", query.starting_after); const headers = {}; if (this.accessToken) headers["Authorization"] = `Bearer ${this.accessToken}`; const res = await this.client.get(`/api/stripe/products${q.toString() ? `?${q.toString()}` : ""}`, { headers }); return this.unwrapApiResponse(res, "Failed to list products"); }, getProduct: async (productId) => { const headers = {}; if (this.accessToken) headers["Authorization"] = `Bearer ${this.accessToken}`; const res = await this.client.get(`/api/stripe/products/${productId}`, { headers }); return this.unwrapApiResponse(res, "Failed to get product"); }, createCustomerPortalSession: async (payload) => { const headers = {}; if (this.accessToken) headers["Authorization"] = `Bearer ${this.accessToken}`; const res = await this.client.post("/api/stripe/portal-session", payload, { headers }); return this.unwrapApiResponse(res, "Failed to create portal session"); }, // Guest checkout helpers createGuestCheckoutSession: async (payload) => { const headers = {}; if (this.accessToken) headers["Authorization"] = `Bearer ${this.accessToken}`; const res = await this.client.post("/api/stripe/guest-checkout-sessions", payload, { headers }); return this.unwrapApiResponse(res, "Failed to create guest checkout session"); }, getGuestCheckoutSession: async (sessionId) => { const headers = {}; if (this.accessToken) headers["Authorization"] = `Bearer ${this.accessToken}`; const res = await this.client.get(`/api/stripe/guest-checkout-sessions/${sessionId}`, { headers }); return this.unwrapApiResponse(res, "Failed to get guest checkout session"); }, listGuestCheckoutSessions: async (query = {}) => { const q = new URLSearchParams(); if (query.limit) q.append("limit", String(query.limit)); if (query.starting_after) q.append("starting_after", query.starting_after); const headers = {}; if (this.accessToken) headers["Authorization"] = `Bearer ${this.accessToken}`; const res = await this.client.get(`/api/stripe/guest-checkout-sessions${q.toString() ? `?${q.toString()}` : ""}`, { headers }); return this.unwrapApiResponse(res, "Failed to list guest checkout sessions"); }, convertGuestCheckoutSession: async (payload) => { const headers = {}; if (this.accessToken) headers["Authorization"] = `Bearer ${this.accessToken}`; const res = await this.client.post("/api/stripe/guest-checkout-sessions/convert", payload, { headers }); return this.unwrapApiResponse(res, "Failed to convert guest checkout session"); }, convertGuestCheckout: async (payload) => this.payments.convertGuestCheckoutSession(payload), // Super-admin product helpers (admin token required) listAllProductsSuperAdmin: async () => { if (!this.accessToken) throw new Error("Authentication required. Please authenticate first."); const res = await this.client.get("/api/stripe/products/super-admin/all", { headers: { Authorization: `Bearer ${this.accessToken}` } }); return this.unwrapApiResponse(res, "Failed to list all products (super admin)"); }, updateProductSuperAdmin: async (productId, updates) => { if (!this.accessToken) throw new Error("Authentication required. Please authenticate first."); const res = await this.client.put(`/api/stripe/products/super-admin/${productId}`, updates, { headers: { Authorization: `Bearer ${this.accessToken}` } }); return this.unwrapApiResponse(res, "Failed to update product (super admin)"); }, deleteProductSuperAdmin: async (productId) => { if (!this.accessToken) throw new Error("Authentication required. Please authenticate first."); const res = await this.client.delete(`/api/stripe/products/super-admin/${productId}`, { headers: { Authorization: `Bearer ${this.accessToken}` } }); return this.unwrapApiResponse(res, "Failed to archive product (super admin)"); }, createProductWithPrice: async (payload) => { if (!this.accessToken) throw new Error("Authentication required. Please authenticate first."); const res = await this.client.post("/api/stripe/products/with-price", payload, { headers: { Authorization: `Bearer ${this.accessToken}` } }); return this.unwrapApiResponse(res, "Failed to create product with price"); }, createProductWithPriceSuperAdmin: async (productId, payload) => { if (!this.accessToken) throw new Error("Authentication required. Please authenticate first."); const res = await this.client.post(`/api/stripe/products/super-admin/${productId}/with-price`, payload, { headers: { Authorization: `Bearer ${this.accessToken}` } }); return this.unwrapApiResponse(res, "Failed to create product with price (super admin)"); }, setDefaultPrice: async (productId, payload) => { if (!this.accessToken) throw new Error("Authentication required. Please authenticate first."); const res = await this.client.post(`/api/stripe/products/${productId}/default-price`, payload, { headers: { Authorization: `Bearer ${this.accessToken}` } }); return this.unwrapApiResponse(res, "Failed to set default price"); }, setDefaultPriceSuperAdmin: async (productId, payload) => { if (!this.accessToken) throw new Error("Authentication required. Please authenticate first."); const res = await this.client.put(`/api/stripe/products/super-admin/${productId}/default-price`, payload, { headers: { Authorization: `Bearer ${this.accessToken}` } }); return this.unwrapApiResponse(res, "Failed to set default price (super admin)"); }, createDefaultNewPrice: async (productId, payload) => { if (!this.accessToken) throw new Error("Authentication required. Please authenticate first."); const res = await this.client.post(`/api/stripe/products/${productId}/prices/default-new`, payload, { headers: { Authorization: `Bearer ${this.accessToken}` } }); return this.unwrapApiResponse(res, "Failed to create default price"); }, superAdminListAllProducts: async () => this.payments.listAllProductsSuperAdmin(), superAdminUpdateProduct: async (productId, updates) => this.payments.updateProductSuperAdmin(productId, updates), superAdminArchiveProduct: async (productId) => this.payments.deleteProductSuperAdmin(productId), superAdminCreateProductWithPrice: async (applicationId, payload) => { if (!this.accessToken) throw new Error("Authentication required. Please authenticate first."); const res = await this.client.post(`/api/stripe/products/super-admin/${applicationId}/with-price`, payload, { headers: { Authorization: `Bearer ${this.accessToken}` } }); return this.unwrapApiResponse(res, "Failed to create product with price for application (super admin)"); }, superAdminCreatePriceAndSetDefault: async (productId, payload) => { if (!this.accessToken) throw new Error("Authentication required. Please authenticate first."); const res = await this.client.post(`/api/stripe/products/super-admin/${productId}/prices/default-new`, payload, { headers: { Authorization: `Bearer ${this.accessToken}` } }); return this.unwrapApiResponse(res, "Failed to create price and set default (super admin)"); }, superAdminSetDefaultPrice: async (productId, payload) => this.payments.setDefaultPriceSuperAdmin(productId, payload) }; sessions = { getCurrent: async () => { const res = await this.client.get("/api/sessions/current", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0); return this.unwrapApiResponse(res, "Failed to get current session"); }, list: async (params = {}) => { const q = new URLSearchParams(); if (params.limit) q.append("limit", String(params.limit)); if (params.starting_after) q.append("starting_after", params.starting_after); const res = await this.client.get(`/api/sessions${q.toString() ? `?${q.toString()}` : ""}`, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0); return this.unwrapApiResponse(res, "Failed to list sessions"); }, validate: async () => { const res = await this.client.post("/api/sessions/validate", {}, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0); return this.unwrapApiResponse(res, "Failed to validate session"); }, revoke: async (sessionId) => { const res = await this.client.delete(`/api/sessions/${sessionId}`, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0); return this.unwrapApiResponse(res, "Failed to revoke session"); }, revokeAll: async () => { const res = await this.client.delete("/api/sessions/all", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0); return this.unwrapApiResponse(res, "Failed to revoke all sessions"); }, touch: async () => { const res = await this.client.post("/api/sessions/touch", {}, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0); return this.unwrapApiResponse(res, "Failed to touch session"); } }; // Stripe Methods async createCheckoutSession(priceId, successUrl, cancelUrl) { return this.client.post("/api/stripe/create-checkout-session", { price_id: priceId, success_url: successUrl, cancel_url: cancelUrl }); } async getSubscription() { return this.client.get("/api/stripe/subscription"); } async cancelSubscription() { await this.client.delete("/api/stripe/subscription"); } // Usage Methods async getUsageBalance() { return this.client.get("/api/usage/balance"); } async recordUsage(feature, amount) { await this.client.post("/api/usage/record", { feature, amount }); } // Secure Messaging Methods async createSecureMessage(payload) { const response = await this.client.post("/api/secure-messages", payload); return this.unwrapApiResponse(response, "Failed to create secure message"); } async listSecureMessages() { const response = await this.client.get("/api/secure-messages"); const payload = this.unwrapApiResponse(response, "Failed to list secure messages"); if (payload && Array.isArray(payload.messages)) { return payload.messages; } return payload; } // Admin Methods (Require admin privileges) /** * Create a new Stripe product (Admin required) */ async createProduct(productData) { if (!this.accessToken) { throw new Error("Authentication required. Please authenticate first."); } return this.client.post("/api/stripe/products", productData, { headers: { "Authorization": `Bearer ${this.accessToken}` } }); } /** * Update an existing Stripe product (Admin required) */ async updateProduct(productId, updates) { if (!this.accessToken) { throw new Error("Authentication required. Please authenticate first."); } return this.client.put(`/api/stripe/products/${productId}`, updates, { headers: { "Authorization": `Bearer ${this.accessToken}` } }); } /** * Archive (soft delete) a Stripe product (Admin required) */ async deleteProduct(productId) { if (!this.accessToken) { throw new Error("Authentication required. Please authenticate first."); } return this.client.delete(`/api/stripe/products/${productId}`, { headers: { "Authorization": `Bearer ${this.accessToken}` } }); } /** * Create a new price for a product (Admin required) */ async createPrice(priceData) { if (!this.accessToken) { throw new Error("Authentication required. Please authenticate first."); } return this.client.post("/api/stripe/prices", priceData, { headers: { "Authorization": `Bearer ${this.accessToken}` } }); } /** * Sync all products from Stripe to local database (Super Admin required) */ async syncProducts() { if (!this.accessToken) { throw new Error("Authentication required. Please authenticate first."); } return this.client.post("/api/stripe/products/sync", {}, { headers: { "Authorization": `Bearer ${this.accessToken}` } }); } /** * Get products with admin metadata (if user is admin) */ async getProducts() { const headers = {}; if (this.accessToken) { headers["Authorization"] = `Bearer ${this.accessToken}`; } return this.client.get("/api/stripe/products", { headers }); } // Utility Methods isAuthenticated() { return !!this.accessToken; } getAccessToken() { return this.accessToken; } setAccessToken(token) { this.accessToken = token; } isLocalMode() { return this._isLocalMode; } /** * Check if current user has admin privileges * Note: This requires the user object from authentication */ isAdmin(user) { if (!user) return false; const identifier = user.email || user.wallet_address; if (!identifier) return false; const { isAdminAccount: isAdminAccount2 } = (init_permissions(), __toCommonJS(permissions_exports)); return isAdminAccount2(identifier); } async health() { return this.client.get("/health"); } }; function verifyCryptoWebhookSignature(options) { const { body, signature, secret, toleranceSeconds = 300 } = options; if (!signature) { throw new Error("Missing webhook signature"); } const parts = signature.split(",").reduce((acc, part) => { const [key, value] = part.split("="); if (key && value) acc[key.trim()] = value.trim(); return acc; }, {}); const timestamp = parts["t"]; const expected = parts["v1"]; if (!timestamp || !expected) { throw new Error("Invalid webhook signature format"); } const rawBody = typeof body === "string" ? body : body instanceof Uint8Array ? Buffer.from(body).toString("utf8") : JSON.stringify(body ?? {}); const computed = import_crypto.default.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex"); const expectedBuffer = Buffer.from(expected, "hex"); const computedBuffer = Buffer.from(computed, "hex"); if (expectedBuffer.length !== computedBuffer.length || !import_crypto.default.timingSafeEqual(expectedBuffer, computedBuffer)) { throw new Error("Invalid webhook signature"); } const ts = Number(timestamp); if (Number.isFinite(ts) && toleranceSeconds > 0) { const ageSeconds = Math.abs(Date.now() / 1e3 - ts); if (ageSeconds > toleranceSeconds) { throw new Error("Webhook signature timestamp outside tolerance window"); } } return true; } var index_default = SPAPSClient; var TokenManager = class _TokenManager { static ACCESS_TOKEN_KEY = "sweet_potato_access_token"; static REFRESH_TOKEN_KEY = "sweet_potato_refresh_token"; static USER_KEY = "sweet_potato_user"; static getStorage() { if (typeof globalThis !== "undefined" && globalThis.localStorage) return globalThis.localStorage; if (typeof globalThis !== "undefined" && globalThis.window?.localStorage) return globalThis.window.localStorage; if (typeof global !== "undefined" && global.window?.localStorage) return global.window.localStorage; return null; } static storeTokens(tokens) { const s = _TokenManager.getStorage(); if (s) { s.setItem(_TokenManager.ACCESS_TOKEN_KEY, tokens.access_token); s.setItem(_TokenManager.REFRESH_TOKEN_KEY, tokens.refresh_token); s.setItem(_TokenManager.USER_KEY, JSON.stringify(tokens.user)); } } static getAccessToken() { const s = _TokenManager.getStorage(); return s ? s.getItem(_TokenManager.ACCESS_TOKEN_KEY) : null; } static getRefreshToken() { const s = _TokenManager.getStorage(); return s ? s.getItem(_TokenManager.REFRESH_TOKEN_KEY) : null; } static getStoredUser() { const s = _TokenManager.getStorage(); if (!s) return null; const v = s.getItem(_TokenManager.USER_KEY); return v ? JSON.parse(v) : null; } static clearTokens() { const s = _TokenManager.getStorage(); if (s) { s.removeItem(_TokenManager.ACCESS_TOKEN_KEY); s.removeItem(_TokenManager.REFRESH_TOKEN_KEY); s.removeItem(_TokenManager.USER_KEY); } } static isTokenExpired(token) { try { const parts = token.split("."); if (parts.length !== 3 || !parts[1]) return true; const payload = JSON.parse(atob(parts[1])); const now = Math.floor(Date.now() / 1e3); return payload.exp < now; } catch { return true; } } static async autoRefreshToken(sdk) { const accessToken = _TokenManager.getAccessToken(); const refreshToken = _TokenManager.getRefreshToken(); if (!accessToken || !refreshToken) return false; if (!_TokenManager.isTokenExpired(accessToken)) { sdk.setAccessToken(accessToken); return true; } try { const newTokens = await sdk.auth.refreshToken(refreshToken); _TokenManager.storeTokens(newTokens); return true; } catch { _TokenManager.clearTokens(); return false; } } }; var WalletUtils = class _WalletUtils { static detectChainType(address) { if (/^0x[a-fA-F0-9]{40}$/.test(address)) return "ethereum"; if (/^bc1[a-z0-9]{39,59}$/.test(address)) return "bitcoin"; if (/^[1-9A-HJ-NP-Za-km-z]{32}$/.test(address) || /^[1-9A-HJ-NP-Za-km-z]{44}$/.test(address)) return "solana"; if (/^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address) && address.length >= 26 && address.length <= 35) return "bitcoin"; if (/^[1-9A-HJ-NP-Za-km-z]{35,44}$/.test(address)) return "solana"; return null; } static isValidAddress(address, chainType) { if (!chainType) chainType = _WalletUtils.detectChainType(address) || "ethereum"; switch (chainType) { case "solana": return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address); case "ethereum": case "base": return /^0x[a-fA-F0-9]{40}$/.test(address); case "bitcoin": return /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address) || /^bc1[a-z0-9]{39,59}$/.test(address); default: return false; } } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { DEFAULT_ADMIN_ACCOUNTS, PermissionChecker, SPAPS, SPAPSClient, TokenManager, WalletUtils, canAccessAdmin, createPermissionChecker, createSecureMessageRequestSchema, defaultPermissionChecker, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, secureMessageMetadataSchema, secureMessageSchema, verifyCryptoWebhookSignature });