// What this app sells and what each item unlocks. Edit these to match the items of your product, created with // node sub3.mjs product create acme/app --name "Acme App" --tier basic:1:5:Basic --tier pro:2:20:Pro \ // --lifetime forever:99:Forever --pack credits:5:1000:"1000 credits" export const allowances = { basic: 10n, pro: 100n }; // actions per monthly cycle, by tier key export const premium = { level: 2, lifetime: 'forever' }; // the premium feature: tier level 2+, or this lifetime item export const actionCost = 1n; // each action: this cycle's allowance first, then purchased credits // What the user may do now, from GET /v1/access and the rules above. export function entitlements(access, now = Math.floor(Date.now() / 1000)) { const m = access.membership; const member = m?.active && m.end > now ? m : null; const cycle = member?.cycle && member.cycle.end > now ? member.cycle : null; return { tier: member?.tier ?? null, level: member?.level ?? 0, end: member?.end ?? null, cycle: cycle?.id ?? null, // allowance is granted once per cycle id; an upgrade starts a new one allowance: cycle ? (allowances[member.tier] ?? 0n) : 0n, premium: (member?.level ?? 0) >= premium.level || access.lifetime.includes(premium.lifetime), lifetime: access.lifetime }; } const SITES = { production: 'https://sub3.dev', sandbox: 'https://sandbox.sub3.dev', local: 'http://localhost:5317' }; const APIS = { production: 'https://api.sub3.dev', sandbox: 'https://api-sandbox.sub3.dev', local: 'http://127.0.0.1:8317' }; export function configuration(env = process.env) { const environment = env.SUB3_ENVIRONMENT ?? 'sandbox'; if (!SITES[environment]) throw new Error('SUB3_ENVIRONMENT must be production, sandbox or local'); const product = env.SUB3_PRODUCT ?? ''; if (!/^[a-z0-9-]{3,32}\/[a-z0-9-]{1,32}$/.test(product)) throw new Error('Set SUB3_PRODUCT to your product, e.g. acme/app'); const port = Number(env.PORT ?? 3317); const config = { environment, product, port, origin: env.APP_ORIGIN ?? `http://localhost:${port}`, // the JWT audience: this site's exact origin site: env.SUB3_SITE ?? SITES[environment], // the JWT issuer and the hosted pages api: env.SUB3_API ?? APIS[environment], database: env.DATABASE ?? `./${environment}-${product.replace('/', '-')}.sqlite` }; for (const url of [config.origin, config.site, config.api]) { const parsed = new URL(url); if (parsed.origin !== url) throw new Error(`${url}: use an origin without a path or trailing slash`); if (parsed.protocol !== 'https:' && !['localhost', '127.0.0.1'].includes(parsed.hostname)) throw new Error(`${url}: use HTTPS outside localhost`); } return config; }