@maniidev/failsafe-license
v2.0.2
Published
Framework-agnostic license validation for Node.js, Express, and Next.js App Router
Maintainers
Readme
@maniidev/failsafe-license
Framework-agnostic license validation for Node.js apps with first-class adapters for Express and Next.js App Router.
The package is designed to fail safe by default: transient validation failures preserve the last usable state when possible and do not automatically take down customer apps.
Installation
npm install @maniidev/failsafe-licenseNode 18+ is required.
Importing
CommonJS:
const license = require('@maniidev/failsafe-license');
const { createLicenseManager } = license;ESM:
import license from '@maniidev/failsafe-license';
const { createLicenseManager } = license;createLicenseManager is available in @maniidev/[email protected]+.
If createLicenseManager is undefined, upgrade your install:
npm install @maniidev/failsafe-license@latestOlder installs only expose the legacy singleton API on the root export.
Usage
Basic Node / server usage
const license = require('@maniidev/failsafe-license');
const { createLicenseManager } = license;
const licenseManager = createLicenseManager({
projectId: process.env.FAILSAFE_PROJECT_ID,
secretKey: process.env.FAILSAFE_SECRET_KEY,
disableAutoRevalidate: true,
staleIfErrorMs: 60 * 60 * 1000,
});
async function main() {
await licenseManager.init();
const state = await licenseManager.ensureValid({ maxAgeMs: 5 * 60 * 1000 });
if (!state.allow) {
throw new Error('License blocked');
}
}
main().catch((error) => {
console.error(error);
process.exit(1);
});Express
const express = require('express');
const license = require('@maniidev/failsafe-license');
const {
createExpressMiddleware,
createGraceBannerMiddleware,
} = require('@maniidev/failsafe-license/express');
const app = express();
const { createLicenseManager } = license;
const licenseManager = createLicenseManager({
projectId: process.env.FAILSAFE_PROJECT_ID,
secretKey: process.env.FAILSAFE_SECRET_KEY,
});
async function main() {
await licenseManager.init();
app.use(
createExpressMiddleware(licenseManager, {
maxAgeMs: 5 * 60 * 1000,
redirectTo: '/license-blocked',
}),
);
app.use(
createGraceBannerMiddleware(licenseManager, {
maxAgeMs: 5 * 60 * 1000,
}),
);
app.get('/', (req, res) => {
res.send('<html><body><h1>Dashboard</h1></body></html>');
});
app.listen(3000);
}
main().catch(console.error);Next.js App Router route handlers
// lib/license.js
const license = require('@maniidev/failsafe-license');
const { createLicenseManager } = license;
const licenseManager = createLicenseManager({
projectId: process.env.FAILSAFE_PROJECT_ID,
secretKey: process.env.FAILSAFE_SECRET_KEY,
disableAutoRevalidate: true,
});
module.exports = {
licenseManager,
};// app/api/protected/route.js
const { withRouteHandlerLicense } = require('@maniidev/failsafe-license/next');
const { licenseManager } = require('../../../lib/license');
exports.GET = withRouteHandlerLicense(
async function GET(request, context, licenseState) {
return Response.json({
ok: true,
status: licenseState.status,
graceDaysLeft: licenseState.graceDaysLeft,
});
},
licenseManager,
{
maxAgeMs: 5 * 60 * 1000,
jsonBody: (state) => ({
error: 'License blocked',
reason: state.reason,
}),
},
);Next.js Server Components / layouts redirect guard
// app/(protected)/layout.js
const { assertValidNext } = require('@maniidev/failsafe-license/next');
const { licenseManager } = require('../../lib/license');
module.exports = async function ProtectedLayout({ children }) {
await assertValidNext(licenseManager, {
maxAgeMs: 5 * 60 * 1000,
redirectTo: '/license-blocked',
});
return children;
};If you only need the current state without asserting, use getLicenseState(licenseManager, options).
React grace banner helper
// app/layout.js
const { LicenseGraceBanner } = require('@maniidev/failsafe-license/react');
const { getLicenseState } = require('@maniidev/failsafe-license/next');
const { licenseManager } = require('../lib/license');
module.exports = async function RootLayout({ children }) {
const licenseState = await getLicenseState(licenseManager, {
maxAgeMs: 5 * 60 * 1000,
});
return (
<html>
<body>
<LicenseGraceBanner state={licenseState} />
{children}
</body>
</html>
);
};Legacy singleton API
The root module still exposes the v1-style singleton methods:
const license = require('@maniidev/failsafe-license');
await license.init({
projectName: process.env.FAILSAFE_PROJECT_ID,
secret: process.env.FAILSAFE_SECRET_KEY,
});
app.use(license.middleware());
app.use(license.graceBannerMiddleware());API notes
createLicenseManager(options)creates an isolated manager instance with its own config, cached state, timer, and in-flight validation promise.ensureValid({ maxAgeMs, revalidateIfStale })is the main request-time guard.revalidate()forces a remote validation attempt.destroy({ resetState })stops background revalidation; passresetState: trueto clear cached state and config.- Normalized state shape:
{
status: 'uninitialized' | 'valid' | 'grace' | 'blocked' | 'error',
allow: boolean,
grace: boolean,
graceDaysLeft: number,
lastChecked: string | null,
error: string | null,
reason: string | null
}Migration notes
axioshas been removed. v2 uses nativefetchand requires Node 18+.- The recommended API is now
createLicenseManager(options)instead of relying on one global singleton. - If your installed package does not expose
createLicenseManager, you are on a pre-v2 release. Upgrade to@latestor keep using the legacy singleton API. - Existing aliases still work:
projectNamemaps toprojectId, andsecretmaps tosecretKey. middleware()andgraceBannerMiddleware()still exist on the root export for backward compatibility, but the preferred Express entrypoint is@maniidev/failsafe-license/express.- For Next.js App Router, use
@maniidev/failsafe-license/nexthelpers instead of Express-style response mutation.
Environment variables
FAILSAFE_PROJECT_ID=proj_xxxxxxxxxxxxxxxx
FAILSAFE_SECRET_KEY=sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FAILSAFE_LICENSE_API_URL=https://license-manager-olive.vercel.app/api/validateFAILSAFE_LICENSE_API_URL is optional.
License
MIT
