diff --git a/src/client/web/package.json b/src/client/web/package.json
index 2a339f0..6596c19 100644
--- a/src/client/web/package.json
+++ b/src/client/web/package.json
@@ -19,6 +19,7 @@
"@types/assert": "^1.4.2",
"@types/deep-diff": "^1.0.0",
"@types/jest": "^27.0.1",
+ "@types/object-hash": "^2.2.1",
"assert": "^2.0.0",
"babel-loader": "^8.2.2",
"deep-diff": "^1.0.2",
@@ -52,6 +53,7 @@
"css-loader": "^5.0.0",
"filesize": "^6.1.0",
"immutable": "^4.0.0-rc.12",
+ "object-hash": "^2.2.0",
"react": "^16.8.6",
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.8.6",
diff --git a/src/client/web/src/app.tsx b/src/client/web/src/app.tsx
index 82177c0..d447c3d 100644
--- a/src/client/web/src/app.tsx
+++ b/src/client/web/src/app.tsx
@@ -14,9 +14,9 @@ window.onerror = (
) => {
const lowerMsg = msg.toLowerCase();
if (lowerMsg.indexOf("script error") > -1) {
- ErrorLogger().error(errCorsScript, "Check Browser Console for Detail");
+ ErrorLogger().error("Check Browser Console for Detail");
}
- ErrorLogger().error(`${source}:${lineno}:${colno}: ${error.toString()}`, "");
+ ErrorLogger().error(`${source}:${lineno}:${colno}: ${error.toString()}`);
};
ReactDOM.render(, document.getElementById("mount"));
diff --git a/src/client/web/src/client/index.ts b/src/client/web/src/client/index.ts
index cf72436..aba783a 100644
--- a/src/client/web/src/client/index.ts
+++ b/src/client/web/src/client/index.ts
@@ -139,6 +139,7 @@ export interface ISettingsClient {
health: () => Promise;
getClientCfg: () => Promise;
setClientCfg: (cfg: ClientConfig) => Promise;
+ reportError: (content: string, version: string) => Promise;
}
export interface Response {
diff --git a/src/client/web/src/client/settings.ts b/src/client/web/src/client/settings.ts
index 6286bf4..079c193 100644
--- a/src/client/web/src/client/settings.ts
+++ b/src/client/web/src/client/settings.ts
@@ -30,4 +30,15 @@ export class SettingsClient extends BaseClient {
},
});
};
+
+ reportError = (content: string, version: string): Promise => {
+ return this.do({
+ method: "post",
+ url: `${this.url}/v1/settings/errors`,
+ data: {
+ content,
+ version,
+ },
+ });
+ };
}
diff --git a/src/client/web/src/client/settings_mock.ts b/src/client/web/src/client/settings_mock.ts
index 99625b1..91bb9a0 100644
--- a/src/client/web/src/client/settings_mock.ts
+++ b/src/client/web/src/client/settings_mock.ts
@@ -7,6 +7,7 @@ export interface SettingsClientResps {
healthMockResp?: Response;
setClientCfgMockResp?: Response;
getClientCfgMockResp?: Response;
+ reportErrorResp?: Response;
}
export const resps = {
@@ -25,9 +26,14 @@ export const resps = {
position: "clientCfg_bg_position",
align: "clientCfg_bg_align",
},
- }
+ },
},
},
+ reportErrorResp: {
+ status: 200,
+ statusText: "",
+ data: {},
+ },
};
export class MockSettingsClient {
private url: string;
@@ -59,4 +65,8 @@ export class MockSettingsClient {
getClientCfg = (): Promise => {
return this.wrapPromise(this.resps.getClientCfgMockResp);
};
+
+ reportError = (): Promise => {
+ return this.wrapPromise(this.resps.reportErrorResp);
+ };
}
diff --git a/src/client/web/src/common/log_error.ts b/src/client/web/src/common/log_error.ts
index d4a3bd2..1fac2e8 100644
--- a/src/client/web/src/common/log_error.ts
+++ b/src/client/web/src/common/log_error.ts
@@ -1,12 +1,27 @@
+import { Map } from "immutable";
+import { sha1 } from "object-hash";
+
import { ILocalStorage, Storage } from "./localstorage";
import { ISettingsClient } from "../client";
import { SettingsClient } from "../client/settings";
+import { ICoreState } from "../components/core_state";
+import { updater } from "../components/state_updater";
+
+const errorVer = "0.0.1";
+const cookieKeyClErrs = "qs_cli_errs";
+
+export interface ClientErrorV001 {
+ version: string;
+ error: string;
+ state: ICoreState;
+}
export interface IErrorLogger {
setClient: (client: ISettingsClient) => void;
setStorage: (storage: ILocalStorage) => void;
- error: (key: string, msg: string) => void;
- report: () => void;
+ error: (msg: string) => null | Error;
+ report: () => Promise;
+ readErrs: () => Map;
}
export class ErrorLog {
@@ -25,18 +40,61 @@ export class ErrorLog {
this.storage = storage;
}
- error = (key: string, msg: string) => {
- const existKey = this.storage.get(key);
- if (existKey === "") {
- this.storage.set(key, msg);
- }
+ private getErrorSign = (errMsg: string): string => {
+ return `e:${sha1(errMsg)}`;
};
- report = () => {
- // TODO:
- // check last submitting, and set submit time
- // report all errors to backend
- // clean storage
+ readErrs = (): Map => {
+ const errsStr = this.storage.get(cookieKeyClErrs);
+ const errsObj = JSON.parse(errsStr);
+ return Map(errsObj);
+ };
+
+ private writeErrs = (errs: Map) => {
+ const errsObj = errs.toObject();
+ const errsStr = JSON.stringify(errsObj);
+ this.storage.set(cookieKeyClErrs, errsStr);
+ };
+
+ error = (msg: string): null | Error => {
+ try {
+ const sign = this.getErrorSign(msg);
+ const clientErr: ClientErrorV001 = {
+ version: errorVer,
+ error: msg,
+ state: updater().props,
+ };
+ let errs = this.readErrs();
+ if (!errs.has(sign)) {
+ errs = errs.set(sign, clientErr);
+ this.writeErrs(errs);
+ }
+ } catch (err: any) {
+ return Error(`failed to save err log: ${err}`);
+ }
+
+ return null;
+ };
+
+ report = async (): Promise => {
+ try {
+ const errs = this.readErrs();
+
+ for (let sign of errs.keySeq().toArray()) {
+ const err = errs.get(sign);
+ const resp = await this.client.reportError(sign, JSON.stringify(err));
+ if (resp.status !== 200) {
+ return Error(`failed to report error: ${resp.data}`);
+ }
+ }
+
+ // truncate errors
+ this.writeErrs(Map());
+ } catch (e: any) {
+ return Error(e);
+ }
+
+ return null;
};
}
diff --git a/yarn.lock b/yarn.lock
index a2a6a5c..3c5a17c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1320,6 +1320,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.2.tgz#81f5a039d6ed1941f8cc57506c74e7c2b8fc64b9"
integrity sha512-ZHty/hKoOLZvSz6BtP1g7tc7nUeJhoCf3flLjh8ZEv1vFKBWHXcnMbJMyN/pftSljNyy0kNW/UqI3DccnBnZ8w==
+"@types/object-hash@^2.2.1":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-2.2.1.tgz#67c169f8f033e0b62abbf81df2d00f4598d540b9"
+ integrity sha512-i/rtaJFCsPljrZvP/akBqEwUP2y5cZLOmvO+JaYnz01aPknrQ+hB5MRcO7iqCUsFaYfTG8kGfKUyboA07xeDHQ==
+
"@types/prettier@^2.1.5":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3"
@@ -3768,6 +3773,11 @@ object-assign@^4.1.1:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+object-hash@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
+ integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
+
object-inspect@^1.11.0, object-inspect@^1.9.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"