@pollar/core 0.4.3 → 0.4.5

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.js CHANGED
@@ -661,47 +661,9 @@ var StateStatus = {
661
661
  };
662
662
  var PollarStateVar = {
663
663
  NETWORK: "network",
664
- AUTHENTICATION: "authentication",
665
664
  TRANSACTION: "transaction"
666
665
  };
667
666
  var STATE_VAR_CODES = {
668
- authentication: {
669
- NONE: "NONE",
670
- LOGOUT: "LOGOUT",
671
- CREATE_SESSION_START: "CREATE_SESSION_START",
672
- CREATE_SESSION_ERROR: "CREATE_SESSION_ERROR",
673
- CREATE_SESSION_SUCCESS: "CREATE_SESSION_SUCCESS",
674
- EMAIL_AUTH_START: "EMAIL_AUTH_START",
675
- EMAIL_AUTH_START_ERROR: "EMAIL_AUTH_START_ERROR",
676
- EMAIL_AUTH_START_SUCCESS: "EMAIL_AUTH_START_SUCCESS",
677
- EMAIL_AUTH_CODE_ERROR: "EMAIL_AUTH_CODE_ERROR",
678
- EMAIL_AUTH_CODE_SUCCESS: "EMAIL_AUTH_CODE_SUCCESS",
679
- WALLET_AUTH_START: "WALLET_AUTH_START",
680
- WALLET_AUTH_FREIGHTER_NOT_INSTALLED: "WALLET_AUTH_FREIGHTER_NOT_INSTALLED",
681
- WALLET_AUTH_ALBEDO_NOT_INSTALLED: "WALLET_AUTH_ALBEDO_NOT_INSTALLED",
682
- WALLET_AUTH_CONNECTED: "WALLET_AUTH_CONNECTED",
683
- WALLET_AUTH_LOGIN_START: "WALLET_AUTH_LOGIN_START",
684
- WALLET_AUTH_LOGIN_START_SUCCESS: "WALLET_AUTH_LOGIN_START_SUCCESS",
685
- WALLET_AUTH_LOGIN_START_ERROR: "WALLET_AUTH_LOGIN_START_ERROR",
686
- WALLET_AUTH_ERROR: "WALLET_AUTH_ERROR",
687
- STREAM_POLL_START: "STREAM_POLL_START",
688
- STREAM_POLL_EVENT: "STREAM_POLL_EVENT",
689
- STREAM_POLL_READY: "STREAM_POLL_READY",
690
- FETCH_SESSION_START: "FETCH_SESSION_START",
691
- FETCH_SESSION_SUCCESS: "FETCH_SESSION_SUCCESS",
692
- FETCH_SESSION_ERROR: "FETCH_SESSION_ERROR",
693
- NO_RESTORED_SESSION: "NO_RESTORED_SESSION",
694
- RESTORED_SESSION_SUCCESS: "RESTORED_SESSION_SUCCESS",
695
- RESTORED_SESSION_ERROR: "RESTORED_SESSION_ERROR",
696
- SESSION_STORED: "SESSION_STORED",
697
- ERROR_ABORTED: "ABORTED",
698
- ERROR_UNKNOWN: "ERROR_UNKNOWN"
699
- },
700
- walletAddress: {
701
- NONE: "NONE",
702
- REMOVED_ADDRESS: "REMOVED_ADDRESS",
703
- UPDATED_ADDRESS: "UPDATED_ADDRESS"
704
- },
705
667
  transaction: {
706
668
  NONE: "NONE",
707
669
  BUILD_TRANSACTION_ERROR_NO_WALLET: "BUILD_TRANSACTION_ERROR_NO_WALLET",
@@ -718,183 +680,23 @@ var STATE_VAR_CODES = {
718
680
  }
719
681
  };
720
682
 
721
- // src/client/helpers.ts
722
- var emitResponse = (state, response, success, errorCode, emitLog) => {
723
- const isSuccess = !response.error && !!response.data && !!response.data?.success;
724
- emitLog(
725
- state,
726
- isSuccess ? success.code : errorCode,
727
- isSuccess ? "info" : "error",
728
- isSuccess ? success.status || StateStatus.LOADING : StateStatus.ERROR,
729
- isSuccess ? response.data : response.error
730
- );
731
- return isSuccess;
732
- };
733
-
734
- // src/wallets/FreighterAdapter.ts
735
- var import_freighter_api = __toESM(require_index_min());
736
-
737
- // src/wallets/types.ts
738
- var WalletType = /* @__PURE__ */ ((WalletType2) => {
739
- WalletType2["FREIGHTER"] = "freighter";
740
- WalletType2["ALBEDO"] = "albedo";
741
- return WalletType2;
742
- })(WalletType || {});
743
-
744
- // src/wallets/FreighterAdapter.ts
745
- var FreighterAdapter = class {
746
- constructor() {
747
- this.type = "freighter" /* FREIGHTER */;
748
- }
749
- async isAvailable() {
750
- try {
751
- return await (0, import_freighter_api.isConnected)();
752
- } catch {
753
- return false;
754
- }
755
- }
756
- async connect() {
757
- const connected = await (0, import_freighter_api.isConnected)();
758
- if (!connected) {
759
- throw new Error("Freighter wallet is not installed");
760
- }
761
- const allowed = await (0, import_freighter_api.isAllowed)();
762
- if (!allowed) {
763
- await (0, import_freighter_api.setAllowed)();
764
- }
765
- const userInfo = await (0, import_freighter_api.getUserInfo)();
766
- if (!userInfo?.publicKey) {
767
- throw new Error("Failed to get user information from Freighter");
768
- }
769
- return { address: userInfo.publicKey, publicKey: userInfo.publicKey };
770
- }
771
- async disconnect() {
772
- }
773
- async getPublicKey() {
774
- try {
775
- const allowed = await (0, import_freighter_api.isAllowed)();
776
- if (!allowed) return null;
777
- const userInfo = await (0, import_freighter_api.getUserInfo)();
778
- return userInfo?.publicKey ?? null;
779
- } catch {
780
- return null;
781
- }
782
- }
783
- async getNetwork() {
784
- return (0, import_freighter_api.getNetwork)();
785
- }
786
- async signTransaction(xdr, options) {
787
- const result = await (0, import_freighter_api.signTransaction)(xdr, {
788
- network: options?.network,
789
- networkPassphrase: options?.networkPassphrase,
790
- accountToSign: options?.accountToSign
791
- });
792
- if (!result || typeof result !== "string") {
793
- throw new Error("Invalid response from Freighter");
794
- }
795
- return { signedTxXdr: result };
796
- }
797
- async signAuthEntry(entryXdr, options) {
798
- const result = await (0, import_freighter_api.signAuthEntry)(entryXdr, { accountToSign: options?.accountToSign });
799
- if (!result || typeof result !== "string") {
800
- throw new Error("Invalid response from Freighter");
801
- }
802
- return { signedAuthEntry: result };
803
- }
683
+ // src/types.ts
684
+ var AUTH_ERROR_CODES = {
685
+ SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
686
+ EMAIL_SEND_FAILED: "EMAIL_SEND_FAILED",
687
+ EMAIL_VERIFY_FAILED: "EMAIL_VERIFY_FAILED",
688
+ EMAIL_CODE_EXPIRED: "EMAIL_CODE_EXPIRED",
689
+ EMAIL_CODE_INVALID: "EMAIL_CODE_INVALID",
690
+ AUTH_FAILED: "AUTH_FAILED",
691
+ WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
692
+ WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
693
+ UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
804
694
  };
805
-
806
- // src/wallets/AlbedoAdapter.ts
807
- function openAlbedoPopup(url) {
808
- const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
809
- if (!popup) {
810
- throw new Error("Failed to open Albedo popup (blocked by browser)");
811
- }
812
- return popup;
813
- }
814
- function waitForAlbedoPopup() {
815
- return new Promise((resolve, reject) => {
816
- const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
817
- function handler(event) {
818
- if (event.origin !== window.location.origin || event.data?.type !== "ALBEDO_RESULT") return;
819
- clearTimeout(timeout);
820
- window.removeEventListener("message", handler);
821
- resolve(event.data.payload);
822
- }
823
- window.addEventListener("message", handler);
824
- });
825
- }
826
- function waitForAlbedoResult() {
827
- return new Promise((resolve, reject) => {
828
- const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
829
- const parseResult = () => {
830
- const params = new URLSearchParams(window.location.search);
831
- if (!params.has("pubkey") && !params.has("signed_envelope_xdr") && !params.has("signed_xdr")) return;
832
- clearTimeout(timeout);
833
- const result = {};
834
- params.forEach((value, key) => {
835
- result[key] = value;
836
- });
837
- window.history.replaceState({}, document.title, window.location.pathname);
838
- resolve(result);
839
- };
840
- parseResult();
841
- window.addEventListener("popstate", parseResult);
842
- });
843
- }
844
- var AlbedoAdapter = class {
845
- constructor() {
846
- this.type = "albedo" /* ALBEDO */;
847
- }
848
- async isAvailable() {
849
- return typeof window !== "undefined";
850
- }
851
- async connect() {
852
- const url = new URL("https://albedo.link");
853
- url.searchParams.set("intent", "public-key");
854
- url.searchParams.set("app_name", "Pollar");
855
- url.searchParams.set("network", "testnet");
856
- url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
857
- url.searchParams.set("origin", window.location.origin);
858
- openAlbedoPopup(url.toString());
859
- const result = await waitForAlbedoPopup();
860
- if (!result.pubkey) {
861
- throw new Error("Albedo connection rejected");
862
- }
863
- return { address: result.pubkey, publicKey: result.pubkey };
864
- }
865
- async disconnect() {
866
- }
867
- async getPublicKey() {
868
- return null;
869
- }
870
- async getNetwork() {
871
- throw new Error("Albedo does not expose network");
872
- }
873
- async signTransaction(xdr, _options) {
874
- const url = new URL("https://albedo.link");
875
- url.searchParams.set("intent", "tx");
876
- url.searchParams.set("xdr", xdr);
877
- url.searchParams.set("app_name", "Pollar");
878
- url.searchParams.set("network", "testnet");
879
- url.searchParams.set("callback", window.location.href);
880
- url.searchParams.set("origin", window.location.origin);
881
- window.location.href = url.toString();
882
- const result = await waitForAlbedoResult();
883
- if (!result.signed_envelope_xdr) throw new Error("Albedo signing rejected");
884
- return { signedTxXdr: result.signed_envelope_xdr };
885
- }
886
- async signAuthEntry(entryXdr, _options) {
887
- const url = new URL("https://albedo.link");
888
- url.searchParams.set("intent", "sign-auth-entry");
889
- url.searchParams.set("xdr", entryXdr);
890
- url.searchParams.set("app_name", "Pollar");
891
- url.searchParams.set("network", "testnet");
892
- url.searchParams.set("callback", window.location.href);
893
- url.searchParams.set("origin", window.location.origin);
894
- window.location.href = url.toString();
895
- const result = await waitForAlbedoResult();
896
- if (!result.signed_xdr) throw new Error("Albedo auth entry signing rejected");
897
- return { signedAuthEntry: result.signed_xdr };
695
+ var PollarFlowError = class extends Error {
696
+ constructor(message) {
697
+ super(message);
698
+ this.code = "INVALID_FLOW";
699
+ this.name = "PollarFlowError";
898
700
  }
899
701
  };
900
702
 
@@ -1109,276 +911,552 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1109
911
  }
1110
912
  }
1111
913
 
1112
- // src/client/login.ts
1113
- function withSignal(promise, signal) {
1114
- return Promise.race([
1115
- promise,
1116
- new Promise((_, reject) => {
1117
- if (signal.aborted) {
1118
- reject(new DOMException("Aborted", "AbortError"));
1119
- return;
1120
- }
1121
- signal.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
1122
- })
1123
- ]);
1124
- }
1125
- async function login(options, deps) {
1126
- const { api, basePath, apiKey, signal, emitState, storeSession, clearSession } = deps;
1127
- emitState("authentication", STATE_VAR_CODES.authentication.CREATE_SESSION_START, "info", StateStatus.LOADING);
1128
- const createSessionResponse = await api.POST("/auth/session", { signal });
1129
- if (!emitResponse(
1130
- PollarStateVar.AUTHENTICATION,
1131
- createSessionResponse,
1132
- { code: STATE_VAR_CODES.authentication.CREATE_SESSION_SUCCESS },
1133
- STATE_VAR_CODES.authentication.CREATE_SESSION_ERROR,
1134
- emitState
1135
- )) {
1136
- return;
1137
- }
1138
- const clientSessionId = createSessionResponse.data.content.clientSessionId;
1139
- switch (options.provider) {
1140
- case "email": {
1141
- emitState("authentication", STATE_VAR_CODES.authentication.EMAIL_AUTH_START, "info", StateStatus.LOADING, {
1142
- email: options.email
1143
- });
1144
- const emailRes = await api.POST(`/auth/email`, {
1145
- body: { clientSessionId, email: options.email },
1146
- signal
1147
- });
1148
- if (!emitResponse(
1149
- PollarStateVar.AUTHENTICATION,
1150
- emailRes,
1151
- { code: STATE_VAR_CODES.authentication.EMAIL_AUTH_START_SUCCESS },
1152
- STATE_VAR_CODES.authentication.EMAIL_AUTH_START_ERROR,
1153
- emitState
1154
- )) {
1155
- return;
1156
- }
1157
- break;
1158
- }
1159
- case "google":
1160
- case "github": {
1161
- const url = new URL(`${basePath}/auth/${options.provider}`);
1162
- url.searchParams.set("api_key", apiKey);
1163
- url.searchParams.set("client_session_id", clientSessionId);
1164
- url.searchParams.set("redirect_uri", window.location.origin);
1165
- window.open(url.toString(), "_blank");
1166
- break;
1167
- }
1168
- case "wallet": {
1169
- try {
1170
- emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_START, "info", StateStatus.LOADING, {
1171
- adapter: options.type
1172
- });
1173
- const adapter = options.type === "freighter" /* FREIGHTER */ ? new FreighterAdapter() : new AlbedoAdapter();
1174
- const available = await withSignal(adapter.isAvailable(), signal);
1175
- if (!available) {
1176
- emitState(
1177
- "authentication",
1178
- options.type === "freighter" /* FREIGHTER */ ? STATE_VAR_CODES.authentication.WALLET_AUTH_FREIGHTER_NOT_INSTALLED : STATE_VAR_CODES.authentication.WALLET_AUTH_ALBEDO_NOT_INSTALLED,
1179
- "info",
1180
- StateStatus.LOADING,
1181
- {
1182
- type: options.type
1183
- }
1184
- );
1185
- }
1186
- const { publicKey } = await withSignal(adapter.connect(), signal);
1187
- emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_CONNECTED, "info", StateStatus.LOADING, {
1188
- adapter: options.type,
1189
- publicKey
1190
- });
1191
- emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_LOGIN_START, "info", StateStatus.LOADING, {
1192
- adapter: options.type,
1193
- publicKey
1194
- });
1195
- const emailRes = await api.POST(`/auth/wallet`, {
1196
- body: { clientSessionId, walletAddress: publicKey },
1197
- signal
1198
- });
1199
- if (!emitResponse(
1200
- PollarStateVar.AUTHENTICATION,
1201
- emailRes,
1202
- { code: STATE_VAR_CODES.authentication.WALLET_AUTH_LOGIN_START_SUCCESS },
1203
- STATE_VAR_CODES.authentication.WALLET_AUTH_LOGIN_START_ERROR,
1204
- emitState
1205
- )) {
1206
- return;
1207
- }
1208
- } catch (error2) {
1209
- emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_ERROR, "error", StateStatus.ERROR, {
1210
- clientSessionId
1211
- });
1212
- }
1213
- break;
1214
- }
1215
- }
1216
- emitState("authentication", STATE_VAR_CODES.authentication.STREAM_POLL_START, "info", StateStatus.LOADING, {
1217
- clientSessionId
1218
- });
1219
- await streamUntilFound(
1220
- api,
1221
- clientSessionId,
1222
- (data2) => {
1223
- const status = data2?.status;
1224
- if (status === "READY") {
1225
- emitState("authentication", STATE_VAR_CODES.authentication.STREAM_POLL_READY, "info", StateStatus.LOADING);
1226
- return true;
1227
- }
1228
- emitState(
1229
- "authentication",
1230
- STATE_VAR_CODES.authentication.STREAM_POLL_EVENT + (status ? `/${status}` : ""),
1231
- "info",
1232
- StateStatus.LOADING,
1233
- data2
1234
- );
1235
- return false;
1236
- },
1237
- 200,
1238
- signal
1239
- );
1240
- emitState("authentication", STATE_VAR_CODES.authentication.FETCH_SESSION_START, "info", StateStatus.LOADING);
1241
- const { data, error } = await api.POST(`/auth/login`, {
914
+ // src/client/auth/authenticate.ts
915
+ async function authenticate(clientSessionId, deps) {
916
+ const { api, signal, setAuthState, storeSession, clearSession } = deps;
917
+ setAuthState({ step: "authenticating" });
918
+ await streamUntilFound(api, clientSessionId, (data2) => data2?.status === "READY", 200, signal);
919
+ const { data, error } = await api.POST("/auth/login", {
1242
920
  body: { clientSessionId },
1243
921
  signal
1244
922
  });
1245
923
  if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content)) {
1246
- emitState("authentication", STATE_VAR_CODES.authentication.FETCH_SESSION_SUCCESS, "info", StateStatus.SUCCESS);
1247
924
  storeSession(data.content);
1248
925
  } else {
1249
- emitState("authentication", STATE_VAR_CODES.authentication.FETCH_SESSION_ERROR, "error", StateStatus.ERROR, {
1250
- error,
1251
- data
926
+ setAuthState({
927
+ step: "error",
928
+ previousStep: "authenticating",
929
+ message: "Failed to load session",
930
+ errorCode: AUTH_ERROR_CODES.AUTH_FAILED
1252
931
  });
1253
932
  clearSession();
1254
933
  }
1255
934
  }
1256
935
 
1257
- // src/client/client.ts
1258
- var isBrowser = typeof window !== "undefined" && typeof localStorage !== "undefined";
1259
- function warnServerSide(method) {
1260
- console.warn(
1261
- `[PollarClient] ${method}() called server-side \u2014 browser APIs unavailable. Use PollarClient only in Client Components.`
1262
- );
936
+ // src/client/auth/emailFlow.ts
937
+ async function initEmailSession(deps) {
938
+ const { api, signal, setAuthState } = deps;
939
+ setAuthState({ step: "creating_session" });
940
+ const { data, error } = await api.POST("/auth/session", { signal });
941
+ if (error || !data?.success) {
942
+ setAuthState({
943
+ step: "error",
944
+ previousStep: "creating_session",
945
+ message: "Failed to create session",
946
+ errorCode: AUTH_ERROR_CODES.SESSION_CREATE_FAILED
947
+ });
948
+ return;
949
+ }
950
+ setAuthState({ step: "entering_email", clientSessionId: data.content.clientSessionId });
1263
951
  }
1264
- var PollarClient = class {
1265
- constructor(config) {
1266
- this._session = null;
1267
- this._stateListeners = /* @__PURE__ */ new Set();
1268
- this._state = {
1269
- network: [],
1270
- authentication: [],
1271
- transaction: []
1272
- };
1273
- this.apiKey = config.apiKey;
1274
- this.id = crypto.randomUUID();
1275
- this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
1276
- this._api = createApiClient(this.basePath);
1277
- this._api.use({
1278
- onRequest({ request }) {
1279
- request.headers.set("x-pollar-api-key", config.apiKey);
1280
- return request;
1281
- }
952
+ async function sendEmailCode(email, clientSessionId, deps) {
953
+ const { api, signal, setAuthState } = deps;
954
+ setAuthState({ step: "sending_email", email });
955
+ const { data, error } = await api.POST("/auth/email", {
956
+ body: { clientSessionId, email },
957
+ signal
958
+ });
959
+ if (error || !data?.success) {
960
+ setAuthState({
961
+ step: "error",
962
+ previousStep: "sending_email",
963
+ message: "Failed to send code",
964
+ errorCode: AUTH_ERROR_CODES.EMAIL_SEND_FAILED
1282
965
  });
1283
- if (!isBrowser) {
1284
- warnServerSide("constructor");
1285
- this._session = null;
1286
- return;
1287
- }
1288
- console.info(`[PollarClient] Initialized \u2014 endpoint: ${this.basePath}`);
1289
- this._readStore();
1290
- window.addEventListener("storage", (e) => {
1291
- if (e.key === STORAGE_KEY) {
1292
- const prev = this._session;
1293
- console.info(`[PollarClient] Storage event \u2014 session ${this._session ? "updated" : prev ? "cleared" : "unchanged"}`);
1294
- this._readStore();
966
+ return;
967
+ }
968
+ setAuthState({ step: "entering_code", clientSessionId, email });
969
+ }
970
+ async function verifyAndAuthenticate(code, clientSessionId, email, deps) {
971
+ const { api, signal, setAuthState } = deps;
972
+ setAuthState({ step: "verifying_email_code", clientSessionId, email });
973
+ const { data, error } = await api.POST("/auth/email/verify-code", {
974
+ body: { clientSessionId, code },
975
+ signal
976
+ });
977
+ if (data?.code === "SDK_EMAIL_CODE_VERIFIED") {
978
+ await authenticate(clientSessionId, deps);
979
+ return;
980
+ }
981
+ const errCode = error?.error ?? data?.code;
982
+ if (errCode === "SDK_EMAIL_CODE_EXPIRED") {
983
+ setAuthState({
984
+ step: "error",
985
+ previousStep: "verifying_email_code",
986
+ message: "Code expired \u2014 request a new one",
987
+ errorCode: AUTH_ERROR_CODES.EMAIL_CODE_EXPIRED,
988
+ clientSessionId,
989
+ email
990
+ });
991
+ return;
992
+ }
993
+ if (errCode === "INVALID_EMAIL_CODE" || errCode === "SDK_EMAIL_CODE_INVALID") {
994
+ setAuthState({
995
+ step: "error",
996
+ previousStep: "verifying_email_code",
997
+ message: "Invalid code \u2014 try again",
998
+ errorCode: AUTH_ERROR_CODES.EMAIL_CODE_INVALID,
999
+ clientSessionId,
1000
+ email
1001
+ });
1002
+ return;
1003
+ }
1004
+ setAuthState({
1005
+ step: "error",
1006
+ previousStep: "verifying_email_code",
1007
+ message: "Failed to verify code \u2014 try again",
1008
+ errorCode: AUTH_ERROR_CODES.EMAIL_VERIFY_FAILED
1009
+ });
1010
+ }
1011
+
1012
+ // src/client/auth/oauthFlow.ts
1013
+ async function initOAuthSession(deps) {
1014
+ const { api, signal, setAuthState } = deps;
1015
+ setAuthState({ step: "creating_session" });
1016
+ const { data, error } = await api.POST("/auth/session", { signal });
1017
+ if (error || !data?.success) {
1018
+ setAuthState({
1019
+ step: "error",
1020
+ previousStep: "creating_session",
1021
+ message: "Failed to create session",
1022
+ errorCode: AUTH_ERROR_CODES.SESSION_CREATE_FAILED
1023
+ });
1024
+ return null;
1025
+ }
1026
+ return data.content.clientSessionId;
1027
+ }
1028
+ async function loginOAuth(provider, deps) {
1029
+ const { setAuthState, basePath, apiKey } = deps;
1030
+ const popup = window.open("about:blank", "_blank");
1031
+ const clientSessionId = await initOAuthSession(deps);
1032
+ if (!clientSessionId) {
1033
+ popup?.close();
1034
+ return;
1035
+ }
1036
+ setAuthState({ step: "opening_oauth", provider });
1037
+ const url = new URL(`${basePath}/auth/${provider}`);
1038
+ url.searchParams.set("api_key", apiKey);
1039
+ url.searchParams.set("client_session_id", clientSessionId);
1040
+ url.searchParams.set("redirect_uri", window.location.origin);
1041
+ if (popup) {
1042
+ popup.location.href = url.toString();
1043
+ } else {
1044
+ window.open(url.toString(), "_blank");
1045
+ }
1046
+ await authenticate(clientSessionId, deps);
1047
+ }
1048
+
1049
+ // src/wallets/FreighterAdapter.ts
1050
+ var import_freighter_api = __toESM(require_index_min());
1051
+
1052
+ // src/wallets/types.ts
1053
+ var WalletType = /* @__PURE__ */ ((WalletType2) => {
1054
+ WalletType2["FREIGHTER"] = "freighter";
1055
+ WalletType2["ALBEDO"] = "albedo";
1056
+ return WalletType2;
1057
+ })(WalletType || {});
1058
+
1059
+ // src/wallets/FreighterAdapter.ts
1060
+ var FreighterAdapter = class {
1061
+ constructor() {
1062
+ this.type = "freighter" /* FREIGHTER */;
1063
+ }
1064
+ async isAvailable() {
1065
+ try {
1066
+ return await (0, import_freighter_api.isConnected)();
1067
+ } catch {
1068
+ return false;
1069
+ }
1070
+ }
1071
+ async connect() {
1072
+ const connected = await (0, import_freighter_api.isConnected)();
1073
+ if (!connected) {
1074
+ throw new Error("Freighter wallet is not installed");
1075
+ }
1076
+ const allowed = await (0, import_freighter_api.isAllowed)();
1077
+ if (!allowed) {
1078
+ await (0, import_freighter_api.setAllowed)();
1079
+ }
1080
+ const userInfo = await (0, import_freighter_api.getUserInfo)();
1081
+ if (!userInfo?.publicKey) {
1082
+ throw new Error("Failed to get user information from Freighter");
1083
+ }
1084
+ return { address: userInfo.publicKey, publicKey: userInfo.publicKey };
1085
+ }
1086
+ async disconnect() {
1087
+ }
1088
+ async getPublicKey() {
1089
+ try {
1090
+ const allowed = await (0, import_freighter_api.isAllowed)();
1091
+ if (!allowed) return null;
1092
+ const userInfo = await (0, import_freighter_api.getUserInfo)();
1093
+ return userInfo?.publicKey ?? null;
1094
+ } catch {
1095
+ return null;
1096
+ }
1097
+ }
1098
+ async getNetwork() {
1099
+ return (0, import_freighter_api.getNetwork)();
1100
+ }
1101
+ async signTransaction(xdr, options) {
1102
+ const result = await (0, import_freighter_api.signTransaction)(xdr, {
1103
+ network: options?.network,
1104
+ networkPassphrase: options?.networkPassphrase,
1105
+ accountToSign: options?.accountToSign
1106
+ });
1107
+ if (!result || typeof result !== "string") {
1108
+ throw new Error("Invalid response from Freighter");
1109
+ }
1110
+ return { signedTxXdr: result };
1111
+ }
1112
+ async signAuthEntry(entryXdr, options) {
1113
+ const result = await (0, import_freighter_api.signAuthEntry)(entryXdr, { accountToSign: options?.accountToSign });
1114
+ if (!result || typeof result !== "string") {
1115
+ throw new Error("Invalid response from Freighter");
1116
+ }
1117
+ return { signedAuthEntry: result };
1118
+ }
1119
+ };
1120
+
1121
+ // src/wallets/AlbedoAdapter.ts
1122
+ function openAlbedoPopup(url) {
1123
+ const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
1124
+ if (!popup) {
1125
+ throw new Error("Failed to open Albedo popup (blocked by browser)");
1126
+ }
1127
+ return popup;
1128
+ }
1129
+ function waitForAlbedoPopup() {
1130
+ return new Promise((resolve, reject) => {
1131
+ const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
1132
+ function handler(event) {
1133
+ if (event.origin !== window.location.origin || event.data?.type !== "ALBEDO_RESULT") return;
1134
+ clearTimeout(timeout);
1135
+ window.removeEventListener("message", handler);
1136
+ resolve(event.data.payload);
1137
+ }
1138
+ window.addEventListener("message", handler);
1139
+ });
1140
+ }
1141
+ function waitForAlbedoResult() {
1142
+ return new Promise((resolve, reject) => {
1143
+ const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
1144
+ const parseResult = () => {
1145
+ const params = new URLSearchParams(window.location.search);
1146
+ if (!params.has("pubkey") && !params.has("signed_envelope_xdr") && !params.has("signed_xdr")) return;
1147
+ clearTimeout(timeout);
1148
+ const result = {};
1149
+ params.forEach((value, key) => {
1150
+ result[key] = value;
1151
+ });
1152
+ window.history.replaceState({}, document.title, window.location.pathname);
1153
+ resolve(result);
1154
+ };
1155
+ parseResult();
1156
+ window.addEventListener("popstate", parseResult);
1157
+ });
1158
+ }
1159
+ var AlbedoAdapter = class {
1160
+ constructor() {
1161
+ this.type = "albedo" /* ALBEDO */;
1162
+ }
1163
+ async isAvailable() {
1164
+ return typeof window !== "undefined";
1165
+ }
1166
+ async connect() {
1167
+ const url = new URL("https://albedo.link");
1168
+ url.searchParams.set("intent", "public-key");
1169
+ url.searchParams.set("app_name", "Pollar");
1170
+ url.searchParams.set("network", "testnet");
1171
+ url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
1172
+ url.searchParams.set("origin", window.location.origin);
1173
+ openAlbedoPopup(url.toString());
1174
+ const result = await waitForAlbedoPopup();
1175
+ if (!result.pubkey) {
1176
+ throw new Error("Albedo connection rejected");
1177
+ }
1178
+ return { address: result.pubkey, publicKey: result.pubkey };
1179
+ }
1180
+ async disconnect() {
1181
+ }
1182
+ async getPublicKey() {
1183
+ return null;
1184
+ }
1185
+ async getNetwork() {
1186
+ throw new Error("Albedo does not expose network");
1187
+ }
1188
+ async signTransaction(xdr, _options) {
1189
+ const url = new URL("https://albedo.link");
1190
+ url.searchParams.set("intent", "tx");
1191
+ url.searchParams.set("xdr", xdr);
1192
+ url.searchParams.set("app_name", "Pollar");
1193
+ url.searchParams.set("network", "testnet");
1194
+ url.searchParams.set("callback", window.location.href);
1195
+ url.searchParams.set("origin", window.location.origin);
1196
+ window.location.href = url.toString();
1197
+ const result = await waitForAlbedoResult();
1198
+ if (!result.signed_envelope_xdr) throw new Error("Albedo signing rejected");
1199
+ return { signedTxXdr: result.signed_envelope_xdr };
1200
+ }
1201
+ async signAuthEntry(entryXdr, _options) {
1202
+ const url = new URL("https://albedo.link");
1203
+ url.searchParams.set("intent", "sign-auth-entry");
1204
+ url.searchParams.set("xdr", entryXdr);
1205
+ url.searchParams.set("app_name", "Pollar");
1206
+ url.searchParams.set("network", "testnet");
1207
+ url.searchParams.set("callback", window.location.href);
1208
+ url.searchParams.set("origin", window.location.origin);
1209
+ window.location.href = url.toString();
1210
+ const result = await waitForAlbedoResult();
1211
+ if (!result.signed_xdr) throw new Error("Albedo auth entry signing rejected");
1212
+ return { signedAuthEntry: result.signed_xdr };
1213
+ }
1214
+ };
1215
+
1216
+ // src/client/auth/walletFlow.ts
1217
+ function withSignal(promise, signal) {
1218
+ return Promise.race([
1219
+ promise,
1220
+ new Promise((_, reject) => {
1221
+ if (signal.aborted) {
1222
+ reject(new DOMException("Aborted", "AbortError"));
1223
+ return;
1224
+ }
1225
+ signal.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
1226
+ })
1227
+ ]);
1228
+ }
1229
+ async function loginWallet(type, deps) {
1230
+ const { api, signal, setAuthState } = deps;
1231
+ setAuthState({ step: "creating_session" });
1232
+ const { data, error } = await api.POST("/auth/session", { signal });
1233
+ if (error || !data?.success) {
1234
+ setAuthState({
1235
+ step: "error",
1236
+ previousStep: "creating_session",
1237
+ message: "Failed to create session",
1238
+ errorCode: AUTH_ERROR_CODES.SESSION_CREATE_FAILED
1239
+ });
1240
+ return;
1241
+ }
1242
+ const clientSessionId = data.content.clientSessionId;
1243
+ try {
1244
+ setAuthState({ step: "connecting_wallet", walletType: type });
1245
+ const adapter = type === "freighter" /* FREIGHTER */ ? new FreighterAdapter() : new AlbedoAdapter();
1246
+ const available = await withSignal(adapter.isAvailable(), signal);
1247
+ if (!available) {
1248
+ setAuthState({ step: "wallet_not_installed", walletType: type });
1249
+ return;
1250
+ }
1251
+ const { publicKey } = await withSignal(adapter.connect(), signal);
1252
+ setAuthState({ step: "authenticating_wallet" });
1253
+ const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
1254
+ body: { clientSessionId, walletAddress: publicKey },
1255
+ signal
1256
+ });
1257
+ if (walletError || !walletData?.success) {
1258
+ setAuthState({
1259
+ step: "error",
1260
+ previousStep: "authenticating_wallet",
1261
+ message: "Wallet authentication failed",
1262
+ errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
1263
+ });
1264
+ return;
1265
+ }
1266
+ } catch {
1267
+ setAuthState({
1268
+ step: "error",
1269
+ previousStep: "connecting_wallet",
1270
+ message: "Wallet connection failed",
1271
+ errorCode: AUTH_ERROR_CODES.WALLET_CONNECT_FAILED
1272
+ });
1273
+ return;
1274
+ }
1275
+ await authenticate(clientSessionId, deps);
1276
+ }
1277
+
1278
+ // src/client/helpers.ts
1279
+ var emitResponse = (state, response, success, errorCode, emitLog) => {
1280
+ const isSuccess = !response.error && !!response.data && !!response.data?.success;
1281
+ emitLog(
1282
+ state,
1283
+ isSuccess ? success.code : errorCode,
1284
+ isSuccess ? "info" : "error",
1285
+ isSuccess ? success.status || StateStatus.LOADING : StateStatus.ERROR,
1286
+ isSuccess ? response.data : response.error
1287
+ );
1288
+ return isSuccess;
1289
+ };
1290
+
1291
+ // src/client/client.ts
1292
+ var isBrowser = typeof window !== "undefined" && typeof localStorage !== "undefined";
1293
+ function warnServerSide(method) {
1294
+ console.warn(
1295
+ `[PollarClient] ${method}() called server-side \u2014 browser APIs unavailable. Use PollarClient only in Client Components.`
1296
+ );
1297
+ }
1298
+ var PollarClient = class {
1299
+ constructor(config) {
1300
+ this._session = null;
1301
+ this._stateListeners = /* @__PURE__ */ new Set();
1302
+ this._state = {
1303
+ network: [],
1304
+ transaction: []
1305
+ };
1306
+ this._authState = { step: "idle" };
1307
+ this._authStateListeners = /* @__PURE__ */ new Set();
1308
+ this._loginController = null;
1309
+ this.apiKey = config.apiKey;
1310
+ this.id = crypto.randomUUID();
1311
+ this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
1312
+ this._api = createApiClient(this.basePath);
1313
+ this._api.use({
1314
+ onRequest({ request }) {
1315
+ request.headers.set("x-pollar-api-key", config.apiKey);
1316
+ return request;
1317
+ }
1318
+ });
1319
+ if (!isBrowser) {
1320
+ warnServerSide("constructor");
1321
+ this._session = null;
1322
+ return;
1323
+ }
1324
+ console.info(`[PollarClient] Initialized \u2014 endpoint: ${this.basePath}`);
1325
+ this._readStore();
1326
+ window.addEventListener("storage", (e) => {
1327
+ if (e.key === STORAGE_KEY) {
1328
+ const prev = this._session;
1329
+ console.info(`[PollarClient] Storage event \u2014 session ${this._session ? "updated" : prev ? "cleared" : "unchanged"}`);
1330
+ this._readStore();
1295
1331
  }
1296
1332
  });
1297
1333
  this._emitState("network", STATE_VAR_CODES.network.NETWORK_UPDATED, "info", StateStatus.SUCCESS, {
1298
1334
  network: "testnet"
1299
1335
  });
1300
1336
  }
1337
+ // ─── Auth state ──────────────────────────────────────────────────────────────
1301
1338
  isAuthenticated() {
1302
1339
  return !!this._session?.wallet?.publicKey;
1303
1340
  }
1304
- getState() {
1305
- return { session: this._session };
1341
+ getAuthState() {
1342
+ return this._authState;
1306
1343
  }
1307
- getApi() {
1308
- return this._api;
1344
+ onAuthStateChange(cb) {
1345
+ this._authStateListeners.add(cb);
1346
+ cb(this._authState);
1347
+ return () => this._authStateListeners.delete(cb);
1309
1348
  }
1349
+ // ─── Login (unified entry point) ─────────────────────────────────────────
1310
1350
  login(options) {
1311
1351
  if (!isBrowser) {
1312
1352
  warnServerSide("login");
1313
- return {
1314
- cancelLogin: () => {
1353
+ return;
1354
+ }
1355
+ if (options.provider === "google" || options.provider === "github") {
1356
+ this.loginOAuth(options.provider);
1357
+ } else if (options.provider === "email") {
1358
+ const { email } = options;
1359
+ const controller = this._newController();
1360
+ const deps = this._flowDeps(controller.signal);
1361
+ initEmailSession(deps).then(() => {
1362
+ if (this._authState.step === "entering_email") {
1363
+ return sendEmailCode(email, this._authState.clientSessionId, deps);
1315
1364
  }
1316
- };
1365
+ }).catch((err) => this._handleFlowError(err));
1366
+ } else if (options.provider === "wallet") {
1367
+ this.loginWallet(options.type);
1317
1368
  }
1318
- const controller = new AbortController();
1319
- console.info(`[PollarClient] Login started \u2014 provider: ${options.provider}`);
1320
- login(options, {
1321
- api: this._api,
1322
- basePath: this.basePath,
1323
- apiKey: this.apiKey,
1324
- signal: controller.signal,
1325
- emitState: this._emitState.bind(this),
1326
- storeSession: this._storeSession.bind(this),
1327
- clearSession: this._clearSession.bind(this)
1328
- }).catch((error) => {
1329
- if (error instanceof Error && error.name === "AbortError") {
1330
- console.info("[PollarClient] Login aborted by user");
1331
- this._emitState("authentication", STATE_VAR_CODES.authentication.ERROR_ABORTED, "error", StateStatus.ERROR);
1332
- return;
1333
- }
1334
- console.error("[PollarClient] Login failed with unexpected error", error);
1335
- this._emitState("authentication", STATE_VAR_CODES.authentication.ERROR_UNKNOWN, "error", StateStatus.ERROR, {
1336
- error
1337
- });
1338
- });
1339
- return { cancelLogin: () => controller.abort() };
1340
1369
  }
1341
- onStateChange(cb) {
1342
- this._stateListeners.add(cb);
1343
- for (const [, stateEntry] of Object.entries(this._state)) {
1344
- if (stateEntry.length >= 1) {
1345
- cb(stateEntry.at(-1));
1346
- }
1370
+ // ─── Email OTP flow (3 steps) ─────────────────────────────────────────────
1371
+ beginEmailLogin() {
1372
+ if (!isBrowser) {
1373
+ warnServerSide("beginEmailLogin");
1374
+ return;
1347
1375
  }
1348
- return () => this._stateListeners.delete(cb);
1376
+ const controller = this._newController();
1377
+ initEmailSession(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
1378
+ }
1379
+ sendEmailCode(email) {
1380
+ if (!isBrowser) {
1381
+ warnServerSide("sendEmailCode");
1382
+ return;
1383
+ }
1384
+ if (this._authState.step !== "entering_email") {
1385
+ throw new PollarFlowError(`sendEmailCode() requires step 'entering_email', current step is '${this._authState.step}'`);
1386
+ }
1387
+ const { clientSessionId } = this._authState;
1388
+ const signal = this._loginController.signal;
1389
+ sendEmailCode(email, clientSessionId, this._flowDeps(signal)).catch((err) => this._handleFlowError(err));
1349
1390
  }
1350
- async verifyEmailCode(clientSessionId, code) {
1391
+ verifyEmailCode(code) {
1351
1392
  if (!isBrowser) {
1352
1393
  warnServerSide("verifyEmailCode");
1353
1394
  return;
1354
1395
  }
1355
- try {
1356
- const { error, data } = await this._api.POST("/auth/email/verify-code", {
1357
- body: { clientSessionId, code }
1358
- });
1359
- if (error || !data || data?.code !== "SDK_EMAIL_CODE_VERIFIED") {
1360
- this._emitState("authentication", STATE_VAR_CODES.authentication.EMAIL_AUTH_CODE_ERROR, "error", StateStatus.ERROR, {
1361
- data,
1362
- error
1363
- });
1364
- return;
1365
- }
1366
- this._emitState("authentication", STATE_VAR_CODES.authentication.EMAIL_AUTH_CODE_SUCCESS, "info", StateStatus.SUCCESS, {
1367
- data,
1368
- error
1369
- });
1370
- } catch (error) {
1371
- this._emitState(
1372
- "authentication",
1373
- STATE_VAR_CODES.authentication.WALLET_AUTH_ALBEDO_NOT_INSTALLED,
1374
- "error",
1375
- StateStatus.ERROR
1376
- );
1396
+ const isRetryableError = this._authState.step === "error" && this._authState.clientSessionId != null && (this._authState.errorCode === AUTH_ERROR_CODES.EMAIL_CODE_INVALID || this._authState.errorCode === AUTH_ERROR_CODES.EMAIL_CODE_EXPIRED);
1397
+ if (this._authState.step !== "entering_code" && !isRetryableError) {
1398
+ throw new PollarFlowError(`verifyEmailCode() requires step 'entering_code', current step is '${this._authState.step}'`);
1377
1399
  }
1400
+ const state = this._authState;
1401
+ const clientSessionId = state.step === "entering_code" ? state.clientSessionId : state.clientSessionId;
1402
+ const email = state.step === "entering_code" ? state.email : state.email ?? "";
1403
+ const controller = this._newController();
1404
+ verifyAndAuthenticate(code, clientSessionId, email, this._flowDeps(controller.signal)).catch(
1405
+ (err) => this._handleFlowError(err)
1406
+ );
1407
+ }
1408
+ // ─── OAuth flow (single call) ─────────────────────────────────────────────
1409
+ loginOAuth(provider) {
1410
+ if (!isBrowser) {
1411
+ warnServerSide("loginOAuth");
1412
+ return;
1413
+ }
1414
+ const controller = this._newController();
1415
+ loginOAuth(provider, {
1416
+ ...this._flowDeps(controller.signal),
1417
+ basePath: this.basePath,
1418
+ apiKey: this.apiKey
1419
+ }).catch((err) => this._handleFlowError(err));
1420
+ }
1421
+ // ─── Wallet flow (single call) ────────────────────────────────────────────
1422
+ loginWallet(type) {
1423
+ if (!isBrowser) {
1424
+ warnServerSide("loginWallet");
1425
+ return;
1426
+ }
1427
+ const controller = this._newController();
1428
+ loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
1429
+ }
1430
+ // ─── Cancel ───────────────────────────────────────────────────────────────
1431
+ cancelLogin() {
1432
+ this._loginController?.abort();
1433
+ this._loginController = null;
1434
+ this._setAuthState({ step: "idle" });
1435
+ }
1436
+ // ─── Logout ───────────────────────────────────────────────────────────────
1437
+ logout() {
1438
+ if (!isBrowser) {
1439
+ warnServerSide("logout");
1440
+ return;
1441
+ }
1442
+ console.info("[PollarClient] Logout requested");
1443
+ this._clearSession();
1444
+ }
1445
+ // ─── General state (network / transaction) ────────────────────────────────
1446
+ getApi() {
1447
+ return this._api;
1378
1448
  }
1379
1449
  getNetwork() {
1380
1450
  return this._state.network.at(-1)?.data?.network === "public" ? "public" : "testnet";
1381
1451
  }
1452
+ onStateChange(cb) {
1453
+ this._stateListeners.add(cb);
1454
+ for (const [, stateEntry] of Object.entries(this._state)) {
1455
+ if (stateEntry.length >= 1) cb(stateEntry.at(-1));
1456
+ }
1457
+ return () => this._stateListeners.delete(cb);
1458
+ }
1459
+ // ─── Transactions ─────────────────────────────────────────────────────────
1382
1460
  async buildTx(operation, params, options) {
1383
1461
  if (!this._session?.wallet?.publicKey) {
1384
1462
  this._emitState("transaction", STATE_VAR_CODES.transaction.BUILD_TRANSACTION_ERROR_NO_WALLET, "error", StateStatus.ERROR);
@@ -1386,7 +1464,7 @@ var PollarClient = class {
1386
1464
  }
1387
1465
  const body = {
1388
1466
  network: this.getNetwork(),
1389
- publicKey: this._session?.wallet?.publicKey,
1467
+ publicKey: this._session.wallet.publicKey,
1390
1468
  operation,
1391
1469
  params,
1392
1470
  options: options || {}
@@ -1394,102 +1472,100 @@ var PollarClient = class {
1394
1472
  try {
1395
1473
  this._emitState("transaction", STATE_VAR_CODES.transaction.BUILD_TRANSACTION_START, "info", StateStatus.LOADING);
1396
1474
  const response = await this._api.POST("/tx/build", { body });
1397
- if (!emitResponse(
1475
+ emitResponse(
1398
1476
  PollarStateVar.TRANSACTION,
1399
1477
  response,
1400
1478
  { code: STATE_VAR_CODES.transaction.BUILD_TRANSACTION_SUCCESS, status: StateStatus.SUCCESS },
1401
1479
  STATE_VAR_CODES.transaction.BUILD_TRANSACTION_ERROR,
1402
1480
  this._emitState.bind(this)
1403
- )) {
1404
- return;
1405
- }
1481
+ );
1406
1482
  } catch (error) {
1407
1483
  this._emitState("transaction", STATE_VAR_CODES.transaction.BUILD_TRANSACTION_ERROR, "error", StateStatus.ERROR, {
1408
- body,
1409
1484
  error
1410
1485
  });
1411
- return;
1412
1486
  }
1413
1487
  }
1414
1488
  async submitTx(signedXdr) {
1415
- const body = {
1416
- network: this.getNetwork(),
1417
- signedXdr
1418
- };
1489
+ const body = { network: this.getNetwork(), signedXdr };
1419
1490
  try {
1420
1491
  this._emitState("transaction", STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_START, "info", StateStatus.LOADING);
1421
1492
  const response = await this._api.POST("/tx/sign-and-send", { body });
1422
- if (!emitResponse(
1493
+ emitResponse(
1423
1494
  PollarStateVar.TRANSACTION,
1424
1495
  response,
1425
1496
  { code: STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_SUCCESS, status: StateStatus.SUCCESS },
1426
1497
  STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_ERROR,
1427
1498
  this._emitState.bind(this)
1428
- )) {
1429
- return;
1430
- }
1499
+ );
1431
1500
  } catch (error) {
1432
1501
  this._emitState("transaction", STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_ERROR, "error", StateStatus.ERROR, {
1433
- body,
1434
1502
  error
1435
1503
  });
1436
- return;
1437
1504
  }
1438
1505
  }
1439
- logout() {
1440
- if (!isBrowser) {
1441
- warnServerSide("logout");
1506
+ // ─── Private ──────────────────────────────────────────────────────────────
1507
+ /** Creates a new AbortController, cancelling any existing flow first. */
1508
+ _newController() {
1509
+ this._loginController?.abort();
1510
+ this._loginController = new AbortController();
1511
+ return this._loginController;
1512
+ }
1513
+ /** Builds the deps object passed to flow functions via bind pattern. */
1514
+ _flowDeps(signal) {
1515
+ return {
1516
+ api: this._api,
1517
+ signal,
1518
+ setAuthState: this._setAuthState.bind(this),
1519
+ storeSession: this._storeSession.bind(this),
1520
+ clearSession: this._clearSession.bind(this)
1521
+ };
1522
+ }
1523
+ _handleFlowError(error) {
1524
+ if (error instanceof Error && error.name === "AbortError") {
1525
+ console.info("[PollarClient] Login cancelled");
1526
+ this._setAuthState({ step: "idle" });
1442
1527
  return;
1443
1528
  }
1444
- console.info("[PollarClient] Logout requested");
1445
- this._clearSession();
1529
+ console.error("[PollarClient] Unexpected error in auth flow", error);
1530
+ this._setAuthState({
1531
+ step: "error",
1532
+ previousStep: this._authState.step,
1533
+ message: "An unexpected error occurred",
1534
+ errorCode: AUTH_ERROR_CODES.UNEXPECTED_ERROR
1535
+ });
1446
1536
  }
1447
1537
  _readStore() {
1448
1538
  this._session = readStorage();
1449
1539
  if (this._session) {
1450
- this._emitState(
1451
- "authentication",
1452
- STATE_VAR_CODES.authentication.RESTORED_SESSION_SUCCESS,
1453
- "info",
1454
- StateStatus.SUCCESS,
1455
- this._session
1456
- );
1540
+ this._authState = { step: "authenticated", session: this._session };
1457
1541
  console.info("[PollarClient] Session restored from storage");
1458
1542
  } else {
1459
- this._emitState("authentication", STATE_VAR_CODES.authentication.NO_RESTORED_SESSION, "info", StateStatus.NONE);
1460
- console.info("[PollarClient] Session NO restored from storage");
1543
+ console.info("[PollarClient] No session in storage");
1461
1544
  }
1462
1545
  }
1463
1546
  _storeSession(session) {
1464
1547
  console.info(`[PollarClient] Session stored \u2014 user: ${session.userId ?? "anonymous"}`);
1465
1548
  this._session = session;
1466
1549
  writeStorage(session);
1467
- this._emitState(
1468
- "authentication",
1469
- STATE_VAR_CODES.authentication.SESSION_STORED,
1470
- "info",
1471
- StateStatus.SUCCESS,
1472
- this._session
1473
- );
1550
+ this._setAuthState({ step: "authenticated", session });
1474
1551
  }
1475
1552
  _clearSession() {
1476
1553
  console.info("[PollarClient] Session cleared");
1477
1554
  this._session = null;
1478
1555
  removeStorage();
1479
- this._state = {
1480
- network: [],
1481
- authentication: [],
1482
- transaction: []
1483
- };
1484
- this._emitState("authentication", STATE_VAR_CODES.authentication.LOGOUT, "info", StateStatus.NONE);
1556
+ this._state = { network: [], transaction: [] };
1557
+ this._setAuthState({ step: "idle" });
1558
+ }
1559
+ _setAuthState(next) {
1560
+ this._authState = next;
1561
+ console.info(`[PollarClient] auth:${next.step}`);
1562
+ for (const cb of this._authStateListeners) cb(next);
1485
1563
  }
1486
1564
  _emitState(fn, code, level, status, data) {
1487
1565
  const stateEntry = { var: fn, code, level, data, status, ts: Date.now() };
1488
1566
  this._state[fn].push(stateEntry);
1489
1567
  console[level](`[PollarClient] ${fn}:${code} \u2014 ${status}`);
1490
- for (const cb of this._stateListeners) {
1491
- cb(stateEntry);
1492
- }
1568
+ for (const cb of this._stateListeners) cb(stateEntry);
1493
1569
  }
1494
1570
  };
1495
1571
 
@@ -1529,6 +1605,7 @@ var StellarClient = class {
1529
1605
  }
1530
1606
  };
1531
1607
 
1608
+ exports.AUTH_ERROR_CODES = AUTH_ERROR_CODES;
1532
1609
  exports.AlbedoAdapter = AlbedoAdapter;
1533
1610
  exports.FreighterAdapter = FreighterAdapter;
1534
1611
  exports.PollarClient = PollarClient;