@mostajs/licensing-ui-env 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/llms.txt +29 -0
- package/package.json +34 -0
- package/src/index.js +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @mostajs/licensing-ui-env
|
|
2
|
+
|
|
3
|
+
**Auteur** : Dr Hamid MADANI <drmdh@msn.com> · **Licence** : AGPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
Service de licence **piloté par `.env`** (statut, activation en ligne, persistance) — **extrait de
|
|
6
|
+
l'app Salsabil** (`src/license.mjs`) et **composant `@mostajs/licensing`**. Sans framework (no-build).
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
import { createLicenseEnv } from '@mostajs/licensing-ui-env';
|
|
10
|
+
const lic = createLicenseEnv({ env: process.env, defaultProject: 'salsabil' });
|
|
11
|
+
lic.status(); // { valid, status, expiresAt, daysLeft, serverUrl, … }
|
|
12
|
+
await lic.activate(code); // active via licences.amia.fr → persiste data/license.json
|
|
13
|
+
await lic.requestCode({ email });
|
|
14
|
+
```
|
|
15
|
+
Vues : `@mostajs/licensing-ui-html` (no-build) ou `@mostajs/licensing-ui-react`. Tests/§12 : `npm install && npm test`.
|
package/llms.txt
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @mostajs/licensing-ui-env — fiche LLM
|
|
2
|
+
> Service de licence piloté par .env (statut, activation en ligne, persistance) — extrait de Salsabil ; compose @mostajs/licensing.
|
|
3
|
+
|
|
4
|
+
- Version: 0.1.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
|
|
5
|
+
- Stack: mosta-licensing-stack · Logique: @mostajs/licensing · Vues: licensing-ui-html (no-build) / licensing-ui-react
|
|
6
|
+
|
|
7
|
+
## RÔLE
|
|
8
|
+
Couche CONTRÔLEUR pilotée par .env pour apps no-build (node:http…). Lit la config .env, vérifie la
|
|
9
|
+
licence (signée .env OU activée+persistée <dataDir>/license.json), expose le statut, et l'activation
|
|
10
|
+
en ligne (compose le client serveur de @mostajs/licensing). AUCUNE UI ici (cf. licensing-ui-html).
|
|
11
|
+
|
|
12
|
+
## EXPORTS
|
|
13
|
+
- createLicenseEnv({ env, dataDir?, defaultProject?, now? }) -> { enabled, enforce, project, serverUrl, machineId, status, activate, requestCode }
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
- status() -> { valid, status, reason?, expiresAt?, daysLeft?, source, enforce, project, serverUrl }
|
|
17
|
+
- activate(activationCode) -> CheckResult (télécharge+vérifie+persiste data/license.json)
|
|
18
|
+
- requestCode({ email, company? })
|
|
19
|
+
|
|
20
|
+
## CONFIG .env
|
|
21
|
+
LICENSE (signée b64), LICENSE_PUBLIC_KEY (PEM b64), LICENSE_PROJECT, LICENSE_SERVER_URL
|
|
22
|
+
(défaut https://licences.amia.fr), LICENSE_ENFORCE (on/off), LICENSE_EXPIRES_AT (essai simple).
|
|
23
|
+
|
|
24
|
+
## PRIORITÉ DE RÉSOLUTION
|
|
25
|
+
1) .env LICENSE 2) <dataDir>/license.json (activation en ligne) 3) LICENSE_EXPIRES_AT (essai) 4) UNLICENSED.
|
|
26
|
+
|
|
27
|
+
## CONSOMMATEUR
|
|
28
|
+
App Salsabil (src/license.mjs en réexporte createLicense = createLicenseEnv). Couplez à
|
|
29
|
+
@mostajs/licensing-ui-html pour les vues (statut/activation/gate).
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mostajs/licensing-ui-env",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Service de licence piloté par .env (statut, activation en ligne, persistance) — extrait de l'app Salsabil ; compose @mostajs/licensing. Sans framework (no-build).",
|
|
5
|
+
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
|
+
"license": "AGPL-3.0-or-later",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "src/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"README.md",
|
|
15
|
+
"llms.txt"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"license",
|
|
19
|
+
"licence",
|
|
20
|
+
"env",
|
|
21
|
+
"config",
|
|
22
|
+
"no-build",
|
|
23
|
+
"mostajs"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@mostajs/licensing": "^0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@mostajs/mjs-unit": "^0.3.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "node test-scripts/unit/licensing-ui-env.test.mjs && node examples/run.mjs"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// @mostajs/licensing-ui-env — service de licence PILOTÉ PAR .env (extrait de l'app Salsabil).
|
|
2
|
+
// COMPOSE @mostajs/licensing (vérif RSA + client serveur). Gérable par .env :
|
|
3
|
+
// LICENSE (signée base64), LICENSE_PUBLIC_KEY (PEM base64), LICENSE_PROJECT, LICENSE_SERVER_URL,
|
|
4
|
+
// LICENSE_ENFORCE (on/off), LICENSE_EXPIRES_AT (essai simple). Activation en ligne → licence
|
|
5
|
+
// persistée dans <dataDir>/license.json. DB-agnostique, sans framework (no-build).
|
|
6
|
+
// @author Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import crypto from 'node:crypto';
|
|
11
|
+
import { checkLicense, createLicenseClient } from '@mostajs/licensing';
|
|
12
|
+
|
|
13
|
+
const b64decode = (s) => Buffer.from(String(s), 'base64').toString('utf8');
|
|
14
|
+
const daysLeft = (expiresAt, n) => (expiresAt ? Math.ceil((new Date(expiresAt).getTime() - n.getTime()) / 86400000) : null);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {object} opts
|
|
18
|
+
* @param {object} opts.env variables (.env)
|
|
19
|
+
* @param {string} [opts.dataDir] dossier de persistance de la licence activée (défaut './data')
|
|
20
|
+
* @param {string} [opts.defaultProject] projet par défaut si LICENSE_PROJECT absent (défaut 'app')
|
|
21
|
+
* @param {() => Date} [opts.now]
|
|
22
|
+
*/
|
|
23
|
+
export function createLicenseEnv({ env = {}, now = () => new Date(), dataDir = './data', defaultProject = 'app' } = {}) {
|
|
24
|
+
const project = env.LICENSE_PROJECT || defaultProject;
|
|
25
|
+
const enforce = String(env.LICENSE_ENFORCE || '').toLowerCase() === 'on';
|
|
26
|
+
const serverUrl = (env.LICENSE_SERVER_URL || 'https://licences.amia.fr').replace(/\/+$/, '');
|
|
27
|
+
const licenseFile = path.join(dataDir, 'license.json');
|
|
28
|
+
let pub = null;
|
|
29
|
+
try { if (env.LICENSE_PUBLIC_KEY) pub = b64decode(env.LICENSE_PUBLIC_KEY); } catch { /* clé illisible */ }
|
|
30
|
+
|
|
31
|
+
// Licence courante : .env LICENSE prioritaire, sinon fichier persisté (activation en ligne).
|
|
32
|
+
function loadSigned() {
|
|
33
|
+
if (env.LICENSE) { try { return JSON.parse(b64decode(env.LICENSE)); } catch { /* */ } }
|
|
34
|
+
try { if (fs.existsSync(licenseFile)) return JSON.parse(fs.readFileSync(licenseFile, 'utf8')); } catch { /* */ }
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function status() {
|
|
39
|
+
const n = now();
|
|
40
|
+
const signed = loadSigned();
|
|
41
|
+
if (signed && pub) {
|
|
42
|
+
const r = checkLicense(signed, { project, publicKeyPem: pub, now: n });
|
|
43
|
+
return { valid: r.valid, status: r.status, reason: r.reason || null, expiresAt: r.expiresAt || null, daysLeft: daysLeft(r.expiresAt, n), enforce, source: 'signed', project, serverUrl };
|
|
44
|
+
}
|
|
45
|
+
if (env.LICENSE_EXPIRES_AT) {
|
|
46
|
+
const exp = new Date(env.LICENSE_EXPIRES_AT); const valid = exp.getTime() > n.getTime();
|
|
47
|
+
return { valid, status: valid ? 'TRIAL' : 'EXPIRED', reason: valid ? null : `Essai expiré le ${env.LICENSE_EXPIRES_AT}`, expiresAt: env.LICENSE_EXPIRES_AT, daysLeft: daysLeft(env.LICENSE_EXPIRES_AT, n), enforce, source: 'trial', project, serverUrl };
|
|
48
|
+
}
|
|
49
|
+
return { valid: !enforce, status: 'UNLICENSED', reason: 'Aucune licence configurée', expiresAt: null, daysLeft: null, enforce, source: 'none', project, serverUrl };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const client = createLicenseClient({ baseUrl: serverUrl, publicKeyPem: pub || undefined });
|
|
53
|
+
const machineId = crypto.createHash('sha256').update(`${os.hostname()}|${project}`).digest('hex').slice(0, 24);
|
|
54
|
+
|
|
55
|
+
/** Active via le serveur : télécharge la licence du code, vérifie, persiste <dataDir>/license.json. */
|
|
56
|
+
async function activate(activationCode) {
|
|
57
|
+
const code = String(activationCode || '').trim();
|
|
58
|
+
if (!code) throw new Error('code d’activation requis');
|
|
59
|
+
const { signed, check } = await client.activate({ activationCode: code, machineId, machineName: os.hostname(), project });
|
|
60
|
+
if (check?.valid) { fs.mkdirSync(dataDir, { recursive: true }); fs.writeFileSync(licenseFile, JSON.stringify(signed)); }
|
|
61
|
+
return check;
|
|
62
|
+
}
|
|
63
|
+
/** Demande un code d'activation par email. */
|
|
64
|
+
const requestCode = ({ email, company } = {}) => client.requestActivationCode({ email, project, company });
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
enabled: !!(env.LICENSE || env.LICENSE_EXPIRES_AT || fs.existsSync(licenseFile)),
|
|
68
|
+
enforce, project, serverUrl, machineId, status, activate, requestCode,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default createLicenseEnv;
|