npm package: @meyicloud-js/license-agent-client
Description: Node.js client + Express middleware/router for a local license-agent service (status, activate, license-info, enforcement)
Author: MeyiConnect
Latest version: 1.0.4

# @meyicloud-js/license-agent-client

Node.js client + Express middleware/router for a local **license-agent** service.

This package standardizes licensing integration across Node applications by calling a sidecar **license-agent** that in turn talks to the **license-platform**:

```
App (Express/Node) → license-agent → license-platform
```

It provides:

- Core client (status / limits / activate / license-info)
- `assertActive()` helper (throws when license is inactive)
- Express middleware to **block product APIs** when unlicensed
- Express router exposing `/v1/license/*` endpoints used by the activation UI
- Built-in activation UI assets (`activate.html`, `license-gate.js`) that apps can use **by default** or **override**

---

## Install

```bash
npm install @meyicloud-js/license-agent-client
```

> `express` is a peer dependency (your app provides it).

---

## Environment variables

| Variable | Default | Description |
|--------|--------|-------------|
| LICENSE_AGENT_URL | `http://license-agent:8090` | Base URL of local license-agent |
| LICENSE_CACHE_SECONDS | `30` | Cache TTL (seconds) for status/limits |
| LICENSE_TIMEOUT_SECONDS | `2` | HTTP timeout (seconds) |
| REQUIRE_LICENSE | `false` | Enable API blocking via middleware |

---

## Core usage (plain Node)

### ESM (`.mjs` / `type: module`)

```js
import { getClient } from "@meyicloud-js/license-agent-client";

const client = getClient();
const st = await client.status();
console.log(st.active, st.reason);

await client.assertActive(); // throws LicenseInactiveError if inactive
```

### CommonJS

```js
const { getClient } = require("@meyicloud-js/license-agent-client");

(async () => {
  const client = getClient();
  const st = await client.status();
  console.log(st.active, st.reason);

  await client.assertActive();
})();
```

---

## Express: block APIs when unlicensed (middleware)

> This matches the behavior you used in FastAPI: **fail closed**.

- Inactive/expired/deactivated → **402**
- Agent unreachable → **503** (fail-closed)

### ESM default import (recommended)

```js
import express from "express";
import licenseGateMiddleware from "@meyicloud-js/license-agent-client";

const app = express();

app.use(licenseGateMiddleware({
  exemptPrefixes: ["/ui", "/health", "/v1/license"], // allow activation UI + license APIs
}));

app.get("/health", (_req, res) => res.json({ ok: true }));
app.get("/api/secure", (_req, res) => res.json({ ok: true }));

app.listen(8080);
```

### Named import also works

```js
import express from "express";
import { licenseGateMiddleware } from "@meyicloud-js/license-agent-client";

const app = express();
app.use(licenseGateMiddleware());
```

---

## Express: expose license APIs (activate / info / status)

Mount the router under `/v1/license`:

```js
import express from "express";
import { createLicenseRouter } from "@meyicloud-js/license-agent-client";

const app = express();

app.use("/v1/license", createLicenseRouter());

app.listen(8080);
```

Routes exposed:

- `POST /v1/license/activate`
- `GET  /v1/license/info?license_key=...`
- `GET  /v1/license/status`

---

## Serve the built-in activation UI (optional)

This package ships default UI files:

- `ui/activate.html`
- `ui/license-gate.js`

Mount them with Express:

```js
import express from "express";
import { getBuiltinUiDir } from "@meyicloud-js/license-agent-client";

const app = express();

app.use("/ui", express.static(getBuiltinUiDir()));

// In your app pages, include:
// <script src="/ui/license-gate.js"></script>

app.listen(8080);
```

### How the UI works

- `ui/license-gate.js` calls `GET /v1/license/status`
- If not active, it redirects to `/ui/activate.html?next=...`
- `ui/activate.html` uses:
  - `GET /v1/license/info`
  - `POST /v1/license/activate`
  - `GET /v1/license/status`

So, for UI to work you must expose the router:

```js
app.use("/v1/license", createLicenseRouter());
```

---

## Override UI logic in an application

If an application needs custom UI or redirect rules, just serve your own `/ui` directory instead of the built-in one:

```js
app.use("/ui", express.static("/app/my-ui"));
```

Or keep the built-in UI and only override one file by serving a more specific route first.

---

## API reference

### `LicenseClient` methods

- `status(force?: boolean)` → calls `GET /local/status`
- `limits(force?: boolean)` → calls `GET /local/limits`
- `activate({license_key, product_code, fingerprint?, version?, vm_meta?})` → `POST /local/activate`
- `licenseInfo(licenseKey)` → `GET /local/license-info?license_key=...`
- `assertActive()` → throws `LicenseInactiveError` if inactive

### Express helpers

- `licenseGateMiddleware({ exemptPrefixes?, failOpen? })`
- `createLicenseRouter({ client?, defaultFingerprint?, defaultVersion? })`
- `getBuiltinUiDir()`

---

## Common pitfalls

### “licenseGateMiddleware is not a function” in ESM
Use either:
- default import:
  ```js
  import licenseGateMiddleware from "@meyicloud-js/license-agent-client";
  ```
- or named import:
  ```js
  import { licenseGateMiddleware } from "@meyicloud-js/license-agent-client";
  ```

---

## License

MIT