diff --git a/common/core/web/tools/sentry-manager/.gitignore b/common/core/web/tools/sentry-manager/.gitignore
new file mode 100644
index 0000000000..a5025863e3
--- /dev/null
+++ b/common/core/web/tools/sentry-manager/.gitignore
@@ -0,0 +1,17 @@
+# Build products
+src/environment.inc.ts
+
+# Legacy build folders.
+output/
+build/
+embedded/
+
+# Current build output folder (matches standard node publish location).
+dist/
+
+# Other local files.
+node_modules/
+unit_tests/modernizr.js
+**/.idea/**/*.xml
+**/*.iml
+**/*.kpj.user
\ No newline at end of file
diff --git a/common/core/web/tools/sentry-manager/package-lock.json b/common/core/web/tools/sentry-manager/package-lock.json
new file mode 100644
index 0000000000..daa0f31829
--- /dev/null
+++ b/common/core/web/tools/sentry-manager/package-lock.json
@@ -0,0 +1,76 @@
+{
+ "name": "@keymanapp/web-sentry-manager",
+ "version": "14.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@sentry/browser": {
+ "version": "5.15.5",
+ "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.15.5.tgz",
+ "integrity": "sha512-rqDvjk/EvogfdbZ4TiEpxM/lwpPKmq23z9YKEO4q81+1SwJNua53H60dOk9HpRU8nOJ1g84TMKT2Ov8H7sqDWA==",
+ "requires": {
+ "@sentry/core": "5.15.5",
+ "@sentry/types": "5.15.5",
+ "@sentry/utils": "5.15.5",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@sentry/core": {
+ "version": "5.15.5",
+ "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.15.5.tgz",
+ "integrity": "sha512-enxBLv5eibBMqcWyr+vApqeix8uqkfn0iGsD3piKvoMXCgKsrfMwlb/qo9Ox0lKr71qIlZVt+9/A2vZohdgnlg==",
+ "requires": {
+ "@sentry/hub": "5.15.5",
+ "@sentry/minimal": "5.15.5",
+ "@sentry/types": "5.15.5",
+ "@sentry/utils": "5.15.5",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@sentry/hub": {
+ "version": "5.15.5",
+ "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.15.5.tgz",
+ "integrity": "sha512-zX9o49PcNIVMA4BZHe//GkbQ4Jx+nVofqU/Il32/IbwKhcpPlhGX3c1sOVQo4uag3cqd/JuQsk+DML9TKkN0Lw==",
+ "requires": {
+ "@sentry/types": "5.15.5",
+ "@sentry/utils": "5.15.5",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@sentry/minimal": {
+ "version": "5.15.5",
+ "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.15.5.tgz",
+ "integrity": "sha512-zQkkJ1l9AjmU/Us5IrOTzu7bic4sTPKCatptXvLSTfyKW7N6K9MPIIFeSpZf9o1yM2sRYdK7GV08wS2eCT3JYw==",
+ "requires": {
+ "@sentry/hub": "5.15.5",
+ "@sentry/types": "5.15.5",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@sentry/types": {
+ "version": "5.15.5",
+ "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.15.5.tgz",
+ "integrity": "sha512-F9A5W7ucgQLJUG4LXw1ZIy4iLevrYZzbeZ7GJ09aMlmXH9PqGThm1t5LSZlVpZvUfQ2rYA8NU6BdKJSt7B5LPw=="
+ },
+ "@sentry/utils": {
+ "version": "5.15.5",
+ "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.15.5.tgz",
+ "integrity": "sha512-Nl9gl/MGnzSkuKeo3QaefoD/OJrFLB8HmwQ7HUbTXb6E7yyEzNKAQMHXGkwNAjbdYyYbd42iABP6Y5F/h39NtA==",
+ "requires": {
+ "@sentry/types": "5.15.5",
+ "tslib": "^1.9.3"
+ }
+ },
+ "tslib": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
+ "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA=="
+ },
+ "typescript": {
+ "version": "3.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
+ "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
+ "dev": true
+ }
+ }
+}
diff --git a/common/core/web/tools/sentry-manager/package.json b/common/core/web/tools/sentry-manager/package.json
new file mode 100644
index 0000000000..68a4379cd5
--- /dev/null
+++ b/common/core/web/tools/sentry-manager/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@keymanapp/web-sentry-manager",
+ "version": "14.0.0",
+ "description": "Common setup for KeymanWeb's Sentry-based error reporting",
+ "main": "dist/index.js",
+ "scripts": {
+ "tsc": "tsc"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/keymanapp/keyman.git"
+ },
+ "author": "SIL International",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/keymanapp/keyman/issues"
+ },
+ "homepage": "https://github.com/keymanapp/keyman#readme",
+ "devDependencies": {
+ "typescript": "^3.8.3"
+ },
+ "dependencies": {
+ "@sentry/browser": "^5.15.5"
+ }
+}
diff --git a/common/core/web/tools/sentry-manager/src/build.sh b/common/core/web/tools/sentry-manager/src/build.sh
new file mode 100755
index 0000000000..b1adcfa1ab
--- /dev/null
+++ b/common/core/web/tools/sentry-manager/src/build.sh
@@ -0,0 +1,66 @@
+#! /bin/bash
+#
+# Compiles development-related KeymanWeb resources for use with developing/running tests.
+# - the Recorder module (for engine tests)
+# - the DOM module (for touch-alias and element-interface tests)
+
+## START STANDARD BUILD SCRIPT INCLUDE
+# adjust relative paths as necessary
+THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")"
+. "$(dirname "$THIS_SCRIPT")/../../../../../../resources/build/build-utils.sh"
+. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
+## END STANDARD BUILD SCRIPT INCLUDE
+
+SCRIPT_DIR="$(dirname "$THIS_SCRIPT")"
+
+display_usage ( ) {
+ echo "build.sh [-skip-dependency-install]"
+ echo
+ echo " -skip-dependency-install skips the `lerna bootstrap` dependency check."
+ echo " (or -S) Intended for use when this script is called by another build script."
+ echo ""
+ echo " If more than one target is specified, the last one will take precedence."
+ exit 1
+}
+
+# Establish default build parameters
+set_default_vars ( ) {
+ FETCH_DEPS=true
+}
+
+set_default_vars
+
+# Parse args
+while [[ $# -gt 0 ]] ; do
+ key="$1"
+ case $key in
+ -skip-package-install|-S)
+ set_default_vars
+ FETCH_DEPS=false
+ ;;
+ esac
+ shift # past argument
+done
+
+if [ $FETCH_DEPS = true ]; then
+ verify_npm_setup
+fi
+
+# Definition of global compile constants
+OUTPUT_DIR="dist"
+OUTPUT="index.js"
+
+# Ensures that we rely first upon the local npm-based install of Typescript.
+# (Facilitates automated setup for build agents.)
+PATH="../node_modules/.bin:$PATH"
+
+# Ensure the environment definition variables are available.
+exportEnvironmentDefinitionTS
+
+compiler="npm run tsc --"
+compilecmd="$compiler"
+
+$compilecmd -p "$SCRIPT_DIR/tsconfig.json"
+if [ $? -ne 0 ]; then
+ fail "Compilation of package for Sentry integration with KeymanWeb failed."
+fi
\ No newline at end of file
diff --git a/common/core/web/tools/sentry-manager/src/index.ts b/common/core/web/tools/sentry-manager/src/index.ts
new file mode 100644
index 0000000000..988757031a
--- /dev/null
+++ b/common/core/web/tools/sentry-manager/src/index.ts
@@ -0,0 +1,147 @@
+///
+
+namespace com.keyman {
+ /**
+ * Controls whether or not the generated Sentry event is logged to the console (true)
+ * or sent to our Sentry server (false).
+ */
+ let DEBUG = false;
+
+ type Options = typeof KeymanSentryManager.DEFAULT_OPTIONS;
+
+ export class KeymanSentryManager {
+ keymanPlatform: string;
+
+ static STANDARD_ALIASABLE_FILES = {
+ 'keymanweb.js': 'keymanweb.js',
+ 'kmwuibutton.js': 'kmwuibutton.js',
+ 'kmwuifloat.js': 'kmwuifloat.js',
+ 'kmwuitoggle.js': 'kmwuitoggle.js',
+ 'kmwuitoolbar.js': 'kmwuitoolbar.js'
+ // Also add entries for the naming system used by Android and iOS - and map them to the EMBEDDED upload, not the std 'native' one.
+ }
+
+ static DEFAULT_OPTIONS = {
+ hostPlatform: "native-web"
+ }
+
+ constructor(options: Options = KeymanSentryManager.DEFAULT_OPTIONS) {
+ this.keymanPlatform = options.hostPlatform;
+ }
+
+ // If we've recognized one of our source files,
+ aliasFilename(filename: string): string|null {
+ if(!this.mayAlias(filename)) {
+ return null;
+ }
+
+ filename = KeymanSentryManager.STANDARD_ALIASABLE_FILES[filename];
+
+ switch(location.protocol) {
+ case 'http:':
+ return 'http://' + location.host + '/' + filename;
+ case 'file:':
+ return 'file:///' + filename;
+ default:
+ return null;
+ }
+ }
+
+ mayAlias(filename: string): boolean {
+ return !!KeymanSentryManager.STANDARD_ALIASABLE_FILES[filename];
+ }
+
+ // Filters all expected but unnecessary path prefixes, affixes, and suffixes reported by Sentry from our products.
+ // This allows us to mask all different sorts of installations with a single uploaded path.
+ // Modifies original object.
+ pathFilter(event: any) {
+ // Get the underlying JS error.
+ let exception = event.exception;
+
+ // Iterate through all wrapped exceptions.
+ for(let e of exception.values) {
+ for(let frame of e.stacktrace.frames) {
+ let URL = frame.filename as string;
+ let filename: string = '';
+ try {
+ filename = URL.substr(URL.lastIndexOf('/')+1);
+ if(this.mayAlias(filename)) {
+ // Make the file look like it's at the host's root so that the Sentry server will recognize it
+ // regardless of actual filepath position.
+ frame.filename = this.aliasFilename(filename) || frame.filename;
+ }
+ } catch {
+ // not sure what to do yet, but... don't make more errors in the error handler. That's a bad idea.
+ }
+ }
+ }
+ }
+
+ // Attaches some useful debugging information to the specified object, pass-by-reference style.
+ attachEventMetadata(event: any) {
+ // Ensure that the 'extra' object exists. (May not exist for synthetic/custom Errors.)
+ event.extra = event.extra || {};
+ event.extra.keymanState = window['keyman']['getDebugInfo']();
+ event.extra.keymanHostPlatform = this.keymanPlatform;
+ }
+
+ /**
+ * Pre-processes a Sentry event object (in-place) to provide more metadata and enhance
+ * the Sentry server's ability to match the error against release artifacts.
+ * @param event A Sentry-generated event
+ */
+ prepareEvent(event: any): boolean {
+ this.pathFilter(event);
+ this.attachEventMetadata(event);
+
+ if(DEBUG) {
+ console.log("DEBUG: event object for Sentry")
+ console.log(event);
+ return false; //event
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Allows debugging our custom event preparation code without bombarding Sentry with errors
+ * during development.
+ *
+ * Note that Sentry expects us either to return the event object to be sent or to return `null`
+ * if we want to prevent the event from being sent to the server.
+ * @param event
+ */
+ prepareEventDebugWrapper(event: any) {
+ if(DEBUG) {
+ try {
+ if(this.prepareEvent(event)) {
+ return event;
+ } else {
+ return null;
+ }
+ } catch(err) {
+ console.log(err);
+ }
+ } else {
+ // If not in DEBUG mode, simply forward to the actual preparer; we should be notified
+ // of any event-prep errors that may occur.
+ if(this.prepareEvent(event)) {
+ return event;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ init() {
+ // Do the actual Sentry initialization.
+ //@ts-ignore
+ Sentry.init({
+ beforeSend: this.prepareEventDebugWrapper.bind(this),
+ debug: DEBUG,
+ dsn: 'https://cf96f32d107c4286ab2fd82af49c4d3b@sentry.keyman.com/11', // keyman-web DSN
+ release: com.keyman.environment.SENTRY_RELEASE
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/core/web/tools/sentry-manager/src/tsconfig.json b/common/core/web/tools/sentry-manager/src/tsconfig.json
new file mode 100644
index 0000000000..b23d357baa
--- /dev/null
+++ b/common/core/web/tools/sentry-manager/src/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "allowJs": true,
+ "module": "none",
+ "outDir": "../dist/",
+ "inlineSources": true,
+ "sourceMap": true,
+ "target": "es5",
+ "lib": ["es6", "dom"],
+ "outFile": "../dist/index.js"
+ },
+ "files": [
+ "environment.inc.ts",
+ "index.ts"
+ ]
+}
\ No newline at end of file
diff --git a/lerna.json b/lerna.json
index 080660cc0c..0ac9963322 100644
--- a/lerna.json
+++ b/lerna.json
@@ -3,6 +3,7 @@
"common/core/web/input-processor",
"common/core/web/keyboard-processor",
"common/core/web/tools/recorder",
+ "common/core/web/tools/sentry-manager",
"common/core/web/utils",
"common/lexical-model-types",
"common/predictive-text",
diff --git a/resources/build/build-utils.sh b/resources/build/build-utils.sh
index bff6e82974..351c29d10f 100755
--- a/resources/build/build-utils.sh
+++ b/resources/build/build-utils.sh
@@ -195,6 +195,7 @@ exportEnvironmentDefinitionTS ( ) {
namespace com.keyman.environment {
export var VERSION = \"$VERSION_RELEASE\";
export var BUILD = $VERSION_PATCH;
+ export var SENTRY_RELEASE = \"release-$VERSION_WITH_TAG\";
}
" >> $ENVIRONMENT_TS
}
\ No newline at end of file
diff --git a/web/package-lock.json b/web/package-lock.json
index aa5301dfd8..94b30729b7 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -4,6 +4,70 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+ "@sentry/cli": {
+ "version": "1.52.3",
+ "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.52.3.tgz",
+ "integrity": "sha512-QOSIg5hxAEa6v6H7oEeF6A/Rpa0wloMhbu0Qed6zHv3lyoqf0Z34Kq2jCXdqGsOE3IzkO+3CNy81F6361j5TKg==",
+ "dev": true,
+ "requires": {
+ "https-proxy-agent": "^5.0.0",
+ "mkdirp": "^0.5.5",
+ "node-fetch": "^2.6.0",
+ "progress": "^2.0.3",
+ "proxy-from-env": "^1.1.0"
+ },
+ "dependencies": {
+ "agent-base": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz",
+ "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==",
+ "dev": true,
+ "requires": {
+ "debug": "4"
+ }
+ },
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "https-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
+ "dev": true,
+ "requires": {
+ "agent-base": "6",
+ "debug": "4"
+ }
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
+ }
+ },
"@types/node": {
"version": "11.9.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.4.tgz",
@@ -1552,12 +1616,6 @@
}
}
},
- "mocha-teamcity-reporter": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/mocha-teamcity-reporter/-/mocha-teamcity-reporter-3.0.0.tgz",
- "integrity": "sha512-FyGgmtFfW2nDwEZU3mrjQShAAK/zhGivwY4HCsqoDoyeS8vV8HGdq1Dn2P+SFaIoCeXTQ0Z+5xVRyikYaKrW5w==",
- "dev": true
- },
"modernizr": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/modernizr/-/modernizr-3.7.1.tgz",
@@ -1585,6 +1643,12 @@
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
"dev": true
},
+ "node-fetch": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
+ "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==",
+ "dev": true
+ },
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -1736,6 +1800,18 @@
"integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==",
"dev": true
},
+ "progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true
+ },
+ "proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "dev": true
+ },
"ps-tree": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.1.tgz",
diff --git a/web/package.json b/web/package.json
index bf89b75d2f..810f7fcde9 100644
--- a/web/package.json
+++ b/web/package.json
@@ -18,6 +18,8 @@
},
"homepage": "https://github.com/keymanapp/keyman#readme",
"devDependencies": {
+ "@keymanapp/web-sentry-manager": "^14.0.0",
+ "@sentry/cli": "^1.52.3",
"chai": "^4.2.0",
"google-closure-compiler-java": "^20200224.0.0",
"html2canvas": "^1.0.0-alpha.12",
@@ -45,7 +47,8 @@
"karma": "karma",
"lerna": "cd ../ && npm run lerna --",
"mocha": "mocha",
- "modernizr": "modernizr"
+ "modernizr": "modernizr",
+ "sentry-cli": "sentry-cli"
},
"dependencies": {
"@keymanapp/input-processor": "^14.0.78",
diff --git a/web/source/build.sh b/web/source/build.sh
index 1c41984a41..eb3e485f7e 100755
--- a/web/source/build.sh
+++ b/web/source/build.sh
@@ -206,6 +206,13 @@ INTERMEDIATE="../intermediate"
SOURCE="."
NODE_SOURCE="source"
+SENTRY_RELEASE_VERSION="release-$VERSION_WITH_TAG"
+
+export SENTRY_URL="https://sentry.keyman.com"
+export SENTRY_ORG=keyman
+export SENTRY_PROJECT=keyman-web
+export SENTRY_LOG_LEVEL=info
+
readonly WEB_OUTPUT
readonly EMBED_OUTPUT
readonly SOURCE
@@ -227,6 +234,7 @@ set_default_vars ( ) {
BUILD_DEBUG_EMBED=false
BUILD_COREWEB=true
DO_MINIFY=true
+ DO_NATIVE_SENTRY=true
FETCH_DEPS=true
}
@@ -264,6 +272,7 @@ while [[ $# -gt 0 ]] ; do
BUILD_FULLWEB=false
BUILD_UI=false
BUILD_COREWEB=false
+ DO_NATIVE_SENTRY=false
;;
-web)
set_default_vars
@@ -276,12 +285,14 @@ while [[ $# -gt 0 ]] ; do
BUILD_COREWEB=false
BUILD_FULLWEB=false
BUILD_DEBUG_EMBED=true
+ DO_NATIVE_SENTRY=false
;;
-h|-?)
display_usage
;;
-no_minify)
DO_MINIFY=false
+ DO_NATIVE_SENTRY=false
;;
-clean)
clean
@@ -314,6 +325,7 @@ if [ $DO_MINIFY = true ]; then
exit 1
fi
+ # Only run if BOTH cases are true b/c it's a minification-focused 'dependency'.
if [ $FETCH_DEPS = true ]; then
# Also, build our sourcemap-root tool for cleaning up the minified version's sourcemaps.
echo "Compiling build tools for minified build products"
@@ -324,6 +336,14 @@ fi
if [ $BUILD_CORE = true ]; then
CORE_FLAGS="-skip-package-install"
+
+ # Build the sentry-manager module - it's used in embedded contexts and on one testing page.
+ echo "${TERM_HEADING}Compiling KeymanWeb's sentry-manager module...${NORMAL}"
+ pushd ../../common/core/web/tools/sentry-manager/src
+ ./build.sh $CORE_FLAGS || fail "Failed to compile the sentry-manager module"
+ popd
+ echo "${TERM_HEADING}sentry-manager module compiled successfully.${NORMAL}"
+
if [ $BUILD_LMLAYER = false ]; then
CORE_FLAGS="$CORE_FLAGS -test"
fi
@@ -381,6 +401,13 @@ if [ $BUILD_EMBED = true ]; then
echo
echo KMEA/KMEI version $VERSION compiled and saved under $EMBED_OUTPUT
echo
+
+ # We always publish "embedded" mode to Sentry when minifying.
+ pushd $EMBED_OUTPUT
+ echo "Uploading to Sentry..."
+ npm run sentry-cli -- releases files "$SENTRY_RELEASE_VERSION" upload-sourcemaps --strip-common-prefix release/embedded --rewrite --ext js --ext map --ext ts || fail "Sentry upload failed."
+ echo "Upload successful."
+ popd
fi
fi
@@ -462,6 +489,16 @@ if [ $BUILD_UI = true ]; then
fi
fi
+# Has its own flag due to complex logic. Should run if $DO_MINIFY && ($BUILD_COREWEB || $BUILD_UI)
+# If only one of the two (COREWEB, UI) is run, we assume the un-run portion is 'current' and still upload it.
+if [ $DO_NATIVE_SENTRY = true ]; then
+ pushd $WEB_OUTPUT
+ echo "Uploading to Sentry..."
+ npm run sentry-cli -- releases files "$SENTRY_RELEASE_VERSION" upload-sourcemaps --strip-common-prefix release/web/ --rewrite --ext js --ext map --ext ts || fail "Sentry upload failed."
+ echo "Upload successful."
+ popd
+fi
+
if [ $BUILD_DEBUG_EMBED = true ]; then
# We currently have an issue with sourcemaps for minified versions.
# We should use the unminified one instead for now.
@@ -470,4 +507,3 @@ if [ $BUILD_DEBUG_EMBED = true ]; then
cp $EMBED_OUTPUT_NO_MINI/keyman.js.map $EMBED_OUTPUT/keyman.js.map
echo Uncompiled embedded application saved as keyman.js
fi
-
diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts
index a1e41fb63a..e6ef3cfa18 100644
--- a/web/source/keyboards/kmwkeyboards.ts
+++ b/web/source/keyboards/kmwkeyboards.ts
@@ -372,7 +372,7 @@ namespace com.keyman.keyboards {
}
this.doBeforeKeyboardChange(PInternalName,PLgCode);
- let p = this._SetActiveKeyboard(PInternalName,PLgCode,true);
+ let p: Promise = this._SetActiveKeyboard(PInternalName,PLgCode,true);
if(this.keymanweb.domManager.getLastActiveElement() != null) {
this.keymanweb.domManager.focusLastActiveElement(); // TODO: Resolve without need for the cast.
}
@@ -383,6 +383,14 @@ namespace com.keyman.keyboards {
// }
this.doKeyboardChange(PInternalName, PLgCode);
+ p.catch(function() {
+ // Rejection indicates a failure of the keyboard to load.
+ //
+ // In case p's rejection is never caught, throwing this error will generate logs that shows up
+ // in Sentry or in the console, with useful information for debugging either way.
+ throw new Error("Unable to load keyboard with internal name \"" + PInternalName + "\", language code \"" + PLgCode + "\".");
+ })
+
return p;
}
diff --git a/web/source/kmwbase.ts b/web/source/kmwbase.ts
index 0ca1f26083..418078c5d2 100644
--- a/web/source/kmwbase.ts
+++ b/web/source/kmwbase.ts
@@ -177,6 +177,25 @@ namespace com.keyman {
dom.DOMEventHandlers.states = new dom.CommonDOMStates();
}
+ /**
+ * Returns a generalized metadata object about the state of KMW for use with error reporting.
+ */
+ ['getDebugInfo']() {
+ let metadata = {
+ attachType: this.options.attachType,
+ device: this.util.device,
+ initialized: this.initialized,
+ isEmbedded: this.isEmbedded,
+ ui: this.ui ? this.ui.name : null
+ }
+
+ if(this.util.device.touchable) {
+ metadata.ui = 'touch';
+ }
+
+ return metadata;
+ }
+
/**
* Expose font testing to allow checking that SpecialOSK or custom font has
* been correctly loaded by browser
diff --git a/web/testing/index.html b/web/testing/index.html
index 5084af2234..5a3a76b5ec 100644
--- a/web/testing/index.html
+++ b/web/testing/index.html
@@ -19,7 +19,7 @@
- KeymanWeb 12 Testing
+ KeymanWeb 14 Testing
@@ -30,6 +30,7 @@
+
Auto-attachment mode tests
diff --git a/web/testing/sentry-integration/errorhdr.js b/web/testing/sentry-integration/errorhdr.js
new file mode 100644
index 0000000000..b784f6a476
--- /dev/null
+++ b/web/testing/sentry-integration/errorhdr.js
@@ -0,0 +1,42 @@
+// JavaScript Document samplehdr.js: Keyboard management for KeymanWeb demonstration pages
+
+/*
+ This script is designed to test KeymanWeb error message handling.
+*/
+
+ function loadKeyboards()
+ {
+ var kmw=keyman;
+
+ // We start by adding a keyboard correctly. It's best to include a 'control' in our experiment.
+ kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'},
+ filename:'../us-1.0.js'});
+
+ // Insert a keyboard that cannot be found.
+ kmw.addKeyboards({id:'lao_2008_basic',name:'wrong-filename',
+ languages:{
+ id:'lo',name:'debugging',region:'Asia',
+ font:{family:'LaoWeb',source:['../font/saysettha_web.ttf','../font/saysettha_web.woff','../font/saysettha_web.eot']}
+ },
+ filename:'./missing_file.js' // Intentional error - the file doesn't exist, so the
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ KeymanWeb Sample Page - Sentry Setup test host
+
+
+
+
+
+
+