555e282cc4
ci / changelog_check (push) Waiting to run
ci / check_changes (push) Waiting to run
ci / build_mem0 (3.10) (push) Blocked by required conditions
ci / build_mem0 (3.11) (push) Blocked by required conditions
ci / build_mem0 (3.12) (push) Blocked by required conditions
CLI Node CI / lint (push) Waiting to run
CLI Node CI / test (20) (push) Waiting to run
CLI Node CI / test (22) (push) Waiting to run
CLI Node CI / build (push) Waiting to run
CLI Python CI / lint (push) Waiting to run
CLI Python CI / test (3.10) (push) Waiting to run
CLI Python CI / test (3.11) (push) Waiting to run
CLI Python CI / test (3.12) (push) Waiting to run
CLI Python CI / build (push) Waiting to run
openclaw checks / lint (push) Waiting to run
openclaw checks / test (20) (push) Waiting to run
openclaw checks / test (22) (push) Waiting to run
openclaw checks / build (push) Waiting to run
opencode-plugin checks / build (push) Waiting to run
pi-agent-plugin checks / lint (push) Waiting to run
pi-agent-plugin checks / test (20) (push) Waiting to run
pi-agent-plugin checks / test (22) (push) Waiting to run
pi-agent-plugin checks / build (push) Waiting to run
TypeScript SDK CI / check_changes (push) Waiting to run
TypeScript SDK CI / changelog_check (push) Blocked by required conditions
TypeScript SDK CI / build_ts_sdk (20) (push) Blocked by required conditions
TypeScript SDK CI / build_ts_sdk (22) (push) Blocked by required conditions
TypeScript SDK CI / integration_ts_sdk (20) (push) Blocked by required conditions
TypeScript SDK CI / integration_ts_sdk (22) (push) Blocked by required conditions
156 lines
3.7 KiB
JavaScript
156 lines
3.7 KiB
JavaScript
/**
|
|
* Standalone telemetry sender — runs as a detached child process.
|
|
*
|
|
* Usage: node telemetry-sender.cjs (JSON context is read from stdin; a single
|
|
* argv argument is still accepted as a legacy fallback)
|
|
*
|
|
* This script is spawned by telemetry.captureEvent() and runs independently
|
|
* of the parent CLI process. It:
|
|
*
|
|
* 1. Resolves the user's email via /v1/ping/ if not already cached
|
|
* 2. Caches the email in ~/.mem0/config.json for future runs
|
|
* 3. Sends the PostHog event
|
|
*
|
|
* All errors are silently swallowed — this process must never produce output
|
|
* or affect the user experience.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
const https = require("https");
|
|
const fs = require("fs");
|
|
|
|
function loadContext() {
|
|
return new Promise((resolve, reject) => {
|
|
if (process.argv[2]) {
|
|
try {
|
|
resolve(JSON.parse(process.argv[2]));
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
return;
|
|
}
|
|
|
|
let data = "";
|
|
process.stdin.setEncoding("utf8");
|
|
process.stdin.on("data", (chunk) => (data += chunk));
|
|
process.stdin.on("end", () => {
|
|
try {
|
|
resolve(JSON.parse(data));
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
});
|
|
process.stdin.on("error", reject);
|
|
});
|
|
}
|
|
|
|
function httpsRequest(url, method, headers, body) {
|
|
return new Promise((resolve, reject) => {
|
|
const u = new URL(url);
|
|
const opts = {
|
|
hostname: u.hostname,
|
|
path: u.pathname + u.search,
|
|
method,
|
|
headers,
|
|
timeout: 10000,
|
|
};
|
|
const req = https.request(opts, (res) => {
|
|
let data = "";
|
|
res.on("data", (chunk) => (data += chunk));
|
|
res.on("end", () => {
|
|
try {
|
|
resolve(JSON.parse(data));
|
|
} catch {
|
|
resolve({});
|
|
}
|
|
});
|
|
});
|
|
req.on("error", reject);
|
|
req.on("timeout", () => {
|
|
req.destroy();
|
|
reject(new Error("timeout"));
|
|
});
|
|
if (body) {
|
|
req.end(body);
|
|
} else {
|
|
req.end();
|
|
}
|
|
});
|
|
}
|
|
|
|
async function resolveAndCacheEmail(ctx, payload) {
|
|
try {
|
|
const pingUrl = ctx.mem0BaseUrl.replace(/\/+$/, "") + "/v1/ping/";
|
|
const data = await httpsRequest(pingUrl, "GET", {
|
|
Authorization: "Token " + ctx.mem0ApiKey,
|
|
"Content-Type": "application/json",
|
|
});
|
|
if (data.user_email) {
|
|
payload.distinct_id = data.user_email;
|
|
cacheEmail(ctx.configPath, data.user_email);
|
|
}
|
|
} catch {
|
|
// silently swallow
|
|
}
|
|
}
|
|
|
|
function cacheEmail(configPath, email) {
|
|
if (!configPath) return;
|
|
try {
|
|
const raw = fs.readFileSync(configPath, "utf-8");
|
|
const cfg = JSON.parse(raw);
|
|
if (!cfg.platform) cfg.platform = {};
|
|
cfg.platform.user_email = email;
|
|
fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
|
|
} catch {
|
|
// silently swallow
|
|
}
|
|
}
|
|
|
|
async function sendPosthogEvent(posthogHost, payload) {
|
|
try {
|
|
const body = JSON.stringify(payload);
|
|
await httpsRequest(posthogHost, "POST", {
|
|
"Content-Type": "application/json",
|
|
"Content-Length": Buffer.byteLength(body),
|
|
}, body);
|
|
} catch {
|
|
// silently swallow
|
|
}
|
|
}
|
|
|
|
async function sendIdentifyEvent(ctx, payload, anonId) {
|
|
const identifyPayload = {
|
|
api_key: payload.api_key,
|
|
event: "$identify",
|
|
distinct_id: payload.distinct_id,
|
|
properties: {
|
|
$anon_distinct_id: anonId,
|
|
$lib: (payload.properties && payload.properties.$lib) || "posthog-node",
|
|
},
|
|
};
|
|
await sendPosthogEvent(ctx.posthogHost, identifyPayload);
|
|
}
|
|
|
|
async function main() {
|
|
const ctx = await loadContext();
|
|
const payload = ctx.payload;
|
|
|
|
if (ctx.needsEmail && ctx.mem0ApiKey) {
|
|
await resolveAndCacheEmail(ctx, payload);
|
|
}
|
|
|
|
// Fire $identify *after* email resolution so PostHog links the stored
|
|
// anonymous id directly to the final identity (email, not the api-key
|
|
// hash). The regular event is sent next so it lands under the merged
|
|
// profile.
|
|
if (ctx.anonDistinctIdToAlias) {
|
|
await sendIdentifyEvent(ctx, payload, ctx.anonDistinctIdToAlias);
|
|
}
|
|
|
|
await sendPosthogEvent(ctx.posthogHost, payload);
|
|
}
|
|
|
|
main().catch(() => {});
|