@memberjunction/ai
Advanced tools
| import { BaseSingleton } from '@memberjunction/global'; | ||
| /** | ||
| * The URL path MJAPI's realtime proxy listens on for websocket upgrades, and that provider drivers | ||
| * embed in the browser-facing `wss://<mjapi-public><REALTIME_PROXY_PATH>?ticket=<id>` URL. Shared here | ||
| * (in Core) so the minting side (driver) and the serving side (MJServer proxy) can never drift. | ||
| */ | ||
| export declare const REALTIME_PROXY_PATH = "/realtime-proxy"; | ||
| /** | ||
| * A short-lived, one-time authorization to open ONE upstream realtime websocket through the | ||
| * MJAPI realtime proxy. Stored server-side only — the upstream URL and (optional) auth header | ||
| * NEVER leave the server; the browser only ever receives the opaque ticket id embedded in the | ||
| * proxy URL it connects to. | ||
| * | ||
| * This is the seam that lets a **self-hosted** realtime provider (e.g. HuggingFace | ||
| * speech-to-speech) participate in the shipped client-direct audio topology WITHOUT exposing | ||
| * the internal endpoint to the browser: the provider driver mints a ticket (via | ||
| * {@link RealtimeProxyRegistry.Issue}) pointing at the internal endpoint, hands the browser a | ||
| * `wss://<mjapi-public>/realtime-proxy?ticket=<id>` URL, and MJAPI's proxy consumes the ticket | ||
| * ({@link RealtimeProxyRegistry.Consume}) to open the authenticated upstream leg and pump frames. | ||
| */ | ||
| export interface RealtimeProxyTicketEntry { | ||
| /** The internal upstream websocket URL to tunnel to (e.g. `ws://hf-s2s.internal:8000/v1/realtime`). */ | ||
| UpstreamUrl: string; | ||
| /** | ||
| * Full `Authorization` header value applied on the UPSTREAM socket only (e.g. `Bearer <key>`). | ||
| * Optional — many self-hosted endpoints are unauthenticated. Never serialized to the browser. | ||
| */ | ||
| UpstreamAuthHeader?: string; | ||
| /** The MJ user this ticket was minted for (for audit / optional validation at consume time). */ | ||
| UserID?: string; | ||
| /** Epoch-ms after which the ticket is invalid. Enforced on {@link RealtimeProxyRegistry.Consume}. */ | ||
| ExpiresAtMs: number; | ||
| } | ||
| /** The parameters for minting a proxy ticket via {@link RealtimeProxyRegistry.Issue}. */ | ||
| export interface RealtimeProxyIssueParams { | ||
| /** The internal upstream websocket URL to tunnel to. */ | ||
| UpstreamUrl: string; | ||
| /** Full `Authorization` header value for the upstream socket (optional). */ | ||
| UpstreamAuthHeader?: string; | ||
| /** The MJ user the ticket is for (optional). */ | ||
| UserID?: string; | ||
| /** Time-to-live, in seconds, for the ONE upstream open this ticket authorizes. */ | ||
| TTLSeconds: number; | ||
| } | ||
| /** The result of minting a ticket: the opaque id to embed in the browser proxy URL, plus its expiry. */ | ||
| export interface RealtimeProxyTicket { | ||
| /** The opaque, single-use ticket id (a UUID) — embedded in the browser-facing proxy URL. */ | ||
| ID: string; | ||
| /** ISO-8601 timestamp at which the ticket expires. */ | ||
| ExpiresAt: string; | ||
| } | ||
| /** | ||
| * Process-wide, in-memory registry of one-time realtime-proxy tickets (Global Object Store backed | ||
| * via {@link BaseSingleton}, so the provider driver that mints and the MJAPI proxy that consumes | ||
| * share the SAME instance even under bundler code duplication). | ||
| * | ||
| * Intentionally has **no** background timer: entries are pruned lazily on every {@link Issue} / | ||
| * {@link Consume}, so there is nothing to shut down and no `IShutdownable` wiring is required. Tickets | ||
| * are short-lived and single-use — the map never grows unbounded in practice. | ||
| * | ||
| * Deliberately transport-agnostic: it stores plain data (URL + optional auth + expiry) and knows | ||
| * nothing about websockets or any specific provider. The proxy transport lives in MJServer; the | ||
| * mint logic lives in the provider driver. This is the neutral shared state between them. | ||
| */ | ||
| export declare class RealtimeProxyRegistry extends BaseSingleton<RealtimeProxyRegistry> { | ||
| private readonly _tickets; | ||
| protected constructor(); | ||
| /** Process-wide singleton accessor. */ | ||
| static get Instance(): RealtimeProxyRegistry; | ||
| /** | ||
| * Mints a single-use ticket authorizing ONE upstream open within its TTL window, and returns the | ||
| * opaque id to embed in the browser-facing proxy URL. Prunes expired tickets as a side effect. | ||
| */ | ||
| Issue(params: RealtimeProxyIssueParams): RealtimeProxyTicket; | ||
| /** | ||
| * Consumes a ticket by id: returns its entry and DELETES it (single-use), or `null` if the id is | ||
| * unknown or expired. Prunes expired tickets as a side effect. The proxy calls this exactly once, | ||
| * during the websocket upgrade, before opening the upstream leg. | ||
| */ | ||
| Consume(id: string): RealtimeProxyTicketEntry | null; | ||
| /** Current number of live (un-consumed, un-expired) tickets — for diagnostics/tests. */ | ||
| get Count(): number; | ||
| /** Drops every ticket whose window has closed. */ | ||
| private pruneExpired; | ||
| /** Generates an opaque ticket id. Uses the platform crypto UUID (Node 16+ / browsers). */ | ||
| private static newTicketId; | ||
| } | ||
| //# sourceMappingURL=realtimeProxyRegistry.d.ts.map |
| {"version":3,"file":"realtimeProxyRegistry.d.ts","sourceRoot":"","sources":["../../src/generic/realtimeProxyRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,wBAAwB;IACrC,uGAAuG;IACvG,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gGAAgG;IAChG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qGAAqG;IACrG,WAAW,EAAE,MAAM,CAAC;CACvB;AAED,yFAAyF;AACzF,MAAM,WAAW,wBAAwB;IACrC,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,wGAAwG;AACxG,MAAM,WAAW,mBAAmB;IAChC,4FAA4F;IAC5F,EAAE,EAAE,MAAM,CAAC;IACX,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,qBAAsB,SAAQ,aAAa,CAAC,qBAAqB,CAAC;IAC3E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoD;IAE7E,SAAS;IAIT,uCAAuC;IACvC,WAAkB,QAAQ,IAAI,qBAAqB,CAElD;IAED;;;OAGG;IACI,KAAK,CAAC,MAAM,EAAE,wBAAwB,GAAG,mBAAmB;IAanE;;;;OAIG;IACI,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,wBAAwB,GAAG,IAAI;IAgB3D,wFAAwF;IACxF,IAAW,KAAK,IAAI,MAAM,CAGzB;IAED,kDAAkD;IAClD,OAAO,CAAC,YAAY;IASpB,0FAA0F;IAC1F,OAAO,CAAC,MAAM,CAAC,WAAW;CAY7B"} |
| import { BaseSingleton } from '@memberjunction/global'; | ||
| /** | ||
| * The URL path MJAPI's realtime proxy listens on for websocket upgrades, and that provider drivers | ||
| * embed in the browser-facing `wss://<mjapi-public><REALTIME_PROXY_PATH>?ticket=<id>` URL. Shared here | ||
| * (in Core) so the minting side (driver) and the serving side (MJServer proxy) can never drift. | ||
| */ | ||
| export const REALTIME_PROXY_PATH = '/realtime-proxy'; | ||
| /** | ||
| * Process-wide, in-memory registry of one-time realtime-proxy tickets (Global Object Store backed | ||
| * via {@link BaseSingleton}, so the provider driver that mints and the MJAPI proxy that consumes | ||
| * share the SAME instance even under bundler code duplication). | ||
| * | ||
| * Intentionally has **no** background timer: entries are pruned lazily on every {@link Issue} / | ||
| * {@link Consume}, so there is nothing to shut down and no `IShutdownable` wiring is required. Tickets | ||
| * are short-lived and single-use — the map never grows unbounded in practice. | ||
| * | ||
| * Deliberately transport-agnostic: it stores plain data (URL + optional auth + expiry) and knows | ||
| * nothing about websockets or any specific provider. The proxy transport lives in MJServer; the | ||
| * mint logic lives in the provider driver. This is the neutral shared state between them. | ||
| */ | ||
| export class RealtimeProxyRegistry extends BaseSingleton { | ||
| constructor() { | ||
| super(); | ||
| this._tickets = new Map(); | ||
| } | ||
| /** Process-wide singleton accessor. */ | ||
| static get Instance() { | ||
| return super.getInstance(); | ||
| } | ||
| /** | ||
| * Mints a single-use ticket authorizing ONE upstream open within its TTL window, and returns the | ||
| * opaque id to embed in the browser-facing proxy URL. Prunes expired tickets as a side effect. | ||
| */ | ||
| Issue(params) { | ||
| this.pruneExpired(); | ||
| const id = RealtimeProxyRegistry.newTicketId(); | ||
| const expiresAtMs = Date.now() + Math.max(1, params.TTLSeconds) * 1000; | ||
| this._tickets.set(id, { | ||
| UpstreamUrl: params.UpstreamUrl, | ||
| UpstreamAuthHeader: params.UpstreamAuthHeader, | ||
| UserID: params.UserID, | ||
| ExpiresAtMs: expiresAtMs, | ||
| }); | ||
| return { ID: id, ExpiresAt: new Date(expiresAtMs).toISOString() }; | ||
| } | ||
| /** | ||
| * Consumes a ticket by id: returns its entry and DELETES it (single-use), or `null` if the id is | ||
| * unknown or expired. Prunes expired tickets as a side effect. The proxy calls this exactly once, | ||
| * during the websocket upgrade, before opening the upstream leg. | ||
| */ | ||
| Consume(id) { | ||
| this.pruneExpired(); | ||
| if (!id) { | ||
| return null; | ||
| } | ||
| const entry = this._tickets.get(id); | ||
| if (!entry) { | ||
| return null; | ||
| } | ||
| this._tickets.delete(id); // single-use — always removed on first consume | ||
| if (entry.ExpiresAtMs <= Date.now()) { | ||
| return null; // expired between prune and lookup — treat as invalid | ||
| } | ||
| return entry; | ||
| } | ||
| /** Current number of live (un-consumed, un-expired) tickets — for diagnostics/tests. */ | ||
| get Count() { | ||
| this.pruneExpired(); | ||
| return this._tickets.size; | ||
| } | ||
| /** Drops every ticket whose window has closed. */ | ||
| pruneExpired() { | ||
| const now = Date.now(); | ||
| for (const [id, entry] of this._tickets) { | ||
| if (entry.ExpiresAtMs <= now) { | ||
| this._tickets.delete(id); | ||
| } | ||
| } | ||
| } | ||
| /** Generates an opaque ticket id. Uses the platform crypto UUID (Node 16+ / browsers). */ | ||
| static newTicketId() { | ||
| const cryptoObj = globalThis.crypto; | ||
| if (cryptoObj?.randomUUID) { | ||
| return cryptoObj.randomUUID(); | ||
| } | ||
| // Fallback: a UUID-shaped random string (only reached on runtimes without crypto.randomUUID). | ||
| return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { | ||
| const r = Math.floor(Math.random() * 16); | ||
| const v = c === 'x' ? r : (r & 0x3) | 0x8; | ||
| return v.toString(16); | ||
| }); | ||
| } | ||
| } | ||
| //# sourceMappingURL=realtimeProxyRegistry.js.map |
| {"version":3,"file":"realtimeProxyRegistry.js","sourceRoot":"","sources":["../../src/generic/realtimeProxyRegistry.ts"],"names":[],"mappings":"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"} |
+1
-0
@@ -15,2 +15,3 @@ export * from './generic/baseModel.js'; | ||
| export * from './generic/baseRealtime.js'; | ||
| export * from './generic/realtimeProxyRegistry.js'; | ||
| export * from './generic/baseRealtimeChannelServer.js'; | ||
@@ -17,0 +18,0 @@ export * from './generic/errorTypes.js'; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qCAAqC,CAAC;AACpD,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC"} |
+1
-0
@@ -15,2 +15,3 @@ export * from './generic/baseModel.js'; | ||
| export * from './generic/baseRealtime.js'; | ||
| export * from './generic/realtimeProxyRegistry.js'; | ||
| export * from './generic/baseRealtimeChannelServer.js'; | ||
@@ -17,0 +18,0 @@ export * from './generic/errorTypes.js'; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qCAAqC,CAAC;AACpD,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC"} | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC"} |
+2
-2
| { | ||
| "name": "@memberjunction/ai", | ||
| "type": "module", | ||
| "version": "5.47.0", | ||
| "version": "5.48.0", | ||
| "description": "MemberJunction: AI - core components for abstracting LLMs and other AI model types that are usable anywhere without ANY other MJ dependencies past @memberjunction/global which itself has zero additional dependencies.", | ||
@@ -20,3 +20,3 @@ "main": "dist/index.js", | ||
| "dependencies": { | ||
| "@memberjunction/global": "5.47.0", | ||
| "@memberjunction/global": "5.48.0", | ||
| "dotenv": "^17.2.4", | ||
@@ -23,0 +23,0 @@ "rxjs": "^7.8.2" |
322212
4.09%82
5.13%5281
3.57%+ Added
- Removed