@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.d.mts +80 -89
- package/dist/index.d.ts +80 -89
- package/dist/index.js +566 -495
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +566 -496
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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/
|
|
722
|
-
var
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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,194 +911,422 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1109
911
|
}
|
|
1110
912
|
}
|
|
1111
913
|
|
|
1112
|
-
// src/client/
|
|
1113
|
-
function
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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
|
-
const oauthPopup = options.provider === "google" || options.provider === "github" ? window.open("about:blank", "_blank") : null;
|
|
1128
|
-
emitState("authentication", STATE_VAR_CODES.authentication.CREATE_SESSION_START, "info", StateStatus.LOADING);
|
|
1129
|
-
const createSessionResponse = await api.POST("/auth/session", { signal });
|
|
1130
|
-
if (!emitResponse(
|
|
1131
|
-
PollarStateVar.AUTHENTICATION,
|
|
1132
|
-
createSessionResponse,
|
|
1133
|
-
{ code: STATE_VAR_CODES.authentication.CREATE_SESSION_SUCCESS },
|
|
1134
|
-
STATE_VAR_CODES.authentication.CREATE_SESSION_ERROR,
|
|
1135
|
-
emitState
|
|
1136
|
-
)) {
|
|
1137
|
-
oauthPopup?.close();
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
const clientSessionId = createSessionResponse.data.content.clientSessionId;
|
|
1141
|
-
switch (options.provider) {
|
|
1142
|
-
case "email": {
|
|
1143
|
-
emitState("authentication", STATE_VAR_CODES.authentication.EMAIL_AUTH_START, "info", StateStatus.LOADING, {
|
|
1144
|
-
email: options.email
|
|
1145
|
-
});
|
|
1146
|
-
const emailRes = await api.POST(`/auth/email`, {
|
|
1147
|
-
body: { clientSessionId, email: options.email },
|
|
1148
|
-
signal
|
|
1149
|
-
});
|
|
1150
|
-
if (!emitResponse(
|
|
1151
|
-
PollarStateVar.AUTHENTICATION,
|
|
1152
|
-
emailRes,
|
|
1153
|
-
{ code: STATE_VAR_CODES.authentication.EMAIL_AUTH_START_SUCCESS },
|
|
1154
|
-
STATE_VAR_CODES.authentication.EMAIL_AUTH_START_ERROR,
|
|
1155
|
-
emitState
|
|
1156
|
-
)) {
|
|
1157
|
-
return;
|
|
1158
|
-
}
|
|
1159
|
-
break;
|
|
1160
|
-
}
|
|
1161
|
-
case "google":
|
|
1162
|
-
case "github": {
|
|
1163
|
-
const url = new URL(`${basePath}/auth/${options.provider}`);
|
|
1164
|
-
url.searchParams.set("api_key", apiKey);
|
|
1165
|
-
url.searchParams.set("client_session_id", clientSessionId);
|
|
1166
|
-
url.searchParams.set("redirect_uri", window.location.origin);
|
|
1167
|
-
if (oauthPopup) {
|
|
1168
|
-
oauthPopup.location.href = url.toString();
|
|
1169
|
-
} else {
|
|
1170
|
-
window.open(url.toString(), "_blank");
|
|
1171
|
-
}
|
|
1172
|
-
break;
|
|
1173
|
-
}
|
|
1174
|
-
case "wallet": {
|
|
1175
|
-
try {
|
|
1176
|
-
emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_START, "info", StateStatus.LOADING, {
|
|
1177
|
-
adapter: options.type
|
|
1178
|
-
});
|
|
1179
|
-
const adapter = options.type === "freighter" /* FREIGHTER */ ? new FreighterAdapter() : new AlbedoAdapter();
|
|
1180
|
-
const available = await withSignal(adapter.isAvailable(), signal);
|
|
1181
|
-
if (!available) {
|
|
1182
|
-
emitState(
|
|
1183
|
-
"authentication",
|
|
1184
|
-
options.type === "freighter" /* FREIGHTER */ ? STATE_VAR_CODES.authentication.WALLET_AUTH_FREIGHTER_NOT_INSTALLED : STATE_VAR_CODES.authentication.WALLET_AUTH_ALBEDO_NOT_INSTALLED,
|
|
1185
|
-
"info",
|
|
1186
|
-
StateStatus.LOADING,
|
|
1187
|
-
{
|
|
1188
|
-
type: options.type
|
|
1189
|
-
}
|
|
1190
|
-
);
|
|
1191
|
-
}
|
|
1192
|
-
const { publicKey } = await withSignal(adapter.connect(), signal);
|
|
1193
|
-
emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_CONNECTED, "info", StateStatus.LOADING, {
|
|
1194
|
-
adapter: options.type,
|
|
1195
|
-
publicKey
|
|
1196
|
-
});
|
|
1197
|
-
emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_LOGIN_START, "info", StateStatus.LOADING, {
|
|
1198
|
-
adapter: options.type,
|
|
1199
|
-
publicKey
|
|
1200
|
-
});
|
|
1201
|
-
const emailRes = await api.POST(`/auth/wallet`, {
|
|
1202
|
-
body: { clientSessionId, walletAddress: publicKey },
|
|
1203
|
-
signal
|
|
1204
|
-
});
|
|
1205
|
-
if (!emitResponse(
|
|
1206
|
-
PollarStateVar.AUTHENTICATION,
|
|
1207
|
-
emailRes,
|
|
1208
|
-
{ code: STATE_VAR_CODES.authentication.WALLET_AUTH_LOGIN_START_SUCCESS },
|
|
1209
|
-
STATE_VAR_CODES.authentication.WALLET_AUTH_LOGIN_START_ERROR,
|
|
1210
|
-
emitState
|
|
1211
|
-
)) {
|
|
1212
|
-
return;
|
|
1213
|
-
}
|
|
1214
|
-
} catch (error2) {
|
|
1215
|
-
emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_ERROR, "error", StateStatus.ERROR, {
|
|
1216
|
-
clientSessionId
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
break;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
emitState("authentication", STATE_VAR_CODES.authentication.STREAM_POLL_START, "info", StateStatus.LOADING, {
|
|
1223
|
-
clientSessionId
|
|
1224
|
-
});
|
|
1225
|
-
await streamUntilFound(
|
|
1226
|
-
api,
|
|
1227
|
-
clientSessionId,
|
|
1228
|
-
(data2) => {
|
|
1229
|
-
const status = data2?.status;
|
|
1230
|
-
if (status === "READY") {
|
|
1231
|
-
emitState("authentication", STATE_VAR_CODES.authentication.STREAM_POLL_READY, "info", StateStatus.LOADING);
|
|
1232
|
-
return true;
|
|
1233
|
-
}
|
|
1234
|
-
emitState(
|
|
1235
|
-
"authentication",
|
|
1236
|
-
STATE_VAR_CODES.authentication.STREAM_POLL_EVENT + (status ? `/${status}` : ""),
|
|
1237
|
-
"info",
|
|
1238
|
-
StateStatus.LOADING,
|
|
1239
|
-
data2
|
|
1240
|
-
);
|
|
1241
|
-
return false;
|
|
1242
|
-
},
|
|
1243
|
-
200,
|
|
1244
|
-
signal
|
|
1245
|
-
);
|
|
1246
|
-
emitState("authentication", STATE_VAR_CODES.authentication.FETCH_SESSION_START, "info", StateStatus.LOADING);
|
|
1247
|
-
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", {
|
|
1248
920
|
body: { clientSessionId },
|
|
1249
921
|
signal
|
|
1250
922
|
});
|
|
1251
923
|
if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content)) {
|
|
1252
|
-
emitState("authentication", STATE_VAR_CODES.authentication.FETCH_SESSION_SUCCESS, "info", StateStatus.SUCCESS);
|
|
1253
924
|
storeSession(data.content);
|
|
1254
925
|
} else {
|
|
1255
|
-
|
|
1256
|
-
error,
|
|
1257
|
-
|
|
926
|
+
setAuthState({
|
|
927
|
+
step: "error",
|
|
928
|
+
previousStep: "authenticating",
|
|
929
|
+
message: "Failed to load session",
|
|
930
|
+
errorCode: AUTH_ERROR_CODES.AUTH_FAILED
|
|
1258
931
|
});
|
|
1259
932
|
clearSession();
|
|
1260
933
|
}
|
|
1261
934
|
}
|
|
1262
935
|
|
|
1263
|
-
// src/client/
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
)
|
|
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 });
|
|
1269
951
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
this._api.use({
|
|
1284
|
-
onRequest({ request }) {
|
|
1285
|
-
request.headers.set("x-pollar-api-key", config.apiKey);
|
|
1286
|
-
return request;
|
|
1287
|
-
}
|
|
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
|
|
1288
965
|
});
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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"}`);
|
|
1300
1330
|
this._readStore();
|
|
1301
1331
|
}
|
|
1302
1332
|
});
|
|
@@ -1304,87 +1334,129 @@ var PollarClient = class {
|
|
|
1304
1334
|
network: "testnet"
|
|
1305
1335
|
});
|
|
1306
1336
|
}
|
|
1337
|
+
// ─── Auth state ──────────────────────────────────────────────────────────────
|
|
1307
1338
|
isAuthenticated() {
|
|
1308
1339
|
return !!this._session?.wallet?.publicKey;
|
|
1309
1340
|
}
|
|
1310
|
-
|
|
1311
|
-
return
|
|
1341
|
+
getAuthState() {
|
|
1342
|
+
return this._authState;
|
|
1312
1343
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1344
|
+
onAuthStateChange(cb) {
|
|
1345
|
+
this._authStateListeners.add(cb);
|
|
1346
|
+
cb(this._authState);
|
|
1347
|
+
return () => this._authStateListeners.delete(cb);
|
|
1315
1348
|
}
|
|
1349
|
+
// ─── Login (unified entry point) ─────────────────────────────────────────
|
|
1316
1350
|
login(options) {
|
|
1317
1351
|
if (!isBrowser) {
|
|
1318
1352
|
warnServerSide("login");
|
|
1319
|
-
return
|
|
1320
|
-
|
|
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);
|
|
1321
1364
|
}
|
|
1322
|
-
};
|
|
1365
|
+
}).catch((err) => this._handleFlowError(err));
|
|
1366
|
+
} else if (options.provider === "wallet") {
|
|
1367
|
+
this.loginWallet(options.type);
|
|
1323
1368
|
}
|
|
1324
|
-
const controller = new AbortController();
|
|
1325
|
-
console.info(`[PollarClient] Login started \u2014 provider: ${options.provider}`);
|
|
1326
|
-
login(options, {
|
|
1327
|
-
api: this._api,
|
|
1328
|
-
basePath: this.basePath,
|
|
1329
|
-
apiKey: this.apiKey,
|
|
1330
|
-
signal: controller.signal,
|
|
1331
|
-
emitState: this._emitState.bind(this),
|
|
1332
|
-
storeSession: this._storeSession.bind(this),
|
|
1333
|
-
clearSession: this._clearSession.bind(this)
|
|
1334
|
-
}).catch((error) => {
|
|
1335
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
1336
|
-
console.info("[PollarClient] Login aborted by user");
|
|
1337
|
-
this._emitState("authentication", STATE_VAR_CODES.authentication.ERROR_ABORTED, "error", StateStatus.ERROR);
|
|
1338
|
-
return;
|
|
1339
|
-
}
|
|
1340
|
-
console.error("[PollarClient] Login failed with unexpected error", error);
|
|
1341
|
-
this._emitState("authentication", STATE_VAR_CODES.authentication.ERROR_UNKNOWN, "error", StateStatus.ERROR, {
|
|
1342
|
-
error
|
|
1343
|
-
});
|
|
1344
|
-
});
|
|
1345
|
-
return { cancelLogin: () => controller.abort() };
|
|
1346
1369
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
}
|
|
1370
|
+
// ─── Email OTP flow (3 steps) ─────────────────────────────────────────────
|
|
1371
|
+
beginEmailLogin() {
|
|
1372
|
+
if (!isBrowser) {
|
|
1373
|
+
warnServerSide("beginEmailLogin");
|
|
1374
|
+
return;
|
|
1353
1375
|
}
|
|
1354
|
-
|
|
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));
|
|
1355
1390
|
}
|
|
1356
|
-
|
|
1391
|
+
verifyEmailCode(code) {
|
|
1357
1392
|
if (!isBrowser) {
|
|
1358
1393
|
warnServerSide("verifyEmailCode");
|
|
1359
1394
|
return;
|
|
1360
1395
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
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}'`);
|
|
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;
|
|
1383
1441
|
}
|
|
1442
|
+
console.info("[PollarClient] Logout requested");
|
|
1443
|
+
this._clearSession();
|
|
1444
|
+
}
|
|
1445
|
+
// ─── General state (network / transaction) ────────────────────────────────
|
|
1446
|
+
getApi() {
|
|
1447
|
+
return this._api;
|
|
1384
1448
|
}
|
|
1385
1449
|
getNetwork() {
|
|
1386
1450
|
return this._state.network.at(-1)?.data?.network === "public" ? "public" : "testnet";
|
|
1387
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 ─────────────────────────────────────────────────────────
|
|
1388
1460
|
async buildTx(operation, params, options) {
|
|
1389
1461
|
if (!this._session?.wallet?.publicKey) {
|
|
1390
1462
|
this._emitState("transaction", STATE_VAR_CODES.transaction.BUILD_TRANSACTION_ERROR_NO_WALLET, "error", StateStatus.ERROR);
|
|
@@ -1392,7 +1464,7 @@ var PollarClient = class {
|
|
|
1392
1464
|
}
|
|
1393
1465
|
const body = {
|
|
1394
1466
|
network: this.getNetwork(),
|
|
1395
|
-
publicKey: this._session
|
|
1467
|
+
publicKey: this._session.wallet.publicKey,
|
|
1396
1468
|
operation,
|
|
1397
1469
|
params,
|
|
1398
1470
|
options: options || {}
|
|
@@ -1400,102 +1472,100 @@ var PollarClient = class {
|
|
|
1400
1472
|
try {
|
|
1401
1473
|
this._emitState("transaction", STATE_VAR_CODES.transaction.BUILD_TRANSACTION_START, "info", StateStatus.LOADING);
|
|
1402
1474
|
const response = await this._api.POST("/tx/build", { body });
|
|
1403
|
-
|
|
1475
|
+
emitResponse(
|
|
1404
1476
|
PollarStateVar.TRANSACTION,
|
|
1405
1477
|
response,
|
|
1406
1478
|
{ code: STATE_VAR_CODES.transaction.BUILD_TRANSACTION_SUCCESS, status: StateStatus.SUCCESS },
|
|
1407
1479
|
STATE_VAR_CODES.transaction.BUILD_TRANSACTION_ERROR,
|
|
1408
1480
|
this._emitState.bind(this)
|
|
1409
|
-
)
|
|
1410
|
-
return;
|
|
1411
|
-
}
|
|
1481
|
+
);
|
|
1412
1482
|
} catch (error) {
|
|
1413
1483
|
this._emitState("transaction", STATE_VAR_CODES.transaction.BUILD_TRANSACTION_ERROR, "error", StateStatus.ERROR, {
|
|
1414
|
-
body,
|
|
1415
1484
|
error
|
|
1416
1485
|
});
|
|
1417
|
-
return;
|
|
1418
1486
|
}
|
|
1419
1487
|
}
|
|
1420
1488
|
async submitTx(signedXdr) {
|
|
1421
|
-
const body = {
|
|
1422
|
-
network: this.getNetwork(),
|
|
1423
|
-
signedXdr
|
|
1424
|
-
};
|
|
1489
|
+
const body = { network: this.getNetwork(), signedXdr };
|
|
1425
1490
|
try {
|
|
1426
1491
|
this._emitState("transaction", STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_START, "info", StateStatus.LOADING);
|
|
1427
1492
|
const response = await this._api.POST("/tx/sign-and-send", { body });
|
|
1428
|
-
|
|
1493
|
+
emitResponse(
|
|
1429
1494
|
PollarStateVar.TRANSACTION,
|
|
1430
1495
|
response,
|
|
1431
1496
|
{ code: STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_SUCCESS, status: StateStatus.SUCCESS },
|
|
1432
1497
|
STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_ERROR,
|
|
1433
1498
|
this._emitState.bind(this)
|
|
1434
|
-
)
|
|
1435
|
-
return;
|
|
1436
|
-
}
|
|
1499
|
+
);
|
|
1437
1500
|
} catch (error) {
|
|
1438
1501
|
this._emitState("transaction", STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_ERROR, "error", StateStatus.ERROR, {
|
|
1439
|
-
body,
|
|
1440
1502
|
error
|
|
1441
1503
|
});
|
|
1442
|
-
return;
|
|
1443
1504
|
}
|
|
1444
1505
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
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" });
|
|
1448
1527
|
return;
|
|
1449
1528
|
}
|
|
1450
|
-
console.
|
|
1451
|
-
this.
|
|
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
|
+
});
|
|
1452
1536
|
}
|
|
1453
1537
|
_readStore() {
|
|
1454
1538
|
this._session = readStorage();
|
|
1455
1539
|
if (this._session) {
|
|
1456
|
-
this.
|
|
1457
|
-
"authentication",
|
|
1458
|
-
STATE_VAR_CODES.authentication.RESTORED_SESSION_SUCCESS,
|
|
1459
|
-
"info",
|
|
1460
|
-
StateStatus.SUCCESS,
|
|
1461
|
-
this._session
|
|
1462
|
-
);
|
|
1540
|
+
this._authState = { step: "authenticated", session: this._session };
|
|
1463
1541
|
console.info("[PollarClient] Session restored from storage");
|
|
1464
1542
|
} else {
|
|
1465
|
-
|
|
1466
|
-
console.info("[PollarClient] Session NO restored from storage");
|
|
1543
|
+
console.info("[PollarClient] No session in storage");
|
|
1467
1544
|
}
|
|
1468
1545
|
}
|
|
1469
1546
|
_storeSession(session) {
|
|
1470
1547
|
console.info(`[PollarClient] Session stored \u2014 user: ${session.userId ?? "anonymous"}`);
|
|
1471
1548
|
this._session = session;
|
|
1472
1549
|
writeStorage(session);
|
|
1473
|
-
this.
|
|
1474
|
-
"authentication",
|
|
1475
|
-
STATE_VAR_CODES.authentication.SESSION_STORED,
|
|
1476
|
-
"info",
|
|
1477
|
-
StateStatus.SUCCESS,
|
|
1478
|
-
this._session
|
|
1479
|
-
);
|
|
1550
|
+
this._setAuthState({ step: "authenticated", session });
|
|
1480
1551
|
}
|
|
1481
1552
|
_clearSession() {
|
|
1482
1553
|
console.info("[PollarClient] Session cleared");
|
|
1483
1554
|
this._session = null;
|
|
1484
1555
|
removeStorage();
|
|
1485
|
-
this._state = {
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
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);
|
|
1491
1563
|
}
|
|
1492
1564
|
_emitState(fn, code, level, status, data) {
|
|
1493
1565
|
const stateEntry = { var: fn, code, level, data, status, ts: Date.now() };
|
|
1494
1566
|
this._state[fn].push(stateEntry);
|
|
1495
1567
|
console[level](`[PollarClient] ${fn}:${code} \u2014 ${status}`);
|
|
1496
|
-
for (const cb of this._stateListeners)
|
|
1497
|
-
cb(stateEntry);
|
|
1498
|
-
}
|
|
1568
|
+
for (const cb of this._stateListeners) cb(stateEntry);
|
|
1499
1569
|
}
|
|
1500
1570
|
};
|
|
1501
1571
|
|
|
@@ -1535,6 +1605,7 @@ var StellarClient = class {
|
|
|
1535
1605
|
}
|
|
1536
1606
|
};
|
|
1537
1607
|
|
|
1608
|
+
exports.AUTH_ERROR_CODES = AUTH_ERROR_CODES;
|
|
1538
1609
|
exports.AlbedoAdapter = AlbedoAdapter;
|
|
1539
1610
|
exports.FreighterAdapter = FreighterAdapter;
|
|
1540
1611
|
exports.PollarClient = PollarClient;
|