monora-ai 2.0.0 → 2.1.3
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 +441 -150
- package/dist/aims_governance.d.ts +238 -0
- package/dist/aims_governance.d.ts.map +1 -0
- package/dist/aims_governance.js +922 -0
- package/dist/alerts.d.ts +16 -0
- package/dist/alerts.d.ts.map +1 -1
- package/dist/alerts.js +16 -0
- package/dist/api.d.ts +6 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -0
- package/dist/assessment.d.ts +269 -0
- package/dist/assessment.d.ts.map +1 -0
- package/dist/assessment.js +1232 -0
- package/dist/attestation.js +23 -1
- package/dist/attribution.d.ts +349 -0
- package/dist/attribution.d.ts.map +1 -0
- package/dist/attribution.js +987 -0
- package/dist/autodetect.d.ts +69 -1
- package/dist/autodetect.d.ts.map +1 -1
- package/dist/autodetect.js +644 -1
- package/dist/bias.d.ts +130 -0
- package/dist/bias.d.ts.map +1 -0
- package/dist/bias.js +223 -0
- package/dist/circuit_breaker.js +3 -3
- package/dist/cli/diagnostics.d.ts +5 -1
- package/dist/cli/diagnostics.d.ts.map +1 -1
- package/dist/cli/diagnostics.js +31 -8
- package/dist/cli/doctor.d.ts +25 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +381 -0
- package/dist/cli/fix.d.ts +16 -0
- package/dist/cli/fix.d.ts.map +1 -0
- package/dist/cli/fix.js +284 -0
- package/dist/cli/init.d.ts +57 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +205 -0
- package/dist/cli.js +1611 -126
- package/dist/complianceTargets.d.ts +111 -0
- package/dist/complianceTargets.d.ts.map +1 -0
- package/dist/complianceTargets.js +521 -0
- package/dist/config.d.ts +301 -17
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +428 -36
- package/dist/config_migrations.d.ts +41 -0
- package/dist/config_migrations.d.ts.map +1 -1
- package/dist/config_migrations.js +205 -0
- package/dist/config_schema.d.ts +2900 -731
- package/dist/config_schema.d.ts.map +1 -1
- package/dist/config_schema.js +257 -55
- package/dist/context.d.ts +34 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +118 -7
- package/dist/control_backbone.d.ts +122 -0
- package/dist/control_backbone.d.ts.map +1 -0
- package/dist/control_backbone.js +698 -0
- package/dist/data-governance.d.ts +187 -0
- package/dist/data-governance.d.ts.map +1 -0
- package/dist/data-governance.js +424 -0
- package/dist/dataResidency.d.ts +44 -0
- package/dist/dataResidency.d.ts.map +1 -0
- package/dist/dataResidency.js +203 -0
- package/dist/dispatcher.d.ts +32 -0
- package/dist/dispatcher.d.ts.map +1 -1
- package/dist/dispatcher.js +91 -4
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +38 -0
- package/dist/evidence_store.d.ts +103 -0
- package/dist/evidence_store.d.ts.map +1 -0
- package/dist/evidence_store.js +459 -0
- package/dist/executiveSummary.d.ts +65 -8
- package/dist/executiveSummary.d.ts.map +1 -1
- package/dist/executiveSummary.js +289 -26
- package/dist/identity.d.ts +143 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +231 -0
- package/dist/impact-assessment.d.ts +350 -0
- package/dist/impact-assessment.d.ts.map +1 -0
- package/dist/impact-assessment.js +580 -0
- package/dist/index.d.ts +25 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +300 -4
- package/dist/instrumentation.d.ts +1 -1
- package/dist/instrumentation.d.ts.map +1 -1
- package/dist/instrumentation.js +243 -27
- package/dist/integrations/anthropic.d.ts +3 -0
- package/dist/integrations/anthropic.d.ts.map +1 -1
- package/dist/integrations/anthropic.js +284 -79
- package/dist/integrations/governance.d.ts +33 -0
- package/dist/integrations/governance.d.ts.map +1 -0
- package/dist/integrations/governance.js +208 -0
- package/dist/integrations/langchain.d.ts +7 -0
- package/dist/integrations/langchain.d.ts.map +1 -1
- package/dist/integrations/langchain.js +387 -143
- package/dist/integrations/openai.d.ts +9 -0
- package/dist/integrations/openai.d.ts.map +1 -1
- package/dist/integrations/openai.js +673 -73
- package/dist/iso42001_consolidation.d.ts +16 -0
- package/dist/iso42001_consolidation.d.ts.map +1 -0
- package/dist/iso42001_consolidation.js +413 -0
- package/dist/iso42001_workflows.d.ts +263 -0
- package/dist/iso42001_workflows.d.ts.map +1 -0
- package/dist/iso42001_workflows.js +781 -0
- package/dist/lifecycle.d.ts +299 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +624 -0
- package/dist/lineage.d.ts +2 -2
- package/dist/lineage.d.ts.map +1 -1
- package/dist/lineage.js +12 -17
- package/dist/middleware/express.d.ts.map +1 -1
- package/dist/middleware/express.js +33 -3
- package/dist/middleware/nextjs.d.ts.map +1 -1
- package/dist/middleware/nextjs.js +42 -68
- package/dist/model.d.ts +143 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +371 -0
- package/dist/onboarding.d.ts +42 -0
- package/dist/onboarding.d.ts.map +1 -0
- package/dist/onboarding.js +1022 -0
- package/dist/oversight.d.ts +264 -0
- package/dist/oversight.d.ts.map +1 -0
- package/dist/oversight.js +497 -0
- package/dist/pdf_report.d.ts.map +1 -1
- package/dist/pdf_report.js +42 -21
- package/dist/presets.d.ts +88 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +520 -0
- package/dist/propagation.d.ts.map +1 -1
- package/dist/propagation.js +34 -2
- package/dist/quotas.d.ts +171 -0
- package/dist/quotas.d.ts.map +1 -0
- package/dist/quotas.js +259 -0
- package/dist/register.d.ts +13 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +99 -0
- package/dist/registry.d.ts +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +7 -0
- package/dist/registryData.json +43 -6
- package/dist/report.d.ts +2 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +189 -2
- package/dist/reporting.d.ts +125 -0
- package/dist/reporting.d.ts.map +1 -1
- package/dist/reporting.js +196 -5
- package/dist/resources.d.ts +285 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +643 -0
- package/dist/risk.d.ts +120 -0
- package/dist/risk.d.ts.map +1 -0
- package/dist/risk.js +220 -0
- package/dist/runtime.d.ts +74 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +598 -22
- package/dist/schemaInference.d.ts +92 -0
- package/dist/schemaInference.d.ts.map +1 -0
- package/dist/schemaInference.js +466 -0
- package/dist/schema_validation.js +2 -2
- package/dist/schemas/config.schema.json +169 -6
- package/dist/schemas/event.schema.json +4 -0
- package/dist/security_report.js +4 -4
- package/dist/signing.d.ts +1 -1
- package/dist/signing.d.ts.map +1 -1
- package/dist/signing.js +4 -0
- package/dist/sinks/file.d.ts +19 -1
- package/dist/sinks/file.d.ts.map +1 -1
- package/dist/sinks/file.js +82 -13
- package/dist/sinks/https.d.ts +10 -0
- package/dist/sinks/https.d.ts.map +1 -1
- package/dist/sinks/https.js +76 -16
- package/dist/sinks/stdout.d.ts +1 -0
- package/dist/sinks/stdout.d.ts.map +1 -1
- package/dist/sinks/stdout.js +12 -1
- package/dist/spec.d.ts +159 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +391 -0
- package/dist/stakeholders.d.ts +199 -0
- package/dist/stakeholders.d.ts.map +1 -0
- package/dist/stakeholders.js +398 -0
- package/dist/standards.d.ts.map +1 -1
- package/dist/standards.js +160 -2
- package/dist/standards_ingest.d.ts +2 -2
- package/dist/standards_ingest.d.ts.map +1 -1
- package/dist/standards_ingest.js +105 -23
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +7 -2
- package/dist/telemetry.d.ts +16 -2
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +79 -14
- package/dist/templates/controls/iso42001_control_catalog.json +1443 -0
- package/dist/traced_emitter.d.ts +3 -0
- package/dist/traced_emitter.d.ts.map +1 -1
- package/dist/traced_emitter.js +142 -25
- package/dist/trust_package.d.ts +21 -1
- package/dist/trust_package.d.ts.map +1 -1
- package/dist/trust_package.js +101 -4
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +9 -2
- package/dist/wal.d.ts.map +1 -1
- package/dist/wal.js +2 -1
- package/package.json +14 -1
- package/scripts/postinstall.js +119 -97
- package/templates/controls/iso42001_control_catalog.json +1443 -0
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Attribution and project registration for Monora SDK.
|
|
4
|
+
*
|
|
5
|
+
* This module provides opt-in attribution and telemetry features to help
|
|
6
|
+
* improve the SDK and understand usage patterns.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { registerProject, reportUsage, enableTelemetry } from 'monora-ai';
|
|
11
|
+
*
|
|
12
|
+
* // Register your project (optional but encouraged)
|
|
13
|
+
* registerProject({
|
|
14
|
+
* company: 'Acme Corp',
|
|
15
|
+
* role: 'ML Engineer',
|
|
16
|
+
* email: 'dev@acme.com',
|
|
17
|
+
* source: 'npm',
|
|
18
|
+
* useCase: 'Customer Support AI',
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Enable and report usage (opt-in)
|
|
22
|
+
* enableTelemetry({ sendData: true });
|
|
23
|
+
* reportUsage({ framework: 'nextjs', environment: 'production' });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
29
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
30
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
31
|
+
}
|
|
32
|
+
Object.defineProperty(o, k2, desc);
|
|
33
|
+
}) : (function(o, m, k, k2) {
|
|
34
|
+
if (k2 === undefined) k2 = k;
|
|
35
|
+
o[k2] = m[k];
|
|
36
|
+
}));
|
|
37
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
38
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
39
|
+
}) : function(o, v) {
|
|
40
|
+
o["default"] = v;
|
|
41
|
+
});
|
|
42
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
43
|
+
var ownKeys = function(o) {
|
|
44
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
45
|
+
var ar = [];
|
|
46
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
47
|
+
return ar;
|
|
48
|
+
};
|
|
49
|
+
return ownKeys(o);
|
|
50
|
+
};
|
|
51
|
+
return function (mod) {
|
|
52
|
+
if (mod && mod.__esModule) return mod;
|
|
53
|
+
var result = {};
|
|
54
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
55
|
+
__setModuleDefault(result, mod);
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
})();
|
|
59
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
60
|
+
exports.getInstallId = getInstallId;
|
|
61
|
+
exports.registerProject = registerProject;
|
|
62
|
+
exports.getRegistration = getRegistration;
|
|
63
|
+
exports.enableTelemetry = enableTelemetry;
|
|
64
|
+
exports.disableTelemetry = disableTelemetry;
|
|
65
|
+
exports.isTelemetryEnabled = isTelemetryEnabled;
|
|
66
|
+
exports.recordFeatureUsage = recordFeatureUsage;
|
|
67
|
+
exports.getFeaturesUsed = getFeaturesUsed;
|
|
68
|
+
exports.buildUsageReport = buildUsageReport;
|
|
69
|
+
exports.reportUsage = reportUsage;
|
|
70
|
+
exports.onAttributionEvent = onAttributionEvent;
|
|
71
|
+
exports.setDataResidency = setDataResidency;
|
|
72
|
+
exports.getDataResidency = getDataResidency;
|
|
73
|
+
exports.configureHttp = configureHttp;
|
|
74
|
+
exports.sendTelemetryEvent = sendTelemetryEvent;
|
|
75
|
+
exports.setAuditMetadata = setAuditMetadata;
|
|
76
|
+
exports.getAuditMetadata = getAuditMetadata;
|
|
77
|
+
exports.hasShownFirstRunPrompt = hasShownFirstRunPrompt;
|
|
78
|
+
exports.hasRegistration = hasRegistration;
|
|
79
|
+
exports.promptForRegistration = promptForRegistration;
|
|
80
|
+
exports.showFirstRunPromptIfNeeded = showFirstRunPromptIfNeeded;
|
|
81
|
+
exports.resetFirstRunPrompt = resetFirstRunPrompt;
|
|
82
|
+
const crypto = __importStar(require("crypto"));
|
|
83
|
+
const fs = __importStar(require("fs"));
|
|
84
|
+
const http = __importStar(require("http"));
|
|
85
|
+
const https = __importStar(require("https"));
|
|
86
|
+
const os = __importStar(require("os"));
|
|
87
|
+
const path = __importStar(require("path"));
|
|
88
|
+
const readline = __importStar(require("readline"));
|
|
89
|
+
// HTTP configuration (can be overridden via config or environment)
|
|
90
|
+
// Note: Uses seconds (not milliseconds) for cross-SDK parity with Python
|
|
91
|
+
const HTTP_CONFIG = {
|
|
92
|
+
endpoints: {
|
|
93
|
+
us: process.env.MONORA_ATTRIBUTION_ENDPOINT_US || null,
|
|
94
|
+
eu: process.env.MONORA_ATTRIBUTION_ENDPOINT_EU || null,
|
|
95
|
+
},
|
|
96
|
+
apiKeyEnv: 'MONORA_ATTRIBUTION_KEY',
|
|
97
|
+
timeoutSec: 5,
|
|
98
|
+
retryAttempts: 2,
|
|
99
|
+
backoffBaseSec: 0.5,
|
|
100
|
+
};
|
|
101
|
+
const retryQueue = [];
|
|
102
|
+
const MAX_RETRY_QUEUE_SIZE = 100;
|
|
103
|
+
let retryTimer = null;
|
|
104
|
+
// Get version from package.json
|
|
105
|
+
let SDK_VERSION = '2.1.3';
|
|
106
|
+
try {
|
|
107
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
108
|
+
if (fs.existsSync(pkgPath)) {
|
|
109
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
110
|
+
SDK_VERSION = pkg.version || SDK_VERSION;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Use default version
|
|
115
|
+
}
|
|
116
|
+
// Installation ID storage path
|
|
117
|
+
const INSTALL_ID_PATH = path.join(os.homedir(), '.monora', 'install_id');
|
|
118
|
+
const ATTRIBUTION_STATE_PATH = path.join(os.homedir(), '.monora', 'attribution.json');
|
|
119
|
+
// Registration from postinstall script (collected during npm install)
|
|
120
|
+
const POSTINSTALL_REGISTRATION_PATH = path.join(os.homedir(), '.monora', 'registration.json');
|
|
121
|
+
// First-run prompt state
|
|
122
|
+
const FIRST_RUN_PROMPT_PATH = path.join(os.homedir(), '.monora', 'first_run_prompt.json');
|
|
123
|
+
// Callback registry
|
|
124
|
+
const attributionCallbacks = [];
|
|
125
|
+
// Global state
|
|
126
|
+
let currentRegistration = null;
|
|
127
|
+
let telemetryEnabled = false;
|
|
128
|
+
let sendDataEnabled = false;
|
|
129
|
+
let dataResidency = null;
|
|
130
|
+
let registrationSentAt = null;
|
|
131
|
+
const featuresUsed = new Set();
|
|
132
|
+
let auditMetadata = null;
|
|
133
|
+
let stateLoaded = false;
|
|
134
|
+
function cleanPayload(payload) {
|
|
135
|
+
const cleaned = {};
|
|
136
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
137
|
+
if (value === undefined || value === null) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (typeof value === 'string' && value.trim() === '') {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
cleaned[key] = value;
|
|
150
|
+
}
|
|
151
|
+
return cleaned;
|
|
152
|
+
}
|
|
153
|
+
function normalizeRegistrationPayload(payload) {
|
|
154
|
+
return cleanPayload({
|
|
155
|
+
company: payload.company,
|
|
156
|
+
role: payload.role,
|
|
157
|
+
email: payload.email,
|
|
158
|
+
source: payload.source,
|
|
159
|
+
useCase: payload.useCase ?? payload.use_case,
|
|
160
|
+
teamSize: payload.teamSize ?? payload.team_size,
|
|
161
|
+
businessOwner: payload.businessOwner ?? payload.business_owner,
|
|
162
|
+
dataCategories: payload.dataCategories ?? payload.data_categories,
|
|
163
|
+
tags: payload.tags,
|
|
164
|
+
registeredAt: payload.registeredAt ?? payload.registered_at,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function normalizeAuditPayload(payload) {
|
|
168
|
+
return cleanPayload({
|
|
169
|
+
useCaseName: payload.useCaseName ?? payload.use_case_name,
|
|
170
|
+
businessOwner: payload.businessOwner ?? payload.business_owner,
|
|
171
|
+
dataCategories: payload.dataCategories ?? payload.data_categories,
|
|
172
|
+
riskLevel: payload.riskLevel ?? payload.risk_level,
|
|
173
|
+
complianceFrameworks: payload.complianceFrameworks ?? payload.compliance_frameworks,
|
|
174
|
+
reviewDate: payload.reviewDate ?? payload.review_date,
|
|
175
|
+
reviewer: payload.reviewer,
|
|
176
|
+
notes: payload.notes,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function ensureStateLoaded() {
|
|
180
|
+
if (stateLoaded) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
stateLoaded = true;
|
|
184
|
+
try {
|
|
185
|
+
if (!fs.existsSync(ATTRIBUTION_STATE_PATH)) {
|
|
186
|
+
// Try loading from postinstall registration if attribution state doesn't exist
|
|
187
|
+
loadPostinstallRegistration();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const raw = fs.readFileSync(ATTRIBUTION_STATE_PATH, 'utf8');
|
|
191
|
+
if (!raw.trim()) {
|
|
192
|
+
loadPostinstallRegistration();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const data = JSON.parse(raw);
|
|
196
|
+
if (data.registration || data.project_registration) {
|
|
197
|
+
currentRegistration = normalizeRegistrationPayload(data.registration || data.project_registration);
|
|
198
|
+
}
|
|
199
|
+
if (data.audit_metadata || data.audit) {
|
|
200
|
+
auditMetadata = normalizeAuditPayload(data.audit_metadata || data.audit);
|
|
201
|
+
}
|
|
202
|
+
if (data.telemetry && typeof data.telemetry === 'object') {
|
|
203
|
+
if (typeof data.telemetry.enabled === 'boolean') {
|
|
204
|
+
telemetryEnabled = data.telemetry.enabled;
|
|
205
|
+
}
|
|
206
|
+
if (typeof data.telemetry.send_data === 'boolean') {
|
|
207
|
+
sendDataEnabled = data.telemetry.send_data;
|
|
208
|
+
}
|
|
209
|
+
if (data.telemetry.data_residency !== undefined) {
|
|
210
|
+
dataResidency = data.telemetry.data_residency;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (typeof data.registration_sent_at === 'string') {
|
|
214
|
+
registrationSentAt = data.registration_sent_at;
|
|
215
|
+
}
|
|
216
|
+
// If no registration in attribution state, try postinstall
|
|
217
|
+
if (!currentRegistration) {
|
|
218
|
+
loadPostinstallRegistration();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Ignore persistence errors
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Load registration data from postinstall script (collected during npm install).
|
|
227
|
+
*/
|
|
228
|
+
function loadPostinstallRegistration() {
|
|
229
|
+
try {
|
|
230
|
+
if (!fs.existsSync(POSTINSTALL_REGISTRATION_PATH)) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const raw = fs.readFileSync(POSTINSTALL_REGISTRATION_PATH, 'utf8');
|
|
234
|
+
if (!raw.trim()) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const data = JSON.parse(raw);
|
|
238
|
+
if (data && typeof data === 'object') {
|
|
239
|
+
currentRegistration = normalizeRegistrationPayload(data);
|
|
240
|
+
// Save to attribution state for future use
|
|
241
|
+
saveState();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// Ignore errors loading postinstall registration
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function saveState() {
|
|
249
|
+
ensureStateLoaded();
|
|
250
|
+
try {
|
|
251
|
+
const dir = path.dirname(ATTRIBUTION_STATE_PATH);
|
|
252
|
+
if (!fs.existsSync(dir)) {
|
|
253
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
254
|
+
}
|
|
255
|
+
const payload = {
|
|
256
|
+
telemetry: {
|
|
257
|
+
enabled: telemetryEnabled,
|
|
258
|
+
send_data: sendDataEnabled,
|
|
259
|
+
data_residency: dataResidency,
|
|
260
|
+
},
|
|
261
|
+
updated_at: new Date().toISOString(),
|
|
262
|
+
};
|
|
263
|
+
if (currentRegistration) {
|
|
264
|
+
payload.registration = cleanPayload({
|
|
265
|
+
company: currentRegistration.company,
|
|
266
|
+
role: currentRegistration.role,
|
|
267
|
+
email: currentRegistration.email,
|
|
268
|
+
source: currentRegistration.source,
|
|
269
|
+
use_case: currentRegistration.useCase,
|
|
270
|
+
team_size: currentRegistration.teamSize,
|
|
271
|
+
business_owner: currentRegistration.businessOwner,
|
|
272
|
+
data_categories: currentRegistration.dataCategories,
|
|
273
|
+
tags: currentRegistration.tags,
|
|
274
|
+
registered_at: currentRegistration.registeredAt,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if (auditMetadata) {
|
|
278
|
+
payload.audit_metadata = cleanPayload({
|
|
279
|
+
use_case_name: auditMetadata.useCaseName,
|
|
280
|
+
business_owner: auditMetadata.businessOwner,
|
|
281
|
+
data_categories: auditMetadata.dataCategories,
|
|
282
|
+
risk_level: auditMetadata.riskLevel,
|
|
283
|
+
compliance_frameworks: auditMetadata.complianceFrameworks,
|
|
284
|
+
review_date: auditMetadata.reviewDate,
|
|
285
|
+
reviewer: auditMetadata.reviewer,
|
|
286
|
+
notes: auditMetadata.notes,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (registrationSentAt) {
|
|
290
|
+
payload.registration_sent_at = registrationSentAt;
|
|
291
|
+
}
|
|
292
|
+
fs.writeFileSync(ATTRIBUTION_STATE_PATH, JSON.stringify(payload, null, 2));
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// Ignore persistence errors
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Get or create a persistent anonymous installation ID.
|
|
300
|
+
*
|
|
301
|
+
* The ID is a hash of a random UUID, stored locally.
|
|
302
|
+
* No personally identifiable information is included.
|
|
303
|
+
*
|
|
304
|
+
* @returns Anonymous installation ID string.
|
|
305
|
+
*/
|
|
306
|
+
function getInstallId() {
|
|
307
|
+
try {
|
|
308
|
+
const dir = path.dirname(INSTALL_ID_PATH);
|
|
309
|
+
if (!fs.existsSync(dir)) {
|
|
310
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
311
|
+
}
|
|
312
|
+
if (fs.existsSync(INSTALL_ID_PATH)) {
|
|
313
|
+
const installId = fs.readFileSync(INSTALL_ID_PATH, 'utf8').trim();
|
|
314
|
+
if (installId) {
|
|
315
|
+
return installId;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// Generate new anonymous ID
|
|
319
|
+
const rawId = crypto.randomUUID();
|
|
320
|
+
const installId = crypto.createHash('sha256').update(rawId).digest('hex').slice(0, 32);
|
|
321
|
+
fs.writeFileSync(INSTALL_ID_PATH, installId);
|
|
322
|
+
return installId;
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// Fallback to session-only ID if file access fails
|
|
326
|
+
return crypto.createHash('sha256').update(crypto.randomUUID()).digest('hex').slice(0, 32);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Register your project with Monora (optional but encouraged).
|
|
331
|
+
*
|
|
332
|
+
* This helps us understand who is using the SDK and improve the product.
|
|
333
|
+
* All fields are optional. Data is only sent if telemetry is enabled.
|
|
334
|
+
*
|
|
335
|
+
* @param options - Registration options.
|
|
336
|
+
* @returns ProjectRegistration object.
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* ```typescript
|
|
340
|
+
* registerProject({
|
|
341
|
+
* company: 'Acme Corp',
|
|
342
|
+
* role: 'ML Engineer',
|
|
343
|
+
* useCase: 'Customer Support AI',
|
|
344
|
+
* dataCategories: ['customer_data'],
|
|
345
|
+
* });
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
function registerProject(options) {
|
|
349
|
+
ensureStateLoaded();
|
|
350
|
+
const registration = {
|
|
351
|
+
...options,
|
|
352
|
+
registeredAt: new Date().toISOString(),
|
|
353
|
+
};
|
|
354
|
+
currentRegistration = registration;
|
|
355
|
+
registrationSentAt = null;
|
|
356
|
+
// Notify callbacks
|
|
357
|
+
for (const callback of attributionCallbacks.slice()) {
|
|
358
|
+
try {
|
|
359
|
+
callback({ type: 'project_registered', data: registration });
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
// Ignore callback errors
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Send if telemetry enabled
|
|
366
|
+
if (telemetryEnabled && sendDataEnabled) {
|
|
367
|
+
sendRegistration(registration);
|
|
368
|
+
registrationSentAt = new Date().toISOString();
|
|
369
|
+
}
|
|
370
|
+
saveState();
|
|
371
|
+
return registration;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get the current project registration.
|
|
375
|
+
*
|
|
376
|
+
* @returns Current ProjectRegistration or null if not registered.
|
|
377
|
+
*/
|
|
378
|
+
function getRegistration() {
|
|
379
|
+
ensureStateLoaded();
|
|
380
|
+
return currentRegistration;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Enable anonymous usage telemetry.
|
|
384
|
+
*
|
|
385
|
+
* By default, telemetry data stays local. Set sendData=true to
|
|
386
|
+
* share anonymous usage data with Monora to help improve the SDK.
|
|
387
|
+
*
|
|
388
|
+
* @param options - Telemetry options.
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```typescript
|
|
392
|
+
* enableTelemetry({ sendData: true, dataResidency: 'eu' });
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
function enableTelemetry(options = {}) {
|
|
396
|
+
ensureStateLoaded();
|
|
397
|
+
telemetryEnabled = true;
|
|
398
|
+
sendDataEnabled = options.sendData ?? false;
|
|
399
|
+
dataResidency = options.dataResidency ?? null;
|
|
400
|
+
if (sendDataEnabled && currentRegistration && !registrationSentAt) {
|
|
401
|
+
sendRegistration(currentRegistration);
|
|
402
|
+
registrationSentAt = new Date().toISOString();
|
|
403
|
+
}
|
|
404
|
+
saveState();
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Disable usage telemetry.
|
|
408
|
+
*
|
|
409
|
+
* No data will be collected or sent after this call.
|
|
410
|
+
*/
|
|
411
|
+
function disableTelemetry() {
|
|
412
|
+
ensureStateLoaded();
|
|
413
|
+
telemetryEnabled = false;
|
|
414
|
+
sendDataEnabled = false;
|
|
415
|
+
saveState();
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Check if telemetry is enabled.
|
|
419
|
+
*
|
|
420
|
+
* @returns True if telemetry is enabled.
|
|
421
|
+
*/
|
|
422
|
+
function isTelemetryEnabled() {
|
|
423
|
+
ensureStateLoaded();
|
|
424
|
+
return telemetryEnabled;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Record that a feature was used (internal).
|
|
428
|
+
*
|
|
429
|
+
* This is called internally by SDK components to track which
|
|
430
|
+
* features are being used. No sensitive data is recorded.
|
|
431
|
+
*
|
|
432
|
+
* @param feature - Feature name (e.g., "signing", "wal", "circuit_breaker").
|
|
433
|
+
*/
|
|
434
|
+
function recordFeatureUsage(feature) {
|
|
435
|
+
featuresUsed.add(feature);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Get list of features used in this session.
|
|
439
|
+
*
|
|
440
|
+
* @returns Sorted list of feature names.
|
|
441
|
+
*/
|
|
442
|
+
function getFeaturesUsed() {
|
|
443
|
+
return Array.from(featuresUsed).sort();
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Build an anonymous usage report.
|
|
447
|
+
*
|
|
448
|
+
* @param options - Report options.
|
|
449
|
+
* @returns UsageReport object with anonymous data.
|
|
450
|
+
*/
|
|
451
|
+
function buildUsageReport(options = {}) {
|
|
452
|
+
ensureStateLoaded();
|
|
453
|
+
return {
|
|
454
|
+
installId: getInstallId(),
|
|
455
|
+
sdkVersion: SDK_VERSION,
|
|
456
|
+
nodeVersion: process.version,
|
|
457
|
+
osName: os.platform(),
|
|
458
|
+
osVersion: os.release(),
|
|
459
|
+
framework: options.framework,
|
|
460
|
+
featuresUsed: getFeaturesUsed(),
|
|
461
|
+
environment: options.environment ?? 'development',
|
|
462
|
+
reportedAt: new Date().toISOString(),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Report anonymous usage data (if telemetry enabled).
|
|
467
|
+
*
|
|
468
|
+
* This sends anonymous usage data to help improve the SDK.
|
|
469
|
+
* Only called if telemetry is enabled with sendData=true.
|
|
470
|
+
*
|
|
471
|
+
* @param options - Report options.
|
|
472
|
+
* @returns UsageReport object.
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```typescript
|
|
476
|
+
* enableTelemetry({ sendData: true });
|
|
477
|
+
* reportUsage({ framework: 'nextjs', environment: 'production' });
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
function reportUsage(options = {}) {
|
|
481
|
+
ensureStateLoaded();
|
|
482
|
+
const report = buildUsageReport(options);
|
|
483
|
+
// Notify callbacks
|
|
484
|
+
for (const callback of attributionCallbacks.slice()) {
|
|
485
|
+
try {
|
|
486
|
+
callback({ type: 'usage_reported', data: report });
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
// Ignore callback errors
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// Send if enabled
|
|
493
|
+
if (telemetryEnabled && sendDataEnabled) {
|
|
494
|
+
sendUsageReport(report);
|
|
495
|
+
}
|
|
496
|
+
return report;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Register a callback for attribution events.
|
|
500
|
+
*
|
|
501
|
+
* Use this to hook into attribution events for your own logging
|
|
502
|
+
* or monitoring stack.
|
|
503
|
+
*
|
|
504
|
+
* @param callback - Function called with event data.
|
|
505
|
+
* @returns Unsubscribe function.
|
|
506
|
+
*
|
|
507
|
+
* @example
|
|
508
|
+
* ```typescript
|
|
509
|
+
* const unsubscribe = onAttributionEvent((event) => {
|
|
510
|
+
* console.log(`Attribution event: ${event.type}`);
|
|
511
|
+
* });
|
|
512
|
+
* // Later: unsubscribe();
|
|
513
|
+
* ```
|
|
514
|
+
*/
|
|
515
|
+
function onAttributionEvent(callback) {
|
|
516
|
+
attributionCallbacks.push(callback);
|
|
517
|
+
return () => {
|
|
518
|
+
const index = attributionCallbacks.indexOf(callback);
|
|
519
|
+
if (index !== -1) {
|
|
520
|
+
attributionCallbacks.splice(index, 1);
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Set data residency region.
|
|
526
|
+
*
|
|
527
|
+
* @param region - Region code ("us", "eu", or "none" to disable sending).
|
|
528
|
+
*/
|
|
529
|
+
function setDataResidency(region) {
|
|
530
|
+
ensureStateLoaded();
|
|
531
|
+
dataResidency = region;
|
|
532
|
+
saveState();
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Get current data residency setting.
|
|
536
|
+
*
|
|
537
|
+
* @returns Region code or null.
|
|
538
|
+
*/
|
|
539
|
+
function getDataResidency() {
|
|
540
|
+
ensureStateLoaded();
|
|
541
|
+
return dataResidency;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Configure HTTP settings from Monora config (internal).
|
|
545
|
+
*
|
|
546
|
+
* Called by runtime.init() to pass endpoint configuration.
|
|
547
|
+
*/
|
|
548
|
+
function configureHttp(config) {
|
|
549
|
+
if (!config)
|
|
550
|
+
return;
|
|
551
|
+
if (config.endpoints) {
|
|
552
|
+
if (config.endpoints.us)
|
|
553
|
+
HTTP_CONFIG.endpoints.us = config.endpoints.us;
|
|
554
|
+
if (config.endpoints.eu)
|
|
555
|
+
HTTP_CONFIG.endpoints.eu = config.endpoints.eu;
|
|
556
|
+
}
|
|
557
|
+
if (config.apiKeyEnv)
|
|
558
|
+
HTTP_CONFIG.apiKeyEnv = config.apiKeyEnv;
|
|
559
|
+
if (config.timeoutSec)
|
|
560
|
+
HTTP_CONFIG.timeoutSec = config.timeoutSec;
|
|
561
|
+
if (config.retryAttempts)
|
|
562
|
+
HTTP_CONFIG.retryAttempts = config.retryAttempts;
|
|
563
|
+
if (config.backoffBaseSec)
|
|
564
|
+
HTTP_CONFIG.backoffBaseSec = config.backoffBaseSec;
|
|
565
|
+
}
|
|
566
|
+
function getEndpoint(endpointType) {
|
|
567
|
+
ensureStateLoaded();
|
|
568
|
+
const residency = dataResidency || 'us';
|
|
569
|
+
if (residency === 'none')
|
|
570
|
+
return null;
|
|
571
|
+
const baseUrl = HTTP_CONFIG.endpoints[residency] || HTTP_CONFIG.endpoints.us;
|
|
572
|
+
if (!baseUrl)
|
|
573
|
+
return null;
|
|
574
|
+
return `${baseUrl}/${endpointType}`;
|
|
575
|
+
}
|
|
576
|
+
function getApiKey() {
|
|
577
|
+
return process.env[HTTP_CONFIG.apiKeyEnv] || null;
|
|
578
|
+
}
|
|
579
|
+
async function makeRequest(url, data, timeout, retries) {
|
|
580
|
+
const apiKey = getApiKey();
|
|
581
|
+
if (!apiKey)
|
|
582
|
+
return false;
|
|
583
|
+
// Convert seconds to milliseconds for setTimeout
|
|
584
|
+
const effectiveTimeout = (timeout ?? HTTP_CONFIG.timeoutSec) * 1000;
|
|
585
|
+
const effectiveRetries = retries ?? HTTP_CONFIG.retryAttempts;
|
|
586
|
+
const payload = JSON.stringify({
|
|
587
|
+
...data,
|
|
588
|
+
sdk_type: 'node',
|
|
589
|
+
sdk_version: SDK_VERSION,
|
|
590
|
+
});
|
|
591
|
+
const parsedUrl = new URL(url);
|
|
592
|
+
const isHttps = parsedUrl.protocol === 'https:';
|
|
593
|
+
const requestModule = isHttps ? https : http;
|
|
594
|
+
const options = {
|
|
595
|
+
hostname: parsedUrl.hostname,
|
|
596
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
597
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
598
|
+
method: 'POST',
|
|
599
|
+
headers: {
|
|
600
|
+
'Content-Type': 'application/json',
|
|
601
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
602
|
+
apikey: apiKey,
|
|
603
|
+
Authorization: `Bearer ${apiKey}`,
|
|
604
|
+
'x-monora-sdk-type': 'node',
|
|
605
|
+
'x-monora-sdk-version': SDK_VERSION,
|
|
606
|
+
'x-data-residency': dataResidency || 'us',
|
|
607
|
+
},
|
|
608
|
+
timeout: effectiveTimeout,
|
|
609
|
+
};
|
|
610
|
+
for (let attempt = 0; attempt <= effectiveRetries; attempt++) {
|
|
611
|
+
try {
|
|
612
|
+
const success = await new Promise((resolve) => {
|
|
613
|
+
const req = requestModule.request(options, (res) => {
|
|
614
|
+
let body = '';
|
|
615
|
+
res.on('data', (chunk) => {
|
|
616
|
+
body += chunk.toString();
|
|
617
|
+
});
|
|
618
|
+
res.on('end', () => {
|
|
619
|
+
if (res.statusCode === 200) {
|
|
620
|
+
resolve(true);
|
|
621
|
+
}
|
|
622
|
+
else if (res.statusCode === 429) {
|
|
623
|
+
resolve(false);
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
resolve(false);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
req.on('error', () => resolve(false));
|
|
631
|
+
req.on('timeout', () => {
|
|
632
|
+
req.destroy();
|
|
633
|
+
resolve(false);
|
|
634
|
+
});
|
|
635
|
+
req.write(payload);
|
|
636
|
+
req.end();
|
|
637
|
+
});
|
|
638
|
+
if (success)
|
|
639
|
+
return true;
|
|
640
|
+
if (attempt < effectiveRetries) {
|
|
641
|
+
// Convert seconds to milliseconds for setTimeout
|
|
642
|
+
const delay = HTTP_CONFIG.backoffBaseSec * 1000 * Math.pow(2, attempt) + Math.random() * 100;
|
|
643
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
if (attempt < effectiveRetries) {
|
|
648
|
+
// Convert seconds to milliseconds for setTimeout
|
|
649
|
+
const delay = HTTP_CONFIG.backoffBaseSec * 1000 * Math.pow(2, attempt) + Math.random() * 100;
|
|
650
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
function sendAsync(endpointType, data) {
|
|
657
|
+
const url = getEndpoint(endpointType);
|
|
658
|
+
if (!url)
|
|
659
|
+
return;
|
|
660
|
+
makeRequest(url, data)
|
|
661
|
+
.then((success) => {
|
|
662
|
+
if (!success) {
|
|
663
|
+
queueForRetry(endpointType, data);
|
|
664
|
+
}
|
|
665
|
+
})
|
|
666
|
+
.catch(() => {
|
|
667
|
+
queueForRetry(endpointType, data);
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
function queueForRetry(endpointType, data) {
|
|
671
|
+
if (retryQueue.length < MAX_RETRY_QUEUE_SIZE) {
|
|
672
|
+
retryQueue.push({
|
|
673
|
+
endpointType,
|
|
674
|
+
data,
|
|
675
|
+
queuedAt: new Date().toISOString(),
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
// Start retry timer if not already running
|
|
679
|
+
if (!retryTimer) {
|
|
680
|
+
retryTimer = setTimeout(processRetryQueue, 30000);
|
|
681
|
+
if (retryTimer.unref) {
|
|
682
|
+
retryTimer.unref(); // Don't block process exit
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async function processRetryQueue() {
|
|
687
|
+
retryTimer = null;
|
|
688
|
+
while (retryQueue.length > 0) {
|
|
689
|
+
const item = retryQueue[0];
|
|
690
|
+
const url = getEndpoint(item.endpointType);
|
|
691
|
+
if (!url) {
|
|
692
|
+
retryQueue.shift();
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
const success = await makeRequest(url, item.data);
|
|
696
|
+
if (success) {
|
|
697
|
+
retryQueue.shift();
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
// Stop on first failure to preserve order
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
// Restart timer if queue still has items
|
|
705
|
+
if (retryQueue.length > 0) {
|
|
706
|
+
retryTimer = setTimeout(processRetryQueue, 30000);
|
|
707
|
+
if (retryTimer.unref) {
|
|
708
|
+
retryTimer.unref();
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Send registration to Monora API (internal).
|
|
714
|
+
* Includes optional email if provided.
|
|
715
|
+
*/
|
|
716
|
+
function sendRegistration(registration) {
|
|
717
|
+
if (!sendDataEnabled)
|
|
718
|
+
return;
|
|
719
|
+
const data = {
|
|
720
|
+
install_id: getInstallId(),
|
|
721
|
+
company: registration.company,
|
|
722
|
+
role: registration.role,
|
|
723
|
+
email: registration.email,
|
|
724
|
+
source: registration.source,
|
|
725
|
+
use_case: registration.useCase,
|
|
726
|
+
team_size: registration.teamSize,
|
|
727
|
+
business_owner: registration.businessOwner,
|
|
728
|
+
data_categories: registration.dataCategories,
|
|
729
|
+
tags: registration.tags,
|
|
730
|
+
registered_at: registration.registeredAt || new Date().toISOString(),
|
|
731
|
+
};
|
|
732
|
+
sendAsync('registration', cleanPayload(data));
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Send usage report to Monora API (internal).
|
|
736
|
+
*/
|
|
737
|
+
function sendUsageReport(report) {
|
|
738
|
+
if (!sendDataEnabled)
|
|
739
|
+
return;
|
|
740
|
+
const data = {
|
|
741
|
+
install_id: report.installId,
|
|
742
|
+
runtime_version: report.nodeVersion,
|
|
743
|
+
os_name: report.osName,
|
|
744
|
+
os_version: report.osVersion,
|
|
745
|
+
framework: report.framework,
|
|
746
|
+
environment: report.environment,
|
|
747
|
+
features_used: report.featuresUsed,
|
|
748
|
+
reported_at: report.reportedAt || new Date().toISOString(),
|
|
749
|
+
};
|
|
750
|
+
sendAsync('usage-report', cleanPayload(data));
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Send a telemetry event (e.g., startup, shutdown, error).
|
|
754
|
+
*
|
|
755
|
+
* This is called internally by the SDK to track usage patterns.
|
|
756
|
+
* Only sends if telemetry is enabled with sendData=true.
|
|
757
|
+
*
|
|
758
|
+
* @param options - Event options.
|
|
759
|
+
*/
|
|
760
|
+
function sendTelemetryEvent(options) {
|
|
761
|
+
ensureStateLoaded();
|
|
762
|
+
if (!telemetryEnabled || !sendDataEnabled)
|
|
763
|
+
return;
|
|
764
|
+
const { eventType, eventData, framework, environment = 'development' } = options;
|
|
765
|
+
const data = {
|
|
766
|
+
install_id: getInstallId(),
|
|
767
|
+
runtime_version: process.version,
|
|
768
|
+
os_name: os.platform(),
|
|
769
|
+
os_version: os.release(),
|
|
770
|
+
framework,
|
|
771
|
+
environment,
|
|
772
|
+
features_used: getFeaturesUsed(),
|
|
773
|
+
event_type: eventType,
|
|
774
|
+
event_data: eventData || {},
|
|
775
|
+
timestamp: new Date().toISOString(),
|
|
776
|
+
};
|
|
777
|
+
sendAsync('telemetry', cleanPayload(data));
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Set audit metadata for compliance purposes.
|
|
781
|
+
*
|
|
782
|
+
* This metadata is included in compliance reports and can be used
|
|
783
|
+
* to satisfy audit requirements for SOC 2, GDPR, ISO 27001, etc.
|
|
784
|
+
*
|
|
785
|
+
* @param metadata - Audit metadata.
|
|
786
|
+
* @returns AuditMetadata object.
|
|
787
|
+
*
|
|
788
|
+
* @example
|
|
789
|
+
* ```typescript
|
|
790
|
+
* setAuditMetadata({
|
|
791
|
+
* useCaseName: 'Customer Support AI',
|
|
792
|
+
* businessOwner: 'Jane Smith',
|
|
793
|
+
* dataCategories: ['customer_data', 'PII'],
|
|
794
|
+
* riskLevel: 'medium',
|
|
795
|
+
* complianceFrameworks: ['SOC2', 'GDPR'],
|
|
796
|
+
* });
|
|
797
|
+
* ```
|
|
798
|
+
*/
|
|
799
|
+
function setAuditMetadata(metadata) {
|
|
800
|
+
ensureStateLoaded();
|
|
801
|
+
auditMetadata = { ...metadata };
|
|
802
|
+
saveState();
|
|
803
|
+
return auditMetadata;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Get current audit metadata.
|
|
807
|
+
*
|
|
808
|
+
* @returns AuditMetadata object or null.
|
|
809
|
+
*/
|
|
810
|
+
function getAuditMetadata() {
|
|
811
|
+
ensureStateLoaded();
|
|
812
|
+
return auditMetadata;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Load the first-run prompt state from disk.
|
|
816
|
+
*/
|
|
817
|
+
function loadFirstRunPromptState() {
|
|
818
|
+
try {
|
|
819
|
+
if (!fs.existsSync(FIRST_RUN_PROMPT_PATH)) {
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
const raw = fs.readFileSync(FIRST_RUN_PROMPT_PATH, 'utf8');
|
|
823
|
+
return JSON.parse(raw);
|
|
824
|
+
}
|
|
825
|
+
catch {
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Save the first-run prompt state to disk.
|
|
831
|
+
*/
|
|
832
|
+
function saveFirstRunPromptState(state) {
|
|
833
|
+
try {
|
|
834
|
+
const dir = path.dirname(FIRST_RUN_PROMPT_PATH);
|
|
835
|
+
if (!fs.existsSync(dir)) {
|
|
836
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
837
|
+
}
|
|
838
|
+
fs.writeFileSync(FIRST_RUN_PROMPT_PATH, JSON.stringify(state, null, 2));
|
|
839
|
+
}
|
|
840
|
+
catch {
|
|
841
|
+
// Silent fail - don't break user's app
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Check if first-run registration prompt has been shown.
|
|
846
|
+
*
|
|
847
|
+
* @returns True if already prompted, false otherwise.
|
|
848
|
+
*/
|
|
849
|
+
function hasShownFirstRunPrompt() {
|
|
850
|
+
const state = loadFirstRunPromptState();
|
|
851
|
+
return state !== null && !!state.promptedAt;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Check if user has registered (either via postinstall or programmatically).
|
|
855
|
+
*
|
|
856
|
+
* @returns True if registered, false otherwise.
|
|
857
|
+
*/
|
|
858
|
+
function hasRegistration() {
|
|
859
|
+
ensureStateLoaded();
|
|
860
|
+
return currentRegistration !== null;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Prompt the user to register their project (interactive, optional).
|
|
864
|
+
*
|
|
865
|
+
* @param options - Optional context about the environment.
|
|
866
|
+
* @returns True if registration was completed, false otherwise.
|
|
867
|
+
*/
|
|
868
|
+
async function promptForRegistration(options) {
|
|
869
|
+
try {
|
|
870
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
if (!options?.force) {
|
|
874
|
+
if (options?.environment === 'production') {
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
if (process.env.CI ||
|
|
878
|
+
process.env.GITHUB_ACTIONS ||
|
|
879
|
+
process.env.GITLAB_CI ||
|
|
880
|
+
process.env.MONORA_SKIP_PROMPT) {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
if (hasRegistration()) {
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
if (options?.silent) {
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
console.log('');
|
|
891
|
+
console.log('Monora SDK optional project registration');
|
|
892
|
+
console.log('This helps us improve the SDK and share best practices.');
|
|
893
|
+
console.log('Email is stored locally and only sent if telemetry is enabled.');
|
|
894
|
+
console.log('');
|
|
895
|
+
const rl = readline.createInterface({
|
|
896
|
+
input: process.stdin,
|
|
897
|
+
output: process.stdout,
|
|
898
|
+
});
|
|
899
|
+
const ask = (question) => new Promise((resolve) => rl.question(question, (answer) => resolve(answer)));
|
|
900
|
+
try {
|
|
901
|
+
const confirm = (await ask('Register this project now? [y/N]: ')).trim().toLowerCase();
|
|
902
|
+
if (confirm !== 'y' && confirm !== 'yes') {
|
|
903
|
+
return false;
|
|
904
|
+
}
|
|
905
|
+
const company = (await ask('Company (optional): ')).trim() || undefined;
|
|
906
|
+
const role = (await ask('Role (optional): ')).trim() || undefined;
|
|
907
|
+
const email = (await ask('Work email (optional): ')).trim() || undefined;
|
|
908
|
+
const source = (await ask('How did you find Monora? (optional): ')).trim() || undefined;
|
|
909
|
+
const useCase = (await ask('Primary use case (optional): ')).trim() || undefined;
|
|
910
|
+
registerProject({
|
|
911
|
+
company,
|
|
912
|
+
role,
|
|
913
|
+
email,
|
|
914
|
+
source,
|
|
915
|
+
useCase,
|
|
916
|
+
});
|
|
917
|
+
return true;
|
|
918
|
+
}
|
|
919
|
+
finally {
|
|
920
|
+
rl.close();
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
catch {
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Show first-run registration prompt if needed.
|
|
929
|
+
*
|
|
930
|
+
* This logs a one-time message suggesting the user run `npx monora init`
|
|
931
|
+
* to register their project for best practices updates. The message is
|
|
932
|
+
* only shown once per installation.
|
|
933
|
+
*
|
|
934
|
+
* @param options - Optional context about the environment.
|
|
935
|
+
* @returns True if prompt was shown, false if skipped.
|
|
936
|
+
*/
|
|
937
|
+
function showFirstRunPromptIfNeeded(options) {
|
|
938
|
+
// Skip in CI environments
|
|
939
|
+
if (process.env.CI ||
|
|
940
|
+
process.env.GITHUB_ACTIONS ||
|
|
941
|
+
process.env.GITLAB_CI ||
|
|
942
|
+
process.env.MONORA_SKIP_PROMPT) {
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
// Skip if already prompted
|
|
946
|
+
if (hasShownFirstRunPrompt()) {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
// Skip if already registered
|
|
950
|
+
if (hasRegistration()) {
|
|
951
|
+
// Mark as prompted so we don't check again
|
|
952
|
+
saveFirstRunPromptState({
|
|
953
|
+
promptedAt: new Date().toISOString(),
|
|
954
|
+
sdkVersion: SDK_VERSION,
|
|
955
|
+
environment: options?.environment,
|
|
956
|
+
});
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
// Save state first to avoid showing multiple times in parallel inits
|
|
960
|
+
saveFirstRunPromptState({
|
|
961
|
+
promptedAt: new Date().toISOString(),
|
|
962
|
+
sdkVersion: SDK_VERSION,
|
|
963
|
+
environment: options?.environment,
|
|
964
|
+
});
|
|
965
|
+
// Show the prompt (unless silent mode)
|
|
966
|
+
if (!options?.silent) {
|
|
967
|
+
console.log('');
|
|
968
|
+
console.log('\x1b[36m[Monora]\x1b[0m Register your project for best practices updates:');
|
|
969
|
+
console.log(' Run: \x1b[33mnpx monora init\x1b[0m');
|
|
970
|
+
console.log(' Or skip this message by setting MONORA_SKIP_PROMPT=1');
|
|
971
|
+
console.log('');
|
|
972
|
+
}
|
|
973
|
+
return true;
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Reset the first-run prompt state (for testing).
|
|
977
|
+
*/
|
|
978
|
+
function resetFirstRunPrompt() {
|
|
979
|
+
try {
|
|
980
|
+
if (fs.existsSync(FIRST_RUN_PROMPT_PATH)) {
|
|
981
|
+
fs.unlinkSync(FIRST_RUN_PROMPT_PATH);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
catch {
|
|
985
|
+
// Silent fail
|
|
986
|
+
}
|
|
987
|
+
}
|