Skip to content

Commit 7af4f97

Browse files
authored
fix: multichain sdk state + bug fixing (#1358)
* fix: better tracking state in sdk * fix: better manage connection state and fix notifications on mwp * fix: set default timeouts for browser transport * fix: better handle rejections in mwp transport * fix: remove over verbose logs from transports
1 parent b2e5984 commit 7af4f97

File tree

6 files changed

+58
-36
lines changed

6 files changed

+58
-36
lines changed

packages/sdk-multichain/src/connect.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ function testSuite<T extends MultichainOptions>({ platform, createSDK, options:
115115
//Expect to find all the transport mocks DISCONNECTED
116116
t.expect(mockedData.mockDefaultTransport.__isConnected).toBe(false);
117117
t.expect(mockedData.mockDappClient.state).toBe('DISCONNECTED');
118+
t.expect(sdk.state === 'disconnected').toBe(true);
118119

119120
mockedData.mockDefaultTransport.connect.mockClear();
120121
(mockedData.mockDappClient as any).connect.mockClear();
@@ -265,6 +266,7 @@ function testSuite<T extends MultichainOptions>({ platform, createSDK, options:
265266
t.expect(() => sdk.transport).toThrow();
266267

267268
await t.expect(() => sdk.connect(scopes, caipAccountIds)).rejects.toThrow(sessionError);
269+
t.expect(sdk.state === 'disconnected').toBe(true);
268270
});
269271

270272
t.it(`${platform} should handle session revocation errors on session upgrade`, async () => {

packages/sdk-multichain/src/domain/logger/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { StoreClient } from '../store/client';
66
* Supported debug namespace types for the MetaMask SDK logger.
77
* These namespaces help categorize and filter debug output.
88
*/
9-
export type LoggerNameSpaces = 'metamask-sdk' | 'metamask-sdk:core' | 'metamask-sdk:provider' | 'metamask-sdk:ui';
9+
export type LoggerNameSpaces = 'metamask-sdk' | 'metamask-sdk:core' | 'metamask-sdk:provider' | 'metamask-sdk:ui' | 'metamask-sdk:transport';
1010

1111
/**
1212
* Creates a debug logger instance with the specified namespace and color.

packages/sdk-multichain/src/multichain/index.ts

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,8 @@ export class MultichainSDK extends MultichainCore {
165165
if (!this.transport.isConnected()) {
166166
this.state = 'connecting';
167167
await this.transport.connect();
168-
this.state = 'connected';
169-
} else {
170-
this.state = 'connected';
171168
}
169+
this.state = 'connected';
172170
} else {
173171
this.state = 'loaded';
174172
}
@@ -187,6 +185,8 @@ export class MultichainSDK extends MultichainCore {
187185
}
188186
}
189187
} catch (error) {
188+
await this.storage.removeTransport();
189+
this.state = 'pending';
190190
logger('MetaMaskSDK error during initialization', error);
191191
}
192192
}
@@ -288,15 +288,18 @@ export class MultichainSDK extends MultichainCore {
288288
.then(() => {
289289
this.options.ui.factory.unload();
290290
this.options.ui.factory.modal?.unmount();
291+
this.state = 'connected';
291292
})
292293
.catch((err) => {
293294
if (err instanceof ProtocolError) {
294295
//Ignore Request expired errors to allow modal to regenerate expired qr codes
295296
if (err.code !== ErrorCode.REQUEST_EXPIRED) {
297+
this.state = 'disconnected';
296298
reject(err);
297299
}
298300
// If request is expires, the QRCode will automatically be regenerated we can ignore this case
299301
} else {
302+
this.state = 'disconnected';
300303
reject(err);
301304
}
302305
});
@@ -305,10 +308,8 @@ export class MultichainSDK extends MultichainCore {
305308
async (error?: Error) => {
306309
if (!error) {
307310
await this.storage.setTransport(TransportType.MPW);
308-
this.state = 'connected';
309311
resolve();
310312
} else {
311-
this.state = 'disconnected';
312313
await this.storage.removeTransport();
313314
reject(error);
314315
}
@@ -366,41 +367,37 @@ export class MultichainSDK extends MultichainCore {
366367
this.openDeeplink(deeplink, universalLink);
367368
}
368369
});
369-
this.state = 'connecting';
370-
this.transport
371-
.connect({ scopes, caipAccountIds })
372-
.then(() => {
373-
this.state = 'connected';
374-
return resolve();
375-
})
376-
.catch(reject);
370+
this.transport.connect({ scopes, caipAccountIds }).then(resolve).catch(reject);
377371
});
378372
}
379373

374+
private async handleConnection(promise: Promise<void>) {
375+
this.state = 'connecting';
376+
return promise
377+
.then(() => {
378+
this.state = 'connected';
379+
})
380+
.catch((err) => {
381+
this.state = 'disconnected';
382+
return Promise.reject(err);
383+
});
384+
}
385+
380386
async connect(scopes: Scope[], caipAccountIds: CaipAccountId[]): Promise<void> {
381387
const { ui } = this.options;
382388
const platformType = getPlatformType();
383389
const isWeb = platformType === PlatformType.MetaMaskMobileWebview || platformType === PlatformType.DesktopWeb;
384390
const { preferExtension = true, preferDesktop = false, headless: _headless = false } = ui;
385391

386392
if (this.__transport?.isConnected()) {
387-
return this.__transport.connect({ scopes, caipAccountIds });
393+
return this.handleConnection(this.__transport.connect({ scopes, caipAccountIds }));
388394
}
389395

390396
if (isWeb && hasExtension() && preferExtension) {
391397
//If metamask extension is available, connect to it
392398
const defaultTransport = await this.setupDefaultTransport();
393399
// Web transport has no initial payload
394-
return defaultTransport
395-
.connect()
396-
.then(() => {
397-
this.state = 'connected';
398-
return Promise.resolve();
399-
})
400-
.catch((err) => {
401-
this.state = 'loaded';
402-
return Promise.reject(err);
403-
});
400+
return this.handleConnection(defaultTransport.connect({ scopes, caipAccountIds }));
404401
}
405402

406403
// Connection will now be InstallModal + QRCodes or Deeplinks, both require mwp
@@ -417,11 +414,11 @@ export class MultichainSDK extends MultichainCore {
417414
const secure = isSecure();
418415
if (secure && !isDesktopPreferred) {
419416
// Desktop is not preferred option, so we use deeplinks (mobile web)
420-
return this.deeplinkConnect(scopes, caipAccountIds);
417+
return this.handleConnection(this.deeplinkConnect(scopes, caipAccountIds));
421418
}
422419

423420
// Show install modal for RN, Web + Node
424-
return this.showInstallModal(isDesktopPreferred, scopes, caipAccountIds);
421+
return this.handleConnection(this.showInstallModal(isDesktopPreferred, scopes, caipAccountIds));
425422
}
426423

427424
public override emit(event: string, args: any): void {

packages/sdk-multichain/src/multichain/transports/default/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const DEFAULT_REQUEST_TIMEOUT = 60 * 1000;
77

88
export class DefaultTransport implements ExtendedTransport {
99
#notificationCallbacks: Set<(data: unknown) => void> = new Set();
10-
#requestId = 0;
1110
#transport: Transport = getDefaultTransport();
1211
#defaultRequestOptions = {
1312
timeout: DEFAULT_REQUEST_TIMEOUT,
@@ -28,6 +27,9 @@ export class DefaultTransport implements ExtendedTransport {
2827

2928
//Get wallet session
3029
const sessionRequest = await this.request({ method: 'wallet_getSession' }, this.#defaultRequestOptions);
30+
if (sessionRequest.error) {
31+
throw new Error(sessionRequest.error.message);
32+
}
3133
let walletSession = sessionRequest.result as SessionData;
3234
if (walletSession && options) {
3335
const currentScopes = Object.keys(walletSession?.sessionScopes ?? {}) as Scope[];
@@ -67,7 +69,7 @@ export class DefaultTransport implements ExtendedTransport {
6769
return this.#transport.isConnected();
6870
}
6971

70-
async request<TRequest extends TransportRequest, TResponse extends TransportResponse>(request: TRequest, options?: { timeout?: number }) {
72+
async request<TRequest extends TransportRequest, TResponse extends TransportResponse>(request: TRequest, options: { timeout?: number } = this.#defaultRequestOptions) {
7173
return this.#transport.request(request, options) as Promise<TResponse>;
7274
}
7375

packages/sdk-multichain/src/multichain/transports/mwp/index.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Session, SessionRequest } from '@metamask/mobile-wallet-protocol-c
33
import { SessionStore } from '@metamask/mobile-wallet-protocol-core';
44
import type { DappClient } from '@metamask/mobile-wallet-protocol-dapp-client';
55

6-
import type { ExtendedTransport, RPCAPI, Scope, SessionData, StoreAdapter } from '../../../domain';
6+
import { createLogger, type ExtendedTransport, type RPCAPI, type Scope, type SessionData, type StoreAdapter } from '../../../domain';
77
import type { CaipAccountId } from '@metamask/utils';
88
import { addValidAccounts, getOptionalScopes, getValidAccounts } from '../../utils';
99

@@ -19,6 +19,8 @@ type PendingRequests = {
1919
timeout: NodeJS.Timeout;
2020
};
2121

22+
const logger = createLogger('metamask-sdk:transport');
23+
2224
/**
2325
* Mobile Wallet Protocol transport implementation
2426
* Bridges the MWP DappClient with the multichain API client Transport interface
@@ -82,14 +84,18 @@ export class MWPTransport implements ExtendedTransport {
8284
...messagePayload,
8385
method: request.method === 'wallet_getSession' || request.method === 'wallet_createSession' ? 'wallet_sessionChanged' : request.method,
8486
} as unknown as TransportResponse<unknown>;
87+
const notification = {
88+
method: request.method === 'wallet_getSession' || request.method === 'wallet_createSession' ? 'wallet_sessionChanged' : request.method,
89+
params: requestWithName.result,
90+
};
8591
clearTimeout(request.timeout);
92+
this.notifyCallbacks(notification);
8693
request.resolve(requestWithName);
8794
this.pendingRequests.delete(messagePayload.id);
8895
return;
8996
}
90-
} else if ('name' in message && message.name === 'metamask-multichain-provider' && messagePayload.method === 'wallet_sessionChanged') {
97+
} else {
9198
this.notifyCallbacks(message.data);
92-
return;
9399
}
94100
}
95101
}
@@ -98,26 +104,40 @@ export class MWPTransport implements ExtendedTransport {
98104
private async onResumeSuccess(resumeResolve: () => void, resumeReject: (err: Error) => void, options?: { scopes: Scope[]; caipAccountIds: CaipAccountId[] }): Promise<void> {
99105
try {
100106
const sessionRequest = await this.request({ method: 'wallet_getSession' });
107+
if (sessionRequest.error) {
108+
return resumeReject(new Error(sessionRequest.error.message));
109+
}
101110
let walletSession = sessionRequest.result as SessionData;
102-
if (options) {
111+
if (walletSession && options) {
103112
const currentScopes = Object.keys(walletSession?.sessionScopes ?? {}) as Scope[];
104113
const proposedScopes = options?.scopes ?? [];
105114
const isSameScopes = currentScopes.every((scope) => proposedScopes.includes(scope)) && proposedScopes.every((scope) => currentScopes.includes(scope));
106115
if (!isSameScopes) {
107116
const optionalScopes = addValidAccounts(getOptionalScopes(options?.scopes ?? []), getValidAccounts(options?.caipAccountIds ?? []));
108117
const sessionRequest: CreateSessionParams<RPCAPI> = { optionalScopes };
109118
const response = await this.request({ method: 'wallet_createSession', params: sessionRequest });
119+
if (response.error) {
120+
return resumeReject(new Error(response.error.message));
121+
}
110122
await this.request({ method: 'wallet_revokeSession', params: walletSession });
111123
walletSession = response.result as SessionData;
112124
}
125+
} else if (!walletSession) {
126+
const optionalScopes = addValidAccounts(getOptionalScopes(options?.scopes ?? []), getValidAccounts(options?.caipAccountIds ?? []));
127+
const sessionRequest: CreateSessionParams<RPCAPI> = { optionalScopes };
128+
const response = await this.request({ method: 'wallet_createSession', params: sessionRequest });
129+
if (response.error) {
130+
return resumeReject(new Error(response.error.message));
131+
}
132+
walletSession = response.result as SessionData;
113133
}
114134
this.notifyCallbacks({
115135
method: 'wallet_sessionChanged',
116136
params: walletSession,
117137
});
118-
resumeResolve();
138+
return resumeResolve();
119139
} catch (err) {
120-
resumeReject(err);
140+
return resumeReject(err);
121141
}
122142
}
123143

@@ -133,6 +153,7 @@ export class MWPTransport implements ExtendedTransport {
133153
try {
134154
const [activeSession] = await sessionStore.list();
135155
if (activeSession) {
156+
logger('active session found', activeSession);
136157
session = activeSession;
137158
}
138159
} catch {}

packages/sdk-multichain/src/session.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ function testSuite<T extends MultichainOptions>({ platform, createSDK, options:
209209

210210
await t.expect(() => sdk.connect(scopes, caipAccountIds)).rejects.toThrow(sessionError);
211211

212-
t.expect(sdk.state === 'loaded').toBe(true);
212+
t.expect(sdk.state === 'disconnected').toBe(true);
213213
});
214214
});
215215
}

0 commit comments

Comments
 (0)