Skip to content

Commit 7707c82

Browse files
authored
Merge pull request #5675 from WalletConnect/feat/multi-session-provider
feat: isolate UP to a particular session
2 parents 70a0f6d + 5f5170d commit 7707c82

File tree

3 files changed

+84
-37
lines changed

3 files changed

+84
-37
lines changed

providers/universal-provider/src/UniversalProvider.ts

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export class UniversalProvider implements IUniversalProvider {
160160
// assign namespaces from session if not already defined
161161
const approved = populateNamespacesChains(this.session.namespaces) as NamespaceConfig;
162162
this.namespaces = mergeRequiredOptionalNamespaces(this.namespaces, approved);
163-
this.persist("namespaces", this.namespaces);
163+
await this.persist("namespaces", this.namespaces);
164164
this.onConnect();
165165
}
166166
return result;
@@ -205,7 +205,8 @@ export class UniversalProvider implements IUniversalProvider {
205205
// assign namespaces from session if not already defined
206206
const approved = populateNamespacesChains(session.namespaces) as NamespaceConfig;
207207
this.namespaces = mergeRequiredOptionalNamespaces(this.namespaces, approved);
208-
this.persist("namespaces", this.namespaces);
208+
await this.persist("namespaces", this.namespaces);
209+
await this.persist("optionalNamespaces", this.optionalNamespaces);
209210

210211
this.onConnect();
211212
return this.session;
@@ -253,13 +254,9 @@ export class UniversalProvider implements IUniversalProvider {
253254
// ---------- Private ----------------------------------------------- //
254255

255256
private async checkStorage() {
256-
this.namespaces = await this.getFromStore("namespaces");
257-
this.optionalNamespaces = (await this.getFromStore("optionalNamespaces")) || {};
258-
if (this.client.session.length) {
259-
const lastKeyIndex = this.client.session.keys.length - 1;
260-
this.session = this.client.session.get(this.client.session.keys[lastKeyIndex]);
261-
this.createProviders();
262-
}
257+
this.namespaces = (await this.getFromStore(`namespaces`)) || {};
258+
this.optionalNamespaces = (await this.getFromStore(`optionalNamespaces`)) || {};
259+
if (this.session) this.createProviders();
263260
}
264261

265262
private async initialize() {
@@ -285,6 +282,19 @@ export class UniversalProvider implements IUniversalProvider {
285282
telemetryEnabled: this.providerOpts.telemetryEnabled,
286283
}));
287284

285+
if (this.providerOpts.session) {
286+
try {
287+
this.session = this.client.session.get(this.providerOpts.session.topic);
288+
} catch (error) {
289+
this.logger.error("Failed to get session", error);
290+
throw new Error(
291+
`The provided session: ${this.providerOpts?.session?.topic} doesn't exist in the Sign client`,
292+
);
293+
}
294+
} else {
295+
const sessions = this.client.session.getAll();
296+
this.session = sessions[0];
297+
}
288298
this.logger.trace(`SignClient Initialized`);
289299
}
290300

@@ -389,11 +399,14 @@ export class UniversalProvider implements IUniversalProvider {
389399
}
390400

391401
this.client.on("session_ping", (args) => {
402+
const { topic } = args;
403+
if (topic !== this.session?.topic) return;
392404
this.events.emit("session_ping", args);
393405
});
394406

395407
this.client.on("session_event", (args) => {
396-
const { params } = args;
408+
const { params, topic } = args;
409+
if (topic !== this.session?.topic) return;
397410
const { event } = params;
398411
if (event.name === "accountsChanged") {
399412
const accounts = event.data;
@@ -419,6 +432,7 @@ export class UniversalProvider implements IUniversalProvider {
419432
});
420433

421434
this.client.on("session_update", ({ topic, params }) => {
435+
if (topic !== this.session?.topic) return;
422436
const { namespaces } = params;
423437
const _session = this.client?.session.get(topic);
424438
this.session = { ..._session, namespaces } as SessionTypes.Struct;
@@ -427,6 +441,7 @@ export class UniversalProvider implements IUniversalProvider {
427441
});
428442

429443
this.client.on("session_delete", async (payload) => {
444+
if (payload.topic !== this.session?.topic) return;
430445
await this.cleanup();
431446
this.events.emit("session_delete", payload);
432447
this.events.emit("disconnect", {
@@ -463,8 +478,6 @@ export class UniversalProvider implements IUniversalProvider {
463478
}
464479
this.sessionProperties = sessionProperties;
465480
this.scopedProperties = scopedProperties;
466-
this.persist("namespaces", namespaces);
467-
this.persist("optionalNamespaces", optionalNamespaces);
468481
}
469482

470483
private validateChain(chain?: string): [string, string] {
@@ -497,7 +510,7 @@ export class UniversalProvider implements IUniversalProvider {
497510
return await this.getProvider(namespace).requestAccounts();
498511
}
499512

500-
private onChainChanged(caip2Chain: string, internal = false): void {
513+
private async onChainChanged(caip2Chain: string, internal = false): Promise<void> {
501514
if (!this.namespaces) return;
502515

503516
const [namespace, chainId] = this.validateChain(caip2Chain);
@@ -517,8 +530,8 @@ export class UniversalProvider implements IUniversalProvider {
517530
this.namespaces[`${namespace}:${chainId}`] = { defaultChain: chainId };
518531
}
519532

520-
this.persist("namespaces", this.namespaces);
521533
this.events.emit("chainChanged", chainId);
534+
await this.persist("namespaces", this.namespaces);
522535
}
523536

524537
private onConnect() {
@@ -527,23 +540,48 @@ export class UniversalProvider implements IUniversalProvider {
527540
}
528541

529542
private async cleanup() {
530-
this.session = undefined;
531543
this.namespaces = undefined;
532544
this.optionalNamespaces = undefined;
533545
this.sessionProperties = undefined;
534-
this.scopedProperties = undefined;
535-
this.persist("namespaces", undefined);
536-
this.persist("optionalNamespaces", undefined);
537-
this.persist("sessionProperties", undefined);
546+
await this.deleteFromStore("namespaces");
547+
await this.deleteFromStore("optionalNamespaces");
548+
await this.deleteFromStore("sessionProperties");
549+
// reset the session after removing from store as the topic is used there
550+
this.session = undefined;
538551
await this.cleanupPendingPairings({ deletePairings: true });
552+
await this.cleanupStorage();
539553
}
540554

541-
private persist(key: string, data: unknown) {
542-
this.client.core.storage.setItem(`${STORAGE}/${key}`, data);
555+
private async persist(key: string, data: unknown) {
556+
const topic = this.session?.topic || "";
557+
await this.client.core.storage.setItem(`${STORAGE}/${key}${topic}`, data);
543558
}
544559

545560
private async getFromStore(key: string) {
546-
return await this.client.core.storage.getItem(`${STORAGE}/${key}`);
561+
const topic = this.session?.topic || "";
562+
return await this.client.core.storage.getItem(`${STORAGE}/${key}${topic}`);
563+
}
564+
565+
private async deleteFromStore(key: string) {
566+
const topic = this.session?.topic || "";
567+
await this.client.core.storage.removeItem(`${STORAGE}/${key}${topic}`);
568+
}
569+
570+
// remove all storage items if there are no sessions left
571+
private async cleanupStorage() {
572+
try {
573+
if (this.client?.session.length > 0) {
574+
return;
575+
}
576+
const keys = await this.client.core.storage.getKeys();
577+
for (const key of keys) {
578+
if (key.startsWith(STORAGE)) {
579+
await this.client.core.storage.removeItem(key);
580+
}
581+
}
582+
} catch (error) {
583+
this.logger.warn("Failed to cleanup storage", error);
584+
}
547585
}
548586
}
549587
export default UniversalProvider;

providers/universal-provider/src/types/misc.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { IEvents } from "@walletconnect/events";
66
import { Logger } from "@walletconnect/logger";
77
import { IProvider } from "./providers";
88

9+
/**
10+
* @param session - The session to use. If not provided, the provider will create a new session.
11+
*/
912
export interface UniversalProviderOpts extends SignClientTypes.Options {
1013
projectId?: string;
1114
metadata?: Metadata;
@@ -16,6 +19,7 @@ export interface UniversalProviderOpts extends SignClientTypes.Options {
1619
storage?: IKeyValueStorage;
1720
name?: string;
1821
disableProviderPing?: boolean;
22+
session?: SessionTypes.Struct;
1923
}
2024

2125
export type Metadata = SignClientTypes.Metadata;

providers/universal-provider/test/index.spec.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -361,20 +361,20 @@ describe("UniversalProvider", function () {
361361
describe("persistence", () => {
362362
describe("after restart", () => {
363363
it("clients can ping each other", async () => {
364+
const dappDbName = getDbName(`dappDB-${Date.now()}`);
365+
const walletDbName = getDbName(`walletDB-${Date.now()}`);
364366
const dapp = await UniversalProvider.init({
365367
...TEST_PROVIDER_OPTS,
366368
name: "dapp",
367-
storageOptions: { database: getDbName("dappDB") },
369+
storageOptions: { database: dappDbName },
368370
});
369371
const wallet = await UniversalProvider.init({
370372
...TEST_PROVIDER_OPTS,
371373
name: "wallet",
372-
storageOptions: { database: getDbName("walletDB") },
374+
storageOptions: { database: walletDbName },
373375
});
374376
const chains = [`eip155:${CHAIN_ID}`, `eip155:${CHAIN_ID_B}`];
375-
const {
376-
sessionA: { topic },
377-
} = await testConnectMethod(
377+
const { sessionA } = await testConnectMethod(
378378
{
379379
dapp,
380380
wallet,
@@ -392,6 +392,8 @@ describe("UniversalProvider", function () {
392392
},
393393
},
394394
);
395+
wallet.session = sessionA;
396+
const topic = sessionA.topic;
395397

396398
await Promise.all([
397399
new Promise((resolve) => {
@@ -417,17 +419,16 @@ describe("UniversalProvider", function () {
417419
const addresses = (await dapp.request({ method: "eth_accounts" })) as string[];
418420
// delete
419421
await deleteProviders({ A: dapp, B: wallet });
420-
421422
// restart
422423
const afterDapp = await UniversalProvider.init({
423424
...TEST_PROVIDER_OPTS,
424425
name: "dapp",
425-
storageOptions: { database: getDbName("dappDB") },
426+
storageOptions: { database: dappDbName },
426427
});
427428
const afterWallet = await UniversalProvider.init({
428429
...TEST_PROVIDER_OPTS,
429430
name: "wallet",
430-
storageOptions: { database: getDbName("walletDB") },
431+
storageOptions: { database: walletDbName },
431432
});
432433

433434
// ping
@@ -445,15 +446,17 @@ describe("UniversalProvider", function () {
445446
});
446447

447448
it("should reload provider data after restart", async () => {
449+
const dappDbName = getDbName(`dappDB-${Date.now()}`);
450+
const walletDbName = getDbName(`walletDB-${Date.now()}`);
448451
const dapp = await UniversalProvider.init({
449452
...TEST_PROVIDER_OPTS,
450453
name: "dapp",
451-
storageOptions: { database: getDbName("dappDB") },
454+
storageOptions: { database: dappDbName },
452455
});
453456
const wallet = await UniversalProvider.init({
454457
...TEST_PROVIDER_OPTS,
455458
name: "wallet",
456-
storageOptions: { database: getDbName("walletDB") },
459+
storageOptions: { database: walletDbName },
457460
});
458461

459462
const {
@@ -474,7 +477,7 @@ describe("UniversalProvider", function () {
474477
const afterDapp = await UniversalProvider.init({
475478
...TEST_PROVIDER_OPTS,
476479
name: "afterDapp",
477-
storageOptions: { database: getDbName("dappDB") },
480+
storageOptions: { database: dappDbName },
478481
});
479482

480483
// load the provider in ethers without new pairing
@@ -769,9 +772,10 @@ describe("UniversalProvider", function () {
769772
});
770773
describe("caip validation", () => {
771774
it("should reload after restart", async () => {
775+
const dappDbName = getDbName(`dappDB-${Date.now()}`);
772776
const dapp = await UniversalProvider.init({
773777
...TEST_PROVIDER_OPTS,
774-
storageOptions: { database: getDbName("dappDB") },
778+
storageOptions: { database: dappDbName },
775779
name: "dapp",
776780
});
777781
const wallet = await UniversalProvider.init({
@@ -814,7 +818,7 @@ describe("UniversalProvider", function () {
814818
// restart
815819
const afterDapp = await UniversalProvider.init({
816820
...TEST_PROVIDER_OPTS,
817-
storageOptions: { database: getDbName("dappDB") },
821+
storageOptions: { database: dappDbName },
818822
name: "dapp",
819823
});
820824

@@ -826,9 +830,10 @@ describe("UniversalProvider", function () {
826830
});
827831
});
828832
it("should reload after restart with correct chain", async () => {
833+
const dappDbName = getDbName(`dappDB-${Date.now()}`);
829834
const dapp = await UniversalProvider.init({
830835
...TEST_PROVIDER_OPTS,
831-
storageOptions: { database: getDbName("dappDB") },
836+
storageOptions: { database: dappDbName },
832837
name: "dapp",
833838
});
834839
const wallet = await UniversalProvider.init({
@@ -876,7 +881,7 @@ describe("UniversalProvider", function () {
876881
// restart
877882
const afterDapp = await UniversalProvider.init({
878883
...TEST_PROVIDER_OPTS,
879-
storageOptions: { database: getDbName("dappDB") },
884+
storageOptions: { database: dappDbName },
880885
name: "dapp",
881886
});
882887

0 commit comments

Comments
 (0)