You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-07 23:02:56 +03:00
Send/receive error details with widgets (#4492)
* Send/receive error details with widgets * Fix embedded client tests * Use all properties of error responses * Lint * Rewrite ternary expression as if statement * Put typehints on overridden functions * Lint * Update matrix-widget-api * Don't @link across packages as gendoc fails when doing so. * Add a missing docstring * Set widget response error string to correct value * Test conversion to/from widget error payloads * Test processing errors thrown by widget transport * Lint * Test processing errors from transport.sendComplete
This commit is contained in:
committed by
GitHub
parent
0df8e81da4
commit
98f7637683
@@ -58,7 +58,7 @@
|
|||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"loglevel": "^1.7.1",
|
"loglevel": "^1.7.1",
|
||||||
"matrix-events-sdk": "0.0.1",
|
"matrix-events-sdk": "0.0.1",
|
||||||
"matrix-widget-api": "^1.8.2",
|
"matrix-widget-api": "^1.10.0",
|
||||||
"oidc-client-ts": "^3.0.1",
|
"oidc-client-ts": "^3.0.1",
|
||||||
"p-retry": "4",
|
"p-retry": "4",
|
||||||
"sdp-transform": "^2.14.1",
|
"sdp-transform": "^2.14.1",
|
||||||
|
@@ -30,9 +30,10 @@ import {
|
|||||||
ITurnServer,
|
ITurnServer,
|
||||||
IRoomEvent,
|
IRoomEvent,
|
||||||
IOpenIDCredentials,
|
IOpenIDCredentials,
|
||||||
|
WidgetApiResponseError,
|
||||||
} from "matrix-widget-api";
|
} from "matrix-widget-api";
|
||||||
|
|
||||||
import { createRoomWidgetClient, MsgType, UpdateDelayedEventAction } from "../../src/matrix";
|
import { createRoomWidgetClient, MatrixError, MsgType, UpdateDelayedEventAction } from "../../src/matrix";
|
||||||
import { MatrixClient, ClientEvent, ITurnServer as IClientTurnServer } from "../../src/client";
|
import { MatrixClient, ClientEvent, ITurnServer as IClientTurnServer } from "../../src/client";
|
||||||
import { SyncState } from "../../src/sync";
|
import { SyncState } from "../../src/sync";
|
||||||
import { ICapabilities, RoomWidgetClient } from "../../src/embedded";
|
import { ICapabilities, RoomWidgetClient } from "../../src/embedded";
|
||||||
@@ -90,7 +91,11 @@ class MockWidgetApi extends EventEmitter {
|
|||||||
public getTurnServers = jest.fn(() => []);
|
public getTurnServers = jest.fn(() => []);
|
||||||
public sendContentLoaded = jest.fn();
|
public sendContentLoaded = jest.fn();
|
||||||
|
|
||||||
public transport = { reply: jest.fn() };
|
public transport = {
|
||||||
|
reply: jest.fn(),
|
||||||
|
send: jest.fn(),
|
||||||
|
sendComplete: jest.fn(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "../../src/types" {
|
declare module "../../src/types" {
|
||||||
@@ -187,6 +192,46 @@ describe("RoomWidgetClient", () => {
|
|||||||
.map((e) => e.getEffectiveEvent()),
|
.map((e) => e.getEffectiveEvent()),
|
||||||
).toEqual([event]);
|
).toEqual([event]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("handles widget errors with generic error data", async () => {
|
||||||
|
const error = new Error("failed to send");
|
||||||
|
widgetApi.transport.send.mockRejectedValue(error);
|
||||||
|
|
||||||
|
await makeClient({ sendEvent: ["org.matrix.rageshake_request"] });
|
||||||
|
widgetApi.sendRoomEvent.mockImplementation(widgetApi.transport.send);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.sendEvent("!1:example.org", "org.matrix.rageshake_request", { request_id: 123 }),
|
||||||
|
).rejects.toThrow(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles widget errors with Matrix API error response data", async () => {
|
||||||
|
const errorStatusCode = 400;
|
||||||
|
const errorUrl = "http://example.org";
|
||||||
|
const errorData = {
|
||||||
|
errcode: "M_BAD_JSON",
|
||||||
|
error: "Invalid body",
|
||||||
|
};
|
||||||
|
|
||||||
|
const widgetError = new WidgetApiResponseError("failed to send", {
|
||||||
|
matrix_api_error: {
|
||||||
|
http_status: errorStatusCode,
|
||||||
|
http_headers: {},
|
||||||
|
url: errorUrl,
|
||||||
|
response: errorData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const matrixError = new MatrixError(errorData, errorStatusCode, errorUrl);
|
||||||
|
|
||||||
|
widgetApi.transport.send.mockRejectedValue(widgetError);
|
||||||
|
|
||||||
|
await makeClient({ sendEvent: ["org.matrix.rageshake_request"] });
|
||||||
|
widgetApi.sendRoomEvent.mockImplementation(widgetApi.transport.send);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.sendEvent("!1:example.org", "org.matrix.rageshake_request", { request_id: 123 }),
|
||||||
|
).rejects.toThrow(matrixError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("delayed events", () => {
|
describe("delayed events", () => {
|
||||||
@@ -598,6 +643,42 @@ describe("RoomWidgetClient", () => {
|
|||||||
await makeClient({});
|
await makeClient({});
|
||||||
expect(await client.getOpenIdToken()).toStrictEqual(testOIDCToken);
|
expect(await client.getOpenIdToken()).toStrictEqual(testOIDCToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("handles widget errors with generic error data", async () => {
|
||||||
|
const error = new Error("failed to get token");
|
||||||
|
widgetApi.transport.sendComplete.mockRejectedValue(error);
|
||||||
|
|
||||||
|
await makeClient({});
|
||||||
|
widgetApi.requestOpenIDConnectToken.mockImplementation(widgetApi.transport.sendComplete as any);
|
||||||
|
|
||||||
|
await expect(client.getOpenIdToken()).rejects.toThrow(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles widget errors with Matrix API error response data", async () => {
|
||||||
|
const errorStatusCode = 400;
|
||||||
|
const errorUrl = "http://example.org";
|
||||||
|
const errorData = {
|
||||||
|
errcode: "M_UNKNOWN",
|
||||||
|
error: "Bad request",
|
||||||
|
};
|
||||||
|
|
||||||
|
const widgetError = new WidgetApiResponseError("failed to get token", {
|
||||||
|
matrix_api_error: {
|
||||||
|
http_status: errorStatusCode,
|
||||||
|
http_headers: {},
|
||||||
|
url: errorUrl,
|
||||||
|
response: errorData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const matrixError = new MatrixError(errorData, errorStatusCode, errorUrl);
|
||||||
|
|
||||||
|
widgetApi.transport.sendComplete.mockRejectedValue(widgetError);
|
||||||
|
|
||||||
|
await makeClient({});
|
||||||
|
widgetApi.requestOpenIDConnectToken.mockImplementation(widgetApi.transport.sendComplete as any);
|
||||||
|
|
||||||
|
await expect(client.getOpenIdToken()).rejects.toThrow(matrixError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("gets TURN servers", async () => {
|
it("gets TURN servers", async () => {
|
||||||
|
@@ -25,8 +25,8 @@ describe("MatrixError", () => {
|
|||||||
headers = new Headers({ "Content-Type": "application/json" });
|
headers = new Headers({ "Content-Type": "application/json" });
|
||||||
});
|
});
|
||||||
|
|
||||||
function makeMatrixError(httpStatus: number, data: IErrorJson): MatrixError {
|
function makeMatrixError(httpStatus: number, data: IErrorJson, url?: string): MatrixError {
|
||||||
return new MatrixError(data, httpStatus, undefined, undefined, headers);
|
return new MatrixError(data, httpStatus, url, undefined, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should accept absent retry time from rate-limit error", () => {
|
it("should accept absent retry time from rate-limit error", () => {
|
||||||
@@ -95,4 +95,95 @@ describe("MatrixError", () => {
|
|||||||
const err = makeMatrixError(429, { errcode: "M_LIMIT_EXCEEDED" });
|
const err = makeMatrixError(429, { errcode: "M_LIMIT_EXCEEDED" });
|
||||||
expect(() => err.getRetryAfterMs()).toThrow("integer value is too large");
|
expect(() => err.getRetryAfterMs()).toThrow("integer value is too large");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("can be converted to data compatible with the widget api", () => {
|
||||||
|
it("from default values", () => {
|
||||||
|
const matrixError = new MatrixError();
|
||||||
|
|
||||||
|
const widgetApiErrorData = {
|
||||||
|
http_status: 400,
|
||||||
|
http_headers: {},
|
||||||
|
url: "",
|
||||||
|
response: {
|
||||||
|
errcode: "M_UNKNOWN",
|
||||||
|
error: "Unknown message",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(matrixError.asWidgetApiErrorData()).toEqual(widgetApiErrorData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("from non-default values", () => {
|
||||||
|
headers.set("Retry-After", "120");
|
||||||
|
const statusCode = 429;
|
||||||
|
const data = {
|
||||||
|
errcode: "M_LIMIT_EXCEEDED",
|
||||||
|
error: "Request is rate-limited.",
|
||||||
|
retry_after_ms: 120000,
|
||||||
|
};
|
||||||
|
const url = "http://example.net";
|
||||||
|
|
||||||
|
const matrixError = makeMatrixError(statusCode, data, url);
|
||||||
|
|
||||||
|
const widgetApiErrorData = {
|
||||||
|
http_status: statusCode,
|
||||||
|
http_headers: {
|
||||||
|
"content-type": "application/json",
|
||||||
|
"retry-after": "120",
|
||||||
|
},
|
||||||
|
url,
|
||||||
|
response: data,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(matrixError.asWidgetApiErrorData()).toEqual(widgetApiErrorData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("can be created from data received from the widget api", () => {
|
||||||
|
it("from minimal data", () => {
|
||||||
|
const statusCode = 400;
|
||||||
|
const data = {
|
||||||
|
errcode: "M_UNKNOWN",
|
||||||
|
error: "Something went wrong.",
|
||||||
|
};
|
||||||
|
const url = "";
|
||||||
|
|
||||||
|
const widgetApiErrorData = {
|
||||||
|
http_status: statusCode,
|
||||||
|
http_headers: {},
|
||||||
|
url,
|
||||||
|
response: data,
|
||||||
|
};
|
||||||
|
|
||||||
|
headers.delete("Content-Type");
|
||||||
|
const matrixError = makeMatrixError(statusCode, data, url);
|
||||||
|
|
||||||
|
expect(MatrixError.fromWidgetApiErrorData(widgetApiErrorData)).toEqual(matrixError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("from more data", () => {
|
||||||
|
const statusCode = 429;
|
||||||
|
const data = {
|
||||||
|
errcode: "M_LIMIT_EXCEEDED",
|
||||||
|
error: "Request is rate-limited.",
|
||||||
|
retry_after_ms: 120000,
|
||||||
|
};
|
||||||
|
const url = "http://example.net";
|
||||||
|
|
||||||
|
const widgetApiErrorData = {
|
||||||
|
http_status: statusCode,
|
||||||
|
http_headers: {
|
||||||
|
"content-type": "application/json",
|
||||||
|
"retry-after": "120",
|
||||||
|
},
|
||||||
|
url,
|
||||||
|
response: data,
|
||||||
|
};
|
||||||
|
|
||||||
|
headers.set("Retry-After", "120");
|
||||||
|
const matrixError = makeMatrixError(statusCode, data, url);
|
||||||
|
|
||||||
|
expect(MatrixError.fromWidgetApiErrorData(widgetApiErrorData)).toEqual(matrixError);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -17,12 +17,17 @@ limitations under the License.
|
|||||||
import {
|
import {
|
||||||
WidgetApi,
|
WidgetApi,
|
||||||
WidgetApiToWidgetAction,
|
WidgetApiToWidgetAction,
|
||||||
|
WidgetApiResponseError,
|
||||||
MatrixCapabilities,
|
MatrixCapabilities,
|
||||||
IWidgetApiRequest,
|
IWidgetApiRequest,
|
||||||
IWidgetApiAcknowledgeResponseData,
|
IWidgetApiAcknowledgeResponseData,
|
||||||
ISendEventToWidgetActionRequest,
|
ISendEventToWidgetActionRequest,
|
||||||
ISendToDeviceToWidgetActionRequest,
|
ISendToDeviceToWidgetActionRequest,
|
||||||
ISendEventFromWidgetResponseData,
|
ISendEventFromWidgetResponseData,
|
||||||
|
IWidgetApiRequestData,
|
||||||
|
WidgetApiAction,
|
||||||
|
IWidgetApiResponse,
|
||||||
|
IWidgetApiResponseData,
|
||||||
} from "matrix-widget-api";
|
} from "matrix-widget-api";
|
||||||
|
|
||||||
import { MatrixEvent, IEvent, IContent, EventStatus } from "./models/event.ts";
|
import { MatrixEvent, IEvent, IContent, EventStatus } from "./models/event.ts";
|
||||||
@@ -45,6 +50,7 @@ import {
|
|||||||
} from "./client.ts";
|
} from "./client.ts";
|
||||||
import { SyncApi, SyncState } from "./sync.ts";
|
import { SyncApi, SyncState } from "./sync.ts";
|
||||||
import { SlidingSyncSdk } from "./sliding-sync-sdk.ts";
|
import { SlidingSyncSdk } from "./sliding-sync-sdk.ts";
|
||||||
|
import { MatrixError } from "./http-api/errors.ts";
|
||||||
import { User } from "./models/user.ts";
|
import { User } from "./models/user.ts";
|
||||||
import { Room } from "./models/room.ts";
|
import { Room } from "./models/room.ts";
|
||||||
import { ToDeviceBatch, ToDevicePayload } from "./models/ToDeviceMessage.ts";
|
import { ToDeviceBatch, ToDevicePayload } from "./models/ToDeviceMessage.ts";
|
||||||
@@ -147,6 +153,33 @@ export class RoomWidgetClient extends MatrixClient {
|
|||||||
) {
|
) {
|
||||||
super(opts);
|
super(opts);
|
||||||
|
|
||||||
|
const transportSend = this.widgetApi.transport.send.bind(this.widgetApi.transport);
|
||||||
|
this.widgetApi.transport.send = async <
|
||||||
|
T extends IWidgetApiRequestData,
|
||||||
|
R extends IWidgetApiResponseData = IWidgetApiAcknowledgeResponseData,
|
||||||
|
>(
|
||||||
|
action: WidgetApiAction,
|
||||||
|
data: T,
|
||||||
|
): Promise<R> => {
|
||||||
|
try {
|
||||||
|
return await transportSend<T, R>(action, data);
|
||||||
|
} catch (error) {
|
||||||
|
processAndThrow(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const transportSendComplete = this.widgetApi.transport.sendComplete.bind(this.widgetApi.transport);
|
||||||
|
this.widgetApi.transport.sendComplete = async <T extends IWidgetApiRequestData, R extends IWidgetApiResponse>(
|
||||||
|
action: WidgetApiAction,
|
||||||
|
data: T,
|
||||||
|
): Promise<R> => {
|
||||||
|
try {
|
||||||
|
return await transportSendComplete<T, R>(action, data);
|
||||||
|
} catch (error) {
|
||||||
|
processAndThrow(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.widgetApiReady = new Promise<void>((resolve) => this.widgetApi.once("ready", resolve));
|
this.widgetApiReady = new Promise<void>((resolve) => this.widgetApi.once("ready", resolve));
|
||||||
|
|
||||||
// Request capabilities for the functionality this client needs to support
|
// Request capabilities for the functionality this client needs to support
|
||||||
@@ -523,3 +556,11 @@ export class RoomWidgetClient extends MatrixClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function processAndThrow(error: unknown): never {
|
||||||
|
if (error instanceof WidgetApiResponseError && error.data.matrix_api_error) {
|
||||||
|
throw MatrixError.fromWidgetApiErrorData(error.data.matrix_api_error);
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { IMatrixApiError as IWidgetMatrixError } from "matrix-widget-api";
|
||||||
|
|
||||||
import { IUsageLimit } from "../@types/partials.ts";
|
import { IUsageLimit } from "../@types/partials.ts";
|
||||||
import { MatrixEvent } from "../models/event.ts";
|
import { MatrixEvent } from "../models/event.ts";
|
||||||
|
|
||||||
@@ -131,6 +133,37 @@ export class MatrixError extends HTTPError {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns this error expressed as a JSON payload
|
||||||
|
* for use by Widget API error responses.
|
||||||
|
*/
|
||||||
|
public asWidgetApiErrorData(): IWidgetMatrixError {
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
if (this.httpHeaders) {
|
||||||
|
for (const [name, value] of this.httpHeaders) {
|
||||||
|
headers[name] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
http_status: this.httpStatus ?? 400,
|
||||||
|
http_headers: headers,
|
||||||
|
url: this.url ?? "",
|
||||||
|
response: {
|
||||||
|
errcode: this.errcode ?? "M_UNKNOWN",
|
||||||
|
error: this.data.error ?? "Unknown message",
|
||||||
|
...this.data,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns a new {@link MatrixError} from a JSON payload
|
||||||
|
* received from Widget API error responses.
|
||||||
|
*/
|
||||||
|
public static fromWidgetApiErrorData(data: IWidgetMatrixError): MatrixError {
|
||||||
|
return new MatrixError(data.response, data.http_status, data.url, undefined, new Headers(data.http_headers));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -4940,10 +4940,10 @@ matrix-mock-request@^2.5.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
expect "^28.1.0"
|
expect "^28.1.0"
|
||||||
|
|
||||||
matrix-widget-api@^1.8.2:
|
matrix-widget-api@^1.10.0:
|
||||||
version "1.9.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.9.0.tgz#884136b405bd3c56e4ea285095c9e01ec52b6b1f"
|
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.10.0.tgz#d31ea073a5871a1fb1a511ef900b0c125a37bf55"
|
||||||
integrity sha512-au8mqralNDqrEvaVAkU37bXOb8I9SCe+ACdPk11QWw58FKstVq31q2wRz+qWA6J+42KJ6s1DggWbG/S3fEs3jw==
|
integrity sha512-rkAJ29briYV7TJnfBVLVSKtpeBrBju15JZFSDP6wj8YdbCu1bdmlplJayQ+vYaw1x4fzI49Q+Nz3E85s46sRDw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/events" "^3.0.0"
|
"@types/events" "^3.0.0"
|
||||||
events "^3.2.0"
|
events "^3.2.0"
|
||||||
|
Reference in New Issue
Block a user