@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.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
Logger: () => Logger,
|
|
27
27
|
MitwayBaasClient: () => MitwayBaasClient,
|
|
28
28
|
MitwayBaasError: () => MitwayBaasError,
|
|
29
|
+
Realtime: () => Realtime,
|
|
29
30
|
TokenManager: () => TokenManager,
|
|
30
31
|
createClient: () => createClient,
|
|
31
32
|
default: () => index_default
|
|
@@ -258,6 +259,30 @@ var TokenManager = class {
|
|
|
258
259
|
}
|
|
259
260
|
};
|
|
260
261
|
|
|
262
|
+
// src/lib/auth-envelope.ts
|
|
263
|
+
function normalizeAuthPayload(raw) {
|
|
264
|
+
if (!raw || typeof raw !== "object") return raw;
|
|
265
|
+
const src = raw;
|
|
266
|
+
const out = { ...src };
|
|
267
|
+
let mutated = false;
|
|
268
|
+
if ("access_token" in src && !("accessToken" in src)) {
|
|
269
|
+
out.accessToken = src.access_token;
|
|
270
|
+
delete out.access_token;
|
|
271
|
+
mutated = true;
|
|
272
|
+
}
|
|
273
|
+
if ("csrf_token" in src && !("csrfToken" in src)) {
|
|
274
|
+
out.csrfToken = src.csrf_token;
|
|
275
|
+
delete out.csrf_token;
|
|
276
|
+
mutated = true;
|
|
277
|
+
}
|
|
278
|
+
if ("refresh_token" in src && !("refreshToken" in src)) {
|
|
279
|
+
out.refreshToken = src.refresh_token;
|
|
280
|
+
delete out.refresh_token;
|
|
281
|
+
mutated = true;
|
|
282
|
+
}
|
|
283
|
+
return mutated ? out : raw;
|
|
284
|
+
}
|
|
285
|
+
|
|
261
286
|
// src/lib/http-client.ts
|
|
262
287
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
|
|
263
288
|
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
@@ -453,17 +478,14 @@ var HttpClient = class {
|
|
|
453
478
|
Date.now() - startTime,
|
|
454
479
|
data
|
|
455
480
|
);
|
|
456
|
-
if (data && typeof data === "object" && "error" in data) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
throw error;
|
|
481
|
+
if (data && typeof data === "object" && "error" in data && data.error !== null && typeof data.error === "object") {
|
|
482
|
+
const envErr = data.error;
|
|
483
|
+
throw new MitwayBaasError(
|
|
484
|
+
envErr.message || response.statusText || "Request failed",
|
|
485
|
+
envErr.statusCode || response.status,
|
|
486
|
+
envErr.code || envErr.error || "REQUEST_FAILED",
|
|
487
|
+
envErr.nextActions
|
|
488
|
+
);
|
|
467
489
|
}
|
|
468
490
|
throw new MitwayBaasError(
|
|
469
491
|
`Request failed: ${response.statusText}`,
|
|
@@ -478,6 +500,9 @@ var HttpClient = class {
|
|
|
478
500
|
Date.now() - startTime,
|
|
479
501
|
data
|
|
480
502
|
);
|
|
503
|
+
if (data && typeof data === "object" && "data" in data && "error" in data && data.error === null) {
|
|
504
|
+
return data.data;
|
|
505
|
+
}
|
|
481
506
|
return data;
|
|
482
507
|
} catch (err) {
|
|
483
508
|
if (timer !== void 0) clearTimeout(timer);
|
|
@@ -590,7 +615,7 @@ var HttpClient = class {
|
|
|
590
615
|
credentials: "include"
|
|
591
616
|
}
|
|
592
617
|
);
|
|
593
|
-
return response;
|
|
618
|
+
return normalizeAuthPayload(response);
|
|
594
619
|
} finally {
|
|
595
620
|
this.isRefreshing = false;
|
|
596
621
|
this.refreshPromise = null;
|
|
@@ -649,11 +674,12 @@ var Auth = class {
|
|
|
649
674
|
*/
|
|
650
675
|
async signUp(request) {
|
|
651
676
|
try {
|
|
652
|
-
const
|
|
677
|
+
const raw = await this.http.post(
|
|
653
678
|
"/api/auth/register",
|
|
654
679
|
request,
|
|
655
680
|
{ credentials: "include" }
|
|
656
681
|
);
|
|
682
|
+
const response = normalizeAuthPayload(raw);
|
|
657
683
|
if (response?.accessToken && response.user) {
|
|
658
684
|
this.saveSessionFromResponse(response);
|
|
659
685
|
}
|
|
@@ -667,11 +693,12 @@ var Auth = class {
|
|
|
667
693
|
*/
|
|
668
694
|
async signInWithPassword(request) {
|
|
669
695
|
try {
|
|
670
|
-
const
|
|
696
|
+
const raw = await this.http.post(
|
|
671
697
|
"/api/auth/login",
|
|
672
698
|
request,
|
|
673
699
|
{ credentials: "include" }
|
|
674
700
|
);
|
|
701
|
+
const response = normalizeAuthPayload(raw);
|
|
675
702
|
if (response?.accessToken && response.user) {
|
|
676
703
|
this.saveSessionFromResponse(response);
|
|
677
704
|
}
|
|
@@ -862,18 +889,239 @@ var Database = class {
|
|
|
862
889
|
}
|
|
863
890
|
};
|
|
864
891
|
|
|
892
|
+
// src/modules/realtime.ts
|
|
893
|
+
var import_socket = require("socket.io-client");
|
|
894
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
895
|
+
var Realtime = class {
|
|
896
|
+
socket = null;
|
|
897
|
+
baseUrl;
|
|
898
|
+
options;
|
|
899
|
+
anonKey;
|
|
900
|
+
tokenManager;
|
|
901
|
+
listeners = /* @__PURE__ */ new Map();
|
|
902
|
+
reserved = {
|
|
903
|
+
connect: /* @__PURE__ */ new Set(),
|
|
904
|
+
disconnect: /* @__PURE__ */ new Set(),
|
|
905
|
+
connect_error: /* @__PURE__ */ new Set(),
|
|
906
|
+
error: /* @__PURE__ */ new Set()
|
|
907
|
+
};
|
|
908
|
+
connecting = null;
|
|
909
|
+
subscribedChannels = /* @__PURE__ */ new Set();
|
|
910
|
+
constructor(baseUrl, tokenManager, anonKey, options = {}) {
|
|
911
|
+
this.baseUrl = baseUrl;
|
|
912
|
+
this.tokenManager = tokenManager;
|
|
913
|
+
this.anonKey = anonKey;
|
|
914
|
+
this.options = options;
|
|
915
|
+
}
|
|
916
|
+
// -----------------------------------------------------------------
|
|
917
|
+
// Connection lifecycle
|
|
918
|
+
// -----------------------------------------------------------------
|
|
919
|
+
get isConnected() {
|
|
920
|
+
return this.socket?.connected === true;
|
|
921
|
+
}
|
|
922
|
+
get socketId() {
|
|
923
|
+
return this.socket?.id;
|
|
924
|
+
}
|
|
925
|
+
/** Explicitly open the connection. Safe to call multiple times; only
|
|
926
|
+
* opens one socket per instance. Subsequent calls during connection
|
|
927
|
+
* return the same in-flight promise. */
|
|
928
|
+
connect() {
|
|
929
|
+
if (this.isConnected) {
|
|
930
|
+
return Promise.resolve();
|
|
931
|
+
}
|
|
932
|
+
if (this.connecting) {
|
|
933
|
+
return this.connecting;
|
|
934
|
+
}
|
|
935
|
+
this.connecting = this.openSocket();
|
|
936
|
+
return this.connecting;
|
|
937
|
+
}
|
|
938
|
+
openSocket() {
|
|
939
|
+
const token = this.tokenManager.getAccessToken() ?? this.anonKey;
|
|
940
|
+
if (!token) {
|
|
941
|
+
const err = new MitwayBaasError(
|
|
942
|
+
"Realtime requires an access token or anonKey",
|
|
943
|
+
401,
|
|
944
|
+
"AUTH_INVALID_API_KEY"
|
|
945
|
+
);
|
|
946
|
+
this.connecting = null;
|
|
947
|
+
return Promise.reject(err);
|
|
948
|
+
}
|
|
949
|
+
const timeoutMs = this.options.timeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
950
|
+
const socket = (0, import_socket.io)(this.baseUrl, {
|
|
951
|
+
path: this.options.path,
|
|
952
|
+
transports: this.options.transports ?? ["websocket"],
|
|
953
|
+
auth: { token, ...this.options.extraAuth ?? {} },
|
|
954
|
+
reconnection: true,
|
|
955
|
+
timeout: timeoutMs
|
|
956
|
+
});
|
|
957
|
+
this.socket = socket;
|
|
958
|
+
socket.onAny((event, ...args) => this.dispatch(event, args));
|
|
959
|
+
socket.on("connect", () => this.emitReserved("connect"));
|
|
960
|
+
socket.on("disconnect", (reason) => this.emitReserved("disconnect", reason));
|
|
961
|
+
socket.on("connect_error", (err) => this.emitReserved("connect_error", err));
|
|
962
|
+
return new Promise((resolve, reject) => {
|
|
963
|
+
const timer = setTimeout(() => {
|
|
964
|
+
socket.off("connect", onConnect);
|
|
965
|
+
socket.off("connect_error", onConnectError);
|
|
966
|
+
reject(
|
|
967
|
+
new MitwayBaasError(
|
|
968
|
+
`Realtime connection timeout after ${timeoutMs}ms`,
|
|
969
|
+
408,
|
|
970
|
+
"CONNECTION_TIMEOUT"
|
|
971
|
+
)
|
|
972
|
+
);
|
|
973
|
+
this.connecting = null;
|
|
974
|
+
}, timeoutMs);
|
|
975
|
+
const clear = () => {
|
|
976
|
+
clearTimeout(timer);
|
|
977
|
+
socket.off("connect", onConnect);
|
|
978
|
+
socket.off("connect_error", onConnectError);
|
|
979
|
+
};
|
|
980
|
+
const onConnect = () => {
|
|
981
|
+
clear();
|
|
982
|
+
this.connecting = null;
|
|
983
|
+
resolve();
|
|
984
|
+
};
|
|
985
|
+
const onConnectError = (err) => {
|
|
986
|
+
clear();
|
|
987
|
+
this.connecting = null;
|
|
988
|
+
reject(
|
|
989
|
+
new MitwayBaasError(err.message, 0, "CONNECTION_FAILED")
|
|
990
|
+
);
|
|
991
|
+
};
|
|
992
|
+
socket.once("connect", onConnect);
|
|
993
|
+
socket.once("connect_error", onConnectError);
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
/** Close the socket and clear in-memory subscription state. Reserved
|
|
997
|
+
* listeners survive so callers can reconnect later via `connect()`. */
|
|
998
|
+
disconnect() {
|
|
999
|
+
if (!this.socket) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
this.socket.disconnect();
|
|
1003
|
+
this.socket = null;
|
|
1004
|
+
this.subscribedChannels.clear();
|
|
1005
|
+
}
|
|
1006
|
+
// -----------------------------------------------------------------
|
|
1007
|
+
// Subscribe / Unsubscribe / Publish
|
|
1008
|
+
// -----------------------------------------------------------------
|
|
1009
|
+
async subscribe(channel) {
|
|
1010
|
+
await this.connect();
|
|
1011
|
+
const socket = this.socket;
|
|
1012
|
+
if (!socket) {
|
|
1013
|
+
return {
|
|
1014
|
+
ok: false,
|
|
1015
|
+
channel,
|
|
1016
|
+
error: { code: "NOT_CONNECTED", message: "Socket is not connected" }
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
const result = await new Promise((resolve) => {
|
|
1020
|
+
socket.emit("realtime:subscribe", { channel }, (ack) => {
|
|
1021
|
+
resolve(ack);
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
if (result.ok) {
|
|
1025
|
+
this.subscribedChannels.add(channel);
|
|
1026
|
+
}
|
|
1027
|
+
return result;
|
|
1028
|
+
}
|
|
1029
|
+
/** Fire-and-forget. No ack from the server. */
|
|
1030
|
+
unsubscribe(channel) {
|
|
1031
|
+
this.subscribedChannels.delete(channel);
|
|
1032
|
+
this.socket?.emit("realtime:unsubscribe", { channel });
|
|
1033
|
+
}
|
|
1034
|
+
/** Publish via the Socket.IO transport. Subject to RLS INSERT policy
|
|
1035
|
+
* on `realtime.messages` (disabled by default — the developer must
|
|
1036
|
+
* add a policy before clients can publish). Returns immediately; any
|
|
1037
|
+
* server rejection comes through the `error` reserved event. */
|
|
1038
|
+
publish(channel, event, payload) {
|
|
1039
|
+
this.socket?.emit("realtime:publish", { channel, event, payload });
|
|
1040
|
+
}
|
|
1041
|
+
// TypeScript overload impl signature must be assignable from every
|
|
1042
|
+
// public overload. The public overloads take arg lists of different
|
|
1043
|
+
// shapes (ConnectionListener: 0 args, DisconnectListener: 1 string
|
|
1044
|
+
// arg, RealtimeListener: 2 args), so the implementation uses the
|
|
1045
|
+
// widest possible signature. This matches the pattern in socket.io
|
|
1046
|
+
// itself and is the standard TypeScript overload idiom.
|
|
1047
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1048
|
+
on(event, cb) {
|
|
1049
|
+
if (isReserved(event)) {
|
|
1050
|
+
this.reserved[event].add(cb);
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (!this.listeners.has(event)) {
|
|
1054
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
1055
|
+
}
|
|
1056
|
+
this.listeners.get(event).add(cb);
|
|
1057
|
+
}
|
|
1058
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1059
|
+
off(event, cb) {
|
|
1060
|
+
if (isReserved(event)) {
|
|
1061
|
+
this.reserved[event].delete(cb);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
this.listeners.get(event)?.delete(cb);
|
|
1065
|
+
}
|
|
1066
|
+
// -----------------------------------------------------------------
|
|
1067
|
+
// Internals
|
|
1068
|
+
// -----------------------------------------------------------------
|
|
1069
|
+
dispatch(event, args) {
|
|
1070
|
+
if (isReserved(event)) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
if (event === "realtime:error") {
|
|
1074
|
+
const err = args[0] ?? {};
|
|
1075
|
+
this.reserved.error.forEach(
|
|
1076
|
+
(cb) => cb(err, {
|
|
1077
|
+
message_id: "",
|
|
1078
|
+
sender_type: "system",
|
|
1079
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1080
|
+
})
|
|
1081
|
+
);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
const set = this.listeners.get(event);
|
|
1085
|
+
if (!set || set.size === 0) {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const envelope = args[0] ?? {};
|
|
1089
|
+
const { meta, ...payload } = envelope;
|
|
1090
|
+
const metaOrStub = meta ?? {
|
|
1091
|
+
message_id: "",
|
|
1092
|
+
sender_type: "system",
|
|
1093
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1094
|
+
};
|
|
1095
|
+
set.forEach((cb) => cb(payload, metaOrStub));
|
|
1096
|
+
}
|
|
1097
|
+
emitReserved(event, ...args) {
|
|
1098
|
+
const set = this.reserved[event];
|
|
1099
|
+
set.forEach((cb) => cb(...args));
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
function isReserved(event) {
|
|
1103
|
+
return event === "connect" || event === "disconnect" || event === "connect_error" || event === "error";
|
|
1104
|
+
}
|
|
1105
|
+
|
|
865
1106
|
// src/client.ts
|
|
866
1107
|
var MitwayBaasClient = class {
|
|
867
1108
|
http;
|
|
868
1109
|
tokenManager;
|
|
869
1110
|
auth;
|
|
870
1111
|
database;
|
|
1112
|
+
realtime;
|
|
871
1113
|
constructor(config = {}) {
|
|
872
1114
|
const logger = new Logger(config.debug);
|
|
873
1115
|
this.tokenManager = new TokenManager();
|
|
874
1116
|
this.http = new HttpClient(config, this.tokenManager, logger);
|
|
875
1117
|
this.auth = new Auth(this.http, this.tokenManager);
|
|
876
1118
|
this.database = new Database(this.http, this.tokenManager, config.anonKey);
|
|
1119
|
+
this.realtime = new Realtime(
|
|
1120
|
+
this.http.baseUrl,
|
|
1121
|
+
this.tokenManager,
|
|
1122
|
+
config.anonKey,
|
|
1123
|
+
config.realtime
|
|
1124
|
+
);
|
|
877
1125
|
}
|
|
878
1126
|
/**
|
|
879
1127
|
* Escape hatch for callers that need to make custom requests against the
|
|
@@ -897,6 +1145,7 @@ var index_default = MitwayBaasClient;
|
|
|
897
1145
|
Logger,
|
|
898
1146
|
MitwayBaasClient,
|
|
899
1147
|
MitwayBaasError,
|
|
1148
|
+
Realtime,
|
|
900
1149
|
TokenManager,
|
|
901
1150
|
createClient
|
|
902
1151
|
});
|