Skip to content

Commit 6fc8afd

Browse files
Add account deletion support when unlinking profiles
1 parent 0299034 commit 6fc8afd

File tree

13 files changed

+196
-9
lines changed

13 files changed

+196
-9
lines changed

.changeset/account-deletion-unlink.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
**Add account deletion support when unlinking profiles**
6+
7+
Added optional `allowAccountDeletion` parameter to `useUnlinkProfile` hook and `unlinkProfile` function. When set to `true`, this allows deleting the entire account when unlinking the last profile associated with it.
8+
9+
**React Hook Example:**
10+
11+
```tsx
12+
import { useUnlinkProfile } from "thirdweb/react";
13+
14+
const { mutate: unlinkProfile } = useUnlinkProfile();
15+
16+
const handleUnlink = () => {
17+
unlinkProfile({
18+
client,
19+
profileToUnlink: connectedProfiles[0],
20+
allowAccountDeletion: true, // Delete account if last profile
21+
});
22+
};
23+
```
24+
25+
**Direct Function Example:**
26+
27+
```ts
28+
import { unlinkProfile } from "thirdweb/wallets/in-app";
29+
30+
const updatedProfiles = await unlinkProfile({
31+
client,
32+
profileToUnlink: profiles[0],
33+
allowAccountDeletion: true, // Delete account if last profile
34+
});
35+
```

packages/thirdweb/src/react/native/hooks/wallets/useUnlinkProfile.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,32 @@ describe("useUnlinkProfile", () => {
4040
client: TEST_CLIENT,
4141
ecosystem: undefined,
4242
profileToUnlink: mockProfile,
43+
allowAccountDeletion: false,
44+
});
45+
expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
46+
queryKey: ["profiles"],
47+
});
48+
});
49+
50+
it("should call unlinkProfile with allowAccountDeletion if true", async () => {
51+
const { result } = renderHook(() => useUnlinkProfile(), {
52+
wrapper,
53+
});
54+
const mutationFn = result.current.mutateAsync;
55+
56+
await act(async () => {
57+
await mutationFn({
58+
client: TEST_CLIENT,
59+
profileToUnlink: mockProfile,
60+
allowAccountDeletion: true,
61+
});
62+
});
63+
64+
expect(unlinkProfile).toHaveBeenCalledWith({
65+
client: TEST_CLIENT,
66+
ecosystem: undefined,
67+
profileToUnlink: mockProfile,
68+
allowAccountDeletion: true,
4369
});
4470
expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
4571
queryKey: ["profiles"],
@@ -70,6 +96,7 @@ describe("useUnlinkProfile", () => {
7096
?.partnerId,
7197
},
7298
profileToUnlink: mockProfile,
99+
allowAccountDeletion: false,
73100
});
74101
});
75102
});

packages/thirdweb/src/react/native/hooks/wallets/useUnlinkProfile.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ import { useConnectedWallets } from "../../../core/hooks/wallets/useConnectedWal
3131
* };
3232
* ```
3333
*
34+
* ### Unlinking an email account with account deletion
35+
*
36+
* ```jsx
37+
* import { useUnlinkProfile } from "thirdweb/react";
38+
*
39+
* const { mutate: unlinkProfile } = useUnlinkProfile();
40+
*
41+
* const onClick = () => {
42+
* unlinkProfile({
43+
* client,
44+
* // Select the profile you want to unlink
45+
* profileToUnlink: connectedProfiles[0],
46+
* allowAccountDeletion: true, // This will delete the account if it's the last profile linked to the account
47+
* });
48+
* };
49+
* ```
50+
*
3451
* @wallet
3552
*/
3653
export function useUnlinkProfile() {
@@ -40,7 +57,12 @@ export function useUnlinkProfile() {
4057
mutationFn: async ({
4158
client,
4259
profileToUnlink,
43-
}: { client: ThirdwebClient; profileToUnlink: Profile }) => {
60+
allowAccountDeletion = false,
61+
}: {
62+
client: ThirdwebClient;
63+
profileToUnlink: Profile;
64+
allowAccountDeletion?: boolean;
65+
}) => {
4466
const ecosystemWallet = wallets.find((w) => isEcosystemWallet(w));
4567
const ecosystem: Ecosystem | undefined = ecosystemWallet
4668
? {
@@ -53,6 +75,7 @@ export function useUnlinkProfile() {
5375
client,
5476
ecosystem,
5577
profileToUnlink,
78+
allowAccountDeletion,
5679
});
5780
},
5881
onSuccess: () => {

packages/thirdweb/src/react/web/hooks/wallets/useUnlinkProfile.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ describe("useUnlinkProfile", () => {
4040
client: TEST_CLIENT,
4141
ecosystem: undefined,
4242
profileToUnlink: mockProfile,
43+
allowAccountDeletion: false,
4344
});
4445
expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
4546
queryKey: ["profiles"],
@@ -70,6 +71,7 @@ describe("useUnlinkProfile", () => {
7071
?.partnerId,
7172
},
7273
profileToUnlink: mockProfile,
74+
allowAccountDeletion: false,
7375
});
7476
});
7577
});

packages/thirdweb/src/react/web/hooks/wallets/useUnlinkProfile.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ import { useConnectedWallets } from "../../../core/hooks/wallets/useConnectedWal
3131
* };
3232
* ```
3333
*
34+
* ### Unlinking an email account with account deletion
35+
*
36+
* ```jsx
37+
* import { useUnlinkProfile } from "thirdweb/react";
38+
*
39+
* const { mutate: unlinkProfile } = useUnlinkProfile();
40+
*
41+
* const onClick = () => {
42+
* unlinkProfile({
43+
* client,
44+
* // Select the profile you want to unlink
45+
* profileToUnlink: connectedProfiles[0],
46+
* allowAccountDeletion: true, // This will delete the account if it's the last profile linked to the account
47+
* });
48+
* };
49+
* ```
50+
*
3451
* @wallet
3552
*/
3653
export function useUnlinkProfile() {
@@ -40,7 +57,12 @@ export function useUnlinkProfile() {
4057
mutationFn: async ({
4158
client,
4259
profileToUnlink,
43-
}: { client: ThirdwebClient; profileToUnlink: Profile }) => {
60+
allowAccountDeletion = false,
61+
}: {
62+
client: ThirdwebClient;
63+
profileToUnlink: Profile;
64+
allowAccountDeletion?: boolean;
65+
}) => {
4466
const ecosystemWallet = wallets.find((w) => isEcosystemWallet(w));
4567
const ecosystem: Ecosystem | undefined = ecosystemWallet
4668
? {
@@ -53,6 +75,7 @@ export function useUnlinkProfile() {
5375
client,
5476
ecosystem,
5577
profileToUnlink,
78+
allowAccountDeletion,
5679
});
5780
},
5881
onSuccess: () => {

packages/thirdweb/src/wallets/in-app/core/authentication/linkAccount.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,37 @@ describe("Account linking functions", () => {
9090
Authorization: "Bearer iaw-auth-token:mock-token",
9191
"Content-Type": "application/json",
9292
},
93-
body: JSON.stringify(profileToUnlink),
93+
body: JSON.stringify({
94+
type: profileToUnlink.type,
95+
details: profileToUnlink.details,
96+
allowAccountDeletion: false,
97+
}),
98+
},
99+
);
100+
expect(result).toEqual(mockLinkedAccounts);
101+
});
102+
103+
it("should successfully unlink an account with allowAccountDeletion", async () => {
104+
const result = await unlinkAccount({
105+
client: mockClient,
106+
profileToUnlink,
107+
storage: mockStorage,
108+
allowAccountDeletion: true,
109+
});
110+
111+
expect(mockFetch).toHaveBeenCalledWith(
112+
"https://embedded-wallet.thirdweb.com/api/2024-05-05/account/disconnect",
113+
{
114+
method: "POST",
115+
headers: {
116+
Authorization: "Bearer iaw-auth-token:mock-token",
117+
"Content-Type": "application/json",
118+
},
119+
body: JSON.stringify({
120+
type: profileToUnlink.type,
121+
details: profileToUnlink.details,
122+
allowAccountDeletion: true,
123+
}),
94124
},
95125
);
96126
expect(result).toEqual(mockLinkedAccounts);

packages/thirdweb/src/wallets/in-app/core/authentication/linkAccount.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,13 @@ export async function unlinkAccount({
6666
client,
6767
ecosystem,
6868
profileToUnlink,
69+
allowAccountDeletion = false,
6970
storage,
7071
}: {
7172
client: ThirdwebClient;
7273
ecosystem?: Ecosystem;
7374
profileToUnlink: Profile;
75+
allowAccountDeletion?: boolean;
7476
storage: ClientScopedStorage;
7577
}): Promise<Profile[]> {
7678
const clientFetch = getClientFetch(client, ecosystem);
@@ -90,7 +92,11 @@ export async function unlinkAccount({
9092
{
9193
method: "POST",
9294
headers,
93-
body: stringify(profileToUnlink),
95+
body: stringify({
96+
type: profileToUnlink.type,
97+
details: profileToUnlink.details,
98+
allowAccountDeletion,
99+
}),
94100
},
95101
);
96102

packages/thirdweb/src/wallets/in-app/core/authentication/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,5 @@ export type UnlinkParams = {
259259
client: ThirdwebClient;
260260
ecosystem?: Ecosystem;
261261
profileToUnlink: Profile;
262+
allowAccountDeletion?: boolean;
262263
};

packages/thirdweb/src/wallets/in-app/core/interfaces/connector.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ export interface InAppConnector {
3737
): Promise<AuthLoginReturnType>;
3838
logout(): Promise<LogoutReturnType>;
3939
linkProfile(args: AuthArgsType): Promise<Profile[]>;
40-
unlinkProfile(args: Profile): Promise<Profile[]>;
40+
unlinkProfile(
41+
args: Profile,
42+
allowAccountDeletion?: boolean,
43+
): Promise<Profile[]>;
4144
getProfiles(): Promise<Profile[]>;
4245
storage: ClientScopedStorage;
4346
}

packages/thirdweb/src/wallets/in-app/native/auth/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,10 @@ export async function linkProfile(args: AuthArgsType) {
203203
*/
204204
export async function unlinkProfile(args: UnlinkParams) {
205205
const connector = await getInAppWalletConnector(args.client, args.ecosystem);
206-
return await connector.unlinkProfile(args.profileToUnlink);
206+
return await connector.unlinkProfile(
207+
args.profileToUnlink,
208+
args.allowAccountDeletion,
209+
);
207210
}
208211

209212
/**

packages/thirdweb/src/wallets/in-app/native/native-connector.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ export class InAppNativeConnector implements InAppConnector {
367367
});
368368
}
369369

370-
async unlinkProfile(profile: Profile) {
370+
async unlinkProfile(profile: Profile, allowAccountDeletion?: boolean) {
371371
const { unlinkAccount } = await import(
372372
"../core/authentication/linkAccount.js"
373373
);
@@ -376,6 +376,7 @@ export class InAppNativeConnector implements InAppConnector {
376376
ecosystem: this.ecosystem,
377377
storage: this.storage,
378378
profileToUnlink: profile,
379+
allowAccountDeletion,
379380
});
380381
}
381382

packages/thirdweb/src/wallets/in-app/web/lib/auth/index.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ export async function linkProfile(args: AuthArgsType) {
224224
* @throws If the unlinking fails. This can happen if the account has no other associated profiles or if the profile that is being unlinked doesn't exists for the current logged in user.
225225
*
226226
* @example
227+
* ### Unlinking an authentication method
228+
*
227229
* ```ts
228230
* import { inAppWallet } from "thirdweb/wallets";
229231
*
@@ -239,11 +241,41 @@ export async function linkProfile(args: AuthArgsType) {
239241
* profileToUnlink: profiles[0],
240242
* });
241243
* ```
244+
*
245+
* ### Unlinking an authentication for ecosystems
246+
*
247+
* ```ts
248+
* import { unlinkProfile } from "thirdweb/wallets/in-app";
249+
*
250+
* const updatedProfiles = await unlinkProfile({
251+
* client,
252+
* ecosystem: {
253+
* id: "ecosystem.your-ecosystem-id",
254+
* },
255+
* profileToUnlink: profiles[0],
256+
* });
257+
* ```
258+
*
259+
* ### Unlinking an authentication method with account deletion
260+
*
261+
* ```ts
262+
* import { unlinkProfile } from "thirdweb/wallets/in-app";
263+
*
264+
* const updatedProfiles = await unlinkProfile({
265+
* client,
266+
* profileToUnlink: profiles[0],
267+
* allowAccountDeletion: true, // This will delete the account if it's the last profile linked to the account
268+
* });
269+
* ```
270+
*
242271
* @wallet
243272
*/
244273
export async function unlinkProfile(args: UnlinkParams) {
245274
const connector = await getInAppWalletConnector(args.client, args.ecosystem);
246-
return await connector.unlinkProfile(args.profileToUnlink);
275+
return await connector.unlinkProfile(
276+
args.profileToUnlink,
277+
args.allowAccountDeletion,
278+
);
247279
}
248280

249281
/**

packages/thirdweb/src/wallets/in-app/web/lib/web-connector.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,12 +470,13 @@ export class InAppWebConnector implements InAppConnector {
470470
});
471471
}
472472

473-
async unlinkProfile(profile: Profile) {
473+
async unlinkProfile(profile: Profile, allowAccountDeletion?: boolean) {
474474
return await unlinkAccount({
475475
client: this.client,
476476
storage: this.storage,
477477
ecosystem: this.ecosystem,
478478
profileToUnlink: profile,
479+
allowAccountDeletion,
479480
});
480481
}
481482

0 commit comments

Comments
 (0)