@mitway/sdk 0.2.1 → 0.2.3
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.
- package/dist/index.cjs +263 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +136 -23
- package/dist/index.d.ts +136 -23
- package/dist/index.js +262 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,135 @@ interface User {
|
|
|
29
29
|
updated_at: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Token Manager for the MITWAY-BaaS SDK.
|
|
34
|
+
*
|
|
35
|
+
* In-memory storage for the access token + user. Browser CSRF token lives
|
|
36
|
+
* in a cookie so the cookie-based refresh flow works across page reloads.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
declare class TokenManager {
|
|
40
|
+
private accessToken;
|
|
41
|
+
private user;
|
|
42
|
+
/** Fired when the access token changes (used by long-lived consumers). */
|
|
43
|
+
onTokenChange: (() => void) | null;
|
|
44
|
+
saveSession(session: AuthSession): void;
|
|
45
|
+
getSession(): AuthSession | null;
|
|
46
|
+
getAccessToken(): string | null;
|
|
47
|
+
setAccessToken(token: string): void;
|
|
48
|
+
getUser(): User | null;
|
|
49
|
+
setUser(user: User): void;
|
|
50
|
+
clearSession(): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Realtime module — Socket.IO client wrapper for MITWAY-BaaS.
|
|
55
|
+
*
|
|
56
|
+
* Provides a thin, typed layer over `socket.io-client` so app code can
|
|
57
|
+
* subscribe / publish / listen without dealing with the underlying
|
|
58
|
+
* transport details. The MITWAY-BaaS backend handles auth (JWT / API
|
|
59
|
+
* key), RLS, rate limiting, and fan-out across replicas — the SDK just
|
|
60
|
+
* opens one socket per `MitwayBaasClient` instance and routes events.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/** Standardized meta envelope emitted by the server on every push. */
|
|
64
|
+
interface RealtimeMessageMeta {
|
|
65
|
+
channel?: string;
|
|
66
|
+
message_id: string;
|
|
67
|
+
sender_type: 'system' | 'user';
|
|
68
|
+
sender_id?: string;
|
|
69
|
+
timestamp: string;
|
|
70
|
+
}
|
|
71
|
+
/** Subscribe ack returned by the server. */
|
|
72
|
+
type SubscribeResult = {
|
|
73
|
+
ok: true;
|
|
74
|
+
channel: string;
|
|
75
|
+
} | {
|
|
76
|
+
ok: false;
|
|
77
|
+
channel: string;
|
|
78
|
+
error: {
|
|
79
|
+
code: string;
|
|
80
|
+
message: string;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
/** Server-pushed unsolicited error. */
|
|
84
|
+
interface RealtimeErrorPayload {
|
|
85
|
+
channel?: string;
|
|
86
|
+
code: string;
|
|
87
|
+
message: string;
|
|
88
|
+
}
|
|
89
|
+
interface RealtimeListener<T = unknown> {
|
|
90
|
+
(payload: T, meta: RealtimeMessageMeta): void;
|
|
91
|
+
}
|
|
92
|
+
type ConnectionListener = () => void;
|
|
93
|
+
type DisconnectListener = (reason: string) => void;
|
|
94
|
+
type ConnectErrorListener = (error: Error) => void;
|
|
95
|
+
interface RealtimeOptions {
|
|
96
|
+
/** Override the path on the server. Defaults to the Socket.IO default. */
|
|
97
|
+
path?: string;
|
|
98
|
+
/** Transport strategy. Defaults to `['websocket']` — modern browsers
|
|
99
|
+
* and Node always support it, no need for the long-polling fallback. */
|
|
100
|
+
transports?: Array<'websocket' | 'polling'>;
|
|
101
|
+
/** Handshake timeout in ms. */
|
|
102
|
+
timeoutMs?: number;
|
|
103
|
+
/** Extra fields merged into socket.handshake.auth. Advanced usage. */
|
|
104
|
+
extraAuth?: Record<string, string>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* MITWAY-BaaS realtime client.
|
|
108
|
+
*
|
|
109
|
+
* Public API mirrors the InsForge realtime SDK for familiarity, but the
|
|
110
|
+
* wire protocol follows our backend (see
|
|
111
|
+
* `MITWAY-BaaS/backend/src/infra/socket/socket.manager.ts`):
|
|
112
|
+
*
|
|
113
|
+
* - Handshake auth uses `auth.token` containing the JWT (preferred)
|
|
114
|
+
* or an opaque API key string.
|
|
115
|
+
* - `realtime:subscribe` / `realtime:unsubscribe` / `realtime:publish`
|
|
116
|
+
* are the client-to-server events.
|
|
117
|
+
* - Server pushes the user-defined `event` name with `{ ...payload,
|
|
118
|
+
* meta }` shape.
|
|
119
|
+
* - `realtime:error` is the unsolicited error channel for publish
|
|
120
|
+
* failures and similar.
|
|
121
|
+
*/
|
|
122
|
+
declare class Realtime {
|
|
123
|
+
private socket;
|
|
124
|
+
private baseUrl;
|
|
125
|
+
private options;
|
|
126
|
+
private anonKey;
|
|
127
|
+
private tokenManager;
|
|
128
|
+
private listeners;
|
|
129
|
+
private reserved;
|
|
130
|
+
private connecting;
|
|
131
|
+
private subscribedChannels;
|
|
132
|
+
constructor(baseUrl: string, tokenManager: TokenManager, anonKey: string | undefined, options?: RealtimeOptions);
|
|
133
|
+
get isConnected(): boolean;
|
|
134
|
+
get socketId(): string | undefined;
|
|
135
|
+
/** Explicitly open the connection. Safe to call multiple times; only
|
|
136
|
+
* opens one socket per instance. Subsequent calls during connection
|
|
137
|
+
* return the same in-flight promise. */
|
|
138
|
+
connect(): Promise<void>;
|
|
139
|
+
private openSocket;
|
|
140
|
+
/** Close the socket and clear in-memory subscription state. Reserved
|
|
141
|
+
* listeners survive so callers can reconnect later via `connect()`. */
|
|
142
|
+
disconnect(): void;
|
|
143
|
+
subscribe(channel: string): Promise<SubscribeResult>;
|
|
144
|
+
/** Fire-and-forget. No ack from the server. */
|
|
145
|
+
unsubscribe(channel: string): void;
|
|
146
|
+
/** Publish via the Socket.IO transport. Subject to RLS INSERT policy
|
|
147
|
+
* on `realtime.messages` (disabled by default — the developer must
|
|
148
|
+
* add a policy before clients can publish). Returns immediately; any
|
|
149
|
+
* server rejection comes through the `error` reserved event. */
|
|
150
|
+
publish(channel: string, event: string, payload: Record<string, unknown>): void;
|
|
151
|
+
on(event: 'connect', cb: ConnectionListener): void;
|
|
152
|
+
on(event: 'disconnect', cb: DisconnectListener): void;
|
|
153
|
+
on(event: 'connect_error', cb: ConnectErrorListener): void;
|
|
154
|
+
on(event: 'error', cb: RealtimeListener<RealtimeErrorPayload>): void;
|
|
155
|
+
on<T = unknown>(event: string, cb: RealtimeListener<T>): void;
|
|
156
|
+
off(event: string, cb: (...args: any[]) => void): void;
|
|
157
|
+
private dispatch;
|
|
158
|
+
private emitReserved;
|
|
159
|
+
}
|
|
160
|
+
|
|
32
161
|
/**
|
|
33
162
|
* MITWAY-BaaS SDK types — only SDK-specific shapes live here.
|
|
34
163
|
* The `User` shape is inlined in `./lib/user` so this package has zero
|
|
@@ -89,6 +218,10 @@ interface MitwayBaasConfig {
|
|
|
89
218
|
* @default true
|
|
90
219
|
*/
|
|
91
220
|
autoRefreshToken?: boolean;
|
|
221
|
+
/**
|
|
222
|
+
* Realtime transport options. See `RealtimeOptions`.
|
|
223
|
+
*/
|
|
224
|
+
realtime?: RealtimeOptions;
|
|
92
225
|
}
|
|
93
226
|
/**
|
|
94
227
|
* Active user session in memory. Mirrors what the auth endpoints return.
|
|
@@ -146,27 +279,6 @@ declare class Logger {
|
|
|
146
279
|
logResponse(method: string, url: string, status: number, durationMs: number, body?: any): void;
|
|
147
280
|
}
|
|
148
281
|
|
|
149
|
-
/**
|
|
150
|
-
* Token Manager for the MITWAY-BaaS SDK.
|
|
151
|
-
*
|
|
152
|
-
* In-memory storage for the access token + user. Browser CSRF token lives
|
|
153
|
-
* in a cookie so the cookie-based refresh flow works across page reloads.
|
|
154
|
-
*/
|
|
155
|
-
|
|
156
|
-
declare class TokenManager {
|
|
157
|
-
private accessToken;
|
|
158
|
-
private user;
|
|
159
|
-
/** Fired when the access token changes (used by long-lived consumers). */
|
|
160
|
-
onTokenChange: (() => void) | null;
|
|
161
|
-
saveSession(session: AuthSession): void;
|
|
162
|
-
getSession(): AuthSession | null;
|
|
163
|
-
getAccessToken(): string | null;
|
|
164
|
-
setAccessToken(token: string): void;
|
|
165
|
-
getUser(): User | null;
|
|
166
|
-
setUser(user: User): void;
|
|
167
|
-
clearSession(): void;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
282
|
/**
|
|
171
283
|
* HttpClient with retry, timeout, abort signal composition, and automatic
|
|
172
284
|
* token refresh on 401 INVALID_TOKEN responses.
|
|
@@ -409,6 +521,7 @@ declare class MitwayBaasClient {
|
|
|
409
521
|
private tokenManager;
|
|
410
522
|
readonly auth: Auth;
|
|
411
523
|
readonly database: Database;
|
|
524
|
+
readonly realtime: Realtime;
|
|
412
525
|
constructor(config?: MitwayBaasConfig);
|
|
413
526
|
/**
|
|
414
527
|
* Escape hatch for callers that need to make custom requests against the
|
|
@@ -423,13 +536,13 @@ declare class MitwayBaasClient {
|
|
|
423
536
|
* Currently ships:
|
|
424
537
|
* - auth (signUp, signInWithPassword, signOut, refreshSession, getSession, getUser)
|
|
425
538
|
* - database (PostgREST-backed query builder via @supabase/postgrest-js)
|
|
539
|
+
* - realtime (Socket.IO transport: subscribe / unsubscribe / publish / on)
|
|
426
540
|
*
|
|
427
541
|
* Not yet included (no backend support):
|
|
428
542
|
* - storage
|
|
429
543
|
* - functions
|
|
430
544
|
* - email
|
|
431
545
|
* - ai
|
|
432
|
-
* - realtime
|
|
433
546
|
*
|
|
434
547
|
* @packageDocumentation
|
|
435
548
|
*/
|
|
@@ -449,4 +562,4 @@ declare class MitwayBaasClient {
|
|
|
449
562
|
*/
|
|
450
563
|
declare function createClient(config: MitwayBaasConfig): MitwayBaasClient;
|
|
451
564
|
|
|
452
|
-
export { type ApiError, Auth, type AuthRefreshResponse, type AuthResponse, type AuthResult, type AuthSession, Database, HttpClient, Logger, MitwayBaasClient, type MitwayBaasConfig, MitwayBaasError, type SignInRequest, type SignUpRequest, TokenManager, type User, createClient, MitwayBaasClient as default };
|
|
565
|
+
export { type ApiError, Auth, type AuthRefreshResponse, type AuthResponse, type AuthResult, type AuthSession, type ConnectErrorListener, type ConnectionListener, Database, type DisconnectListener, HttpClient, Logger, MitwayBaasClient, type MitwayBaasConfig, MitwayBaasError, Realtime, type RealtimeErrorPayload, type RealtimeListener, type RealtimeMessageMeta, type RealtimeOptions, type SignInRequest, type SignUpRequest, type SubscribeResult, TokenManager, type User, createClient, MitwayBaasClient as default };
|
package/dist/index.js
CHANGED
|
@@ -224,6 +224,30 @@ var TokenManager = class {
|
|
|
224
224
|
}
|
|
225
225
|
};
|
|
226
226
|
|
|
227
|
+
// src/lib/auth-envelope.ts
|
|
228
|
+
function normalizeAuthPayload(raw) {
|
|
229
|
+
if (!raw || typeof raw !== "object") return raw;
|
|
230
|
+
const src = raw;
|
|
231
|
+
const out = { ...src };
|
|
232
|
+
let mutated = false;
|
|
233
|
+
if ("access_token" in src && !("accessToken" in src)) {
|
|
234
|
+
out.accessToken = src.access_token;
|
|
235
|
+
delete out.access_token;
|
|
236
|
+
mutated = true;
|
|
237
|
+
}
|
|
238
|
+
if ("csrf_token" in src && !("csrfToken" in src)) {
|
|
239
|
+
out.csrfToken = src.csrf_token;
|
|
240
|
+
delete out.csrf_token;
|
|
241
|
+
mutated = true;
|
|
242
|
+
}
|
|
243
|
+
if ("refresh_token" in src && !("refreshToken" in src)) {
|
|
244
|
+
out.refreshToken = src.refresh_token;
|
|
245
|
+
delete out.refresh_token;
|
|
246
|
+
mutated = true;
|
|
247
|
+
}
|
|
248
|
+
return mutated ? out : raw;
|
|
249
|
+
}
|
|
250
|
+
|
|
227
251
|
// src/lib/http-client.ts
|
|
228
252
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
|
|
229
253
|
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
@@ -419,17 +443,14 @@ var HttpClient = class {
|
|
|
419
443
|
Date.now() - startTime,
|
|
420
444
|
data
|
|
421
445
|
);
|
|
422
|
-
if (data && typeof data === "object" && "error" in data) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
throw error;
|
|
446
|
+
if (data && typeof data === "object" && "error" in data && data.error !== null && typeof data.error === "object") {
|
|
447
|
+
const envErr = data.error;
|
|
448
|
+
throw new MitwayBaasError(
|
|
449
|
+
envErr.message || response.statusText || "Request failed",
|
|
450
|
+
envErr.statusCode || response.status,
|
|
451
|
+
envErr.code || envErr.error || "REQUEST_FAILED",
|
|
452
|
+
envErr.nextActions
|
|
453
|
+
);
|
|
433
454
|
}
|
|
434
455
|
throw new MitwayBaasError(
|
|
435
456
|
`Request failed: ${response.statusText}`,
|
|
@@ -444,6 +465,9 @@ var HttpClient = class {
|
|
|
444
465
|
Date.now() - startTime,
|
|
445
466
|
data
|
|
446
467
|
);
|
|
468
|
+
if (data && typeof data === "object" && "data" in data && "error" in data && data.error === null) {
|
|
469
|
+
return data.data;
|
|
470
|
+
}
|
|
447
471
|
return data;
|
|
448
472
|
} catch (err) {
|
|
449
473
|
if (timer !== void 0) clearTimeout(timer);
|
|
@@ -556,7 +580,7 @@ var HttpClient = class {
|
|
|
556
580
|
credentials: "include"
|
|
557
581
|
}
|
|
558
582
|
);
|
|
559
|
-
return response;
|
|
583
|
+
return normalizeAuthPayload(response);
|
|
560
584
|
} finally {
|
|
561
585
|
this.isRefreshing = false;
|
|
562
586
|
this.refreshPromise = null;
|
|
@@ -615,11 +639,12 @@ var Auth = class {
|
|
|
615
639
|
*/
|
|
616
640
|
async signUp(request) {
|
|
617
641
|
try {
|
|
618
|
-
const
|
|
642
|
+
const raw = await this.http.post(
|
|
619
643
|
"/api/auth/register",
|
|
620
644
|
request,
|
|
621
645
|
{ credentials: "include" }
|
|
622
646
|
);
|
|
647
|
+
const response = normalizeAuthPayload(raw);
|
|
623
648
|
if (response?.accessToken && response.user) {
|
|
624
649
|
this.saveSessionFromResponse(response);
|
|
625
650
|
}
|
|
@@ -633,11 +658,12 @@ var Auth = class {
|
|
|
633
658
|
*/
|
|
634
659
|
async signInWithPassword(request) {
|
|
635
660
|
try {
|
|
636
|
-
const
|
|
661
|
+
const raw = await this.http.post(
|
|
637
662
|
"/api/auth/login",
|
|
638
663
|
request,
|
|
639
664
|
{ credentials: "include" }
|
|
640
665
|
);
|
|
666
|
+
const response = normalizeAuthPayload(raw);
|
|
641
667
|
if (response?.accessToken && response.user) {
|
|
642
668
|
this.saveSessionFromResponse(response);
|
|
643
669
|
}
|
|
@@ -828,18 +854,239 @@ var Database = class {
|
|
|
828
854
|
}
|
|
829
855
|
};
|
|
830
856
|
|
|
857
|
+
// src/modules/realtime.ts
|
|
858
|
+
import { io } from "socket.io-client";
|
|
859
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
860
|
+
var Realtime = class {
|
|
861
|
+
socket = null;
|
|
862
|
+
baseUrl;
|
|
863
|
+
options;
|
|
864
|
+
anonKey;
|
|
865
|
+
tokenManager;
|
|
866
|
+
listeners = /* @__PURE__ */ new Map();
|
|
867
|
+
reserved = {
|
|
868
|
+
connect: /* @__PURE__ */ new Set(),
|
|
869
|
+
disconnect: /* @__PURE__ */ new Set(),
|
|
870
|
+
connect_error: /* @__PURE__ */ new Set(),
|
|
871
|
+
error: /* @__PURE__ */ new Set()
|
|
872
|
+
};
|
|
873
|
+
connecting = null;
|
|
874
|
+
subscribedChannels = /* @__PURE__ */ new Set();
|
|
875
|
+
constructor(baseUrl, tokenManager, anonKey, options = {}) {
|
|
876
|
+
this.baseUrl = baseUrl;
|
|
877
|
+
this.tokenManager = tokenManager;
|
|
878
|
+
this.anonKey = anonKey;
|
|
879
|
+
this.options = options;
|
|
880
|
+
}
|
|
881
|
+
// -----------------------------------------------------------------
|
|
882
|
+
// Connection lifecycle
|
|
883
|
+
// -----------------------------------------------------------------
|
|
884
|
+
get isConnected() {
|
|
885
|
+
return this.socket?.connected === true;
|
|
886
|
+
}
|
|
887
|
+
get socketId() {
|
|
888
|
+
return this.socket?.id;
|
|
889
|
+
}
|
|
890
|
+
/** Explicitly open the connection. Safe to call multiple times; only
|
|
891
|
+
* opens one socket per instance. Subsequent calls during connection
|
|
892
|
+
* return the same in-flight promise. */
|
|
893
|
+
connect() {
|
|
894
|
+
if (this.isConnected) {
|
|
895
|
+
return Promise.resolve();
|
|
896
|
+
}
|
|
897
|
+
if (this.connecting) {
|
|
898
|
+
return this.connecting;
|
|
899
|
+
}
|
|
900
|
+
this.connecting = this.openSocket();
|
|
901
|
+
return this.connecting;
|
|
902
|
+
}
|
|
903
|
+
openSocket() {
|
|
904
|
+
const token = this.tokenManager.getAccessToken() ?? this.anonKey;
|
|
905
|
+
if (!token) {
|
|
906
|
+
const err = new MitwayBaasError(
|
|
907
|
+
"Realtime requires an access token or anonKey",
|
|
908
|
+
401,
|
|
909
|
+
"AUTH_INVALID_API_KEY"
|
|
910
|
+
);
|
|
911
|
+
this.connecting = null;
|
|
912
|
+
return Promise.reject(err);
|
|
913
|
+
}
|
|
914
|
+
const timeoutMs = this.options.timeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
915
|
+
const socket = io(this.baseUrl, {
|
|
916
|
+
path: this.options.path,
|
|
917
|
+
transports: this.options.transports ?? ["websocket"],
|
|
918
|
+
auth: { token, ...this.options.extraAuth ?? {} },
|
|
919
|
+
reconnection: true,
|
|
920
|
+
timeout: timeoutMs
|
|
921
|
+
});
|
|
922
|
+
this.socket = socket;
|
|
923
|
+
socket.onAny((event, ...args) => this.dispatch(event, args));
|
|
924
|
+
socket.on("connect", () => this.emitReserved("connect"));
|
|
925
|
+
socket.on("disconnect", (reason) => this.emitReserved("disconnect", reason));
|
|
926
|
+
socket.on("connect_error", (err) => this.emitReserved("connect_error", err));
|
|
927
|
+
return new Promise((resolve, reject) => {
|
|
928
|
+
const timer = setTimeout(() => {
|
|
929
|
+
socket.off("connect", onConnect);
|
|
930
|
+
socket.off("connect_error", onConnectError);
|
|
931
|
+
reject(
|
|
932
|
+
new MitwayBaasError(
|
|
933
|
+
`Realtime connection timeout after ${timeoutMs}ms`,
|
|
934
|
+
408,
|
|
935
|
+
"CONNECTION_TIMEOUT"
|
|
936
|
+
)
|
|
937
|
+
);
|
|
938
|
+
this.connecting = null;
|
|
939
|
+
}, timeoutMs);
|
|
940
|
+
const clear = () => {
|
|
941
|
+
clearTimeout(timer);
|
|
942
|
+
socket.off("connect", onConnect);
|
|
943
|
+
socket.off("connect_error", onConnectError);
|
|
944
|
+
};
|
|
945
|
+
const onConnect = () => {
|
|
946
|
+
clear();
|
|
947
|
+
this.connecting = null;
|
|
948
|
+
resolve();
|
|
949
|
+
};
|
|
950
|
+
const onConnectError = (err) => {
|
|
951
|
+
clear();
|
|
952
|
+
this.connecting = null;
|
|
953
|
+
reject(
|
|
954
|
+
new MitwayBaasError(err.message, 0, "CONNECTION_FAILED")
|
|
955
|
+
);
|
|
956
|
+
};
|
|
957
|
+
socket.once("connect", onConnect);
|
|
958
|
+
socket.once("connect_error", onConnectError);
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
/** Close the socket and clear in-memory subscription state. Reserved
|
|
962
|
+
* listeners survive so callers can reconnect later via `connect()`. */
|
|
963
|
+
disconnect() {
|
|
964
|
+
if (!this.socket) {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
this.socket.disconnect();
|
|
968
|
+
this.socket = null;
|
|
969
|
+
this.subscribedChannels.clear();
|
|
970
|
+
}
|
|
971
|
+
// -----------------------------------------------------------------
|
|
972
|
+
// Subscribe / Unsubscribe / Publish
|
|
973
|
+
// -----------------------------------------------------------------
|
|
974
|
+
async subscribe(channel) {
|
|
975
|
+
await this.connect();
|
|
976
|
+
const socket = this.socket;
|
|
977
|
+
if (!socket) {
|
|
978
|
+
return {
|
|
979
|
+
ok: false,
|
|
980
|
+
channel,
|
|
981
|
+
error: { code: "NOT_CONNECTED", message: "Socket is not connected" }
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
const result = await new Promise((resolve) => {
|
|
985
|
+
socket.emit("realtime:subscribe", { channel }, (ack) => {
|
|
986
|
+
resolve(ack);
|
|
987
|
+
});
|
|
988
|
+
});
|
|
989
|
+
if (result.ok) {
|
|
990
|
+
this.subscribedChannels.add(channel);
|
|
991
|
+
}
|
|
992
|
+
return result;
|
|
993
|
+
}
|
|
994
|
+
/** Fire-and-forget. No ack from the server. */
|
|
995
|
+
unsubscribe(channel) {
|
|
996
|
+
this.subscribedChannels.delete(channel);
|
|
997
|
+
this.socket?.emit("realtime:unsubscribe", { channel });
|
|
998
|
+
}
|
|
999
|
+
/** Publish via the Socket.IO transport. Subject to RLS INSERT policy
|
|
1000
|
+
* on `realtime.messages` (disabled by default — the developer must
|
|
1001
|
+
* add a policy before clients can publish). Returns immediately; any
|
|
1002
|
+
* server rejection comes through the `error` reserved event. */
|
|
1003
|
+
publish(channel, event, payload) {
|
|
1004
|
+
this.socket?.emit("realtime:publish", { channel, event, payload });
|
|
1005
|
+
}
|
|
1006
|
+
// TypeScript overload impl signature must be assignable from every
|
|
1007
|
+
// public overload. The public overloads take arg lists of different
|
|
1008
|
+
// shapes (ConnectionListener: 0 args, DisconnectListener: 1 string
|
|
1009
|
+
// arg, RealtimeListener: 2 args), so the implementation uses the
|
|
1010
|
+
// widest possible signature. This matches the pattern in socket.io
|
|
1011
|
+
// itself and is the standard TypeScript overload idiom.
|
|
1012
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1013
|
+
on(event, cb) {
|
|
1014
|
+
if (isReserved(event)) {
|
|
1015
|
+
this.reserved[event].add(cb);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (!this.listeners.has(event)) {
|
|
1019
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
1020
|
+
}
|
|
1021
|
+
this.listeners.get(event).add(cb);
|
|
1022
|
+
}
|
|
1023
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1024
|
+
off(event, cb) {
|
|
1025
|
+
if (isReserved(event)) {
|
|
1026
|
+
this.reserved[event].delete(cb);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
this.listeners.get(event)?.delete(cb);
|
|
1030
|
+
}
|
|
1031
|
+
// -----------------------------------------------------------------
|
|
1032
|
+
// Internals
|
|
1033
|
+
// -----------------------------------------------------------------
|
|
1034
|
+
dispatch(event, args) {
|
|
1035
|
+
if (isReserved(event)) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
if (event === "realtime:error") {
|
|
1039
|
+
const err = args[0] ?? {};
|
|
1040
|
+
this.reserved.error.forEach(
|
|
1041
|
+
(cb) => cb(err, {
|
|
1042
|
+
message_id: "",
|
|
1043
|
+
sender_type: "system",
|
|
1044
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1045
|
+
})
|
|
1046
|
+
);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
const set = this.listeners.get(event);
|
|
1050
|
+
if (!set || set.size === 0) {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
const envelope = args[0] ?? {};
|
|
1054
|
+
const { meta, ...payload } = envelope;
|
|
1055
|
+
const metaOrStub = meta ?? {
|
|
1056
|
+
message_id: "",
|
|
1057
|
+
sender_type: "system",
|
|
1058
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1059
|
+
};
|
|
1060
|
+
set.forEach((cb) => cb(payload, metaOrStub));
|
|
1061
|
+
}
|
|
1062
|
+
emitReserved(event, ...args) {
|
|
1063
|
+
const set = this.reserved[event];
|
|
1064
|
+
set.forEach((cb) => cb(...args));
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
function isReserved(event) {
|
|
1068
|
+
return event === "connect" || event === "disconnect" || event === "connect_error" || event === "error";
|
|
1069
|
+
}
|
|
1070
|
+
|
|
831
1071
|
// src/client.ts
|
|
832
1072
|
var MitwayBaasClient = class {
|
|
833
1073
|
http;
|
|
834
1074
|
tokenManager;
|
|
835
1075
|
auth;
|
|
836
1076
|
database;
|
|
1077
|
+
realtime;
|
|
837
1078
|
constructor(config = {}) {
|
|
838
1079
|
const logger = new Logger(config.debug);
|
|
839
1080
|
this.tokenManager = new TokenManager();
|
|
840
1081
|
this.http = new HttpClient(config, this.tokenManager, logger);
|
|
841
1082
|
this.auth = new Auth(this.http, this.tokenManager);
|
|
842
1083
|
this.database = new Database(this.http, this.tokenManager, config.anonKey);
|
|
1084
|
+
this.realtime = new Realtime(
|
|
1085
|
+
this.http.baseUrl,
|
|
1086
|
+
this.tokenManager,
|
|
1087
|
+
config.anonKey,
|
|
1088
|
+
config.realtime
|
|
1089
|
+
);
|
|
843
1090
|
}
|
|
844
1091
|
/**
|
|
845
1092
|
* Escape hatch for callers that need to make custom requests against the
|
|
@@ -862,6 +1109,7 @@ export {
|
|
|
862
1109
|
Logger,
|
|
863
1110
|
MitwayBaasClient,
|
|
864
1111
|
MitwayBaasError,
|
|
1112
|
+
Realtime,
|
|
865
1113
|
TokenManager,
|
|
866
1114
|
createClient,
|
|
867
1115
|
index_default as default
|