@iamgame/wallet-sdk 0.1.1 → 0.1.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/dist/index.cjs +192 -1
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +192 -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);
|
|
@@ -362,6 +393,8 @@ function useSolvenAuth() {
|
|
|
362
393
|
status,
|
|
363
394
|
connectExternal,
|
|
364
395
|
connectTelegram,
|
|
396
|
+
requestEmailOtp,
|
|
397
|
+
connectEmail,
|
|
365
398
|
logout
|
|
366
399
|
};
|
|
367
400
|
}
|
|
@@ -553,9 +586,13 @@ var defaultTheme = {
|
|
|
553
586
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
|
554
587
|
};
|
|
555
588
|
function SolvenLoginContent(props) {
|
|
556
|
-
const { connectExternal, connectTelegram, status, user } = useSolvenAuth();
|
|
589
|
+
const { connectExternal, connectTelegram, requestEmailOtp, connectEmail, status, user } = useSolvenAuth();
|
|
557
590
|
const [busy, setBusy] = react.useState(null);
|
|
558
591
|
const [error, setError] = react.useState(null);
|
|
592
|
+
const [email, setEmail] = react.useState("");
|
|
593
|
+
const [otp, setOtp] = react.useState("");
|
|
594
|
+
const [otpSent, setOtpSent] = react.useState(false);
|
|
595
|
+
const showEmail = props.showEmail ?? true;
|
|
559
596
|
const theme = react.useMemo(
|
|
560
597
|
() => ({ ...defaultTheme, ...props.theme ?? {} }),
|
|
561
598
|
[props.theme]
|
|
@@ -622,6 +659,31 @@ function SolvenLoginContent(props) {
|
|
|
622
659
|
setBusy(null);
|
|
623
660
|
}
|
|
624
661
|
};
|
|
662
|
+
const handleSendOtp = async () => {
|
|
663
|
+
setBusy("email");
|
|
664
|
+
setError(null);
|
|
665
|
+
try {
|
|
666
|
+
await requestEmailOtp(email.trim());
|
|
667
|
+
setOtpSent(true);
|
|
668
|
+
} catch (e) {
|
|
669
|
+
setError(e instanceof Error ? e.message : "Couldn't send the code");
|
|
670
|
+
} finally {
|
|
671
|
+
setBusy(null);
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
const handleVerifyOtp = async () => {
|
|
675
|
+
setBusy("email");
|
|
676
|
+
setError(null);
|
|
677
|
+
try {
|
|
678
|
+
await connectEmail(email.trim(), otp.trim());
|
|
679
|
+
props.onSignIn?.();
|
|
680
|
+
props.onCloseAfterSignIn?.();
|
|
681
|
+
} catch (e) {
|
|
682
|
+
setError(e instanceof Error ? e.message : "Couldn't verify the code");
|
|
683
|
+
} finally {
|
|
684
|
+
setBusy(null);
|
|
685
|
+
}
|
|
686
|
+
};
|
|
625
687
|
const buttonBase = {
|
|
626
688
|
width: "100%",
|
|
627
689
|
padding: "0.75rem 0.875rem",
|
|
@@ -707,6 +769,135 @@ function SolvenLoginContent(props) {
|
|
|
707
769
|
w.id
|
|
708
770
|
);
|
|
709
771
|
}) }),
|
|
772
|
+
!inTma && showEmail && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1rem" }, children: [
|
|
773
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
774
|
+
"div",
|
|
775
|
+
{
|
|
776
|
+
style: {
|
|
777
|
+
display: "flex",
|
|
778
|
+
alignItems: "center",
|
|
779
|
+
gap: "0.625rem",
|
|
780
|
+
margin: "0.25rem 0 0.875rem",
|
|
781
|
+
color: theme.muted,
|
|
782
|
+
fontSize: "0.8125rem"
|
|
783
|
+
},
|
|
784
|
+
children: [
|
|
785
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1, height: 1, background: theme.border } }),
|
|
786
|
+
"or",
|
|
787
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1, height: 1, background: theme.border } })
|
|
788
|
+
]
|
|
789
|
+
}
|
|
790
|
+
),
|
|
791
|
+
!otpSent ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
792
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
793
|
+
"input",
|
|
794
|
+
{
|
|
795
|
+
type: "email",
|
|
796
|
+
inputMode: "email",
|
|
797
|
+
autoComplete: "email",
|
|
798
|
+
placeholder: "you@email.com",
|
|
799
|
+
value: email,
|
|
800
|
+
onChange: (e) => setEmail(e.target.value),
|
|
801
|
+
style: {
|
|
802
|
+
width: "100%",
|
|
803
|
+
padding: "0.75rem 0.875rem",
|
|
804
|
+
borderRadius: theme.radius,
|
|
805
|
+
border: `1px solid ${theme.border}`,
|
|
806
|
+
background: theme.surface,
|
|
807
|
+
color: theme.foreground,
|
|
808
|
+
fontSize: "0.9375rem",
|
|
809
|
+
outline: "none",
|
|
810
|
+
boxSizing: "border-box"
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
),
|
|
814
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
815
|
+
"button",
|
|
816
|
+
{
|
|
817
|
+
type: "button",
|
|
818
|
+
onClick: handleSendOtp,
|
|
819
|
+
disabled: busy !== null || !email.includes("@"),
|
|
820
|
+
style: {
|
|
821
|
+
...buttonBase,
|
|
822
|
+
justifyContent: "center",
|
|
823
|
+
background: theme.primary,
|
|
824
|
+
color: "#ffffff",
|
|
825
|
+
border: "none",
|
|
826
|
+
opacity: busy === "email" || !email.includes("@") ? 0.6 : 1
|
|
827
|
+
},
|
|
828
|
+
children: busy === "email" ? "Sending\u2026" : "Email me a code"
|
|
829
|
+
}
|
|
830
|
+
)
|
|
831
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
832
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: theme.muted, fontSize: "0.8125rem" }, children: [
|
|
833
|
+
"Enter the 6-digit code sent to ",
|
|
834
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: email.trim() }),
|
|
835
|
+
"."
|
|
836
|
+
] }),
|
|
837
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
838
|
+
"input",
|
|
839
|
+
{
|
|
840
|
+
type: "text",
|
|
841
|
+
inputMode: "numeric",
|
|
842
|
+
autoComplete: "one-time-code",
|
|
843
|
+
maxLength: 6,
|
|
844
|
+
placeholder: "123456",
|
|
845
|
+
value: otp,
|
|
846
|
+
onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
|
|
847
|
+
style: {
|
|
848
|
+
width: "100%",
|
|
849
|
+
padding: "0.75rem 0.875rem",
|
|
850
|
+
borderRadius: theme.radius,
|
|
851
|
+
border: `1px solid ${theme.border}`,
|
|
852
|
+
background: theme.surface,
|
|
853
|
+
color: theme.foreground,
|
|
854
|
+
fontSize: "1.125rem",
|
|
855
|
+
letterSpacing: "0.3em",
|
|
856
|
+
textAlign: "center",
|
|
857
|
+
outline: "none",
|
|
858
|
+
boxSizing: "border-box"
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
),
|
|
862
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
863
|
+
"button",
|
|
864
|
+
{
|
|
865
|
+
type: "button",
|
|
866
|
+
onClick: handleVerifyOtp,
|
|
867
|
+
disabled: busy !== null || otp.length !== 6,
|
|
868
|
+
style: {
|
|
869
|
+
...buttonBase,
|
|
870
|
+
justifyContent: "center",
|
|
871
|
+
background: theme.primary,
|
|
872
|
+
color: "#ffffff",
|
|
873
|
+
border: "none",
|
|
874
|
+
opacity: busy === "email" || otp.length !== 6 ? 0.6 : 1
|
|
875
|
+
},
|
|
876
|
+
children: busy === "email" ? "Verifying\u2026" : "Verify & sign in"
|
|
877
|
+
}
|
|
878
|
+
),
|
|
879
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
880
|
+
"button",
|
|
881
|
+
{
|
|
882
|
+
type: "button",
|
|
883
|
+
onClick: () => {
|
|
884
|
+
setOtpSent(false);
|
|
885
|
+
setOtp("");
|
|
886
|
+
setError(null);
|
|
887
|
+
},
|
|
888
|
+
style: {
|
|
889
|
+
background: "none",
|
|
890
|
+
border: "none",
|
|
891
|
+
color: theme.muted,
|
|
892
|
+
fontSize: "0.8125rem",
|
|
893
|
+
cursor: "pointer",
|
|
894
|
+
padding: "0.25rem"
|
|
895
|
+
},
|
|
896
|
+
children: "Use a different email"
|
|
897
|
+
}
|
|
898
|
+
)
|
|
899
|
+
] })
|
|
900
|
+
] }),
|
|
710
901
|
error && /* @__PURE__ */ jsxRuntime.jsx(
|
|
711
902
|
"div",
|
|
712
903
|
{
|
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,12 @@ 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
|
+
/** Email + OTP step 1: email a one-time code to the address. */
|
|
265
|
+
requestEmailOtp: (email: string) => Promise<{
|
|
266
|
+
expiresAt: string;
|
|
267
|
+
}>;
|
|
268
|
+
/** Email + OTP step 2: exchange the code for a session. */
|
|
269
|
+
connectEmail: (email: string, code: string) => Promise<void>;
|
|
254
270
|
logout: () => Promise<void>;
|
|
255
271
|
}
|
|
256
272
|
declare function useIAMGameAuth(): IUseIAMGameAuth;
|
|
@@ -307,6 +323,12 @@ interface CommonProps {
|
|
|
307
323
|
* no clicks. Defaults to true.
|
|
308
324
|
*/
|
|
309
325
|
autoTelegram?: boolean;
|
|
326
|
+
/**
|
|
327
|
+
* Show the email + one-time-code login option (web / non-TMA). Defaults to true.
|
|
328
|
+
* The app must also enable `email` in its allowed auth methods, or the send-code
|
|
329
|
+
* call is rejected.
|
|
330
|
+
*/
|
|
331
|
+
showEmail?: boolean;
|
|
310
332
|
}
|
|
311
333
|
type IAMGameLoginProps = CommonProps;
|
|
312
334
|
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,12 @@ 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
|
+
/** Email + OTP step 1: email a one-time code to the address. */
|
|
265
|
+
requestEmailOtp: (email: string) => Promise<{
|
|
266
|
+
expiresAt: string;
|
|
267
|
+
}>;
|
|
268
|
+
/** Email + OTP step 2: exchange the code for a session. */
|
|
269
|
+
connectEmail: (email: string, code: string) => Promise<void>;
|
|
254
270
|
logout: () => Promise<void>;
|
|
255
271
|
}
|
|
256
272
|
declare function useIAMGameAuth(): IUseIAMGameAuth;
|
|
@@ -307,6 +323,12 @@ interface CommonProps {
|
|
|
307
323
|
* no clicks. Defaults to true.
|
|
308
324
|
*/
|
|
309
325
|
autoTelegram?: boolean;
|
|
326
|
+
/**
|
|
327
|
+
* Show the email + one-time-code login option (web / non-TMA). Defaults to true.
|
|
328
|
+
* The app must also enable `email` in its allowed auth methods, or the send-code
|
|
329
|
+
* call is rejected.
|
|
330
|
+
*/
|
|
331
|
+
showEmail?: boolean;
|
|
310
332
|
}
|
|
311
333
|
type IAMGameLoginProps = CommonProps;
|
|
312
334
|
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);
|
|
@@ -360,6 +391,8 @@ function useSolvenAuth() {
|
|
|
360
391
|
status,
|
|
361
392
|
connectExternal,
|
|
362
393
|
connectTelegram,
|
|
394
|
+
requestEmailOtp,
|
|
395
|
+
connectEmail,
|
|
363
396
|
logout
|
|
364
397
|
};
|
|
365
398
|
}
|
|
@@ -551,9 +584,13 @@ var defaultTheme = {
|
|
|
551
584
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
|
552
585
|
};
|
|
553
586
|
function SolvenLoginContent(props) {
|
|
554
|
-
const { connectExternal, connectTelegram, status, user } = useSolvenAuth();
|
|
587
|
+
const { connectExternal, connectTelegram, requestEmailOtp, connectEmail, status, user } = useSolvenAuth();
|
|
555
588
|
const [busy, setBusy] = useState(null);
|
|
556
589
|
const [error, setError] = useState(null);
|
|
590
|
+
const [email, setEmail] = useState("");
|
|
591
|
+
const [otp, setOtp] = useState("");
|
|
592
|
+
const [otpSent, setOtpSent] = useState(false);
|
|
593
|
+
const showEmail = props.showEmail ?? true;
|
|
557
594
|
const theme = useMemo(
|
|
558
595
|
() => ({ ...defaultTheme, ...props.theme ?? {} }),
|
|
559
596
|
[props.theme]
|
|
@@ -620,6 +657,31 @@ function SolvenLoginContent(props) {
|
|
|
620
657
|
setBusy(null);
|
|
621
658
|
}
|
|
622
659
|
};
|
|
660
|
+
const handleSendOtp = async () => {
|
|
661
|
+
setBusy("email");
|
|
662
|
+
setError(null);
|
|
663
|
+
try {
|
|
664
|
+
await requestEmailOtp(email.trim());
|
|
665
|
+
setOtpSent(true);
|
|
666
|
+
} catch (e) {
|
|
667
|
+
setError(e instanceof Error ? e.message : "Couldn't send the code");
|
|
668
|
+
} finally {
|
|
669
|
+
setBusy(null);
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
const handleVerifyOtp = async () => {
|
|
673
|
+
setBusy("email");
|
|
674
|
+
setError(null);
|
|
675
|
+
try {
|
|
676
|
+
await connectEmail(email.trim(), otp.trim());
|
|
677
|
+
props.onSignIn?.();
|
|
678
|
+
props.onCloseAfterSignIn?.();
|
|
679
|
+
} catch (e) {
|
|
680
|
+
setError(e instanceof Error ? e.message : "Couldn't verify the code");
|
|
681
|
+
} finally {
|
|
682
|
+
setBusy(null);
|
|
683
|
+
}
|
|
684
|
+
};
|
|
623
685
|
const buttonBase = {
|
|
624
686
|
width: "100%",
|
|
625
687
|
padding: "0.75rem 0.875rem",
|
|
@@ -705,6 +767,135 @@ function SolvenLoginContent(props) {
|
|
|
705
767
|
w.id
|
|
706
768
|
);
|
|
707
769
|
}) }),
|
|
770
|
+
!inTma && showEmail && /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem" }, children: [
|
|
771
|
+
/* @__PURE__ */ jsxs(
|
|
772
|
+
"div",
|
|
773
|
+
{
|
|
774
|
+
style: {
|
|
775
|
+
display: "flex",
|
|
776
|
+
alignItems: "center",
|
|
777
|
+
gap: "0.625rem",
|
|
778
|
+
margin: "0.25rem 0 0.875rem",
|
|
779
|
+
color: theme.muted,
|
|
780
|
+
fontSize: "0.8125rem"
|
|
781
|
+
},
|
|
782
|
+
children: [
|
|
783
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1, height: 1, background: theme.border } }),
|
|
784
|
+
"or",
|
|
785
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1, height: 1, background: theme.border } })
|
|
786
|
+
]
|
|
787
|
+
}
|
|
788
|
+
),
|
|
789
|
+
!otpSent ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
790
|
+
/* @__PURE__ */ jsx(
|
|
791
|
+
"input",
|
|
792
|
+
{
|
|
793
|
+
type: "email",
|
|
794
|
+
inputMode: "email",
|
|
795
|
+
autoComplete: "email",
|
|
796
|
+
placeholder: "you@email.com",
|
|
797
|
+
value: email,
|
|
798
|
+
onChange: (e) => setEmail(e.target.value),
|
|
799
|
+
style: {
|
|
800
|
+
width: "100%",
|
|
801
|
+
padding: "0.75rem 0.875rem",
|
|
802
|
+
borderRadius: theme.radius,
|
|
803
|
+
border: `1px solid ${theme.border}`,
|
|
804
|
+
background: theme.surface,
|
|
805
|
+
color: theme.foreground,
|
|
806
|
+
fontSize: "0.9375rem",
|
|
807
|
+
outline: "none",
|
|
808
|
+
boxSizing: "border-box"
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
),
|
|
812
|
+
/* @__PURE__ */ jsx(
|
|
813
|
+
"button",
|
|
814
|
+
{
|
|
815
|
+
type: "button",
|
|
816
|
+
onClick: handleSendOtp,
|
|
817
|
+
disabled: busy !== null || !email.includes("@"),
|
|
818
|
+
style: {
|
|
819
|
+
...buttonBase,
|
|
820
|
+
justifyContent: "center",
|
|
821
|
+
background: theme.primary,
|
|
822
|
+
color: "#ffffff",
|
|
823
|
+
border: "none",
|
|
824
|
+
opacity: busy === "email" || !email.includes("@") ? 0.6 : 1
|
|
825
|
+
},
|
|
826
|
+
children: busy === "email" ? "Sending\u2026" : "Email me a code"
|
|
827
|
+
}
|
|
828
|
+
)
|
|
829
|
+
] }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
830
|
+
/* @__PURE__ */ jsxs("div", { style: { color: theme.muted, fontSize: "0.8125rem" }, children: [
|
|
831
|
+
"Enter the 6-digit code sent to ",
|
|
832
|
+
/* @__PURE__ */ jsx("strong", { children: email.trim() }),
|
|
833
|
+
"."
|
|
834
|
+
] }),
|
|
835
|
+
/* @__PURE__ */ jsx(
|
|
836
|
+
"input",
|
|
837
|
+
{
|
|
838
|
+
type: "text",
|
|
839
|
+
inputMode: "numeric",
|
|
840
|
+
autoComplete: "one-time-code",
|
|
841
|
+
maxLength: 6,
|
|
842
|
+
placeholder: "123456",
|
|
843
|
+
value: otp,
|
|
844
|
+
onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
|
|
845
|
+
style: {
|
|
846
|
+
width: "100%",
|
|
847
|
+
padding: "0.75rem 0.875rem",
|
|
848
|
+
borderRadius: theme.radius,
|
|
849
|
+
border: `1px solid ${theme.border}`,
|
|
850
|
+
background: theme.surface,
|
|
851
|
+
color: theme.foreground,
|
|
852
|
+
fontSize: "1.125rem",
|
|
853
|
+
letterSpacing: "0.3em",
|
|
854
|
+
textAlign: "center",
|
|
855
|
+
outline: "none",
|
|
856
|
+
boxSizing: "border-box"
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
),
|
|
860
|
+
/* @__PURE__ */ jsx(
|
|
861
|
+
"button",
|
|
862
|
+
{
|
|
863
|
+
type: "button",
|
|
864
|
+
onClick: handleVerifyOtp,
|
|
865
|
+
disabled: busy !== null || otp.length !== 6,
|
|
866
|
+
style: {
|
|
867
|
+
...buttonBase,
|
|
868
|
+
justifyContent: "center",
|
|
869
|
+
background: theme.primary,
|
|
870
|
+
color: "#ffffff",
|
|
871
|
+
border: "none",
|
|
872
|
+
opacity: busy === "email" || otp.length !== 6 ? 0.6 : 1
|
|
873
|
+
},
|
|
874
|
+
children: busy === "email" ? "Verifying\u2026" : "Verify & sign in"
|
|
875
|
+
}
|
|
876
|
+
),
|
|
877
|
+
/* @__PURE__ */ jsx(
|
|
878
|
+
"button",
|
|
879
|
+
{
|
|
880
|
+
type: "button",
|
|
881
|
+
onClick: () => {
|
|
882
|
+
setOtpSent(false);
|
|
883
|
+
setOtp("");
|
|
884
|
+
setError(null);
|
|
885
|
+
},
|
|
886
|
+
style: {
|
|
887
|
+
background: "none",
|
|
888
|
+
border: "none",
|
|
889
|
+
color: theme.muted,
|
|
890
|
+
fontSize: "0.8125rem",
|
|
891
|
+
cursor: "pointer",
|
|
892
|
+
padding: "0.25rem"
|
|
893
|
+
},
|
|
894
|
+
children: "Use a different email"
|
|
895
|
+
}
|
|
896
|
+
)
|
|
897
|
+
] })
|
|
898
|
+
] }),
|
|
708
899
|
error && /* @__PURE__ */ jsx(
|
|
709
900
|
"div",
|
|
710
901
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iamgame/wallet-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|