Skip to content

Commit

Permalink
Merge pull request #1851 from WalletConnect/feat/optional-request-expiry
Browse files Browse the repository at this point in the history
feat: optional request expiry
  • Loading branch information
ganchoradkov authored Jan 10, 2023
2 parents b090551 + 6e520d6 commit 50bd164
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 9 deletions.
1 change: 1 addition & 0 deletions packages/core/src/constants/relayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const RELAYER_EVENTS = {
error: "relayer_error",
connection_stalled: "relayer_connection_stalled",
transport_closed: "relayer_transport_closed",
publish: "relayer_publish",
};

export const RELAYER_SUBSCRIBER_SUFFIX = "_subscription";
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/controllers/publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class Publisher extends IPublisher {
this.publishTimeout,
);
await publish;
this.relayer.events.emit(RELAYER_EVENTS.publish, params);
} catch (err) {
this.logger.debug(`Publishing Payload stalled`);
this.relayer.events.emit(RELAYER_EVENTS.connection_stalled);
Expand Down
7 changes: 6 additions & 1 deletion packages/sign-client/src/constants/engine.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FIVE_MINUTES, ONE_DAY, THIRTY_SECONDS } from "@walletconnect/time";
import { FIVE_MINUTES, ONE_DAY, SEVEN_DAYS, THIRTY_SECONDS } from "@walletconnect/time";
import { EngineTypes } from "@walletconnect/types";

export const ENGINE_CONTEXT = "engine";
Expand Down Expand Up @@ -102,3 +102,8 @@ export const ENGINE_RPC_OPTS: EngineTypes.RpcOptsMap = {
},
},
};

export const SESSION_REQUEST_EXPIRY_BOUNDARIES = {
min: FIVE_MINUTES,
max: SEVEN_DAYS,
};
26 changes: 20 additions & 6 deletions packages/sign-client/src/controllers/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,14 @@ import {
TYPE_1,
getRequiredNamespacesFromNamespaces,
isValidObject,
isValidRequestExpiry,
} from "@walletconnect/utils";
import { SESSION_EXPIRY, ENGINE_CONTEXT, ENGINE_RPC_OPTS } from "../constants";
import {
SESSION_EXPIRY,
ENGINE_CONTEXT,
ENGINE_RPC_OPTS,
SESSION_REQUEST_EXPIRY_BOUNDARIES,
} from "../constants";

export class Engine extends IEngine {
public name = ENGINE_CONTEXT;
Expand Down Expand Up @@ -258,9 +264,9 @@ export class Engine extends IEngine {
public request: IEngine["request"] = async <T>(params: EngineTypes.RequestParams) => {
this.isInitialized();
await this.isValidRequest(params);
const { chainId, request, topic } = params;
const id = await this.sendRequest(topic, "wc_sessionRequest", { request, chainId });
const { done, resolve, reject } = createDelayedPromise<T>();
const { chainId, request, topic, expiry } = params;
const id = await this.sendRequest(topic, "wc_sessionRequest", { request, chainId }, expiry);
const { done, resolve, reject } = createDelayedPromise<T>(expiry);
this.events.once<"session_request">(engineEvent("session_request", id), ({ error, result }) => {
if (error) reject(error);
else resolve(result);
Expand Down Expand Up @@ -384,10 +390,11 @@ export class Engine extends IEngine {
if (expiry) this.client.core.expirer.set(id, calcExpiry(expiry));
};

private sendRequest: EnginePrivate["sendRequest"] = async (topic, method, params) => {
private sendRequest: EnginePrivate["sendRequest"] = async (topic, method, params, expiry) => {
const payload = formatJsonRpcRequest(method, params);
const message = await this.client.core.crypto.encode(topic, payload);
const opts = ENGINE_RPC_OPTS[method].req;
if (expiry) opts.ttl = expiry;
this.client.core.history.set(topic, payload);
this.client.core.relayer.publish(topic, message, opts);
return payload.id;
Expand Down Expand Up @@ -980,7 +987,7 @@ export class Engine extends IEngine {
const { message } = getInternalError("MISSING_OR_INVALID", `request() params: ${params}`);
throw new Error(message);
}
const { topic, request, chainId } = params;
const { topic, request, chainId, expiry } = params;
await this.isValidSessionTopic(topic);
const { namespaces } = this.client.session.get(topic);
if (!isValidNamespacesChainId(namespaces, chainId)) {
Expand All @@ -1001,6 +1008,13 @@ export class Engine extends IEngine {
);
throw new Error(message);
}
if (expiry && !isValidRequestExpiry(expiry, SESSION_REQUEST_EXPIRY_BOUNDARIES)) {
const { message } = getInternalError(
"MISSING_OR_INVALID",
`request() expiry: ${expiry}. Expiry must be a number (in seconds) between ${SESSION_REQUEST_EXPIRY_BOUNDARIES.min} and ${SESSION_REQUEST_EXPIRY_BOUNDARIES.max}`,
);
throw new Error(message);
}
};

private isValidRespond: EnginePrivate["isValidRespond"] = async (params) => {
Expand Down
31 changes: 31 additions & 0 deletions packages/sign-client/test/sdk/client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { RELAYER_EVENTS } from "@walletconnect/core";
import { formatJsonRpcError, JsonRpcError } from "@walletconnect/jsonrpc-utils";
import { RelayerTypes } from "@walletconnect/types";
import { getSdkError } from "@walletconnect/utils";
import { expect, describe, it, vi } from "vitest";
import SignClient from "../../src";
Expand Down Expand Up @@ -227,4 +229,33 @@ describe("Sign Client Integration", () => {
await deleteClients(clients);
});
});

describe("session requests", () => {
it("should set custom request expiry", async () => {
const clients = await initTwoClients();
const {
sessionA: { topic },
} = await testConnectMethod(clients);

const expiry = 10000;

await Promise.all([
new Promise<void>((resolve) => {
clients.A.core.relayer.once(
RELAYER_EVENTS.publish,
(payload: RelayerTypes.PublishPayload) => {
// ttl of the request should match the expiry
expect(payload?.opts?.ttl).toEqual(expiry);
resolve();
},
);
}),
new Promise<void>((resolve) => {
clients.A.request({ ...TEST_REQUEST_PARAMS, topic, expiry });
resolve();
}),
]);
await deleteClients(clients);
});
});
});
8 changes: 8 additions & 0 deletions packages/sign-client/test/sdk/validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,14 @@ describe("Sign Client Validation", () => {
clients.A.request({ ...TEST_REQUEST_PARAMS, topic, request: { method: "unknown" } }),
).rejects.toThrowError("Missing or invalid. request() method: unknown");
});

it("throws when invalid expiry is provider", async () => {
await expect(
clients.A.request({ ...TEST_REQUEST_PARAMS, topic, expiry: 10 }),
).rejects.toThrowError(
"Missing or invalid. request() expiry: 10. Expiry must be a number (in seconds) between 300 and 604800",
);
});
});

describe("respond", () => {
Expand Down
5 changes: 5 additions & 0 deletions packages/types/src/core/relayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export declare namespace RelayerTypes {

export type RequestOptions = PublishOptions | SubscribeOptions | UnsubscribeOptions;

export interface PublishPayload {
topic: string;
message: string;
opts?: RelayerTypes.PublishOptions;
}
export interface MessageEvent {
topic: string;
message: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/sign-client/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export declare namespace EngineTypes {
params: any;
};
chainId: string;
expiry?: number;
}

interface RespondParams {
Expand Down Expand Up @@ -149,6 +150,7 @@ export interface EnginePrivate {
topic: string,
method: M,
params: JsonRpcTypes.RequestParams[M],
expiry?: number,
): Promise<number>;

sendResult<M extends JsonRpcTypes.WcMethod>(
Expand Down
4 changes: 2 additions & 2 deletions packages/utils/src/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ export function isExpired(expiry: number) {
}

// -- promises --------------------------------------------- //
export function createDelayedPromise<T>() {
const timeout = toMiliseconds(FIVE_MINUTES);
export function createDelayedPromise<T>(expiry?: number | undefined) {
const timeout = toMiliseconds(expiry || FIVE_MINUTES);
let cacheResolve: undefined | ((value: T | PromiseLike<T>) => void);
let cacheReject: undefined | ((value?: ErrorResponse) => void);
let cacheTimeout: undefined | NodeJS.Timeout;
Expand Down
4 changes: 4 additions & 0 deletions packages/utils/src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,7 @@ export function isConformingNamespaces(

return error;
}

export function isValidRequestExpiry(expiry: number, boundaries: { min: number; max: number }) {
return isValidNumber(expiry, false) && expiry <= boundaries.max && expiry >= boundaries.min;
}

0 comments on commit 50bd164

Please sign in to comment.