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