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
JavaScript
"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
});