@memberjunction/ai 5.46.0 → 5.48.0

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.
@@ -0,0 +1,88 @@
1
+ import { BaseSingleton } from '@memberjunction/global';
2
+ /**
3
+ * The URL path MJAPI's realtime proxy listens on for websocket upgrades, and that provider drivers
4
+ * embed in the browser-facing `wss://<mjapi-public><REALTIME_PROXY_PATH>?ticket=<id>` URL. Shared here
5
+ * (in Core) so the minting side (driver) and the serving side (MJServer proxy) can never drift.
6
+ */
7
+ export declare const REALTIME_PROXY_PATH = "/realtime-proxy";
8
+ /**
9
+ * A short-lived, one-time authorization to open ONE upstream realtime websocket through the
10
+ * MJAPI realtime proxy. Stored server-side only — the upstream URL and (optional) auth header
11
+ * NEVER leave the server; the browser only ever receives the opaque ticket id embedded in the
12
+ * proxy URL it connects to.
13
+ *
14
+ * This is the seam that lets a **self-hosted** realtime provider (e.g. HuggingFace
15
+ * speech-to-speech) participate in the shipped client-direct audio topology WITHOUT exposing
16
+ * the internal endpoint to the browser: the provider driver mints a ticket (via
17
+ * {@link RealtimeProxyRegistry.Issue}) pointing at the internal endpoint, hands the browser a
18
+ * `wss://<mjapi-public>/realtime-proxy?ticket=<id>` URL, and MJAPI's proxy consumes the ticket
19
+ * ({@link RealtimeProxyRegistry.Consume}) to open the authenticated upstream leg and pump frames.
20
+ */
21
+ export interface RealtimeProxyTicketEntry {
22
+ /** The internal upstream websocket URL to tunnel to (e.g. `ws://hf-s2s.internal:8000/v1/realtime`). */
23
+ UpstreamUrl: string;
24
+ /**
25
+ * Full `Authorization` header value applied on the UPSTREAM socket only (e.g. `Bearer <key>`).
26
+ * Optional — many self-hosted endpoints are unauthenticated. Never serialized to the browser.
27
+ */
28
+ UpstreamAuthHeader?: string;
29
+ /** The MJ user this ticket was minted for (for audit / optional validation at consume time). */
30
+ UserID?: string;
31
+ /** Epoch-ms after which the ticket is invalid. Enforced on {@link RealtimeProxyRegistry.Consume}. */
32
+ ExpiresAtMs: number;
33
+ }
34
+ /** The parameters for minting a proxy ticket via {@link RealtimeProxyRegistry.Issue}. */
35
+ export interface RealtimeProxyIssueParams {
36
+ /** The internal upstream websocket URL to tunnel to. */
37
+ UpstreamUrl: string;
38
+ /** Full `Authorization` header value for the upstream socket (optional). */
39
+ UpstreamAuthHeader?: string;
40
+ /** The MJ user the ticket is for (optional). */
41
+ UserID?: string;
42
+ /** Time-to-live, in seconds, for the ONE upstream open this ticket authorizes. */
43
+ TTLSeconds: number;
44
+ }
45
+ /** The result of minting a ticket: the opaque id to embed in the browser proxy URL, plus its expiry. */
46
+ export interface RealtimeProxyTicket {
47
+ /** The opaque, single-use ticket id (a UUID) — embedded in the browser-facing proxy URL. */
48
+ ID: string;
49
+ /** ISO-8601 timestamp at which the ticket expires. */
50
+ ExpiresAt: string;
51
+ }
52
+ /**
53
+ * Process-wide, in-memory registry of one-time realtime-proxy tickets (Global Object Store backed
54
+ * via {@link BaseSingleton}, so the provider driver that mints and the MJAPI proxy that consumes
55
+ * share the SAME instance even under bundler code duplication).
56
+ *
57
+ * Intentionally has **no** background timer: entries are pruned lazily on every {@link Issue} /
58
+ * {@link Consume}, so there is nothing to shut down and no `IShutdownable` wiring is required. Tickets
59
+ * are short-lived and single-use — the map never grows unbounded in practice.
60
+ *
61
+ * Deliberately transport-agnostic: it stores plain data (URL + optional auth + expiry) and knows
62
+ * nothing about websockets or any specific provider. The proxy transport lives in MJServer; the
63
+ * mint logic lives in the provider driver. This is the neutral shared state between them.
64
+ */
65
+ export declare class RealtimeProxyRegistry extends BaseSingleton<RealtimeProxyRegistry> {
66
+ private readonly _tickets;
67
+ protected constructor();
68
+ /** Process-wide singleton accessor. */
69
+ static get Instance(): RealtimeProxyRegistry;
70
+ /**
71
+ * Mints a single-use ticket authorizing ONE upstream open within its TTL window, and returns the
72
+ * opaque id to embed in the browser-facing proxy URL. Prunes expired tickets as a side effect.
73
+ */
74
+ Issue(params: RealtimeProxyIssueParams): RealtimeProxyTicket;
75
+ /**
76
+ * Consumes a ticket by id: returns its entry and DELETES it (single-use), or `null` if the id is
77
+ * unknown or expired. Prunes expired tickets as a side effect. The proxy calls this exactly once,
78
+ * during the websocket upgrade, before opening the upstream leg.
79
+ */
80
+ Consume(id: string): RealtimeProxyTicketEntry | null;
81
+ /** Current number of live (un-consumed, un-expired) tickets — for diagnostics/tests. */
82
+ get Count(): number;
83
+ /** Drops every ticket whose window has closed. */
84
+ private pruneExpired;
85
+ /** Generates an opaque ticket id. Uses the platform crypto UUID (Node 16+ / browsers). */
86
+ private static newTicketId;
87
+ }
88
+ //# sourceMappingURL=realtimeProxyRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"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"}
@@ -0,0 +1,94 @@
1
+ import { BaseSingleton } from '@memberjunction/global';
2
+ /**
3
+ * The URL path MJAPI's realtime proxy listens on for websocket upgrades, and that provider drivers
4
+ * embed in the browser-facing `wss://<mjapi-public><REALTIME_PROXY_PATH>?ticket=<id>` URL. Shared here
5
+ * (in Core) so the minting side (driver) and the serving side (MJServer proxy) can never drift.
6
+ */
7
+ export const REALTIME_PROXY_PATH = '/realtime-proxy';
8
+ /**
9
+ * Process-wide, in-memory registry of one-time realtime-proxy tickets (Global Object Store backed
10
+ * via {@link BaseSingleton}, so the provider driver that mints and the MJAPI proxy that consumes
11
+ * share the SAME instance even under bundler code duplication).
12
+ *
13
+ * Intentionally has **no** background timer: entries are pruned lazily on every {@link Issue} /
14
+ * {@link Consume}, so there is nothing to shut down and no `IShutdownable` wiring is required. Tickets
15
+ * are short-lived and single-use — the map never grows unbounded in practice.
16
+ *
17
+ * Deliberately transport-agnostic: it stores plain data (URL + optional auth + expiry) and knows
18
+ * nothing about websockets or any specific provider. The proxy transport lives in MJServer; the
19
+ * mint logic lives in the provider driver. This is the neutral shared state between them.
20
+ */
21
+ export class RealtimeProxyRegistry extends BaseSingleton {
22
+ constructor() {
23
+ super();
24
+ this._tickets = new Map();
25
+ }
26
+ /** Process-wide singleton accessor. */
27
+ static get Instance() {
28
+ return super.getInstance();
29
+ }
30
+ /**
31
+ * Mints a single-use ticket authorizing ONE upstream open within its TTL window, and returns the
32
+ * opaque id to embed in the browser-facing proxy URL. Prunes expired tickets as a side effect.
33
+ */
34
+ Issue(params) {
35
+ this.pruneExpired();
36
+ const id = RealtimeProxyRegistry.newTicketId();
37
+ const expiresAtMs = Date.now() + Math.max(1, params.TTLSeconds) * 1000;
38
+ this._tickets.set(id, {
39
+ UpstreamUrl: params.UpstreamUrl,
40
+ UpstreamAuthHeader: params.UpstreamAuthHeader,
41
+ UserID: params.UserID,
42
+ ExpiresAtMs: expiresAtMs,
43
+ });
44
+ return { ID: id, ExpiresAt: new Date(expiresAtMs).toISOString() };
45
+ }
46
+ /**
47
+ * Consumes a ticket by id: returns its entry and DELETES it (single-use), or `null` if the id is
48
+ * unknown or expired. Prunes expired tickets as a side effect. The proxy calls this exactly once,
49
+ * during the websocket upgrade, before opening the upstream leg.
50
+ */
51
+ Consume(id) {
52
+ this.pruneExpired();
53
+ if (!id) {
54
+ return null;
55
+ }
56
+ const entry = this._tickets.get(id);
57
+ if (!entry) {
58
+ return null;
59
+ }
60
+ this._tickets.delete(id); // single-use — always removed on first consume
61
+ if (entry.ExpiresAtMs <= Date.now()) {
62
+ return null; // expired between prune and lookup — treat as invalid
63
+ }
64
+ return entry;
65
+ }
66
+ /** Current number of live (un-consumed, un-expired) tickets — for diagnostics/tests. */
67
+ get Count() {
68
+ this.pruneExpired();
69
+ return this._tickets.size;
70
+ }
71
+ /** Drops every ticket whose window has closed. */
72
+ pruneExpired() {
73
+ const now = Date.now();
74
+ for (const [id, entry] of this._tickets) {
75
+ if (entry.ExpiresAtMs <= now) {
76
+ this._tickets.delete(id);
77
+ }
78
+ }
79
+ }
80
+ /** Generates an opaque ticket id. Uses the platform crypto UUID (Node 16+ / browsers). */
81
+ static newTicketId() {
82
+ const cryptoObj = globalThis.crypto;
83
+ if (cryptoObj?.randomUUID) {
84
+ return cryptoObj.randomUUID();
85
+ }
86
+ // Fallback: a UUID-shaped random string (only reached on runtimes without crypto.randomUUID).
87
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
88
+ const r = Math.floor(Math.random() * 16);
89
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
90
+ return v.toString(16);
91
+ });
92
+ }
93
+ }
94
+ //# sourceMappingURL=realtimeProxyRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtimeProxyRegistry.js","sourceRoot":"","sources":["../../src/generic/realtimeProxyRegistry.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export * from './generic/baseEmbeddings.js';
12
12
  export * from './generic/baseAudio.js';
13
13
  export * from './generic/baseVideo.js';
14
14
  export * from './generic/baseRealtime.js';
15
+ export * from './generic/realtimeProxyRegistry.js';
15
16
  export * from './generic/baseRealtimeChannelServer.js';
16
17
  export * from './generic/errorTypes.js';
17
18
  export * from './generic/errorAnalyzer.js';
@@ -1 +1 @@
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"}
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,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export * from './generic/baseEmbeddings.js';
12
12
  export * from './generic/baseAudio.js';
13
13
  export * from './generic/baseVideo.js';
14
14
  export * from './generic/baseRealtime.js';
15
+ export * from './generic/realtimeProxyRegistry.js';
15
16
  export * from './generic/baseRealtimeChannelServer.js';
16
17
  export * from './generic/errorTypes.js';
17
18
  export * from './generic/errorAnalyzer.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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"}
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,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@memberjunction/ai",
3
3
  "type": "module",
4
- "version": "5.46.0",
4
+ "version": "5.48.0",
5
5
  "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.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "author": "MemberJunction.com",
18
18
  "license": "ISC",
19
19
  "dependencies": {
20
- "@memberjunction/global": "5.46.0",
20
+ "@memberjunction/global": "5.48.0",
21
21
  "dotenv": "^17.2.4",
22
22
  "rxjs": "^7.8.2"
23
23
  },