@horus-wallet/sdk-react 0.1.0-beta.2
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/README.md +398 -0
- package/dist/index.cjs +682 -0
- package/dist/index.d.cts +314 -0
- package/dist/index.d.ts +314 -0
- package/dist/index.js +648 -0
- package/package.json +69 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
HorusHttpError: () => HorusHttpError,
|
|
24
|
+
HorusLoginButton: () => HorusLoginButton,
|
|
25
|
+
HorusProvider: () => HorusProvider,
|
|
26
|
+
useHorusAuth: () => useHorusAuth,
|
|
27
|
+
useSignMessage: () => useSignMessage,
|
|
28
|
+
useTransfer: () => useTransfer,
|
|
29
|
+
useUser: () => useUser,
|
|
30
|
+
useWallets: () => useWallets
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/HorusProvider.tsx
|
|
35
|
+
var import_react2 = require("react");
|
|
36
|
+
|
|
37
|
+
// src/HorusContext.ts
|
|
38
|
+
var import_react = require("react");
|
|
39
|
+
var HorusContext = (0, import_react.createContext)(null);
|
|
40
|
+
function useHorusContext() {
|
|
41
|
+
const ctx = (0, import_react.useContext)(HorusContext);
|
|
42
|
+
if (!ctx) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
'@horus-wallet/sdk-react: hook used outside <HorusProvider>. Wrap your app with <HorusProvider appId="\u2026"> at the root.'
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return ctx;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/tokenStore.ts
|
|
51
|
+
var STORAGE_KEY = "horus.auth.tokens";
|
|
52
|
+
var WebStorageStore = class {
|
|
53
|
+
constructor(storage) {
|
|
54
|
+
this.storage = storage;
|
|
55
|
+
}
|
|
56
|
+
read() {
|
|
57
|
+
try {
|
|
58
|
+
const raw = this.storage.getItem(STORAGE_KEY);
|
|
59
|
+
if (!raw) return null;
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
if (typeof parsed?.idToken !== "string") return null;
|
|
62
|
+
return parsed;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
write(tokens) {
|
|
68
|
+
try {
|
|
69
|
+
this.storage.setItem(STORAGE_KEY, JSON.stringify(tokens));
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
clear() {
|
|
74
|
+
try {
|
|
75
|
+
this.storage.removeItem(STORAGE_KEY);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var MemoryStore = class {
|
|
81
|
+
constructor() {
|
|
82
|
+
this.value = null;
|
|
83
|
+
}
|
|
84
|
+
read() {
|
|
85
|
+
return this.value;
|
|
86
|
+
}
|
|
87
|
+
write(t) {
|
|
88
|
+
this.value = t;
|
|
89
|
+
}
|
|
90
|
+
clear() {
|
|
91
|
+
this.value = null;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
function createTokenStore(kind = "localStorage") {
|
|
95
|
+
if (kind === "memory" || typeof window === "undefined") {
|
|
96
|
+
return new MemoryStore();
|
|
97
|
+
}
|
|
98
|
+
if (kind === "sessionStorage") return new WebStorageStore(window.sessionStorage);
|
|
99
|
+
return new WebStorageStore(window.localStorage);
|
|
100
|
+
}
|
|
101
|
+
function stampExpiry(raw) {
|
|
102
|
+
const seconds = typeof raw.expiresIn === "string" ? Number(raw.expiresIn) : raw.expiresIn;
|
|
103
|
+
return {
|
|
104
|
+
...raw,
|
|
105
|
+
localExpiresAt: Math.floor(Date.now() / 1e3) + (Number.isFinite(seconds) ? seconds : 3600)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/http.ts
|
|
110
|
+
var HorusHttpError = class extends Error {
|
|
111
|
+
constructor(status, message, body, code) {
|
|
112
|
+
super(message);
|
|
113
|
+
this.name = "HorusHttpError";
|
|
114
|
+
this.status = status;
|
|
115
|
+
this.code = code;
|
|
116
|
+
this.body = body;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
function makeHttp(deps) {
|
|
120
|
+
const url = (path) => {
|
|
121
|
+
const base = deps.apiBase.endsWith("/") ? deps.apiBase.slice(0, -1) : deps.apiBase;
|
|
122
|
+
return path.startsWith("/") ? `${base}${path}` : `${base}/${path}`;
|
|
123
|
+
};
|
|
124
|
+
async function rawRequest(method, path, body, options = {}) {
|
|
125
|
+
const headers = { "Content-Type": "application/json" };
|
|
126
|
+
if (options.auth !== false) {
|
|
127
|
+
const tokens = deps.getTokens();
|
|
128
|
+
if (tokens?.idToken) {
|
|
129
|
+
headers["x-horus-id-token"] = tokens.idToken;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const res = await fetch(url(path), {
|
|
133
|
+
method,
|
|
134
|
+
headers,
|
|
135
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
136
|
+
signal: options.signal,
|
|
137
|
+
credentials: "same-origin"
|
|
138
|
+
});
|
|
139
|
+
const text = await res.text();
|
|
140
|
+
let parsed = void 0;
|
|
141
|
+
if (text.length > 0) {
|
|
142
|
+
try {
|
|
143
|
+
parsed = JSON.parse(text);
|
|
144
|
+
} catch {
|
|
145
|
+
parsed = text;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (res.ok) return parsed;
|
|
149
|
+
const code = parsed?.code;
|
|
150
|
+
const message = parsed?.message ?? `HTTP ${res.status}`;
|
|
151
|
+
throw new HorusHttpError(res.status, message, parsed, code);
|
|
152
|
+
}
|
|
153
|
+
async function request(method, path, body, options = {}) {
|
|
154
|
+
try {
|
|
155
|
+
return await rawRequest(method, path, body, options);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (err instanceof HorusHttpError && err.status === 401 && options.auth !== false && deps.autoRefresh) {
|
|
158
|
+
try {
|
|
159
|
+
await deps.refreshTokens();
|
|
160
|
+
} catch {
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
return await rawRequest(method, path, body, options);
|
|
164
|
+
}
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
get: (path, opts) => request("GET", path, void 0, opts),
|
|
170
|
+
post: (path, body, opts) => request("POST", path, body, opts),
|
|
171
|
+
put: (path, body, opts) => request("PUT", path, body, opts),
|
|
172
|
+
del: (path, opts) => request("DELETE", path, void 0, opts)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/HorusProvider.tsx
|
|
177
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
178
|
+
var DEFAULT_API_BASE = "/api/horus";
|
|
179
|
+
var DEFAULT_AUTH_PAGE = "https://auth.horuswallet.com";
|
|
180
|
+
var REFRESH_LEAD_SECONDS = 60;
|
|
181
|
+
function HorusProvider(props) {
|
|
182
|
+
const {
|
|
183
|
+
appId,
|
|
184
|
+
apiBase = DEFAULT_API_BASE,
|
|
185
|
+
authPageUrl = DEFAULT_AUTH_PAGE,
|
|
186
|
+
branding,
|
|
187
|
+
tokenStorage = "localStorage",
|
|
188
|
+
autoRefresh = true,
|
|
189
|
+
children
|
|
190
|
+
} = props;
|
|
191
|
+
const tokenStoreRef = (0, import_react2.useRef)(createTokenStore(tokenStorage));
|
|
192
|
+
const tokensRef = (0, import_react2.useRef)(null);
|
|
193
|
+
const [state, setState] = (0, import_react2.useState)({ status: "loading" });
|
|
194
|
+
const setTokens = (0, import_react2.useCallback)((tokens) => {
|
|
195
|
+
tokensRef.current = tokens;
|
|
196
|
+
if (tokens) {
|
|
197
|
+
tokenStoreRef.current.write(tokens);
|
|
198
|
+
setState({
|
|
199
|
+
status: "authenticated",
|
|
200
|
+
tokens,
|
|
201
|
+
user: userFromTokens(tokens)
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
tokenStoreRef.current.clear();
|
|
205
|
+
setState({ status: "unauthenticated" });
|
|
206
|
+
}
|
|
207
|
+
}, []);
|
|
208
|
+
const http = (0, import_react2.useMemo)(() => {
|
|
209
|
+
const refreshOnce = async () => {
|
|
210
|
+
const cur = tokensRef.current;
|
|
211
|
+
if (!cur?.refreshToken) throw new Error("no refresh token cached");
|
|
212
|
+
const fresh = await fetch(joinUrl(apiBase, "/auth/refresh"), {
|
|
213
|
+
method: "POST",
|
|
214
|
+
headers: { "Content-Type": "application/json" },
|
|
215
|
+
body: JSON.stringify({ refreshToken: cur.refreshToken }),
|
|
216
|
+
credentials: "same-origin"
|
|
217
|
+
});
|
|
218
|
+
if (!fresh.ok) throw new Error(`refresh failed: ${fresh.status}`);
|
|
219
|
+
const raw = await fresh.json();
|
|
220
|
+
const stamped = stampExpiry(raw);
|
|
221
|
+
setTokens(stamped);
|
|
222
|
+
return stamped;
|
|
223
|
+
};
|
|
224
|
+
return makeHttp({
|
|
225
|
+
apiBase,
|
|
226
|
+
getTokens: () => tokensRef.current,
|
|
227
|
+
refreshTokens: refreshOnce,
|
|
228
|
+
autoRefresh
|
|
229
|
+
});
|
|
230
|
+
}, [apiBase, autoRefresh, setTokens]);
|
|
231
|
+
(0, import_react2.useEffect)(() => {
|
|
232
|
+
const stored = tokenStoreRef.current.read();
|
|
233
|
+
if (stored) {
|
|
234
|
+
tokensRef.current = stored;
|
|
235
|
+
setState({
|
|
236
|
+
status: "authenticated",
|
|
237
|
+
tokens: stored,
|
|
238
|
+
user: userFromTokens(stored)
|
|
239
|
+
});
|
|
240
|
+
} else {
|
|
241
|
+
setState({ status: "unauthenticated" });
|
|
242
|
+
}
|
|
243
|
+
}, []);
|
|
244
|
+
(0, import_react2.useEffect)(() => {
|
|
245
|
+
if (typeof window === "undefined") return;
|
|
246
|
+
const onStorage = (ev) => {
|
|
247
|
+
if (ev.key !== "horus.auth.tokens") return;
|
|
248
|
+
const next = tokenStoreRef.current.read();
|
|
249
|
+
tokensRef.current = next;
|
|
250
|
+
if (next) {
|
|
251
|
+
setState({ status: "authenticated", tokens: next, user: userFromTokens(next) });
|
|
252
|
+
} else {
|
|
253
|
+
setState({ status: "unauthenticated" });
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
window.addEventListener("storage", onStorage);
|
|
257
|
+
return () => window.removeEventListener("storage", onStorage);
|
|
258
|
+
}, []);
|
|
259
|
+
(0, import_react2.useEffect)(() => {
|
|
260
|
+
if (state.status !== "authenticated" || !autoRefresh) return;
|
|
261
|
+
const secondsUntil = state.tokens.localExpiresAt - Math.floor(Date.now() / 1e3);
|
|
262
|
+
const refreshIn = Math.max(0, secondsUntil - REFRESH_LEAD_SECONDS) * 1e3;
|
|
263
|
+
const handle = setTimeout(() => {
|
|
264
|
+
http.post("/auth/refresh", { refreshToken: state.tokens.refreshToken }, { auth: false }).then((raw) => setTokens(stampExpiry(raw))).catch(() => {
|
|
265
|
+
});
|
|
266
|
+
}, refreshIn);
|
|
267
|
+
return () => clearTimeout(handle);
|
|
268
|
+
}, [state, autoRefresh, http, setTokens]);
|
|
269
|
+
const ctx = (0, import_react2.useMemo)(
|
|
270
|
+
() => ({
|
|
271
|
+
state,
|
|
272
|
+
http,
|
|
273
|
+
authPageUrl,
|
|
274
|
+
appId,
|
|
275
|
+
branding,
|
|
276
|
+
signIn: setTokens,
|
|
277
|
+
signOut: () => setTokens(null),
|
|
278
|
+
refreshTokens: async () => {
|
|
279
|
+
const cur = tokensRef.current;
|
|
280
|
+
if (!cur?.refreshToken) throw new Error("not authenticated");
|
|
281
|
+
const raw = await http.post(
|
|
282
|
+
"/auth/refresh",
|
|
283
|
+
{ refreshToken: cur.refreshToken },
|
|
284
|
+
{ auth: false }
|
|
285
|
+
);
|
|
286
|
+
const stamped = stampExpiry(raw);
|
|
287
|
+
setTokens(stamped);
|
|
288
|
+
return stamped;
|
|
289
|
+
}
|
|
290
|
+
}),
|
|
291
|
+
[state, http, authPageUrl, appId, branding, setTokens]
|
|
292
|
+
);
|
|
293
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HorusContext.Provider, { value: ctx, children });
|
|
294
|
+
}
|
|
295
|
+
function userFromTokens(t) {
|
|
296
|
+
return {
|
|
297
|
+
uid: t.localId,
|
|
298
|
+
email: t.email,
|
|
299
|
+
emailVerified: t.emailVerified,
|
|
300
|
+
displayName: t.displayName,
|
|
301
|
+
photoUrl: t.photoUrl,
|
|
302
|
+
providerId: t.providerId
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function joinUrl(base, path) {
|
|
306
|
+
const b = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
307
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
308
|
+
return `${b}${p}`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/hooks/useHorusAuth.ts
|
|
312
|
+
var import_react3 = require("react");
|
|
313
|
+
function useHorusAuth() {
|
|
314
|
+
const ctx = useHorusContext();
|
|
315
|
+
const { state, http, signIn, signOut, refreshTokens, authPageUrl, appId, branding } = ctx;
|
|
316
|
+
const loginWithEmail = (0, import_react3.useCallback)(async (input) => {
|
|
317
|
+
const raw = await http.post(
|
|
318
|
+
"/auth/email/signin",
|
|
319
|
+
input,
|
|
320
|
+
{ auth: false }
|
|
321
|
+
);
|
|
322
|
+
const stamped = stampExpiry(raw);
|
|
323
|
+
signIn(stamped);
|
|
324
|
+
return userFromTokens2(stamped);
|
|
325
|
+
}, [http, signIn]);
|
|
326
|
+
const signUpWithEmail = (0, import_react3.useCallback)(async (input) => {
|
|
327
|
+
const raw = await http.post(
|
|
328
|
+
"/auth/email/signup",
|
|
329
|
+
input,
|
|
330
|
+
{ auth: false }
|
|
331
|
+
);
|
|
332
|
+
const stamped = stampExpiry(raw);
|
|
333
|
+
signIn(stamped);
|
|
334
|
+
return userFromTokens2(stamped);
|
|
335
|
+
}, [http, signIn]);
|
|
336
|
+
const sendEmailLink = (0, import_react3.useCallback)(async (input) => {
|
|
337
|
+
await http.post("/auth/email-link/send", input, { auth: false });
|
|
338
|
+
}, [http]);
|
|
339
|
+
const verifyEmailLink = (0, import_react3.useCallback)(async (input) => {
|
|
340
|
+
const raw = await http.post(
|
|
341
|
+
"/auth/email-link/verify",
|
|
342
|
+
input,
|
|
343
|
+
{ auth: false }
|
|
344
|
+
);
|
|
345
|
+
const stamped = stampExpiry(raw);
|
|
346
|
+
signIn(stamped);
|
|
347
|
+
return userFromTokens2(stamped);
|
|
348
|
+
}, [http, signIn]);
|
|
349
|
+
const popupFlow = (0, import_react3.useCallback)(
|
|
350
|
+
async (flow, extras = {}) => {
|
|
351
|
+
const tokens = await openPopupFlow({
|
|
352
|
+
flow,
|
|
353
|
+
appId,
|
|
354
|
+
baseUrl: authPageUrl,
|
|
355
|
+
branding,
|
|
356
|
+
phone: extras.phone
|
|
357
|
+
});
|
|
358
|
+
const stamped = stampExpiry({
|
|
359
|
+
idToken: tokens.idToken,
|
|
360
|
+
refreshToken: "",
|
|
361
|
+
// popup path doesn't expose refresh — partner refreshes via backend on next 401
|
|
362
|
+
expiresIn: tokens.expiresAt ? Math.max(60, tokens.expiresAt - Math.floor(Date.now() / 1e3)) : 3600,
|
|
363
|
+
localId: tokens.user?.uid ?? "",
|
|
364
|
+
email: tokens.user?.email,
|
|
365
|
+
displayName: tokens.user?.displayName,
|
|
366
|
+
photoUrl: tokens.user?.photoURL,
|
|
367
|
+
providerId: tokens.user?.providerId
|
|
368
|
+
});
|
|
369
|
+
signIn(stamped);
|
|
370
|
+
return userFromTokens2(stamped);
|
|
371
|
+
},
|
|
372
|
+
[appId, authPageUrl, branding, signIn]
|
|
373
|
+
);
|
|
374
|
+
const loginWithGoogle = (0, import_react3.useCallback)(() => popupFlow("google"), [popupFlow]);
|
|
375
|
+
const loginWithEmailLinkPopup = (0, import_react3.useCallback)(() => popupFlow("email_link"), [popupFlow]);
|
|
376
|
+
const loginWithPhone = (0, import_react3.useCallback)(
|
|
377
|
+
(opts) => popupFlow("phone", { phone: opts?.phone }),
|
|
378
|
+
[popupFlow]
|
|
379
|
+
);
|
|
380
|
+
return {
|
|
381
|
+
ready: state.status !== "loading",
|
|
382
|
+
authenticated: state.status === "authenticated",
|
|
383
|
+
user: state.status === "authenticated" ? state.user : void 0,
|
|
384
|
+
loginWithEmail,
|
|
385
|
+
signUpWithEmail,
|
|
386
|
+
sendEmailLink,
|
|
387
|
+
verifyEmailLink,
|
|
388
|
+
loginWithGoogle,
|
|
389
|
+
loginWithEmailLink: loginWithEmailLinkPopup,
|
|
390
|
+
loginWithPhone,
|
|
391
|
+
logout: signOut,
|
|
392
|
+
refresh: refreshTokens
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function userFromTokens2(t) {
|
|
396
|
+
return {
|
|
397
|
+
uid: t.localId,
|
|
398
|
+
email: t.email,
|
|
399
|
+
emailVerified: t.emailVerified,
|
|
400
|
+
displayName: t.displayName,
|
|
401
|
+
photoUrl: t.photoUrl,
|
|
402
|
+
providerId: t.providerId
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function openPopupFlow(params) {
|
|
406
|
+
return new Promise((resolve, reject) => {
|
|
407
|
+
if (typeof window === "undefined") {
|
|
408
|
+
reject(new Error("useHorusAuth popup methods require a browser context."));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const state = randomState();
|
|
412
|
+
const url = buildAuthUrl(params, state);
|
|
413
|
+
const popup = window.open(url, "horus-auth", defaultPopupFeatures());
|
|
414
|
+
if (!popup) {
|
|
415
|
+
reject(new Error("Popup blocked \u2014 call from a click handler so the browser allows it."));
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const expectedOrigin = new URL(params.baseUrl).origin;
|
|
419
|
+
let settled = false;
|
|
420
|
+
const cleanup = () => {
|
|
421
|
+
window.removeEventListener("message", onMessage);
|
|
422
|
+
clearInterval(poll);
|
|
423
|
+
};
|
|
424
|
+
const onMessage = (ev) => {
|
|
425
|
+
if (ev.origin !== expectedOrigin) return;
|
|
426
|
+
const data = ev.data;
|
|
427
|
+
if (!data || data.channel !== "horus.auth" || data.version !== 1) return;
|
|
428
|
+
if (data.state !== void 0 && data.state !== state) return;
|
|
429
|
+
const body = data.body;
|
|
430
|
+
if (!body) return;
|
|
431
|
+
switch (body.type) {
|
|
432
|
+
case "success":
|
|
433
|
+
if (settled) return;
|
|
434
|
+
settled = true;
|
|
435
|
+
cleanup();
|
|
436
|
+
try {
|
|
437
|
+
popup.close();
|
|
438
|
+
} catch {
|
|
439
|
+
}
|
|
440
|
+
resolve({ idToken: body.idToken, expiresAt: body.expiresAt, user: body.user });
|
|
441
|
+
return;
|
|
442
|
+
case "cancel":
|
|
443
|
+
if (settled) return;
|
|
444
|
+
settled = true;
|
|
445
|
+
cleanup();
|
|
446
|
+
try {
|
|
447
|
+
popup.close();
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
reject(new Error("User cancelled sign-in."));
|
|
451
|
+
return;
|
|
452
|
+
case "error":
|
|
453
|
+
if (settled) return;
|
|
454
|
+
settled = true;
|
|
455
|
+
cleanup();
|
|
456
|
+
try {
|
|
457
|
+
popup.close();
|
|
458
|
+
} catch {
|
|
459
|
+
}
|
|
460
|
+
reject(new Error(body.message || "Sign-in failed."));
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
window.addEventListener("message", onMessage);
|
|
465
|
+
const poll = setInterval(() => {
|
|
466
|
+
if (popup.closed && !settled) {
|
|
467
|
+
settled = true;
|
|
468
|
+
cleanup();
|
|
469
|
+
reject(new Error("Auth popup was closed before sign-in completed."));
|
|
470
|
+
}
|
|
471
|
+
}, 400);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
function buildAuthUrl(p, state) {
|
|
475
|
+
const url = new URL(p.baseUrl);
|
|
476
|
+
url.searchParams.set("flow", p.flow);
|
|
477
|
+
url.searchParams.set("origin", window.location.origin);
|
|
478
|
+
url.searchParams.set("state", state);
|
|
479
|
+
url.searchParams.set("appKey", p.appId);
|
|
480
|
+
if (p.phone) url.searchParams.set("phone", p.phone);
|
|
481
|
+
const b = p.branding;
|
|
482
|
+
if (b) {
|
|
483
|
+
if (b.logoUrl) url.searchParams.set("b_logo", b.logoUrl);
|
|
484
|
+
if (b.brandName) url.searchParams.set("b_name", b.brandName);
|
|
485
|
+
if (b.primaryColor) url.searchParams.set("b_color", b.primaryColor);
|
|
486
|
+
if (b.backgroundColor) url.searchParams.set("b_bg", b.backgroundColor);
|
|
487
|
+
if (b.fontFamily) url.searchParams.set("b_font", b.fontFamily);
|
|
488
|
+
if (b.termsUrl) url.searchParams.set("b_terms", b.termsUrl);
|
|
489
|
+
if (b.privacyUrl) url.searchParams.set("b_privacy", b.privacyUrl);
|
|
490
|
+
if (b.showPoweredByHorus === false) url.searchParams.set("b_poweredby", "0");
|
|
491
|
+
}
|
|
492
|
+
return url.toString();
|
|
493
|
+
}
|
|
494
|
+
function defaultPopupFeatures() {
|
|
495
|
+
const w = 480, h = 640;
|
|
496
|
+
if (typeof window === "undefined") return `width=${w},height=${h}`;
|
|
497
|
+
const left = Math.max(0, (window.innerWidth - w) / 2 + (window.screenX ?? 0));
|
|
498
|
+
const top = Math.max(0, (window.innerHeight - h) / 2 + (window.screenY ?? 0));
|
|
499
|
+
return `width=${w},height=${h},left=${left},top=${top},popup=1`;
|
|
500
|
+
}
|
|
501
|
+
function randomState() {
|
|
502
|
+
const bytes = new Uint8Array(16);
|
|
503
|
+
crypto.getRandomValues(bytes);
|
|
504
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/hooks/useUser.ts
|
|
508
|
+
function useUser() {
|
|
509
|
+
const { state } = useHorusContext();
|
|
510
|
+
return state.status === "authenticated" ? state.user : void 0;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/hooks/useWallets.ts
|
|
514
|
+
var import_react4 = require("react");
|
|
515
|
+
function useWallets() {
|
|
516
|
+
const { http, state } = useHorusContext();
|
|
517
|
+
const [wallets, setWallets] = (0, import_react4.useState)([]);
|
|
518
|
+
const [ready, setReady] = (0, import_react4.useState)(false);
|
|
519
|
+
const [error, setError] = (0, import_react4.useState)(void 0);
|
|
520
|
+
const load = (0, import_react4.useCallback)(async () => {
|
|
521
|
+
if (state.status !== "authenticated") {
|
|
522
|
+
setWallets([]);
|
|
523
|
+
setReady(true);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
setReady(false);
|
|
527
|
+
setError(void 0);
|
|
528
|
+
try {
|
|
529
|
+
const response = await http.get(
|
|
530
|
+
"/wallets"
|
|
531
|
+
);
|
|
532
|
+
const flat = [];
|
|
533
|
+
for (const [network, group] of Object.entries(response.wallets ?? {})) {
|
|
534
|
+
for (const w of group?.wallets ?? []) {
|
|
535
|
+
flat.push({ ...w, network });
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
setWallets(flat);
|
|
539
|
+
} catch (err) {
|
|
540
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
541
|
+
} finally {
|
|
542
|
+
setReady(true);
|
|
543
|
+
}
|
|
544
|
+
}, [http, state.status]);
|
|
545
|
+
(0, import_react4.useEffect)(() => {
|
|
546
|
+
void load();
|
|
547
|
+
}, [load]);
|
|
548
|
+
return { ready, wallets, refresh: load, error };
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/hooks/useSignMessage.ts
|
|
552
|
+
var import_react5 = require("react");
|
|
553
|
+
function useSignMessage() {
|
|
554
|
+
const { http } = useHorusContext();
|
|
555
|
+
const [pending, setPending] = (0, import_react5.useState)(false);
|
|
556
|
+
const [error, setError] = (0, import_react5.useState)(void 0);
|
|
557
|
+
const signMessage = (0, import_react5.useCallback)(
|
|
558
|
+
async (input) => {
|
|
559
|
+
setPending(true);
|
|
560
|
+
setError(void 0);
|
|
561
|
+
try {
|
|
562
|
+
const response = await http.post("/sign-message", input);
|
|
563
|
+
return response.signature;
|
|
564
|
+
} catch (err) {
|
|
565
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
566
|
+
setError(e);
|
|
567
|
+
throw e;
|
|
568
|
+
} finally {
|
|
569
|
+
setPending(false);
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
[http]
|
|
573
|
+
);
|
|
574
|
+
return { signMessage, pending, error };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/hooks/useTransfer.ts
|
|
578
|
+
var import_react6 = require("react");
|
|
579
|
+
function useTransfer() {
|
|
580
|
+
const { http } = useHorusContext();
|
|
581
|
+
const [pending, setPending] = (0, import_react6.useState)(false);
|
|
582
|
+
const [error, setError] = (0, import_react6.useState)(void 0);
|
|
583
|
+
const wrap = (0, import_react6.useCallback)(
|
|
584
|
+
async (fn) => {
|
|
585
|
+
setPending(true);
|
|
586
|
+
setError(void 0);
|
|
587
|
+
try {
|
|
588
|
+
return await fn();
|
|
589
|
+
} catch (err) {
|
|
590
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
591
|
+
setError(e);
|
|
592
|
+
throw e;
|
|
593
|
+
} finally {
|
|
594
|
+
setPending(false);
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
[]
|
|
598
|
+
);
|
|
599
|
+
const native = (0, import_react6.useCallback)(
|
|
600
|
+
(input) => wrap(
|
|
601
|
+
() => http.post("/transfer/native", {
|
|
602
|
+
...input,
|
|
603
|
+
amount: typeof input.amount === "bigint" ? input.amount.toString() : String(input.amount)
|
|
604
|
+
})
|
|
605
|
+
),
|
|
606
|
+
[http, wrap]
|
|
607
|
+
);
|
|
608
|
+
const token = (0, import_react6.useCallback)(
|
|
609
|
+
(input) => wrap(
|
|
610
|
+
() => http.post("/transfer/token", {
|
|
611
|
+
...input,
|
|
612
|
+
amount: typeof input.amount === "bigint" ? input.amount.toString() : String(input.amount)
|
|
613
|
+
})
|
|
614
|
+
),
|
|
615
|
+
[http, wrap]
|
|
616
|
+
);
|
|
617
|
+
return { native, token, pending, error };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/components/HorusLoginButton.tsx
|
|
621
|
+
var import_react7 = require("react");
|
|
622
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
623
|
+
function HorusLoginButton({
|
|
624
|
+
flow = "google",
|
|
625
|
+
phone,
|
|
626
|
+
onAuthenticated,
|
|
627
|
+
onError,
|
|
628
|
+
children,
|
|
629
|
+
disabled,
|
|
630
|
+
style,
|
|
631
|
+
...rest
|
|
632
|
+
}) {
|
|
633
|
+
const auth = useHorusAuth();
|
|
634
|
+
const [busy, setBusy] = (0, import_react7.useState)(false);
|
|
635
|
+
const onClick = async () => {
|
|
636
|
+
if (busy) return;
|
|
637
|
+
setBusy(true);
|
|
638
|
+
try {
|
|
639
|
+
let user;
|
|
640
|
+
if (flow === "google") user = await auth.loginWithGoogle();
|
|
641
|
+
else if (flow === "email_link") user = await auth.loginWithEmailLink();
|
|
642
|
+
else user = await auth.loginWithPhone({ phone });
|
|
643
|
+
onAuthenticated?.(user);
|
|
644
|
+
} catch (err) {
|
|
645
|
+
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
646
|
+
} finally {
|
|
647
|
+
setBusy(false);
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
const defaultStyle = {
|
|
651
|
+
padding: "10px 16px",
|
|
652
|
+
fontSize: 15,
|
|
653
|
+
fontWeight: 500,
|
|
654
|
+
borderRadius: 8,
|
|
655
|
+
border: "1px solid #e2e8f0",
|
|
656
|
+
background: "#ffffff",
|
|
657
|
+
color: "#0f172a",
|
|
658
|
+
cursor: busy || disabled ? "progress" : "pointer",
|
|
659
|
+
opacity: busy || disabled ? 0.7 : 1
|
|
660
|
+
};
|
|
661
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
662
|
+
"button",
|
|
663
|
+
{
|
|
664
|
+
...rest,
|
|
665
|
+
onClick,
|
|
666
|
+
disabled: busy || disabled,
|
|
667
|
+
style: { ...defaultStyle, ...style },
|
|
668
|
+
children: children ?? (flow === "google" ? "Continue with Google" : flow === "email_link" ? "Continue with email" : "Continue with phone")
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
673
|
+
0 && (module.exports = {
|
|
674
|
+
HorusHttpError,
|
|
675
|
+
HorusLoginButton,
|
|
676
|
+
HorusProvider,
|
|
677
|
+
useHorusAuth,
|
|
678
|
+
useSignMessage,
|
|
679
|
+
useTransfer,
|
|
680
|
+
useUser,
|
|
681
|
+
useWallets
|
|
682
|
+
});
|