@iamgame/wallet-sdk 0.1.1 → 0.1.3
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.cjs +193 -1
- package/dist/index.d.cts +32 -3
- package/dist/index.d.ts +32 -3
- package/dist/index.js +193 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -80,6 +80,23 @@ var SolvenClient = class {
|
|
|
80
80
|
this.opts.storage.set(session);
|
|
81
81
|
return session;
|
|
82
82
|
}
|
|
83
|
+
/** Email + OTP: send a one-time code to the address. */
|
|
84
|
+
async initiateEmail(email) {
|
|
85
|
+
return this.publicCall(
|
|
86
|
+
"POST",
|
|
87
|
+
"/auth/email/initiate",
|
|
88
|
+
{ email }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
/** Email + OTP: exchange the code for a session. */
|
|
92
|
+
async verifyEmail(email, code) {
|
|
93
|
+
const session = await this.publicCall("POST", "/auth/email/verify", {
|
|
94
|
+
email,
|
|
95
|
+
code
|
|
96
|
+
});
|
|
97
|
+
this.opts.storage.set(session);
|
|
98
|
+
return session;
|
|
99
|
+
}
|
|
83
100
|
async refreshSession() {
|
|
84
101
|
const current = this.opts.storage.get();
|
|
85
102
|
if (!current) return null;
|
|
@@ -353,6 +370,20 @@ function useSolvenAuth() {
|
|
|
353
370
|
const session2 = await client.verifyTelegram({ initData });
|
|
354
371
|
setSession(session2);
|
|
355
372
|
}, [client, setSession]);
|
|
373
|
+
const requestEmailOtp = react.useCallback(
|
|
374
|
+
async (email) => {
|
|
375
|
+
const { expiresAt } = await client.initiateEmail(email);
|
|
376
|
+
return { expiresAt };
|
|
377
|
+
},
|
|
378
|
+
[client]
|
|
379
|
+
);
|
|
380
|
+
const connectEmail = react.useCallback(
|
|
381
|
+
async (email, code) => {
|
|
382
|
+
const session2 = await client.verifyEmail(email, code);
|
|
383
|
+
setSession(session2);
|
|
384
|
+
},
|
|
385
|
+
[client, setSession]
|
|
386
|
+
);
|
|
356
387
|
const logout = react.useCallback(async () => {
|
|
357
388
|
await client.logout();
|
|
358
389
|
setSession(null);
|
|
@@ -360,8 +391,11 @@ function useSolvenAuth() {
|
|
|
360
391
|
return {
|
|
361
392
|
user: session?.user ?? null,
|
|
362
393
|
status,
|
|
394
|
+
accessToken: session?.accessToken ?? null,
|
|
363
395
|
connectExternal,
|
|
364
396
|
connectTelegram,
|
|
397
|
+
requestEmailOtp,
|
|
398
|
+
connectEmail,
|
|
365
399
|
logout
|
|
366
400
|
};
|
|
367
401
|
}
|
|
@@ -553,9 +587,13 @@ var defaultTheme = {
|
|
|
553
587
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
|
554
588
|
};
|
|
555
589
|
function SolvenLoginContent(props) {
|
|
556
|
-
const { connectExternal, connectTelegram, status, user } = useSolvenAuth();
|
|
590
|
+
const { connectExternal, connectTelegram, requestEmailOtp, connectEmail, status, user } = useSolvenAuth();
|
|
557
591
|
const [busy, setBusy] = react.useState(null);
|
|
558
592
|
const [error, setError] = react.useState(null);
|
|
593
|
+
const [email, setEmail] = react.useState("");
|
|
594
|
+
const [otp, setOtp] = react.useState("");
|
|
595
|
+
const [otpSent, setOtpSent] = react.useState(false);
|
|
596
|
+
const showEmail = props.showEmail ?? true;
|
|
559
597
|
const theme = react.useMemo(
|
|
560
598
|
() => ({ ...defaultTheme, ...props.theme ?? {} }),
|
|
561
599
|
[props.theme]
|
|
@@ -622,6 +660,31 @@ function SolvenLoginContent(props) {
|
|
|
622
660
|
setBusy(null);
|
|
623
661
|
}
|
|
624
662
|
};
|
|
663
|
+
const handleSendOtp = async () => {
|
|
664
|
+
setBusy("email");
|
|
665
|
+
setError(null);
|
|
666
|
+
try {
|
|
667
|
+
await requestEmailOtp(email.trim());
|
|
668
|
+
setOtpSent(true);
|
|
669
|
+
} catch (e) {
|
|
670
|
+
setError(e instanceof Error ? e.message : "Couldn't send the code");
|
|
671
|
+
} finally {
|
|
672
|
+
setBusy(null);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
const handleVerifyOtp = async () => {
|
|
676
|
+
setBusy("email");
|
|
677
|
+
setError(null);
|
|
678
|
+
try {
|
|
679
|
+
await connectEmail(email.trim(), otp.trim());
|
|
680
|
+
props.onSignIn?.();
|
|
681
|
+
props.onCloseAfterSignIn?.();
|
|
682
|
+
} catch (e) {
|
|
683
|
+
setError(e instanceof Error ? e.message : "Couldn't verify the code");
|
|
684
|
+
} finally {
|
|
685
|
+
setBusy(null);
|
|
686
|
+
}
|
|
687
|
+
};
|
|
625
688
|
const buttonBase = {
|
|
626
689
|
width: "100%",
|
|
627
690
|
padding: "0.75rem 0.875rem",
|
|
@@ -707,6 +770,135 @@ function SolvenLoginContent(props) {
|
|
|
707
770
|
w.id
|
|
708
771
|
);
|
|
709
772
|
}) }),
|
|
773
|
+
!inTma && showEmail && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1rem" }, children: [
|
|
774
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
775
|
+
"div",
|
|
776
|
+
{
|
|
777
|
+
style: {
|
|
778
|
+
display: "flex",
|
|
779
|
+
alignItems: "center",
|
|
780
|
+
gap: "0.625rem",
|
|
781
|
+
margin: "0.25rem 0 0.875rem",
|
|
782
|
+
color: theme.muted,
|
|
783
|
+
fontSize: "0.8125rem"
|
|
784
|
+
},
|
|
785
|
+
children: [
|
|
786
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1, height: 1, background: theme.border } }),
|
|
787
|
+
"or",
|
|
788
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1, height: 1, background: theme.border } })
|
|
789
|
+
]
|
|
790
|
+
}
|
|
791
|
+
),
|
|
792
|
+
!otpSent ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
793
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
794
|
+
"input",
|
|
795
|
+
{
|
|
796
|
+
type: "email",
|
|
797
|
+
inputMode: "email",
|
|
798
|
+
autoComplete: "email",
|
|
799
|
+
placeholder: "you@email.com",
|
|
800
|
+
value: email,
|
|
801
|
+
onChange: (e) => setEmail(e.target.value),
|
|
802
|
+
style: {
|
|
803
|
+
width: "100%",
|
|
804
|
+
padding: "0.75rem 0.875rem",
|
|
805
|
+
borderRadius: theme.radius,
|
|
806
|
+
border: `1px solid ${theme.border}`,
|
|
807
|
+
background: theme.surface,
|
|
808
|
+
color: theme.foreground,
|
|
809
|
+
fontSize: "0.9375rem",
|
|
810
|
+
outline: "none",
|
|
811
|
+
boxSizing: "border-box"
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
),
|
|
815
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
816
|
+
"button",
|
|
817
|
+
{
|
|
818
|
+
type: "button",
|
|
819
|
+
onClick: handleSendOtp,
|
|
820
|
+
disabled: busy !== null || !email.includes("@"),
|
|
821
|
+
style: {
|
|
822
|
+
...buttonBase,
|
|
823
|
+
justifyContent: "center",
|
|
824
|
+
background: theme.primary,
|
|
825
|
+
color: "#ffffff",
|
|
826
|
+
border: "none",
|
|
827
|
+
opacity: busy === "email" || !email.includes("@") ? 0.6 : 1
|
|
828
|
+
},
|
|
829
|
+
children: busy === "email" ? "Sending\u2026" : "Email me a code"
|
|
830
|
+
}
|
|
831
|
+
)
|
|
832
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
833
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: theme.muted, fontSize: "0.8125rem" }, children: [
|
|
834
|
+
"Enter the 6-digit code sent to ",
|
|
835
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: email.trim() }),
|
|
836
|
+
"."
|
|
837
|
+
] }),
|
|
838
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
839
|
+
"input",
|
|
840
|
+
{
|
|
841
|
+
type: "text",
|
|
842
|
+
inputMode: "numeric",
|
|
843
|
+
autoComplete: "one-time-code",
|
|
844
|
+
maxLength: 6,
|
|
845
|
+
placeholder: "123456",
|
|
846
|
+
value: otp,
|
|
847
|
+
onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
|
|
848
|
+
style: {
|
|
849
|
+
width: "100%",
|
|
850
|
+
padding: "0.75rem 0.875rem",
|
|
851
|
+
borderRadius: theme.radius,
|
|
852
|
+
border: `1px solid ${theme.border}`,
|
|
853
|
+
background: theme.surface,
|
|
854
|
+
color: theme.foreground,
|
|
855
|
+
fontSize: "1.125rem",
|
|
856
|
+
letterSpacing: "0.3em",
|
|
857
|
+
textAlign: "center",
|
|
858
|
+
outline: "none",
|
|
859
|
+
boxSizing: "border-box"
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
),
|
|
863
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
864
|
+
"button",
|
|
865
|
+
{
|
|
866
|
+
type: "button",
|
|
867
|
+
onClick: handleVerifyOtp,
|
|
868
|
+
disabled: busy !== null || otp.length !== 6,
|
|
869
|
+
style: {
|
|
870
|
+
...buttonBase,
|
|
871
|
+
justifyContent: "center",
|
|
872
|
+
background: theme.primary,
|
|
873
|
+
color: "#ffffff",
|
|
874
|
+
border: "none",
|
|
875
|
+
opacity: busy === "email" || otp.length !== 6 ? 0.6 : 1
|
|
876
|
+
},
|
|
877
|
+
children: busy === "email" ? "Verifying\u2026" : "Verify & sign in"
|
|
878
|
+
}
|
|
879
|
+
),
|
|
880
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
881
|
+
"button",
|
|
882
|
+
{
|
|
883
|
+
type: "button",
|
|
884
|
+
onClick: () => {
|
|
885
|
+
setOtpSent(false);
|
|
886
|
+
setOtp("");
|
|
887
|
+
setError(null);
|
|
888
|
+
},
|
|
889
|
+
style: {
|
|
890
|
+
background: "none",
|
|
891
|
+
border: "none",
|
|
892
|
+
color: theme.muted,
|
|
893
|
+
fontSize: "0.8125rem",
|
|
894
|
+
cursor: "pointer",
|
|
895
|
+
padding: "0.25rem"
|
|
896
|
+
},
|
|
897
|
+
children: "Use a different email"
|
|
898
|
+
}
|
|
899
|
+
)
|
|
900
|
+
] })
|
|
901
|
+
] }),
|
|
710
902
|
error && /* @__PURE__ */ jsxRuntime.jsx(
|
|
711
903
|
"div",
|
|
712
904
|
{
|
package/dist/index.d.cts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React$1 from 'react';
|
|
3
3
|
|
|
4
|
-
type AuthMethod = "siws" | "telegram";
|
|
4
|
+
type AuthMethod = "siws" | "telegram" | "email";
|
|
5
5
|
interface IUserIdentity {
|
|
6
6
|
id: string;
|
|
7
7
|
type: AuthMethod;
|
|
8
|
-
/** For SIWS: base58 Solana pubkey. For Telegram: numeric user id as string. */
|
|
8
|
+
/** For SIWS: base58 Solana pubkey. For Telegram: numeric user id as string. For email: the address. */
|
|
9
9
|
externalId: string;
|
|
10
|
+
/** Provider-specific profile captured at login (e.g. Telegram { username, firstName }).
|
|
11
|
+
* Display/attribution only — null when none was captured. */
|
|
12
|
+
profile?: Record<string, unknown> | null;
|
|
10
13
|
}
|
|
11
14
|
interface IUserWallet {
|
|
12
15
|
/** base58 Solana pubkey */
|
|
@@ -156,7 +159,7 @@ interface IWebhookSignatureHeader {
|
|
|
156
159
|
signature: string;
|
|
157
160
|
}
|
|
158
161
|
|
|
159
|
-
type IAMGameErrorCode = "auth/invalid_signature" | "auth/challenge_expired" | "auth/challenge_already_redeemed" | "auth/invalid_init_data" | "auth/init_data_stale" | "auth/init_data_replayed" | "auth/missing_token" | "auth/invalid_token" | "auth/expired_token" | "wallet/not_found" | "wallet/insufficient_balance" | "wallet/export_blocked" | "wallet/already_exported" | "user/not_found" | "user/suspended" | "compliance/blocked" | "sign/invalid_transaction" | "sign/wallet_archived" | "sign/internal_failure" | "withdrawal/insufficient_balance" | "withdrawal/limit_exceeded" | "ledger/insufficient_balance" | "ledger/session_not_found" | "ledger/session_closed" | "ledger/bet_not_found" | "ledger/bet_rolled_back" | "ledger/currency_mismatch" | "ratelimit/exceeded" | "idempotency/key_in_use" | "validation/bad_request" | "server/internal";
|
|
162
|
+
type IAMGameErrorCode = "auth/invalid_signature" | "auth/challenge_expired" | "auth/challenge_already_redeemed" | "auth/invalid_init_data" | "auth/init_data_stale" | "auth/init_data_replayed" | "auth/missing_token" | "auth/invalid_token" | "auth/expired_token" | "auth/method_not_allowed" | "auth/invalid_otp" | "auth/otp_not_found" | "auth/otp_locked" | "wallet/not_found" | "wallet/insufficient_balance" | "wallet/export_blocked" | "wallet/already_exported" | "user/not_found" | "user/suspended" | "compliance/blocked" | "sign/invalid_transaction" | "sign/wallet_archived" | "sign/internal_failure" | "withdrawal/insufficient_balance" | "withdrawal/limit_exceeded" | "ledger/insufficient_balance" | "ledger/session_not_found" | "ledger/session_closed" | "ledger/bet_not_found" | "ledger/bet_rolled_back" | "ledger/currency_mismatch" | "ratelimit/exceeded" | "idempotency/key_in_use" | "validation/bad_request" | "server/internal";
|
|
160
163
|
interface IIAMGameErrorEnvelope {
|
|
161
164
|
code: IAMGameErrorCode;
|
|
162
165
|
message: string;
|
|
@@ -194,6 +197,13 @@ declare class IAMGameClient {
|
|
|
194
197
|
requestSiwsChallenge(publicKey: string): Promise<ISiwsChallenge>;
|
|
195
198
|
verifySiws(req: ISiwsVerifyRequest): Promise<ISession>;
|
|
196
199
|
verifyTelegram(req: ITelegramVerifyRequest): Promise<ISession>;
|
|
200
|
+
/** Email + OTP: send a one-time code to the address. */
|
|
201
|
+
initiateEmail(email: string): Promise<{
|
|
202
|
+
sent: boolean;
|
|
203
|
+
expiresAt: string;
|
|
204
|
+
}>;
|
|
205
|
+
/** Email + OTP: exchange the code for a session. */
|
|
206
|
+
verifyEmail(email: string, code: string): Promise<ISession>;
|
|
197
207
|
refreshSession(): Promise<ISession | null>;
|
|
198
208
|
logout(): Promise<void>;
|
|
199
209
|
getSession(): ISession | null;
|
|
@@ -251,6 +261,19 @@ interface IUseIAMGameAuth {
|
|
|
251
261
|
connectExternal: (adapter: IExternalWalletAdapter) => Promise<void>;
|
|
252
262
|
/** Run the Telegram TMA flow. Reads `window.Telegram.WebApp.initData` and posts to IAMGame. */
|
|
253
263
|
connectTelegram: () => Promise<void>;
|
|
264
|
+
/**
|
|
265
|
+
* The current session's access token, or null when anonymous. Hand this to YOUR
|
|
266
|
+
* backend, which verifies it server-to-server (POST /v1/sessions/verify with your
|
|
267
|
+
* secret key) and mints its own app session. Never trust it as proof of identity
|
|
268
|
+
* on its own — it must be verified server-side.
|
|
269
|
+
*/
|
|
270
|
+
accessToken: string | null;
|
|
271
|
+
/** Email + OTP step 1: email a one-time code to the address. */
|
|
272
|
+
requestEmailOtp: (email: string) => Promise<{
|
|
273
|
+
expiresAt: string;
|
|
274
|
+
}>;
|
|
275
|
+
/** Email + OTP step 2: exchange the code for a session. */
|
|
276
|
+
connectEmail: (email: string, code: string) => Promise<void>;
|
|
254
277
|
logout: () => Promise<void>;
|
|
255
278
|
}
|
|
256
279
|
declare function useIAMGameAuth(): IUseIAMGameAuth;
|
|
@@ -307,6 +330,12 @@ interface CommonProps {
|
|
|
307
330
|
* no clicks. Defaults to true.
|
|
308
331
|
*/
|
|
309
332
|
autoTelegram?: boolean;
|
|
333
|
+
/**
|
|
334
|
+
* Show the email + one-time-code login option (web / non-TMA). Defaults to true.
|
|
335
|
+
* The app must also enable `email` in its allowed auth methods, or the send-code
|
|
336
|
+
* call is rejected.
|
|
337
|
+
*/
|
|
338
|
+
showEmail?: boolean;
|
|
310
339
|
}
|
|
311
340
|
type IAMGameLoginProps = CommonProps;
|
|
312
341
|
interface IAMGameLoginModalProps extends CommonProps {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React$1 from 'react';
|
|
3
3
|
|
|
4
|
-
type AuthMethod = "siws" | "telegram";
|
|
4
|
+
type AuthMethod = "siws" | "telegram" | "email";
|
|
5
5
|
interface IUserIdentity {
|
|
6
6
|
id: string;
|
|
7
7
|
type: AuthMethod;
|
|
8
|
-
/** For SIWS: base58 Solana pubkey. For Telegram: numeric user id as string. */
|
|
8
|
+
/** For SIWS: base58 Solana pubkey. For Telegram: numeric user id as string. For email: the address. */
|
|
9
9
|
externalId: string;
|
|
10
|
+
/** Provider-specific profile captured at login (e.g. Telegram { username, firstName }).
|
|
11
|
+
* Display/attribution only — null when none was captured. */
|
|
12
|
+
profile?: Record<string, unknown> | null;
|
|
10
13
|
}
|
|
11
14
|
interface IUserWallet {
|
|
12
15
|
/** base58 Solana pubkey */
|
|
@@ -156,7 +159,7 @@ interface IWebhookSignatureHeader {
|
|
|
156
159
|
signature: string;
|
|
157
160
|
}
|
|
158
161
|
|
|
159
|
-
type IAMGameErrorCode = "auth/invalid_signature" | "auth/challenge_expired" | "auth/challenge_already_redeemed" | "auth/invalid_init_data" | "auth/init_data_stale" | "auth/init_data_replayed" | "auth/missing_token" | "auth/invalid_token" | "auth/expired_token" | "wallet/not_found" | "wallet/insufficient_balance" | "wallet/export_blocked" | "wallet/already_exported" | "user/not_found" | "user/suspended" | "compliance/blocked" | "sign/invalid_transaction" | "sign/wallet_archived" | "sign/internal_failure" | "withdrawal/insufficient_balance" | "withdrawal/limit_exceeded" | "ledger/insufficient_balance" | "ledger/session_not_found" | "ledger/session_closed" | "ledger/bet_not_found" | "ledger/bet_rolled_back" | "ledger/currency_mismatch" | "ratelimit/exceeded" | "idempotency/key_in_use" | "validation/bad_request" | "server/internal";
|
|
162
|
+
type IAMGameErrorCode = "auth/invalid_signature" | "auth/challenge_expired" | "auth/challenge_already_redeemed" | "auth/invalid_init_data" | "auth/init_data_stale" | "auth/init_data_replayed" | "auth/missing_token" | "auth/invalid_token" | "auth/expired_token" | "auth/method_not_allowed" | "auth/invalid_otp" | "auth/otp_not_found" | "auth/otp_locked" | "wallet/not_found" | "wallet/insufficient_balance" | "wallet/export_blocked" | "wallet/already_exported" | "user/not_found" | "user/suspended" | "compliance/blocked" | "sign/invalid_transaction" | "sign/wallet_archived" | "sign/internal_failure" | "withdrawal/insufficient_balance" | "withdrawal/limit_exceeded" | "ledger/insufficient_balance" | "ledger/session_not_found" | "ledger/session_closed" | "ledger/bet_not_found" | "ledger/bet_rolled_back" | "ledger/currency_mismatch" | "ratelimit/exceeded" | "idempotency/key_in_use" | "validation/bad_request" | "server/internal";
|
|
160
163
|
interface IIAMGameErrorEnvelope {
|
|
161
164
|
code: IAMGameErrorCode;
|
|
162
165
|
message: string;
|
|
@@ -194,6 +197,13 @@ declare class IAMGameClient {
|
|
|
194
197
|
requestSiwsChallenge(publicKey: string): Promise<ISiwsChallenge>;
|
|
195
198
|
verifySiws(req: ISiwsVerifyRequest): Promise<ISession>;
|
|
196
199
|
verifyTelegram(req: ITelegramVerifyRequest): Promise<ISession>;
|
|
200
|
+
/** Email + OTP: send a one-time code to the address. */
|
|
201
|
+
initiateEmail(email: string): Promise<{
|
|
202
|
+
sent: boolean;
|
|
203
|
+
expiresAt: string;
|
|
204
|
+
}>;
|
|
205
|
+
/** Email + OTP: exchange the code for a session. */
|
|
206
|
+
verifyEmail(email: string, code: string): Promise<ISession>;
|
|
197
207
|
refreshSession(): Promise<ISession | null>;
|
|
198
208
|
logout(): Promise<void>;
|
|
199
209
|
getSession(): ISession | null;
|
|
@@ -251,6 +261,19 @@ interface IUseIAMGameAuth {
|
|
|
251
261
|
connectExternal: (adapter: IExternalWalletAdapter) => Promise<void>;
|
|
252
262
|
/** Run the Telegram TMA flow. Reads `window.Telegram.WebApp.initData` and posts to IAMGame. */
|
|
253
263
|
connectTelegram: () => Promise<void>;
|
|
264
|
+
/**
|
|
265
|
+
* The current session's access token, or null when anonymous. Hand this to YOUR
|
|
266
|
+
* backend, which verifies it server-to-server (POST /v1/sessions/verify with your
|
|
267
|
+
* secret key) and mints its own app session. Never trust it as proof of identity
|
|
268
|
+
* on its own — it must be verified server-side.
|
|
269
|
+
*/
|
|
270
|
+
accessToken: string | null;
|
|
271
|
+
/** Email + OTP step 1: email a one-time code to the address. */
|
|
272
|
+
requestEmailOtp: (email: string) => Promise<{
|
|
273
|
+
expiresAt: string;
|
|
274
|
+
}>;
|
|
275
|
+
/** Email + OTP step 2: exchange the code for a session. */
|
|
276
|
+
connectEmail: (email: string, code: string) => Promise<void>;
|
|
254
277
|
logout: () => Promise<void>;
|
|
255
278
|
}
|
|
256
279
|
declare function useIAMGameAuth(): IUseIAMGameAuth;
|
|
@@ -307,6 +330,12 @@ interface CommonProps {
|
|
|
307
330
|
* no clicks. Defaults to true.
|
|
308
331
|
*/
|
|
309
332
|
autoTelegram?: boolean;
|
|
333
|
+
/**
|
|
334
|
+
* Show the email + one-time-code login option (web / non-TMA). Defaults to true.
|
|
335
|
+
* The app must also enable `email` in its allowed auth methods, or the send-code
|
|
336
|
+
* call is rejected.
|
|
337
|
+
*/
|
|
338
|
+
showEmail?: boolean;
|
|
310
339
|
}
|
|
311
340
|
type IAMGameLoginProps = CommonProps;
|
|
312
341
|
interface IAMGameLoginModalProps extends CommonProps {
|
package/dist/index.js
CHANGED
|
@@ -78,6 +78,23 @@ var SolvenClient = class {
|
|
|
78
78
|
this.opts.storage.set(session);
|
|
79
79
|
return session;
|
|
80
80
|
}
|
|
81
|
+
/** Email + OTP: send a one-time code to the address. */
|
|
82
|
+
async initiateEmail(email) {
|
|
83
|
+
return this.publicCall(
|
|
84
|
+
"POST",
|
|
85
|
+
"/auth/email/initiate",
|
|
86
|
+
{ email }
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
/** Email + OTP: exchange the code for a session. */
|
|
90
|
+
async verifyEmail(email, code) {
|
|
91
|
+
const session = await this.publicCall("POST", "/auth/email/verify", {
|
|
92
|
+
email,
|
|
93
|
+
code
|
|
94
|
+
});
|
|
95
|
+
this.opts.storage.set(session);
|
|
96
|
+
return session;
|
|
97
|
+
}
|
|
81
98
|
async refreshSession() {
|
|
82
99
|
const current = this.opts.storage.get();
|
|
83
100
|
if (!current) return null;
|
|
@@ -351,6 +368,20 @@ function useSolvenAuth() {
|
|
|
351
368
|
const session2 = await client.verifyTelegram({ initData });
|
|
352
369
|
setSession(session2);
|
|
353
370
|
}, [client, setSession]);
|
|
371
|
+
const requestEmailOtp = useCallback(
|
|
372
|
+
async (email) => {
|
|
373
|
+
const { expiresAt } = await client.initiateEmail(email);
|
|
374
|
+
return { expiresAt };
|
|
375
|
+
},
|
|
376
|
+
[client]
|
|
377
|
+
);
|
|
378
|
+
const connectEmail = useCallback(
|
|
379
|
+
async (email, code) => {
|
|
380
|
+
const session2 = await client.verifyEmail(email, code);
|
|
381
|
+
setSession(session2);
|
|
382
|
+
},
|
|
383
|
+
[client, setSession]
|
|
384
|
+
);
|
|
354
385
|
const logout = useCallback(async () => {
|
|
355
386
|
await client.logout();
|
|
356
387
|
setSession(null);
|
|
@@ -358,8 +389,11 @@ function useSolvenAuth() {
|
|
|
358
389
|
return {
|
|
359
390
|
user: session?.user ?? null,
|
|
360
391
|
status,
|
|
392
|
+
accessToken: session?.accessToken ?? null,
|
|
361
393
|
connectExternal,
|
|
362
394
|
connectTelegram,
|
|
395
|
+
requestEmailOtp,
|
|
396
|
+
connectEmail,
|
|
363
397
|
logout
|
|
364
398
|
};
|
|
365
399
|
}
|
|
@@ -551,9 +585,13 @@ var defaultTheme = {
|
|
|
551
585
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
|
552
586
|
};
|
|
553
587
|
function SolvenLoginContent(props) {
|
|
554
|
-
const { connectExternal, connectTelegram, status, user } = useSolvenAuth();
|
|
588
|
+
const { connectExternal, connectTelegram, requestEmailOtp, connectEmail, status, user } = useSolvenAuth();
|
|
555
589
|
const [busy, setBusy] = useState(null);
|
|
556
590
|
const [error, setError] = useState(null);
|
|
591
|
+
const [email, setEmail] = useState("");
|
|
592
|
+
const [otp, setOtp] = useState("");
|
|
593
|
+
const [otpSent, setOtpSent] = useState(false);
|
|
594
|
+
const showEmail = props.showEmail ?? true;
|
|
557
595
|
const theme = useMemo(
|
|
558
596
|
() => ({ ...defaultTheme, ...props.theme ?? {} }),
|
|
559
597
|
[props.theme]
|
|
@@ -620,6 +658,31 @@ function SolvenLoginContent(props) {
|
|
|
620
658
|
setBusy(null);
|
|
621
659
|
}
|
|
622
660
|
};
|
|
661
|
+
const handleSendOtp = async () => {
|
|
662
|
+
setBusy("email");
|
|
663
|
+
setError(null);
|
|
664
|
+
try {
|
|
665
|
+
await requestEmailOtp(email.trim());
|
|
666
|
+
setOtpSent(true);
|
|
667
|
+
} catch (e) {
|
|
668
|
+
setError(e instanceof Error ? e.message : "Couldn't send the code");
|
|
669
|
+
} finally {
|
|
670
|
+
setBusy(null);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
const handleVerifyOtp = async () => {
|
|
674
|
+
setBusy("email");
|
|
675
|
+
setError(null);
|
|
676
|
+
try {
|
|
677
|
+
await connectEmail(email.trim(), otp.trim());
|
|
678
|
+
props.onSignIn?.();
|
|
679
|
+
props.onCloseAfterSignIn?.();
|
|
680
|
+
} catch (e) {
|
|
681
|
+
setError(e instanceof Error ? e.message : "Couldn't verify the code");
|
|
682
|
+
} finally {
|
|
683
|
+
setBusy(null);
|
|
684
|
+
}
|
|
685
|
+
};
|
|
623
686
|
const buttonBase = {
|
|
624
687
|
width: "100%",
|
|
625
688
|
padding: "0.75rem 0.875rem",
|
|
@@ -705,6 +768,135 @@ function SolvenLoginContent(props) {
|
|
|
705
768
|
w.id
|
|
706
769
|
);
|
|
707
770
|
}) }),
|
|
771
|
+
!inTma && showEmail && /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem" }, children: [
|
|
772
|
+
/* @__PURE__ */ jsxs(
|
|
773
|
+
"div",
|
|
774
|
+
{
|
|
775
|
+
style: {
|
|
776
|
+
display: "flex",
|
|
777
|
+
alignItems: "center",
|
|
778
|
+
gap: "0.625rem",
|
|
779
|
+
margin: "0.25rem 0 0.875rem",
|
|
780
|
+
color: theme.muted,
|
|
781
|
+
fontSize: "0.8125rem"
|
|
782
|
+
},
|
|
783
|
+
children: [
|
|
784
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1, height: 1, background: theme.border } }),
|
|
785
|
+
"or",
|
|
786
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1, height: 1, background: theme.border } })
|
|
787
|
+
]
|
|
788
|
+
}
|
|
789
|
+
),
|
|
790
|
+
!otpSent ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
791
|
+
/* @__PURE__ */ jsx(
|
|
792
|
+
"input",
|
|
793
|
+
{
|
|
794
|
+
type: "email",
|
|
795
|
+
inputMode: "email",
|
|
796
|
+
autoComplete: "email",
|
|
797
|
+
placeholder: "you@email.com",
|
|
798
|
+
value: email,
|
|
799
|
+
onChange: (e) => setEmail(e.target.value),
|
|
800
|
+
style: {
|
|
801
|
+
width: "100%",
|
|
802
|
+
padding: "0.75rem 0.875rem",
|
|
803
|
+
borderRadius: theme.radius,
|
|
804
|
+
border: `1px solid ${theme.border}`,
|
|
805
|
+
background: theme.surface,
|
|
806
|
+
color: theme.foreground,
|
|
807
|
+
fontSize: "0.9375rem",
|
|
808
|
+
outline: "none",
|
|
809
|
+
boxSizing: "border-box"
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
),
|
|
813
|
+
/* @__PURE__ */ jsx(
|
|
814
|
+
"button",
|
|
815
|
+
{
|
|
816
|
+
type: "button",
|
|
817
|
+
onClick: handleSendOtp,
|
|
818
|
+
disabled: busy !== null || !email.includes("@"),
|
|
819
|
+
style: {
|
|
820
|
+
...buttonBase,
|
|
821
|
+
justifyContent: "center",
|
|
822
|
+
background: theme.primary,
|
|
823
|
+
color: "#ffffff",
|
|
824
|
+
border: "none",
|
|
825
|
+
opacity: busy === "email" || !email.includes("@") ? 0.6 : 1
|
|
826
|
+
},
|
|
827
|
+
children: busy === "email" ? "Sending\u2026" : "Email me a code"
|
|
828
|
+
}
|
|
829
|
+
)
|
|
830
|
+
] }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
831
|
+
/* @__PURE__ */ jsxs("div", { style: { color: theme.muted, fontSize: "0.8125rem" }, children: [
|
|
832
|
+
"Enter the 6-digit code sent to ",
|
|
833
|
+
/* @__PURE__ */ jsx("strong", { children: email.trim() }),
|
|
834
|
+
"."
|
|
835
|
+
] }),
|
|
836
|
+
/* @__PURE__ */ jsx(
|
|
837
|
+
"input",
|
|
838
|
+
{
|
|
839
|
+
type: "text",
|
|
840
|
+
inputMode: "numeric",
|
|
841
|
+
autoComplete: "one-time-code",
|
|
842
|
+
maxLength: 6,
|
|
843
|
+
placeholder: "123456",
|
|
844
|
+
value: otp,
|
|
845
|
+
onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
|
|
846
|
+
style: {
|
|
847
|
+
width: "100%",
|
|
848
|
+
padding: "0.75rem 0.875rem",
|
|
849
|
+
borderRadius: theme.radius,
|
|
850
|
+
border: `1px solid ${theme.border}`,
|
|
851
|
+
background: theme.surface,
|
|
852
|
+
color: theme.foreground,
|
|
853
|
+
fontSize: "1.125rem",
|
|
854
|
+
letterSpacing: "0.3em",
|
|
855
|
+
textAlign: "center",
|
|
856
|
+
outline: "none",
|
|
857
|
+
boxSizing: "border-box"
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
),
|
|
861
|
+
/* @__PURE__ */ jsx(
|
|
862
|
+
"button",
|
|
863
|
+
{
|
|
864
|
+
type: "button",
|
|
865
|
+
onClick: handleVerifyOtp,
|
|
866
|
+
disabled: busy !== null || otp.length !== 6,
|
|
867
|
+
style: {
|
|
868
|
+
...buttonBase,
|
|
869
|
+
justifyContent: "center",
|
|
870
|
+
background: theme.primary,
|
|
871
|
+
color: "#ffffff",
|
|
872
|
+
border: "none",
|
|
873
|
+
opacity: busy === "email" || otp.length !== 6 ? 0.6 : 1
|
|
874
|
+
},
|
|
875
|
+
children: busy === "email" ? "Verifying\u2026" : "Verify & sign in"
|
|
876
|
+
}
|
|
877
|
+
),
|
|
878
|
+
/* @__PURE__ */ jsx(
|
|
879
|
+
"button",
|
|
880
|
+
{
|
|
881
|
+
type: "button",
|
|
882
|
+
onClick: () => {
|
|
883
|
+
setOtpSent(false);
|
|
884
|
+
setOtp("");
|
|
885
|
+
setError(null);
|
|
886
|
+
},
|
|
887
|
+
style: {
|
|
888
|
+
background: "none",
|
|
889
|
+
border: "none",
|
|
890
|
+
color: theme.muted,
|
|
891
|
+
fontSize: "0.8125rem",
|
|
892
|
+
cursor: "pointer",
|
|
893
|
+
padding: "0.25rem"
|
|
894
|
+
},
|
|
895
|
+
children: "Use a different email"
|
|
896
|
+
}
|
|
897
|
+
)
|
|
898
|
+
] })
|
|
899
|
+
] }),
|
|
708
900
|
error && /* @__PURE__ */ jsx(
|
|
709
901
|
"div",
|
|
710
902
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iamgame/wallet-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "IAMGame Wallet browser SDK — Telegram & Solana wallet auth, balances, server-side signing, and key export. Drop-in React provider for game frontends.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://wallet.iamgame.com",
|