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