122 lines
4.6 KiB
TypeScript
122 lines
4.6 KiB
TypeScript
// ---------------------------------------------------------------------------
|
|
// OddSockets Client — realtime pub/sub + presence for generated sites
|
|
// ---------------------------------------------------------------------------
|
|
// The realtime leg of the tyga backend bundle (alongside lib/anydb.ts data +
|
|
// lib/tygarun.ts SaaS). Channels, presence and history via OddSockets.
|
|
//
|
|
// This stub ships UNCONFIGURED. When the project connects OddSockets, the
|
|
// builder replaces ODDSOCKETS_CONFIG with real values (like lib/tygarun.ts)
|
|
// and this file is swapped for the real client (oddsocketsService.getClientCode,
|
|
// which imports `oddsockets-js`). Until then isConfigured() is false and the
|
|
// hooks fall back to a LOCAL in-memory demo bus so the UI is alive out of the
|
|
// box. The demo bus is clearly ephemeral — a broken real connect must surface
|
|
// as an error, never be masked by the demo state.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const ODDSOCKETS_CONFIG = {
|
|
// Always connect the manager LB — it assigns the worker and the SDK follows
|
|
// the handoff. NEVER hardcode a worker URL.
|
|
connectUrl: "__ODDSOCKETS_CONNECT_URL__", // e.g. https://connect.oddsockets.tyga.network
|
|
// Per-site key (Authorization: Bearer ak_live_...). Isolation boundary = the
|
|
// key owner (account). Provisioned per site by our reseller rail on connect.
|
|
apiKey: "__ODDSOCKETS_KEY__",
|
|
};
|
|
|
|
export function isConfigured(): boolean {
|
|
return !ODDSOCKETS_CONFIG.apiKey.startsWith("__");
|
|
}
|
|
|
|
export const CONNECT_URL = ODDSOCKETS_CONFIG.connectUrl;
|
|
|
|
export interface PresenceMember {
|
|
id: string;
|
|
state?: Record<string, any>;
|
|
}
|
|
|
|
export interface ChannelMessage {
|
|
id: string;
|
|
data: any;
|
|
at: string;
|
|
}
|
|
|
|
export interface OddSocketsChannel {
|
|
subscribe(cb: (msg: ChannelMessage) => void, opts?: Record<string, any>): () => void;
|
|
publish(data: any, opts?: Record<string, any>): Promise<void>;
|
|
getHistory(opts?: { count?: number; start?: string; end?: string }): Promise<ChannelMessage[]>;
|
|
getPresence(): Promise<PresenceMember[]>;
|
|
updateState(state: Record<string, any>): Promise<void>;
|
|
unsubscribe(): void;
|
|
}
|
|
|
|
export interface OddSocketsClient {
|
|
channel(name: string): OddSocketsChannel;
|
|
connected: boolean;
|
|
}
|
|
|
|
// ── Local in-memory demo bus (used until OddSockets is connected) ────────────
|
|
// Simulates pub/sub + presence within the tab so realtime UIs render live in
|
|
// demo mode. Not shared across clients — purely local, seed/demo only.
|
|
type Listener = (msg: ChannelMessage) => void;
|
|
|
|
function createDemoBus(): OddSocketsClient {
|
|
const listeners: Record<string, Set<Listener>> = {};
|
|
const history: Record<string, ChannelMessage[]> = {};
|
|
const presence: Record<string, Record<string, PresenceMember>> = {};
|
|
const selfId = `demo_${Math.random().toString(36).slice(2, 8)}`;
|
|
|
|
function channel(name: string): OddSocketsChannel {
|
|
listeners[name] = listeners[name] || new Set();
|
|
history[name] = history[name] || [];
|
|
presence[name] = presence[name] || {};
|
|
|
|
return {
|
|
subscribe(cb, _opts) {
|
|
listeners[name].add(cb);
|
|
// announce demo self-presence
|
|
presence[name][selfId] = { id: selfId, state: {} };
|
|
return () => listeners[name].delete(cb);
|
|
},
|
|
async publish(data, _opts) {
|
|
const msg: ChannelMessage = {
|
|
id: `m_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
data,
|
|
at: new Date().toISOString(),
|
|
};
|
|
history[name].push(msg);
|
|
if (history[name].length > 200) history[name].shift();
|
|
listeners[name].forEach((cb) => cb(msg));
|
|
},
|
|
async getHistory(opts = {}) {
|
|
const all = history[name] || [];
|
|
return opts.count ? all.slice(-opts.count) : all.slice();
|
|
},
|
|
async getPresence() {
|
|
return Object.values(presence[name] || {});
|
|
},
|
|
async updateState(state) {
|
|
presence[name][selfId] = { id: selfId, state };
|
|
},
|
|
unsubscribe() {
|
|
listeners[name].clear();
|
|
delete presence[name][selfId];
|
|
},
|
|
};
|
|
}
|
|
|
|
return { channel, connected: false };
|
|
}
|
|
|
|
// Singleton client — demo bus in the stub; the real client (oddsockets-js)
|
|
// replaces this whole file on connect via oddsocketsService.getClientCode.
|
|
const client: OddSocketsClient = createDemoBus();
|
|
|
|
export default client;
|
|
|
|
// Usage:
|
|
// import oddsockets, { isConfigured } from '@/lib/oddsockets';
|
|
// const ch = oddsockets.channel('room:lobby');
|
|
// const off = ch.subscribe((m) => console.log(m.data));
|
|
// await ch.publish({ text: 'hello' });
|
|
// await ch.updateState({ typing: true });
|
|
// const members = await ch.getPresence();
|