Initial public commit
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Licensing template: CONFIGURATION — FILL IN THE THREE VALUES BELOW.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// This is the one file you must edit to adopt the licensing template. The
|
||||
// action files consume these constants; nothing else should need changes.
|
||||
//
|
||||
// After a buyer (Bob) installs your Start9 package, the "Activate license" and
|
||||
// "Buy license" actions in his package dashboard will talk to YOUR licensing
|
||||
// server (the `BASE_URL` below), paid in Bitcoin via your BTCPay.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The public URL of YOUR running licensing-service, as reachable from the
|
||||
* buyer's Start9 server. Typically a Tor .onion, a clearnet domain, or both
|
||||
* (see TOR_FALLBACK below).
|
||||
*
|
||||
* Example: 'https://license.example.com'
|
||||
*/
|
||||
export const LICENSING_BASE_URL = 'https://license.YOUR-DOMAIN.example'
|
||||
|
||||
/**
|
||||
* Optional Tor fallback. If set, the actions will try this URL if the primary
|
||||
* URL fails — useful when your licensing server is behind Tor and the buyer's
|
||||
* clearnet is flaky.
|
||||
*
|
||||
* Example: 'http://abc...xyz.onion'
|
||||
*/
|
||||
export const LICENSING_BASE_URL_TOR: string | null = null
|
||||
|
||||
/**
|
||||
* The product slug you configured in your licensing-service (via the
|
||||
* "Create product" action or the admin API). This is how the service knows
|
||||
* which product a license is for.
|
||||
*
|
||||
* Example: 'bitcoin-ticker-pro'
|
||||
*/
|
||||
export const PRODUCT_SLUG = 'your-product-slug'
|
||||
|
||||
/**
|
||||
* Your licensing-service's Ed25519 public key in PEM form. Embed the full
|
||||
* multi-line block as a template literal. The buyer's device verifies license
|
||||
* keys AGAINST THIS KEY offline — no network required for the common case.
|
||||
*
|
||||
* Get this from your licensing-service: run the "Show admin credentials"
|
||||
* action on your own Start9, or hit GET /v1/pubkey.
|
||||
*
|
||||
* Example:
|
||||
* -----BEGIN PUBLIC KEY-----
|
||||
* MCowBQYDK2VwAyEA...
|
||||
* -----END PUBLIC KEY-----
|
||||
*/
|
||||
export const ISSUER_PUBKEY_PEM = `-----BEGIN PUBLIC KEY-----
|
||||
PASTE YOUR ISSUER PUBLIC KEY PEM HERE
|
||||
-----END PUBLIC KEY-----`
|
||||
|
||||
/**
|
||||
* The product name shown in UI prompts. Purely cosmetic.
|
||||
*/
|
||||
export const PRODUCT_DISPLAY_NAME = 'Your Product'
|
||||
@@ -0,0 +1,47 @@
|
||||
// Runtime helper: "Is this install licensed?" — call from `main.ts` before
|
||||
// you start your app's main process, so the app can refuse to boot (or boot
|
||||
// in a reduced-functionality trial mode) when no valid key is present.
|
||||
//
|
||||
// This never touches the network — only the signature. The offline check is
|
||||
// what you want at startup: nothing on the internet should be able to keep
|
||||
// the buyer's app from starting if the key is legitimate.
|
||||
|
||||
import { Verifier, PublicKey, type VerifyOk } from '@keysat/licensing-client'
|
||||
import { ISSUER_PUBKEY_PEM, PRODUCT_SLUG } from './config'
|
||||
|
||||
export interface LicenseGateResult {
|
||||
licensed: boolean
|
||||
/** Populated if licensed === true. */
|
||||
details?: VerifyOk
|
||||
/** Populated if licensed === false. Human-readable. */
|
||||
reason?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the license key stored in the package store. Pure offline — runs in
|
||||
* a few milliseconds.
|
||||
*
|
||||
* Typical usage in main.ts:
|
||||
*
|
||||
* const gate = checkLicenseGate(await sdk.store.getOwn(effects, sdk.StorePath).const())
|
||||
* if (!gate.licensed) {
|
||||
* // options: exit, or inject a "trial mode" env var into your daemon
|
||||
* }
|
||||
*/
|
||||
export function checkLicenseGate(store: {
|
||||
license_key?: string | null
|
||||
}): LicenseGateResult {
|
||||
const key = store.license_key
|
||||
if (!key) return { licensed: false, reason: 'no license key stored' }
|
||||
try {
|
||||
const ok = new Verifier(PublicKey.fromPem(ISSUER_PUBKEY_PEM)).verify(key)
|
||||
return { licensed: true, details: ok }
|
||||
} catch (e) {
|
||||
return {
|
||||
licensed: false,
|
||||
reason: e instanceof Error ? e.message : 'verification failed',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { PRODUCT_SLUG }
|
||||
@@ -0,0 +1,31 @@
|
||||
// Store shape additions for the licensing template.
|
||||
//
|
||||
// Merge these fields into your package's existing store shape (e.g. in
|
||||
// `startos/init/index.ts` or wherever you declare `sdk.setupStore`).
|
||||
//
|
||||
// Example merge:
|
||||
//
|
||||
// export const initStore: StoreShape = {
|
||||
// // ...your existing fields...
|
||||
// ...licensingStoreDefaults,
|
||||
// }
|
||||
//
|
||||
// The actions here write into these fields; your main process reads them.
|
||||
|
||||
export interface LicensingStoreFields {
|
||||
/** The raw license key string, e.g. "LIC1-...-..." */
|
||||
license_key: string | null
|
||||
/** ISO timestamp of when the key was activated. */
|
||||
license_activated_at: string | null
|
||||
/** Last known validation status from the licensing server, for display. */
|
||||
license_last_status: string | null
|
||||
/** Invoice id of an in-progress purchase, if any. */
|
||||
license_pending_invoice_id: string | null
|
||||
}
|
||||
|
||||
export const licensingStoreDefaults: LicensingStoreFields = {
|
||||
license_key: null,
|
||||
license_activated_at: null,
|
||||
license_last_status: null,
|
||||
license_pending_invoice_id: null,
|
||||
}
|
||||
Reference in New Issue
Block a user