@spacelr/sdk 0.1.8 → 0.1.10

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.mjs CHANGED
@@ -869,6 +869,8 @@ var RealtimeClient = class {
869
869
  // libs/sdk/src/modules/auth.module.ts
870
870
  var AuthModule = class {
871
871
  constructor(http, tokenManager, config) {
872
+ this.stateListeners = /* @__PURE__ */ new Set();
873
+ this.lastEmittedState = null;
872
874
  this.http = http;
873
875
  this.tokenManager = tokenManager;
874
876
  this.config = config;
@@ -881,6 +883,84 @@ var AuthModule = class {
881
883
  expiresAt
882
884
  };
883
885
  });
886
+ this.unsubscribeAuthLost = this.tokenManager.onAuthLost(
887
+ () => this.emitState("unauthenticated")
888
+ );
889
+ }
890
+ /**
891
+ * Returns true if a non-expired access token is currently in storage.
892
+ * Does NOT make a network request — safe for route guards and other
893
+ * hot paths that run on every navigation.
894
+ *
895
+ * A token within the refresh buffer (about to expire) still counts as
896
+ * authenticated because the next protected request will auto-refresh it.
897
+ *
898
+ * Any error from the underlying TokenStorage (corrupt JSON, quota, etc.)
899
+ * is treated as "not authenticated" rather than propagated, so route
900
+ * guards can't be crashed by a misbehaving storage backend.
901
+ */
902
+ async isAuthenticated() {
903
+ try {
904
+ const tokens = await this.tokenManager.getStoredTokens();
905
+ if (!tokens?.accessToken) return false;
906
+ if (tokens.expiresAt && tokens.expiresAt * 1e3 <= Date.now()) return false;
907
+ return true;
908
+ } catch {
909
+ return false;
910
+ }
911
+ }
912
+ /**
913
+ * Subscribe to auth-state transitions. The callback fires:
914
+ * - 'authenticated' after a successful login/register/exchange/2FA-verify
915
+ * - 'unauthenticated' after logout or when a token refresh fails
916
+ *
917
+ * Only fires for transitions that happen after the subscription. If the
918
+ * user is already logged in at subscribe time (e.g. tokens restored from
919
+ * storage on app boot), no 'authenticated' event is emitted — call
920
+ * `isAuthenticated()` once up-front for the initial state.
921
+ *
922
+ * Silent token refreshes do NOT produce an event (auth state is
923
+ * unchanged). Subscribe to `spacelrClient.onTokenRefreshed(...)` if you
924
+ * need to observe successful refreshes.
925
+ *
926
+ * Listener may return `void` or `Promise<void>`. Rejections are swallowed
927
+ * so one broken subscriber can't poison others or the auth flow. The
928
+ * dispatch is fire-and-forget: `logout()` / `login()` resolve as soon as
929
+ * the dispatch loop returns, without awaiting async listeners.
930
+ *
931
+ * Returns an unsubscribe function.
932
+ */
933
+ onAuthStateChange(listener) {
934
+ this.stateListeners.add(listener);
935
+ return () => {
936
+ this.stateListeners.delete(listener);
937
+ };
938
+ }
939
+ /**
940
+ * Detach this AuthModule from the TokenManager. Call when discarding the
941
+ * client (tests, HMR, multi-client setups) to avoid leaking the internal
942
+ * onAuthLost subscription. Idempotent — safe to call more than once.
943
+ */
944
+ dispose() {
945
+ this.unsubscribeAuthLost();
946
+ this.unsubscribeAuthLost = () => {
947
+ };
948
+ this.stateListeners.clear();
949
+ this.lastEmittedState = null;
950
+ }
951
+ emitState(state) {
952
+ if (state === this.lastEmittedState) return;
953
+ this.lastEmittedState = state;
954
+ for (const listener of this.stateListeners) {
955
+ try {
956
+ const result = listener(state);
957
+ if (result && typeof result.then === "function") {
958
+ result.then(void 0, () => {
959
+ });
960
+ }
961
+ } catch {
962
+ }
963
+ }
884
964
  }
885
965
  async login(params) {
886
966
  const response = await this.http.request({
@@ -926,6 +1006,7 @@ var AuthModule = class {
926
1006
  } catch {
927
1007
  }
928
1008
  await this.tokenManager.clearTokens();
1009
+ this.emitState("unauthenticated");
929
1010
  }
930
1011
  async verifyEmail(token) {
931
1012
  return this.http.request({
@@ -996,6 +1077,7 @@ var AuthModule = class {
996
1077
  refreshToken: response.refresh_token,
997
1078
  expiresAt
998
1079
  });
1080
+ this.emitState("authenticated");
999
1081
  return response;
1000
1082
  }
1001
1083
  async generatePKCE() {
@@ -1078,6 +1160,7 @@ var AuthModule = class {
1078
1160
  refreshToken: response.refresh_token,
1079
1161
  expiresAt
1080
1162
  });
1163
+ this.emitState("authenticated");
1081
1164
  }
1082
1165
  async storeTokensFromRegister(response) {
1083
1166
  if (!response.access_token) return;
@@ -1085,6 +1168,7 @@ var AuthModule = class {
1085
1168
  accessToken: response.access_token,
1086
1169
  refreshToken: response.refresh_token
1087
1170
  });
1171
+ this.emitState("authenticated");
1088
1172
  }
1089
1173
  };
1090
1174
 
@@ -1656,19 +1740,32 @@ var FunctionsModule = class {
1656
1740
  this.http = http;
1657
1741
  }
1658
1742
  /**
1659
- * Invoke a function via webhook trigger.
1660
- * Calls POST /api/v1/functions/:projectId/:functionId/invoke
1661
- * with X-Webhook-Secret header.
1743
+ * Invoke a function.
1744
+ * Calls POST `/functions/:projectId/:functionId/invoke`, resolved against
1745
+ * `config.apiUrl` (which already carries the `/api/v1` prefix).
1746
+ *
1747
+ * Auth defaults, based on `invokeMode` semantics:
1748
+ * - webhook: pass `secret` → Authorization is NOT attached
1749
+ * - authenticated: pass nothing → Authorization IS attached (from token manager)
1750
+ * - public: pass nothing → Authorization is attached if logged in, else omitted
1751
+ * - hybrid: pass both `secret` and `authenticated: true`
1752
+ *
1753
+ * To force a specific behaviour, set `authenticated` explicitly — it wins
1754
+ * over the `secret`-based default.
1662
1755
  */
1663
- async invoke(projectId, functionId, options) {
1756
+ async invoke(projectId, functionId, options = {}) {
1757
+ const hasSecret = (options.secret?.length ?? 0) > 0;
1758
+ const headers = {};
1759
+ if (hasSecret) {
1760
+ headers["X-Webhook-Secret"] = options.secret;
1761
+ }
1762
+ const authenticated = options.authenticated ?? !hasSecret;
1664
1763
  return this.http.request({
1665
1764
  method: "POST",
1666
- path: `/api/v1/functions/${encodeURIComponent(projectId)}/${encodeURIComponent(functionId)}/invoke`,
1667
- headers: {
1668
- "X-Webhook-Secret": options.secret
1669
- },
1765
+ path: `/functions/${encodeURIComponent(projectId)}/${encodeURIComponent(functionId)}/invoke`,
1766
+ headers,
1670
1767
  body: options.payload ?? {},
1671
- authenticated: false
1768
+ authenticated
1672
1769
  });
1673
1770
  }
1674
1771
  };