@chittyos/core
Version:
ChittyOS Core - Essential package with ID, auth, verification, beacon tracking, and brand components for all ChittyOS applications
546 lines (544 loc) • 16 kB
JavaScript
// src/registry/index.ts
import { EventEmitter } from "eventemitter3";
import { nanoid as nanoid2 } from "nanoid";
// src/beacon/index.ts
import { nanoid } from "nanoid";
import * as os from "os";
import * as fs from "fs";
import { execSync } from "child_process";
var DEFAULT_CONFIG = {
endpoint: process.env.CHITTY_BEACON_ENDPOINT || "https://beacon.chitty.cc",
interval: parseInt(process.env.CHITTY_BEACON_INTERVAL || "") || 3e5,
enabled: process.env.CHITTY_BEACON_DISABLED !== "true",
silent: process.env.CHITTY_BEACON_VERBOSE !== "true"
};
var config = { ...DEFAULT_CONFIG };
var appInfo = null;
var heartbeatInterval = null;
function configure(customConfig) {
config = { ...config, ...customConfig };
}
function detectApp() {
const app = {
id: generateAppId(),
name: detectAppName(),
version: detectVersion(),
platform: detectPlatform(),
environment: process.env.NODE_ENV || "production",
hostname: os.hostname(),
nodeVersion: process.version,
os: `${os.type()} ${os.release()}`,
hasClaudeCode: detectClaudeCode(),
hasGit: fs.existsSync(".git"),
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
pid: process.pid,
chittyos: {
core: "1.0.0",
modules: detectChittyModules()
}
};
if (app.hasGit) {
try {
app.git = {
branch: execSync("git branch --show-current", { encoding: "utf8" }).trim(),
commit: execSync("git rev-parse --short HEAD", { encoding: "utf8" }).trim(),
remote: execSync("git remote get-url origin", { encoding: "utf8" }).trim()
};
} catch (e) {
}
}
return app;
}
function generateAppId() {
if (config.appId) return config.appId;
if (process.env.REPL_ID) return `replit-${process.env.REPL_ID}`;
if (process.env.GITHUB_REPOSITORY) return `github-${process.env.GITHUB_REPOSITORY.replace("/", "-")}`;
if (process.env.VERCEL_URL) return `vercel-${process.env.VERCEL_URL}`;
if (process.env.HEROKU_APP_NAME) return `heroku-${process.env.HEROKU_APP_NAME}`;
try {
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8"));
return `npm-${pkg.name}-${nanoid(8)}`;
} catch (e) {
return `chitty-${nanoid()}`;
}
}
function detectAppName() {
if (config.appName) return config.appName;
return process.env.CHITTY_APP_NAME || process.env.REPL_SLUG || process.env.GITHUB_REPOSITORY || process.env.VERCEL_URL || process.env.HEROKU_APP_NAME || process.env.npm_package_name || (() => {
try {
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8"));
return pkg.name;
} catch (e) {
return "chittyos-app";
}
})();
}
function detectVersion() {
try {
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8"));
return pkg.version;
} catch (e) {
return "0.0.0";
}
}
function detectPlatform() {
if (process.env.REPL_ID) return "replit";
if (process.env.GITHUB_ACTIONS) return "github-actions";
if (process.env.VERCEL) return "vercel";
if (process.env.NETLIFY) return "netlify";
if (process.env.RENDER) return "render";
if (process.env.HEROKU_APP_NAME) return "heroku";
if (process.env.AWS_LAMBDA_FUNCTION_NAME) return "aws-lambda";
if (process.env.GOOGLE_CLOUD_PROJECT) return "google-cloud";
if (process.env.WEBSITE_INSTANCE_ID) return "azure";
if (process.env.CF_PAGES) return "cloudflare-pages";
if (process.env.CLOUDFLARE_ACCOUNT_ID) return "cloudflare-workers";
return "unknown";
}
function detectClaudeCode() {
return process.env.CLAUDE_CODE === "true" || fs.existsSync(".claude") || fs.existsSync("CLAUDE.md") || fs.existsSync("claude.json");
}
function detectChittyModules() {
const modules = [];
try {
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8"));
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
for (const dep of Object.keys(deps)) {
if (dep.startsWith("@chittyos/") || dep.startsWith("@chittycorp/")) {
modules.push(dep);
}
}
} catch (e) {
}
return modules;
}
async function sendBeacon(event, data) {
if (!config.enabled) return;
const payload = {
...appInfo,
event,
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
uptime: process.uptime(),
...data
};
try {
const response = await fetch(`${config.endpoint}/track`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "@chittyos/core/1.0.0"
},
body: JSON.stringify(payload)
});
if (!config.silent && !response.ok) {
console.log(`[ChittyBeacon] Response: ${response.status}`);
}
} catch (error) {
if (!config.silent) {
console.log(`[ChittyBeacon] Error: ${error.message}`);
}
}
}
function init(customConfig) {
if (customConfig) {
configure(customConfig);
}
if (!config.enabled) {
if (!config.silent) {
console.log("[ChittyBeacon] Disabled");
}
return;
}
appInfo = detectApp();
sendBeacon("startup");
heartbeatInterval = setInterval(() => {
sendBeacon("heartbeat");
}, config.interval);
heartbeatInterval.unref();
const shutdown = () => {
sendBeacon("shutdown");
};
process.once("exit", shutdown);
process.once("SIGINT", shutdown);
process.once("SIGTERM", shutdown);
if (!config.silent) {
console.log(`[ChittyBeacon] Tracking ${appInfo.name} on ${appInfo.platform}`);
}
}
if (process.env.NODE_ENV !== "test") {
init();
}
// src/registry/index.ts
var ServiceRegistry = class extends EventEmitter {
config;
services = /* @__PURE__ */ new Map();
connections = /* @__PURE__ */ new Map();
healthStatus = /* @__PURE__ */ new Map();
healthCheckTimers = /* @__PURE__ */ new Map();
discoveryTimer = null;
constructor(config2 = {}) {
super();
this.config = {
discoveryEndpoint: config2.discoveryEndpoint || process.env.CHITTY_REGISTRY_ENDPOINT || "https://registry.chitty.cc",
healthCheckInterval: config2.healthCheckInterval || 3e4,
connectionTimeout: config2.connectionTimeout || 1e4,
maxRetries: config2.maxRetries || 3,
enableAutoDiscovery: config2.enableAutoDiscovery ?? true
};
if (this.config.enableAutoDiscovery) {
this.startAutoDiscovery();
}
}
/**
* Register a service endpoint
*/
registerService(service) {
const registeredService = {
...service,
id: service.name + "_" + nanoid2(8)
};
try {
const url = new URL(service.url);
registeredService.protocol = url.protocol.replace(":", "");
registeredService.host = url.hostname;
registeredService.port = url.port ? parseInt(url.port) : void 0;
registeredService.path = url.pathname !== "/" ? url.pathname : void 0;
} catch (error) {
console.warn("[Registry] Invalid URL for service:", service.name);
}
this.services.set(registeredService.id, registeredService);
this.emit("service:registered", registeredService);
this.startHealthCheck(registeredService.id);
sendBeacon("registry_service_registered", {
serviceId: registeredService.id,
name: registeredService.name,
type: registeredService.type
});
return registeredService;
}
/**
* Unregister a service
*/
unregisterService(serviceId) {
const service = this.services.get(serviceId);
if (!service) {
return false;
}
this.stopHealthCheck(serviceId);
for (const [connId, conn] of this.connections) {
if (conn.serviceId === serviceId) {
this.disconnectConnection(connId);
}
}
this.services.delete(serviceId);
this.healthStatus.delete(serviceId);
this.emit("service:unregistered", serviceId);
return true;
}
/**
* Get service by ID or name
*/
getService(idOrName) {
if (this.services.has(idOrName)) {
return this.services.get(idOrName);
}
for (const service of this.services.values()) {
if (service.name === idOrName) {
return service;
}
}
return void 0;
}
/**
* Get all services of a specific type
*/
getServicesByType(type2) {
return Array.from(this.services.values()).filter((s) => s.type === type2);
}
/**
* Establish connection to a service
*/
async connectToService(serviceId, chittyId, metadata) {
const service = this.services.get(serviceId);
if (!service) {
throw new Error(`Service ${serviceId} not found`);
}
const connection = {
id: `conn_${nanoid2()}`,
serviceId,
chittyId,
status: "connecting",
metadata
};
this.connections.set(connection.id, connection);
try {
const startTime = Date.now();
await this.testConnection(service);
const latency = Date.now() - startTime;
connection.status = "connected";
connection.establishedAt = (/* @__PURE__ */ new Date()).toISOString();
connection.latency = latency;
this.connections.set(connection.id, connection);
this.emit("connection:established", connection);
sendBeacon("registry_connection_established", {
serviceId,
chittyId,
latency
});
return connection;
} catch (error) {
connection.status = "error";
connection.error = error.message;
this.connections.set(connection.id, connection);
throw error;
}
}
/**
* Disconnect a connection
*/
disconnectConnection(connectionId) {
const connection = this.connections.get(connectionId);
if (!connection) {
return false;
}
connection.status = "disconnected";
this.emit("connection:lost", connection);
this.connections.delete(connectionId);
return true;
}
/**
* Get connection status
*/
getConnection(connectionId) {
return this.connections.get(connectionId);
}
/**
* Get all connections for a ChittyID
*/
getConnectionsByChittyId(chittyId) {
return Array.from(this.connections.values()).filter((c) => c.chittyId === chittyId);
}
/**
* Test connection to a service
*/
async testConnection(service) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), this.config.connectionTimeout);
try {
const response = await fetch(service.url, {
method: "HEAD",
signal: controller.signal
});
if (!response.ok && response.status !== 405) {
throw new Error(`Service returned ${response.status}`);
}
} catch (error) {
if (error.name === "AbortError") {
throw new Error("Connection timeout");
}
throw error;
} finally {
clearTimeout(timeout);
}
}
/**
* Perform health check on a service
*/
async performHealthCheck(serviceId) {
const service = this.services.get(serviceId);
if (!service) {
return;
}
const health = {
serviceId,
status: "unknown",
lastCheckAt: (/* @__PURE__ */ new Date()).toISOString(),
checks: []
};
try {
const startTime = Date.now();
await this.testConnection(service);
const responseTime = Date.now() - startTime;
health.responseTime = responseTime;
health.status = responseTime < 1e3 ? "healthy" : "degraded";
health.checks.push({
name: "connectivity",
status: "pass",
message: `Response time: ${responseTime}ms`
});
try {
const healthUrl = new URL(service.url);
healthUrl.pathname = "/health";
const healthResponse = await fetch(healthUrl.toString(), {
signal: AbortSignal.timeout(5e3)
});
if (healthResponse.ok) {
const healthData = await healthResponse.json();
if (healthData.status) {
health.status = healthData.status;
}
if (healthData.checks) {
health.checks.push(...healthData.checks);
}
}
} catch (error) {
}
this.healthStatus.set(serviceId, health);
if (health.status === "healthy") {
this.emit("service:healthy", serviceId);
} else if (health.status === "unhealthy") {
this.emit("service:unhealthy", serviceId, "Health check failed");
}
} catch (error) {
health.status = "unhealthy";
health.checks.push({
name: "connectivity",
status: "fail",
message: error.message
});
this.healthStatus.set(serviceId, health);
this.emit("service:unhealthy", serviceId, error.message);
}
}
/**
* Start health checks for a service
*/
startHealthCheck(serviceId) {
this.performHealthCheck(serviceId);
const timer = setInterval(() => {
this.performHealthCheck(serviceId);
}, this.config.healthCheckInterval);
this.healthCheckTimers.set(serviceId, timer);
}
/**
* Stop health checks for a service
*/
stopHealthCheck(serviceId) {
const timer = this.healthCheckTimers.get(serviceId);
if (timer) {
clearInterval(timer);
this.healthCheckTimers.delete(serviceId);
}
}
/**
* Auto-discover services from registry endpoint
*/
async discoverServices() {
if (!this.config.discoveryEndpoint) {
return;
}
try {
const response = await fetch(`${this.config.discoveryEndpoint}/discover`);
if (response.ok) {
const services = await response.json();
for (const service of services) {
if (!this.getService(service.id)) {
this.registerService(service);
}
}
this.emit("discovery:update", services);
}
} catch (error) {
console.error("[Registry] Discovery failed:", error);
}
}
/**
* Start auto-discovery
*/
startAutoDiscovery() {
this.discoverServices();
this.discoveryTimer = setInterval(() => {
this.discoverServices();
}, 6e4);
}
/**
* Stop auto-discovery
*/
stopAutoDiscovery() {
if (this.discoveryTimer) {
clearInterval(this.discoveryTimer);
this.discoveryTimer = null;
}
}
/**
* Get service health status
*/
getHealth(serviceId) {
return this.healthStatus.get(serviceId);
}
/**
* Get all registered services
*/
getAllServices() {
return Array.from(this.services.values());
}
/**
* Get all active connections
*/
getAllConnections() {
return Array.from(this.connections.values());
}
/**
* Get registry statistics
*/
getStats() {
const healthyServices = Array.from(this.healthStatus.values()).filter((h) => h.status === "healthy").length;
const unhealthyServices = Array.from(this.healthStatus.values()).filter((h) => h.status === "unhealthy").length;
const activeConnections = Array.from(this.connections.values()).filter((c) => c.status === "connected").length;
return {
totalServices: this.services.size,
healthyServices,
unhealthyServices,
totalConnections: this.connections.size,
activeConnections
};
}
/**
* Cleanup and shutdown
*/
shutdown() {
for (const serviceId of this.healthCheckTimers.keys()) {
this.stopHealthCheck(serviceId);
}
this.stopAutoDiscovery();
this.services.clear();
this.connections.clear();
this.healthStatus.clear();
}
};
var registryInstance = null;
function getRegistry(config2) {
if (!registryInstance) {
registryInstance = new ServiceRegistry(config2);
}
return registryInstance;
}
function registerService(service) {
return getRegistry().registerService(service);
}
function connectToService(serviceId, chittyId, metadata) {
return getRegistry().connectToService(serviceId, chittyId, metadata);
}
function getService(idOrName) {
return getRegistry().getService(idOrName);
}
function getAllServices() {
return getRegistry().getAllServices();
}
var registry_default = {
getRegistry,
registerService,
connectToService,
getService,
getAllServices,
ServiceRegistry
};
export {
connectToService,
registry_default as default,
getAllServices,
getRegistry,
getService,
registerService
};
//# sourceMappingURL=index.mjs.map