basic support for sending rageshake in view model

This commit is contained in:
Bruno Windels 2022-06-14 18:46:02 +02:00
parent 4ed7e01dfd
commit a644621889
5 changed files with 109 additions and 3 deletions

69
src/domain/rageshake.ts Normal file
View file

@ -0,0 +1,69 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {encodeBody} from "../matrix/net/common";
import type {BlobHandle} from "../platform/web/dom/BlobHandle";
import type {RequestFunction} from "../platform/types/types";
// see https://github.com/matrix-org/rageshake#readme
type RageshakeData = {
// A textual description of the problem. Included in the details.log.gz file.
text: string | undefined;
// Application user-agent. Included in the details.log.gz file.
userAgent: string;
// Identifier for the application (eg 'riot-web'). Should correspond to a mapping configured in the configuration file for github issue reporting to work.
app: string;
// Application version. Included in the details.log.gz file.
version: string;
// Label to attach to the github issue, and include in the details file.
label: string | undefined;
};
export async function submitLogsToRageshakeServer(data: RageshakeData, logsBlob: BlobHandle, submitUrl: string, request: RequestFunction): Promise<string> {
const formData = new Map<string, string | {name: string, blob: BlobHandle}>();
if (data.text) {
formData.set("text", data.text);
}
formData.set("user_agent", data.userAgent);
formData.set("app", data.app);
formData.set("version", data.version);
if (data.label) {
formData.set("label", data.label);
}
formData.set("file", {name: "logs.json", blob: logsBlob});
const encoded = encodeBody(formData);
const headers: Map<string, string> = new Map();
headers.set("Accept", "application/json");
//headers.set("Content-Type", encoded.mimeType);
const result = request(submitUrl, {
method: "POST",
body: encoded.body,
headers
});
let response;
try {
response = await result.response();
} catch (err) {
throw new Error(`Could not submit logs to ${submitUrl}, got error ${err.message}`);
}
const {status, body} = response;
if (status >= 200 && status < 300) {
return body.report_url;
} else {
throw new Error(`Could not submit logs to ${submitUrl}, got status code ${status} with body ${body}`);
}
}

View file

@ -16,6 +16,7 @@ limitations under the License.
import {ViewModel} from "../../ViewModel"; import {ViewModel} from "../../ViewModel";
import {KeyBackupViewModel} from "./KeyBackupViewModel.js"; import {KeyBackupViewModel} from "./KeyBackupViewModel.js";
import {submitLogsToRageshakeServer} from "../../../domain/rageshake";
class PushNotificationStatus { class PushNotificationStatus {
constructor() { constructor() {
@ -152,6 +153,21 @@ export class SettingsViewModel extends ViewModel {
this.platform.saveFileAs(logExport.asBlob(), `hydrogen-logs-${this.platform.clock.now()}.json`); this.platform.saveFileAs(logExport.asBlob(), `hydrogen-logs-${this.platform.clock.now()}.json`);
} }
async sendLogsToServer() {
const logExport = await this.logger.export();
await submitLogsToRageshakeServer(
{
app: "hydrogen",
userAgent: "<missing>",
version: DEFINE_VERSION,
text: "Submit logs from settings",
},
logExport.asBlob(),
this.platform.config.bugReportEndpointUrl,
this.platform.request
);
}
async togglePushNotifications() { async togglePushNotifications() {
this.pushNotifications.updating = true; this.pushNotifications.updating = true;
this.pushNotifications.enabledOnServer = null; this.pushNotifications.enabledOnServer = null;

View file

@ -19,7 +19,8 @@ import {BlobHandle} from "../../platform/web/dom/BlobHandle.js";
export type EncodedBody = { export type EncodedBody = {
mimeType: string; mimeType: string;
body: BlobHandle | string; // the map gets transformed to a FormData object on the web
body: BlobHandle | string | Map<string, string | {blob: BlobHandle, name: string}>;
} }
export function encodeQueryParams(queryParams?: object): string { export function encodeQueryParams(queryParams?: object): string {
@ -41,6 +42,11 @@ export function encodeBody(body: BlobHandle | object): EncodedBody {
mimeType: blob.mimeType, mimeType: blob.mimeType,
body: blob // will be unwrapped in request fn body: blob // will be unwrapped in request fn
}; };
} else if (body instanceof Map) {
return {
mimeType: "multipart/form-data",
body: body
}
} else if (typeof body === "object") { } else if (typeof body === "object") {
const json = JSON.stringify(body); const json = JSON.stringify(body);
return { return {

View file

@ -4,5 +4,6 @@
"gatewayUrl": "https://matrix.org", "gatewayUrl": "https://matrix.org",
"applicationServerKey": "BC-gpSdVHEXhvHSHS0AzzWrQoukv2BE7KzpoPO_FfPacqOo3l1pdqz7rSgmB04pZCWaHPz7XRe6fjLaC-WPDopM" "applicationServerKey": "BC-gpSdVHEXhvHSHS0AzzWrQoukv2BE7KzpoPO_FfPacqOo3l1pdqz7rSgmB04pZCWaHPz7XRe6fjLaC-WPDopM"
}, },
"defaultHomeServer": "matrix.org" "defaultHomeServer": "matrix.org",
"bugReportEndpointUrl": "https://element.io/bugreports/submit"
} }

View file

@ -64,12 +64,26 @@ export function createFetchRequest(createTimeout, serviceWorkerHandler) {
if (requestOptions?.uploadProgress) { if (requestOptions?.uploadProgress) {
return xhrRequest(url, requestOptions); return xhrRequest(url, requestOptions);
} }
let {method, headers, body, timeout, format, cache = false} = requestOptions; let {method, headers, body, formData, timeout, format, cache = false} = requestOptions;
const controller = typeof AbortController === "function" ? new AbortController() : null; const controller = typeof AbortController === "function" ? new AbortController() : null;
// if a BlobHandle, take native blob // if a BlobHandle, take native blob
if (body?.nativeBlob) { if (body?.nativeBlob) {
body = body.nativeBlob; body = body.nativeBlob;
} }
if (body instanceof Map) {
const formData = new FormData();
for (const [name, value] of body) {
let filename;
// Special case {name: string, blob: BlobHandle} to set a filename.
// This is the format returned by platform.openFile
if (value.blob?.nativeBlob && value.name) {
formData.set(name, value.blob.nativeBlob, value.name);
} else {
formData.set(name, value);
}
}
body = formData;
}
let options = {method, body}; let options = {method, body};
if (controller) { if (controller) {
options = Object.assign(options, { options = Object.assign(options, {