@modern-admin/telemetry 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/dist/collect.d.ts +17 -0
- package/dist/collect.d.ts.map +1 -0
- package/dist/collect.js +58 -0
- package/dist/collect.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/report.d.ts +23 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +47 -0
- package/dist/report.js.map +1 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +45 -0
- package/src/collect.ts +63 -0
- package/src/index.ts +3 -0
- package/src/report.ts +52 -0
- package/src/types.ts +39 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ModernAdmin } from '@modern-admin/core';
|
|
2
|
+
import type { TelemetryInfo } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build a `TelemetryInfo` snapshot from a running `ModernAdmin` instance.
|
|
5
|
+
* Reads only technical/aggregate data — no resource names, record
|
|
6
|
+
* contents, user data, or secrets are captured.
|
|
7
|
+
*
|
|
8
|
+
* This function is pure (no network I/O). Pass the result to
|
|
9
|
+
* `reportTelemetry` to ship it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function collectTelemetryInfo(admin: ModernAdmin): TelemetryInfo;
|
|
12
|
+
/**
|
|
13
|
+
* Reset the per-process instance ID (test helper only).
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export declare function _resetInstanceId(): void;
|
|
17
|
+
//# sourceMappingURL=collect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../src/collect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAK/C;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAuCtE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
|
package/dist/collect.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { uuidv7 } from '@modern-admin/core';
|
|
2
|
+
/** Stable per-process identifier (not persisted). */
|
|
3
|
+
let _instanceId;
|
|
4
|
+
/**
|
|
5
|
+
* Build a `TelemetryInfo` snapshot from a running `ModernAdmin` instance.
|
|
6
|
+
* Reads only technical/aggregate data — no resource names, record
|
|
7
|
+
* contents, user data, or secrets are captured.
|
|
8
|
+
*
|
|
9
|
+
* This function is pure (no network I/O). Pass the result to
|
|
10
|
+
* `reportTelemetry` to ship it.
|
|
11
|
+
*/
|
|
12
|
+
export function collectTelemetryInfo(admin) {
|
|
13
|
+
if (!_instanceId)
|
|
14
|
+
_instanceId = uuidv7();
|
|
15
|
+
// Derive a short adapter name from the Database constructor class name.
|
|
16
|
+
// Examples: "PrismaDatabase" → "prisma", "DrizzleDatabase" → "drizzle",
|
|
17
|
+
// anything else → "unknown".
|
|
18
|
+
const adapters = (admin.options.adapters ?? []).map((a) => {
|
|
19
|
+
const raw = a.Database.name ?? '';
|
|
20
|
+
const lower = raw.toLowerCase();
|
|
21
|
+
if (lower.includes('prisma'))
|
|
22
|
+
return 'prisma';
|
|
23
|
+
if (lower.includes('drizzle'))
|
|
24
|
+
return 'drizzle';
|
|
25
|
+
return 'unknown';
|
|
26
|
+
});
|
|
27
|
+
// Deduplicate (e.g. two Prisma databases registered)
|
|
28
|
+
const uniqueAdapters = [...new Set(adapters)];
|
|
29
|
+
// Collect only the enabled (true) feature flags so the payload stays
|
|
30
|
+
// compact and never leaks false/undefined entries.
|
|
31
|
+
const features = [];
|
|
32
|
+
const adminFeatures = admin.options.features ?? {};
|
|
33
|
+
for (const [key, val] of Object.entries(adminFeatures)) {
|
|
34
|
+
if (val === true)
|
|
35
|
+
features.push(key);
|
|
36
|
+
}
|
|
37
|
+
const versions = process.versions;
|
|
38
|
+
const runtime = versions.bun
|
|
39
|
+
? `bun/${versions.bun}`
|
|
40
|
+
: `node/${versions.node ?? 'unknown'}`;
|
|
41
|
+
return {
|
|
42
|
+
instanceId: _instanceId,
|
|
43
|
+
adapters: uniqueAdapters,
|
|
44
|
+
resourceCount: admin.resources.length,
|
|
45
|
+
featureFlags: admin.options.featureFlags ?? [],
|
|
46
|
+
features,
|
|
47
|
+
platform: process.platform,
|
|
48
|
+
runtime,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Reset the per-process instance ID (test helper only).
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export function _resetInstanceId() {
|
|
56
|
+
_instanceId = undefined;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=collect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.js","sourceRoot":"","sources":["../src/collect.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAG3C,qDAAqD;AACrD,IAAI,WAA+B,CAAA;AAEnC;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAkB;IACrD,IAAI,CAAC,WAAW;QAAE,WAAW,GAAG,MAAM,EAAE,CAAA;IAExC,wEAAwE;IACxE,wEAAwE;IACxE,6BAA6B;IAC7B,MAAM,QAAQ,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACxD,MAAM,GAAG,GAAI,CAAC,CAAC,QAA8B,CAAC,IAAI,IAAI,EAAE,CAAA;QACxD,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;QAC/B,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAQ,CAAA;QAC7C,IAAI,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAA;QAC/C,OAAO,SAAS,CAAA;IAClB,CAAC,CAAC,CAAA;IAEF,qDAAqD;IACrD,MAAM,cAAc,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAA;IAE7C,qEAAqE;IACrE,mDAAmD;IACnD,MAAM,QAAQ,GAAa,EAAE,CAAA;IAC7B,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAA;IAClD,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QACvD,IAAI,GAAG,KAAK,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACtC,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,QAA8C,CAAA;IACvE,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG;QAC1B,CAAC,CAAC,OAAO,QAAQ,CAAC,GAAG,EAAE;QACvB,CAAC,CAAC,QAAQ,QAAQ,CAAC,IAAI,IAAI,SAAS,EAAE,CAAA;IAExC,OAAO;QACL,UAAU,EAAE,WAAW;QACvB,QAAQ,EAAE,cAAc;QACxB,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM;QACrC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE;QAC9C,QAAQ;QACR,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,OAAO;KACR,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,WAAW,GAAG,SAAS,CAAA;AACzB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACrE,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACrE,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/report.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TelemetryInfo } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fire-and-forget telemetry ping. Exits immediately when the
|
|
4
|
+
* `MODERN_ADMIN_TELEMETRY` environment variable is not set to `"1"`.
|
|
5
|
+
*
|
|
6
|
+
* The function:
|
|
7
|
+
* - Never throws — any network error is silently swallowed.
|
|
8
|
+
* - Pings at most once per process lifetime (subsequent calls are no-ops).
|
|
9
|
+
* - Times out after 5 s to prevent blocking server startup.
|
|
10
|
+
* - Sends a single `POST` with the `TelemetryInfo` payload as JSON.
|
|
11
|
+
*
|
|
12
|
+
* @param info Payload built by `collectTelemetryInfo`.
|
|
13
|
+
* @param opts.endpoint Override the default endpoint (useful in tests).
|
|
14
|
+
*/
|
|
15
|
+
export declare function reportTelemetry(info: TelemetryInfo, opts?: {
|
|
16
|
+
endpoint?: string;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Reset the "already reported" guard (test helper only).
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export declare function _resetReported(): void;
|
|
23
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAQ/C;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,aAAa,EACnB,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC"}
|
package/dist/report.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Default endpoint — the license-issuance / telemetry backend. */
|
|
2
|
+
const TELEMETRY_ENDPOINT = 'https://api.modernadminpro.com/telemetry';
|
|
3
|
+
/** Guard against double-pings from hot-reload without a process restart. */
|
|
4
|
+
let _reported = false;
|
|
5
|
+
/**
|
|
6
|
+
* Fire-and-forget telemetry ping. Exits immediately when the
|
|
7
|
+
* `MODERN_ADMIN_TELEMETRY` environment variable is not set to `"1"`.
|
|
8
|
+
*
|
|
9
|
+
* The function:
|
|
10
|
+
* - Never throws — any network error is silently swallowed.
|
|
11
|
+
* - Pings at most once per process lifetime (subsequent calls are no-ops).
|
|
12
|
+
* - Times out after 5 s to prevent blocking server startup.
|
|
13
|
+
* - Sends a single `POST` with the `TelemetryInfo` payload as JSON.
|
|
14
|
+
*
|
|
15
|
+
* @param info Payload built by `collectTelemetryInfo`.
|
|
16
|
+
* @param opts.endpoint Override the default endpoint (useful in tests).
|
|
17
|
+
*/
|
|
18
|
+
export async function reportTelemetry(info, opts) {
|
|
19
|
+
if (process.env.MODERN_ADMIN_TELEMETRY !== '1')
|
|
20
|
+
return;
|
|
21
|
+
if (_reported)
|
|
22
|
+
return;
|
|
23
|
+
_reported = true;
|
|
24
|
+
const url = opts?.endpoint ?? TELEMETRY_ENDPOINT;
|
|
25
|
+
try {
|
|
26
|
+
const ac = new AbortController();
|
|
27
|
+
const timer = setTimeout(() => ac.abort(), 5_000);
|
|
28
|
+
await fetch(url, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { 'content-type': 'application/json' },
|
|
31
|
+
body: JSON.stringify(info),
|
|
32
|
+
signal: ac.signal,
|
|
33
|
+
});
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Telemetry failures must NEVER surface to or affect the host application.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Reset the "already reported" guard (test helper only).
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export function _resetReported() {
|
|
45
|
+
_reported = false;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAEA,mEAAmE;AACnE,MAAM,kBAAkB,GAAG,0CAA0C,CAAA;AAErE,4EAA4E;AAC5E,IAAI,SAAS,GAAG,KAAK,CAAA;AAErB;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAmB,EACnB,IAA4B;IAE5B,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,KAAK,GAAG;QAAE,OAAM;IACtD,IAAI,SAAS;QAAE,OAAM;IACrB,SAAS,GAAG,IAAI,CAAA;IAEhB,MAAM,GAAG,GAAG,IAAI,EAAE,QAAQ,IAAI,kBAAkB,CAAA;IAChD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAA;QAChC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,KAAK,CAAC,CAAA;QACjD,MAAM,KAAK,CAAC,GAAG,EAAE;YACf,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAC1B,MAAM,EAAE,EAAE,CAAC,MAAM;SAClB,CAAC,CAAA;QACF,YAAY,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;IAC7E,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,SAAS,GAAG,KAAK,CAAA;AACnB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape of a single telemetry ping. Contains only technical/aggregate data —
|
|
3
|
+
* no resource names, record contents, user identifiers, or secrets.
|
|
4
|
+
*
|
|
5
|
+
* The `instanceId` is a UUID v7 generated once per process start. It is
|
|
6
|
+
* **not** persisted to disk and cannot be used to track installations across
|
|
7
|
+
* restarts. Its only purpose is server-side deduplication of rapid re-pings
|
|
8
|
+
* from the same running process (e.g., on hot-reload).
|
|
9
|
+
*/
|
|
10
|
+
export interface TelemetryInfo {
|
|
11
|
+
/** UUID v7, generated fresh each process start, never persisted. */
|
|
12
|
+
instanceId: string;
|
|
13
|
+
/**
|
|
14
|
+
* Short adapter name(s) in use — derived from the adapter `Database`
|
|
15
|
+
* constructor name (e.g. `"prisma"`, `"drizzle"`, `"unknown"`).
|
|
16
|
+
* Does not reveal schema details.
|
|
17
|
+
*/
|
|
18
|
+
adapters: string[];
|
|
19
|
+
/** Total number of registered resources (no names). */
|
|
20
|
+
resourceCount: number;
|
|
21
|
+
/**
|
|
22
|
+
* Active commercial feature flags — the string list passed to
|
|
23
|
+
* `new ModernAdmin({ featureFlags: [...] })`. Consumers who don't use
|
|
24
|
+
* Pro packages always see `[]` here.
|
|
25
|
+
*/
|
|
26
|
+
featureFlags: string[];
|
|
27
|
+
/**
|
|
28
|
+
* Enabled admin-subsystem capabilities (keys from `AdminFeatures`).
|
|
29
|
+
* Only the `true` entries are included to keep payloads compact.
|
|
30
|
+
*/
|
|
31
|
+
features: string[];
|
|
32
|
+
/** `process.platform` value (e.g. `"linux"`, `"darwin"`, `"win32"`). */
|
|
33
|
+
platform: string;
|
|
34
|
+
/**
|
|
35
|
+
* Runtime and version string. `"bun/1.3.13"` or `"node/20.0.0"`,
|
|
36
|
+
* depending on which runtime executes the server.
|
|
37
|
+
*/
|
|
38
|
+
runtime: string;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,uDAAuD;IACvD,aAAa,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@modern-admin/telemetry",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Opt-in usage telemetry for Modern Admin (privacy-first, MODERN_ADMIN_TELEMETRY=1 required).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/modern-admin/modern-admin.git",
|
|
10
|
+
"directory": "packages/telemetry"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"registry": "https://registry.npmjs.org",
|
|
26
|
+
"access": "public",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@modern-admin/core": "0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@modern-admin/core": "0.1.0",
|
|
41
|
+
"@modern-admin/tsconfig": "0.1.0",
|
|
42
|
+
"@types/bun": "^1.3.13",
|
|
43
|
+
"typescript": "^6.0.3"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/collect.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ModernAdmin } from '@modern-admin/core'
|
|
2
|
+
import { uuidv7 } from '@modern-admin/core'
|
|
3
|
+
import type { TelemetryInfo } from './types.js'
|
|
4
|
+
|
|
5
|
+
/** Stable per-process identifier (not persisted). */
|
|
6
|
+
let _instanceId: string | undefined
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Build a `TelemetryInfo` snapshot from a running `ModernAdmin` instance.
|
|
10
|
+
* Reads only technical/aggregate data — no resource names, record
|
|
11
|
+
* contents, user data, or secrets are captured.
|
|
12
|
+
*
|
|
13
|
+
* This function is pure (no network I/O). Pass the result to
|
|
14
|
+
* `reportTelemetry` to ship it.
|
|
15
|
+
*/
|
|
16
|
+
export function collectTelemetryInfo(admin: ModernAdmin): TelemetryInfo {
|
|
17
|
+
if (!_instanceId) _instanceId = uuidv7()
|
|
18
|
+
|
|
19
|
+
// Derive a short adapter name from the Database constructor class name.
|
|
20
|
+
// Examples: "PrismaDatabase" → "prisma", "DrizzleDatabase" → "drizzle",
|
|
21
|
+
// anything else → "unknown".
|
|
22
|
+
const adapters = (admin.options.adapters ?? []).map((a) => {
|
|
23
|
+
const raw = (a.Database as { name?: string }).name ?? ''
|
|
24
|
+
const lower = raw.toLowerCase()
|
|
25
|
+
if (lower.includes('prisma')) return 'prisma'
|
|
26
|
+
if (lower.includes('drizzle')) return 'drizzle'
|
|
27
|
+
return 'unknown'
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Deduplicate (e.g. two Prisma databases registered)
|
|
31
|
+
const uniqueAdapters = [...new Set(adapters)]
|
|
32
|
+
|
|
33
|
+
// Collect only the enabled (true) feature flags so the payload stays
|
|
34
|
+
// compact and never leaks false/undefined entries.
|
|
35
|
+
const features: string[] = []
|
|
36
|
+
const adminFeatures = admin.options.features ?? {}
|
|
37
|
+
for (const [key, val] of Object.entries(adminFeatures)) {
|
|
38
|
+
if (val === true) features.push(key)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const versions = process.versions as Record<string, string | undefined>
|
|
42
|
+
const runtime = versions.bun
|
|
43
|
+
? `bun/${versions.bun}`
|
|
44
|
+
: `node/${versions.node ?? 'unknown'}`
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
instanceId: _instanceId,
|
|
48
|
+
adapters: uniqueAdapters,
|
|
49
|
+
resourceCount: admin.resources.length,
|
|
50
|
+
featureFlags: admin.options.featureFlags ?? [],
|
|
51
|
+
features,
|
|
52
|
+
platform: process.platform,
|
|
53
|
+
runtime,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Reset the per-process instance ID (test helper only).
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
export function _resetInstanceId(): void {
|
|
62
|
+
_instanceId = undefined
|
|
63
|
+
}
|
package/src/index.ts
ADDED
package/src/report.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { TelemetryInfo } from './types.js'
|
|
2
|
+
|
|
3
|
+
/** Default endpoint — the license-issuance / telemetry backend. */
|
|
4
|
+
const TELEMETRY_ENDPOINT = 'https://api.modernadminpro.com/telemetry'
|
|
5
|
+
|
|
6
|
+
/** Guard against double-pings from hot-reload without a process restart. */
|
|
7
|
+
let _reported = false
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fire-and-forget telemetry ping. Exits immediately when the
|
|
11
|
+
* `MODERN_ADMIN_TELEMETRY` environment variable is not set to `"1"`.
|
|
12
|
+
*
|
|
13
|
+
* The function:
|
|
14
|
+
* - Never throws — any network error is silently swallowed.
|
|
15
|
+
* - Pings at most once per process lifetime (subsequent calls are no-ops).
|
|
16
|
+
* - Times out after 5 s to prevent blocking server startup.
|
|
17
|
+
* - Sends a single `POST` with the `TelemetryInfo` payload as JSON.
|
|
18
|
+
*
|
|
19
|
+
* @param info Payload built by `collectTelemetryInfo`.
|
|
20
|
+
* @param opts.endpoint Override the default endpoint (useful in tests).
|
|
21
|
+
*/
|
|
22
|
+
export async function reportTelemetry(
|
|
23
|
+
info: TelemetryInfo,
|
|
24
|
+
opts?: { endpoint?: string },
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
if (process.env.MODERN_ADMIN_TELEMETRY !== '1') return
|
|
27
|
+
if (_reported) return
|
|
28
|
+
_reported = true
|
|
29
|
+
|
|
30
|
+
const url = opts?.endpoint ?? TELEMETRY_ENDPOINT
|
|
31
|
+
try {
|
|
32
|
+
const ac = new AbortController()
|
|
33
|
+
const timer = setTimeout(() => ac.abort(), 5_000)
|
|
34
|
+
await fetch(url, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'content-type': 'application/json' },
|
|
37
|
+
body: JSON.stringify(info),
|
|
38
|
+
signal: ac.signal,
|
|
39
|
+
})
|
|
40
|
+
clearTimeout(timer)
|
|
41
|
+
} catch {
|
|
42
|
+
// Telemetry failures must NEVER surface to or affect the host application.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Reset the "already reported" guard (test helper only).
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export function _resetReported(): void {
|
|
51
|
+
_reported = false
|
|
52
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape of a single telemetry ping. Contains only technical/aggregate data —
|
|
3
|
+
* no resource names, record contents, user identifiers, or secrets.
|
|
4
|
+
*
|
|
5
|
+
* The `instanceId` is a UUID v7 generated once per process start. It is
|
|
6
|
+
* **not** persisted to disk and cannot be used to track installations across
|
|
7
|
+
* restarts. Its only purpose is server-side deduplication of rapid re-pings
|
|
8
|
+
* from the same running process (e.g., on hot-reload).
|
|
9
|
+
*/
|
|
10
|
+
export interface TelemetryInfo {
|
|
11
|
+
/** UUID v7, generated fresh each process start, never persisted. */
|
|
12
|
+
instanceId: string
|
|
13
|
+
/**
|
|
14
|
+
* Short adapter name(s) in use — derived from the adapter `Database`
|
|
15
|
+
* constructor name (e.g. `"prisma"`, `"drizzle"`, `"unknown"`).
|
|
16
|
+
* Does not reveal schema details.
|
|
17
|
+
*/
|
|
18
|
+
adapters: string[]
|
|
19
|
+
/** Total number of registered resources (no names). */
|
|
20
|
+
resourceCount: number
|
|
21
|
+
/**
|
|
22
|
+
* Active commercial feature flags — the string list passed to
|
|
23
|
+
* `new ModernAdmin({ featureFlags: [...] })`. Consumers who don't use
|
|
24
|
+
* Pro packages always see `[]` here.
|
|
25
|
+
*/
|
|
26
|
+
featureFlags: string[]
|
|
27
|
+
/**
|
|
28
|
+
* Enabled admin-subsystem capabilities (keys from `AdminFeatures`).
|
|
29
|
+
* Only the `true` entries are included to keep payloads compact.
|
|
30
|
+
*/
|
|
31
|
+
features: string[]
|
|
32
|
+
/** `process.platform` value (e.g. `"linux"`, `"darwin"`, `"win32"`). */
|
|
33
|
+
platform: string
|
|
34
|
+
/**
|
|
35
|
+
* Runtime and version string. `"bun/1.3.13"` or `"node/20.0.0"`,
|
|
36
|
+
* depending on which runtime executes the server.
|
|
37
|
+
*/
|
|
38
|
+
runtime: string
|
|
39
|
+
}
|