/
magic.ts
415 lines (395 loc) · 11.8 KB
/
magic.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
import { Connector, WagmiAdapter } from "../interfaces/connector";
import { AbstractClientWallet, WalletOptions } from "./base";
import { MagicAuthOptions } from "../connectors/magic/types";
import type {
MagicAuthConnectOptions,
MagicAuthConnector as MagicAuthConnectorType,
} from "../connectors/magic";
import type {
OAuthProvider as _OAuthProvider,
OAuthRedirectResult,
} from "@magic-ext/oauth";
import { walletIds } from "../constants/walletIds";
export type MagicLinkAdditionalOptions = MagicAuthOptions;
export type MagicLinkOptions = WalletOptions<MagicAuthOptions>;
export type MagicLinkConnectOptions = MagicAuthConnectOptions;
export type MagicOAuthProvider = _OAuthProvider;
/**
* Allows users to connect to your app using [Magic Auth](https://magic.link/docs/auth/overview) or [Magic Connect](https://magic.link/docs/connect/overview)
*
* Magic is a developer SDK that integrates with your application to enable passwordless Web3 onboarding (no seed phrases) and authentication using magic links
*
*
* @example
*
* Magic offers two flavors of our SDK: Magic Connect, which provides a plug-and-play experience, and Magic Auth, a customizable white-labeled wallet solution.
*
* ## Magic Auth
*
* #### Login with Email or Phone Number
*
* With below configuration, users will be able to log in using their email or phone number.
*
* If you want to restrict login via email only - pass `smsLogin:false`. If you want to restrict login via phone number only - pass `emailLogin:false` to the `MagicLink` constructor.
*
* ```javascript
* import { MagicLink } from "@thirdweb-dev/wallets";
*
* const wallet = new MagicLink({
* apiKey: "YOUR_API_KEY",
* type: "auth",
* });
*
* // connect with email or phone number
*
* await wallet.connect({
* email: "user@example.com",
* });
*
* // OR
*
* await wallet.connect({
* phoneNumber: "+123456789",
* });
* ```
*
*
* #### Social Login
*
* ```javascript
* import { MagicLink } from "@thirdweb-dev/wallets";
*
* const wallet = new MagicLink({
* apiKey: "YOUR_API_KEY",
* type: "auth",
*
* // specify which Oauth providers to enable
* // and the URI to redirect to after the oauth flow is complete
* oauthOptions: {
* redirectURI: "https://example.com/foobar",
* providers: ["google", "facebook"],
* },
* });
*
* // connect with a oauth provider
* await magic.connect({
* oauthProvider: "google",
* });
* ```
*
* ## Magic Connect
*
* ```javascript
* import { MagicLink } from "@thirdweb-dev/wallets";
*
* const wallet = new MagicLink({
* apiKey: "YOUR_API_KEY",
* type: "connect",
* });
*
* await wallet.connect();
* ```
*
* @wallet
*/
export class MagicLink extends AbstractClientWallet<
MagicLinkOptions,
MagicAuthConnectOptions
> {
/**
* @internal
*/
connector?: Connector;
/**
* @internal
*/
magicConnector?: MagicAuthConnectorType;
/**
* @internal
*/
oAuthRedirectResult?: OAuthRedirectResult;
/**
* @internal
*/
static meta = {
iconURL:
"ipfs://QmUMBFZGXxBpgDmZzZAHhbcCL5nYvZnVaYLTajsNjLcxMU/1-Icon_Magic_Color.svg",
name: "Magic Link",
};
/**
* @internal
*/
static id = walletIds.magicLink as string;
/**
* @internal
*/
public get walletName() {
return "Magic Link" as const;
}
/**
* @internal
*/
options: MagicLinkOptions;
/**
* Create an instance of the `MagicLink` wallet
* @param options -
* The `options` object includes the following properties:
*
* ### apiKey (required)
* Your Magic Link apiKey. You can get an API key by signing up for an account on [Magic Link's website](https://magic.link/).
*
* Must be a `string`.
*
* ### clientId (recommended)
* Provide `clientId` to use the thirdweb RPCs for given `chains`
*
* You can create a client ID for your application from [thirdweb dashboard](https://thirdweb.com/create-api-key).
*
* ### type (optional)
* Whether to use [Magic Auth](https://magic.link/docs/auth/overview) or [Magic Connect](https://magic.link/docs/connect/overview) to connect to the wallet.
*
* Default is `"auth"`.
*
* ```ts
* type: "auth" | "connect";
* ```
*
* ### magicSdkConfiguration (optional)
* Configuration for [Magic Auth](https://magic.link/docs/auth/overview) SDK.
*
* This is only relevant if you are using `type: 'auth'`.
*
* ```ts
* {
* locale?: string;
* endpoint?: string;
* testMode?: boolean;
* }
* ```
*
* * locale (optional) - Customize the language of Magic's modal, email and confirmation screen. See [Localization](https://magic.link/docs/auth/more/customization/localization) for more.
*
* * endpoint (optional) - A URL pointing to the Magic iframe application.
*
* * testMode (optional) - Enable [testMode](https://magic.link/docs/auth/introduction/test-mode) to assert the desired behavior through the email address so that you don't have to go through the auth flow.
*
*
* ### smsLogin
* Specify whether you want to allow users to log in with their phone number or not. It is `true` by default
*
* This is only relevant if you are using `type: 'auth'`.
*
* Must be a `boolean`.
*
* ### emailLogin (optional)
* Specify whether you want to allow users to log in with their email or not. It is `true` by default
*
* This is only relevant if you are using `type: 'auth'`.
*
* Must be a `boolean`.
*
*
* ### oauthOptions (optional)
* Specify which oauth providers you support in `providers` array. This is only relevant if you are using `type: 'auth'`.
*
* Specify which URI to redirect to after the oauth flow is complete in `redirectURI` option. If no `redirectURI` is specified, the user will be redirected to the current page.
*
* You must pass full URL and not just a relative path. For example, `"https://example.com/foo"` is valid but `"/foo"` is not.
* You can use `new URL("/foo", window.location.origin).href` to get the full URL from a relative path based on the current origin.
*
* You also need to enable the oauth providers for your apiKey from [Magic dashboard](https://dashboard.magic.link/).
*
* ```ts
* type OauthOptions = {
* redirectURI?: string;
* providers: OauthProvider[];
* };
*
* type OauthProvider =
* | "google"
* | "facebook"
* | "apple"
* | "github"
* | "bitbucket"
* | "gitlab"
* | "linkedin"
* | "twitter"
* | "discord"
* | "twitch"
* | "microsoft";
* ```
*
* ```ts
* const wallet = new MagicLink({
* apiKey: "YOUR_API_KEY",
* type: "auth",
* // specify which Oauth providers to enable
* oauthOptions: {
* redirectURI: new URL("/foo", window.location.origin).href,
* providers: ["google", "facebook", "github", "bitbucket"],
* },
* });
* ```
*
*
* ### chains (optional)
* Provide an array of chains you want to support.
*
* Must be an array of `Chain` objects, from the [`@thirdweb-dev/chains`](https://www.npmjs.com/package/\@thirdweb-dev/chains) package.
*
*/
constructor(options: MagicLinkOptions) {
super(MagicLink.id, options);
this.options = options;
}
/**
* @internal
*/
async initializeConnector() {
// import the connector dynamically
const { MagicAuthConnector } = await import("../connectors/magic");
const magicConnector = new MagicAuthConnector({
chains: this.chains,
options: this.options,
});
this.magicConnector = magicConnector;
this.connector = new WagmiAdapter(magicConnector);
return this.connector;
}
protected async getConnector(): Promise<Connector> {
if (!this.connector) {
return await this.initializeConnector();
}
return this.connector;
}
/**
* Get Magic Auth SDK instance. Learn more about [Magic Auth SDK](https://magic.link/docs/auth/overview)
*
* you use all methods available in the Magic Auth SDK from the SDK instance. Refer to [Magic Auth API](https://magic.link/docs/auth/api-reference/client-side-sdks/web) for more details.
*
* ```javascript
* const magicSDK = await wallet.getMagic();
* ```
*/
getMagic() {
if (!this.magicConnector) {
throw new Error("Magic connector is not initialized");
}
return this.magicConnector.getMagicSDK();
}
/**
* Auto connect wallet if the user is already logged in.
* @returns
*/
async autoConnect(options?: MagicAuthConnectOptions) {
await this.initializeConnector();
await this.magicConnector?.initializeMagicSDK(options);
const magic = this.getMagic();
if (typeof window !== "undefined") {
const url = new URL(window.location.href);
const isMagicRedirect = url.searchParams.get("magic_credential");
if (isMagicRedirect) {
try {
this.oAuthRedirectResult = await magic.oauth.getRedirectResult(); // required to do this for social login
} catch {
// ignore
}
}
}
const isLoggedIn = await magic.user.isLoggedIn();
if (isLoggedIn) {
return super.autoConnect(options);
}
throw new Error("Magic user is not logged in");
}
/**
* Disconnect wallet
*/
async disconnect() {
this.oAuthRedirectResult = undefined;
const magic = this.getMagic();
await magic.user.logout();
return super.disconnect();
}
/**
* Connect Wallet using Magic Auth or Magic Connect
*
* ### Magic Auth
* There are three ways to call the `connect` function - `email` or `phoneNumber` or `oauthProvider`
*
* #### email
* This opens the Magic Link's Modal and prompts the user to click on the link sent to their email.
*
* ```ts
* await wallet.connect({
* email: "user@example.com",
* });
* ```
*
* #### phoneNumber
* This opens the Magic Link's Modal and prompts the user to enter the OTP sent to their phone via SMS.
*
* ```ts
* await wallet.connect({
* phoneNumber: "+123456789",
* });
* ```
*
* #### oauthProvider
* This redirects the user to given provider's sign-in page and once the user is authenticated, it redirects the user back to the `redirectURI` provided in `MagicLink` constructor.
*
* ```ts
* await magic.connect({
* oauthProvider: "google",
* });
* ```
*
* #### Additional Configuration
*
* ```typescript
* wallet.connect({
* chainId: 5,
* });
* ```
*
* If `chainId` is provided, the wallet will be connected to the network with the given chainId, else it will be connected to the Ethereum Mainnet.
*
* ### Magic Connect
* You can call the `connect` function without any arguments. Calling `connect` opens the Magic Link's Modal and prompts the user to login via Google or email.
*
* ```ts
* await wallet.connect();
* ```
*
* #### Additional Configuration
*
* ```typescript
* wallet.connect({
* chainId: 5,
* });
* ```
*
* If `chainId` is provided, the wallet will be connected to the network with the given chainId, else it will be connected to the Ethereum Mainnet.
*
* @param options - The `options` object can include the following properties:
* ### Magic Auth
* If you are using `type: 'auth'`, you can pass any one of the following properties
* - `email` - The email address of the user
* - `phoneNumber` - The phone number of the user
* - `oauthProvider` - The oauth provider to use for login
*
* ### Magic Connect
* If you are using `type: 'connect'`, you don't need to pass any arguments to `connect` function.
*
* @returns
*/
async connect(options: MagicAuthConnectOptions) {
if ("email" in options && this.options.emailLogin === false) {
throw new Error("Email login is disabled");
}
if ("phoneNumber" in options && this.options.smsLogin === false) {
throw new Error("SMS login is disabled");
}
return super.connect(options);
}
}