@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 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
- if (!data.statusCode && !data.status) {
458
- data.statusCode = response.status;
459
- }
460
- const error = MitwayBaasError.fromApiError(data);
461
- Object.keys(data).forEach((key) => {
462
- if (key !== "error" && key !== "message" && key !== "statusCode") {
463
- error[key] = data[key];
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 response = await this.http.post(
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 response = await this.http.post(
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
  });