learning_ai_invt_trdg/backend/src/services/tradingTelemetry.ts
Saravana Achu Mac 4cfb446f57 feat(backend): WebSocket namespaces, audit persistence, tab flags, telemetry
- Add /trading and /admin named Socket.IO namespaces; root namespace kept for
  backward compat; admin namespace rejects non-admins at connect time
- Wire auditRepository.ts: persist TradeAuditEvent to Cosmos audit-events
  container (best-effort); expose GET /api/admin/audit for admin queries
- Add tradingTelemetry singleton (Node.js Map-based storage adapter); init
  and fatal-error tracking wired in index.ts main()
- Add TAB_MARKETPLACE_ENABLED / TAB_MEMBERSHIP_ENABLED config flags; expose
  tabs.* shape in GET /api/feature-flags response
- Fix SupabaseService URL validation (regex check before createClient)
- Wire check:api-contract and check:audit-repository into npm run test
- Switch @bytelyst/* deps to file:../vendor/* references

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 19:35:00 -04:00

97 lines
3.1 KiB
TypeScript

/**
* Backend telemetry singleton — wraps @bytelyst/telemetry-client for Node.js.
*
* Uses a Map-based storage adapter in place of localStorage (browser/RN default).
* All tracking calls are fire-and-forget; this module never throws.
*
* Initialise once in bootstrap.ts after secrets are resolved:
* import { tradingTelemetry } from './services/tradingTelemetry.js';
* tradingTelemetry.init();
*/
import { createTelemetryClient } from '@bytelyst/telemetry-client';
import type { TelemetryClient, TelemetryStorage } from '@bytelyst/telemetry-client';
import { config } from '../config/index.js';
import { productConfig } from '../../../shared/product.js';
// ---------------------------------------------------------------------------
// Node.js storage adapter — Map replaces localStorage
// ---------------------------------------------------------------------------
const nodeStorage: TelemetryStorage = (() => {
const store = new Map<string, string>();
return {
getItem: (key: string) => store.get(key) ?? null,
setItem: (key: string, value: string) => { store.set(key, value); },
};
})();
// ---------------------------------------------------------------------------
// Singleton client
// ---------------------------------------------------------------------------
const client: TelemetryClient = createTelemetryClient({
productId: productConfig.productId,
baseUrl: config.PLATFORM_API_URL,
platform: 'backend',
channel: 'invttrdg_backend',
transport: 'fetch',
appVersion: productConfig.version,
releaseChannel: process.env.NODE_ENV === 'production' ? 'production' : 'dev',
osFamily: 'node',
osVersion: process.version,
storage: nodeStorage,
// Flush every 60s — matches the trading loop polling interval
flushIntervalMs: 60_000,
maxQueue: 100,
});
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/** Initialise telemetry and start periodic flushing. Call once at startup. */
function init(): void {
try {
client.init();
} catch {
// Non-fatal — telemetry must not prevent trading from starting
}
}
/** Gracefully flush remaining events and stop the flush timer. */
async function shutdown(): Promise<void> {
try {
client.shutdown();
} catch {
// Ignore — we're shutting down anyway
}
}
/**
* Track a backend telemetry event. Never throws.
*
* @param eventType Severity/class: 'info' | 'error' | 'lifecycle' | 'perf'
* @param module Source module label, e.g. 'trading_loop', 'trade_executor'
* @param eventName Specific event, e.g. 'cycle_complete', 'order_filled'
* @param extra Optional extra fields (userId, tags, metrics, message)
*/
function trackEvent(
eventType: string,
module: string,
eventName: string,
extra?: {
feature?: string;
message?: string;
tags?: Record<string, string>;
metrics?: Record<string, number>;
userId?: string;
},
): void {
try {
client.trackEvent(eventType, module, eventName, extra);
} catch {
// Non-fatal
}
}
export const tradingTelemetry = { init, shutdown, trackEvent };