2
0
Fork 0

Merge pull request #2557 from NoelDeMartin/MOBILE-3320

MOBILE-3320 Updated linting rules + add Error tests
main
Dani Palou 2020-10-14 17:40:40 +02:00 committed by GitHub
commit 1ab5c78491
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 3709 additions and 1126 deletions

View File

@ -1,33 +1,30 @@
module.exports = { var appConfig = {
root: true,
overrides: [
{
files: ['*.ts'],
env: { env: {
browser: true, browser: true,
es6: true, es6: true,
node: true, node: true,
}, },
plugins: [
'@typescript-eslint',
'header',
'jsdoc',
'prefer-arrow',
'promise',
],
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'prettier', 'prettier',
'prettier/@typescript-eslint', 'prettier/@typescript-eslint',
'plugin:jest/recommended',
'plugin:@angular-eslint/recommended', 'plugin:@angular-eslint/recommended',
'plugin:promise/recommended',
], ],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
project: 'tsconfig.json', project: 'tsconfig.json',
sourceType: 'module', sourceType: 'module',
}, },
plugins: [ reportUnusedDisableDirectives: true,
'eslint-plugin-prefer-arrow',
'eslint-plugin-jsdoc',
'@typescript-eslint',
'header',
'jest',
],
rules: { rules: {
'@angular-eslint/component-class-suffix': ['error', { suffixes: ['Component', 'Page'] }], '@angular-eslint/component-class-suffix': ['error', { suffixes: ['Component', 'Page'] }],
'@typescript-eslint/adjacent-overload-signatures': 'error', '@typescript-eslint/adjacent-overload-signatures': 'error',
@ -56,7 +53,29 @@ module.exports = {
accessibility: 'no-public', accessibility: 'no-public',
}, },
], ],
'@typescript-eslint/indent': 'error', '@typescript-eslint/explicit-module-boundary-types': [
'error',
{
allowArgumentsExplicitlyTypedAsAny: true,
},
],
'@typescript-eslint/indent': [
'error',
4,
{
SwitchCase: 1,
ignoredNodes: [
'ClassProperty *',
],
},
],
'@typescript-eslint/lines-between-class-members': [
'error',
'always',
{
exceptAfterSingleLine: true,
},
],
'@typescript-eslint/member-delimiter-style': [ '@typescript-eslint/member-delimiter-style': [
'error', 'error',
{ {
@ -85,19 +104,14 @@ module.exports = {
], ],
'@typescript-eslint/no-empty-function': 'error', '@typescript-eslint/no-empty-function': 'error',
'@typescript-eslint/no-empty-interface': 'off', '@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-explicit-any': [ '@typescript-eslint/no-explicit-any': 'warn',
'warn',
{
fixToUnknown: true,
},
],
'@typescript-eslint/no-inferrable-types': [ '@typescript-eslint/no-inferrable-types': [
'error', 'error',
{ {
ignoreParameters: true, ignoreParameters: true,
}, },
], ],
'@typescript-eslint/no-non-null-assertion': 'error', '@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-this-alias': 'error',
'@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/quotes': [ '@typescript-eslint/quotes': [
@ -109,18 +123,6 @@ module.exports = {
'always', 'always',
], ],
'@typescript-eslint/type-annotation-spacing': 'error', '@typescript-eslint/type-annotation-spacing': 'error',
'@typescript-eslint/typedef': [
'error',
{
arrayDestructuring: false,
arrowParameter: false,
memberVariableDeclaration: true,
objectDestructuring: false,
parameter: true,
propertyDeclaration: true,
variableDeclaration: false,
},
],
'@typescript-eslint/unified-signatures': 'error', '@typescript-eslint/unified-signatures': 'error',
'header/header': [ 'header/header': [
2, 2,
@ -142,13 +144,20 @@ module.exports = {
], ],
1, 1,
], ],
'promise/catch-or-return': [
'warn',
{
allowFinally: true,
},
],
'arrow-body-style': ['error', 'as-needed'], 'arrow-body-style': ['error', 'as-needed'],
'array-bracket-spacing': ['error', 'never'], 'array-bracket-spacing': ['error', 'never'],
'comma-dangle': ['error', 'always-multiline'], 'comma-dangle': ['error', 'always-multiline'],
'constructor-super': 'error', 'constructor-super': 'error',
'curly': 'error', 'curly': 'error',
'default-case': 'error',
'eol-last': 'error', 'eol-last': 'error',
'function-call-argument-newline': ['error', 'consistent'],
'function-paren-newline': ['error', 'multiline-arguments'],
'id-blacklist': [ 'id-blacklist': [
'error', 'error',
'any', 'any',
@ -163,13 +172,17 @@ module.exports = {
], ],
'id-match': 'error', 'id-match': 'error',
'jsdoc/check-alignment': 'error', 'jsdoc/check-alignment': 'error',
'jsdoc/check-indentation': 'error', 'jsdoc/check-indentation': [
'error',
{
excludeTags: ['param'],
},
],
'jsdoc/newline-after-description': 'error', 'jsdoc/newline-after-description': 'error',
'linebreak-style': [ 'linebreak-style': [
'error', 'error',
'unix', 'unix',
], ],
'lines-between-class-members': ['error', 'always'],
'max-len': [ 'max-len': [
'error', 'error',
{ {
@ -238,7 +251,24 @@ module.exports = {
'use-isnan': 'error', 'use-isnan': 'error',
'yoda': 'error', 'yoda': 'error',
}, },
};
var testsConfig = Object.assign({}, appConfig);
testsConfig['rules']['padded-blocks'] = [
'error',
{
classes: 'always',
switches: 'never',
}, },
];
testsConfig['plugins'].push('jest');
testsConfig['extends'].push('plugin:jest/recommended');
module.exports = {
root: true,
overrides: [
Object.assign({ files: ['*.ts'] }, appConfig),
Object.assign({ files: ['*.test.ts'] }, testsConfig),
{ {
files: ['*.html'], files: ['*.html'],
extends: ['plugin:@angular-eslint/template/recommended'], extends: ['plugin:@angular-eslint/template/recommended'],

1
.gitignore vendored
View File

@ -22,7 +22,6 @@ npm-debug.log*
/.sass-cache /.sass-cache
/.sourcemaps /.sourcemaps
/.versions /.versions
/.vscode
/coverage /coverage
/dist /dist
/node_modules /node_modules

35
.vscode/launch.json vendored 100644
View File

@ -0,0 +1,35 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jest All",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
}
},
{
"type": "node",
"request": "launch",
"name": "Jest Current File",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": [
"${fileBasenameNoExtension}",
"--config",
"jest.config.js"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
}
}
]
}

145
package-lock.json generated
View File

@ -3849,12 +3849,6 @@
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
} }
}, },
"app-root-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz",
"integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==",
"dev": true
},
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
@ -3876,16 +3870,6 @@
"sprintf-js": "~1.0.2" "sprintf-js": "~1.0.2"
} }
}, },
"aria-query": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
"integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=",
"dev": true,
"requires": {
"ast-types-flow": "0.0.7",
"commander": "^2.11.0"
}
},
"arity-n": { "arity-n": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz",
@ -4059,12 +4043,6 @@
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
"dev": true "dev": true
}, },
"ast-types-flow": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
"integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
"dev": true
},
"astral-regex": { "astral-regex": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
@ -4184,15 +4162,6 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz",
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA=="
}, },
"axobject-query": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
"integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==",
"dev": true,
"requires": {
"ast-types-flow": "0.0.7"
}
},
"babel-jest": { "babel-jest": {
"version": "26.5.2", "version": "26.5.2",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.5.2.tgz", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.5.2.tgz",
@ -5291,60 +5260,6 @@
} }
} }
}, },
"codelyzer": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.1.tgz",
"integrity": "sha512-cOyGQgMdhnRYtW2xrJUNrNYDjEgwQ+BrE2y93Bwz3h4DJ6vJRLfupemU5N3pbYsUlBHJf0u1j1UGk+NLW4d97g==",
"dev": true,
"requires": {
"@angular/compiler": "9.0.0",
"@angular/core": "9.0.0",
"app-root-path": "^3.0.0",
"aria-query": "^3.0.0",
"axobject-query": "2.0.2",
"css-selector-tokenizer": "^0.7.1",
"cssauron": "^1.4.0",
"damerau-levenshtein": "^1.0.4",
"rxjs": "^6.5.3",
"semver-dsl": "^1.0.1",
"source-map": "^0.5.7",
"sprintf-js": "^1.1.2",
"tslib": "^1.10.0",
"zone.js": "~0.10.3"
},
"dependencies": {
"@angular/compiler": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz",
"integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==",
"dev": true
},
"@angular/core": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz",
"integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==",
"dev": true
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
},
"sprintf-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
"dev": true
},
"tslib": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.0.tgz",
"integrity": "sha512-+Zw5lu0D9tvBMjGP8LpvMb0u2WW2QV3y+D8mO6J+cNzCYIN4sVy43Bf9vl92nqFahutN0I8zHa7cc4vihIshnw==",
"dev": true
}
}
},
"collect-v8-coverage": { "collect-v8-coverage": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz",
@ -6559,16 +6474,6 @@
"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==",
"dev": true "dev": true
}, },
"css-selector-tokenizer": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz",
"integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"fastparse": "^1.1.2"
}
},
"css-tree": { "css-tree": {
"version": "1.0.0-alpha.37", "version": "1.0.0-alpha.37",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@ -6593,15 +6498,6 @@
"integrity": "sha512-wHOppVDKl4vTAOWzJt5Ek37Sgd9qq1Bmj/T1OjvicWbU5W7ru7Pqbn0Jdqii3Drx/h+dixHKXNhZYx7blthL7g==", "integrity": "sha512-wHOppVDKl4vTAOWzJt5Ek37Sgd9qq1Bmj/T1OjvicWbU5W7ru7Pqbn0Jdqii3Drx/h+dixHKXNhZYx7blthL7g==",
"dev": true "dev": true
}, },
"cssauron": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz",
"integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=",
"dev": true,
"requires": {
"through": "X.X.X"
}
},
"cssesc": { "cssesc": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -6765,12 +6661,6 @@
"type": "^1.0.1" "type": "^1.0.1"
} }
}, },
"damerau-levenshtein": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz",
"integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==",
"dev": true
},
"dashdash": { "dashdash": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@ -7936,6 +7826,12 @@
"integrity": "sha512-C8YMhL+r8RMeMdYAw/rQtE6xNdMulj+zGWud/qIGnlmomiPRaLDGLMeskZ3alN6uMBojmooRimtdrXebLN4svQ==", "integrity": "sha512-C8YMhL+r8RMeMdYAw/rQtE6xNdMulj+zGWud/qIGnlmomiPRaLDGLMeskZ3alN6uMBojmooRimtdrXebLN4svQ==",
"dev": true "dev": true
}, },
"eslint-plugin-promise": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz",
"integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==",
"dev": true
},
"eslint-scope": { "eslint-scope": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
@ -8329,6 +8225,12 @@
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz",
"integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=" "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8="
}, },
"faker": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/faker/-/faker-5.1.0.tgz",
"integrity": "sha512-RrWKFSSA/aNLP0g3o2WW1Zez7/MnMr7xkiZmoCfAGZmdkDQZ6l2KtuXHN5XjdvpRjDl8+3vf+Rrtl06Z352+Mw==",
"dev": true
},
"fast-deep-equal": { "fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -8363,12 +8265,6 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true "dev": true
}, },
"fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
"dev": true
},
"fastq": { "fastq": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz",
@ -15642,23 +15538,6 @@
} }
} }
}, },
"semver-dsl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz",
"integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=",
"dev": true,
"requires": {
"semver": "^5.3.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"semver-intersect": { "semver-intersect": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz",

View File

@ -121,7 +121,6 @@
"@angular/language-service": "~10.0.0", "@angular/language-service": "~10.0.0",
"@ionic/angular-toolkit": "^2.3.0", "@ionic/angular-toolkit": "^2.3.0",
"@types/node": "^12.12.64", "@types/node": "^12.12.64",
"codelyzer": "^6.0.0",
"@typescript-eslint/eslint-plugin": "4.3.0", "@typescript-eslint/eslint-plugin": "4.3.0",
"@typescript-eslint/parser": "4.3.0", "@typescript-eslint/parser": "4.3.0",
"eslint": "^7.6.0", "eslint": "^7.6.0",
@ -131,6 +130,8 @@
"eslint-plugin-jest": "^24.1.0", "eslint-plugin-jest": "^24.1.0",
"eslint-plugin-jsdoc": "^30.6.3", "eslint-plugin-jsdoc": "^30.6.3",
"eslint-plugin-prefer-arrow": "^1.2.2", "eslint-plugin-prefer-arrow": "^1.2.2",
"eslint-plugin-promise": "^4.2.1",
"faker": "^5.1.0",
"jest": "^26.5.0", "jest": "^26.5.0",
"jest-preset-angular": "^8.3.1", "jest-preset-angular": "^8.3.1",
"ts-jest": "^26.4.1", "ts-jest": "^26.4.1",

View File

@ -0,0 +1,75 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 Faker from 'faker';
import { CoreError } from './error';
describe('CoreError', () => {
it('behaves like an error', () => {
// Arrange
const message = Faker.lorem.sentence();
let error: CoreError | null = null;
// Act
try {
throw new CoreError(message);
} catch (e) {
error = e;
}
// Assert
expect(error).not.toBeNull();
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(CoreError);
expect(error!.name).toEqual('CoreError');
expect(error!.message).toEqual(message);
expect(error!.stack).not.toBeNull();
expect(error!.stack).toContain('src/app/classes/error.test.ts');
});
it('can be subclassed', () => {
// Arrange
class CustomCoreError extends CoreError {
constructor(m: string) {
super(`Custom message: ${m}`);
}
}
const message = Faker.lorem.sentence();
let error: CustomCoreError | null = null;
// Act
try {
throw new CustomCoreError(message);
} catch (e) {
error = e;
}
// Assert
expect(error).not.toBeNull();
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(CoreError);
expect(error).toBeInstanceOf(CustomCoreError);
expect(error!.name).toEqual('CustomCoreError');
expect(error!.message).toEqual(`Custom message: ${message}`);
expect(error!.stack).not.toBeNull();
expect(error!.stack).toContain('src/app/classes/error.test.ts');
});
});

View File

@ -13,9 +13,10 @@
// limitations under the License. // limitations under the License.
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { NavController } from '@ionic/angular';
import { WKUserScriptWindow, WKUserScriptInjectionTime } from 'cordova-plugin-wkuserscript'; import { WKUserScriptWindow, WKUserScriptInjectionTime } from 'cordova-plugin-wkuserscript';
import { CoreApp, CoreAppProvider } from '@services/app'; import { CoreApp } from '@services/app';
import { CoreFile } from '@services/file'; import { CoreFile } from '@services/file';
import { CoreFileHelper } from '@services/file-helper'; import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
@ -24,42 +25,35 @@ import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url'; import { CoreUrlUtils } from '@services/utils/url';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { makeSingleton, Translate, Network, Platform, NgZone } from '@singletons/core.singletons'; import { makeSingleton, Network, Platform, NgZone } from '@singletons/core.singletons';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { CoreUrl } from '@singletons/url'; import { CoreUrl } from '@singletons/url';
import { CoreWindow } from '@singletons/window'; import { CoreWindow } from '@singletons/window';
/**
* Possible types of frame elements.
*/
type CoreFrameElement = (HTMLIFrameElement | HTMLFrameElement | HTMLObjectElement | HTMLEmbedElement) & {
window?: Window;
getWindow?(): Window;
};
/* /*
* "Utils" service with helper functions for iframes, embed and similar. * "Utils" service with helper functions for iframes, embed and similar.
*/ */
@Injectable() @Injectable()
export class CoreIframeUtilsProvider { export class CoreIframeUtilsProvider {
static FRAME_TAGS = ['iframe', 'frame', 'object', 'embed'];
static readonly FRAME_TAGS = ['iframe', 'frame', 'object', 'embed'];
protected logger: CoreLogger; protected logger: CoreLogger;
constructor() { constructor() {
this.logger = CoreLogger.getInstance('CoreUtilsProvider'); this.logger = CoreLogger.getInstance('CoreUtilsProvider');
const win = <WKUserScriptWindow> window; if (CoreApp.instance.isIOS() && 'WKUserScript' in window) {
// eslint-disable-next-line promise/catch-or-return
if (CoreApp.instance.isIOS() && win.WKUserScript) { Platform.instance.ready().then(() => this.injectiOSScripts(window));
Platform.instance.ready().then(() => {
// Inject code to the iframes because we cannot access the online ones.
const wwwPath = CoreFile.instance.getWWWAbsolutePath();
const linksPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js');
const recaptchaPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js');
win.WKUserScript.addScript({id: 'CoreIframeUtilsLinksScript', file: linksPath});
win.WKUserScript.addScript({
id: 'CoreIframeUtilsRecaptchaScript',
file: recaptchaPath,
injectionTime: WKUserScriptInjectionTime.END,
});
// Handle post messages received by iframes.
window.addEventListener('message', this.handleIframeMessage.bind(this));
});
} }
} }
@ -70,8 +64,8 @@ export class CoreIframeUtilsProvider {
* @param isSubframe Whether it's a frame inside another frame. * @param isSubframe Whether it's a frame inside another frame.
* @return True if frame is online and the app is offline, false otherwise. * @return True if frame is online and the app is offline, false otherwise.
*/ */
checkOnlineFrameInOffline(element: any, isSubframe?: boolean): boolean { checkOnlineFrameInOffline(element: CoreFrameElement, isSubframe?: boolean): boolean {
const src = element.src || element.data; const src = 'src' in element ? element.src : element.data;
if (src && src != 'about:blank' && !CoreUrlUtils.instance.isLocalFileUrl(src) && !CoreApp.instance.isOnline()) { if (src && src != 'about:blank' && !CoreUrlUtils.instance.isLocalFileUrl(src) && !CoreApp.instance.isOnline()) {
if (element.classList.contains('core-iframe-offline-disabled')) { if (element.classList.contains('core-iframe-offline-disabled')) {
@ -86,8 +80,6 @@ export class CoreIframeUtilsProvider {
div.setAttribute('padding', ''); div.setAttribute('padding', '');
div.classList.add('core-iframe-offline-warning'); div.classList.add('core-iframe-offline-warning');
const site = CoreSites.instance.getCurrentSite();
const username = site ? site.getInfo().username : undefined;
// @todo Handle link // @todo Handle link
// Add a class to specify that the iframe is hidden. // Add a class to specify that the iframe is hidden.
@ -112,8 +104,13 @@ export class CoreIframeUtilsProvider {
return true; return true;
} else if (element.classList.contains('core-iframe-offline-disabled')) { } else if (element.classList.contains('core-iframe-offline-disabled')) {
// Reload the frame. // Reload the frame.
if ('src' in element) {
// eslint-disable-next-line no-self-assign
element.src = element.src; element.src = element.src;
} else {
// eslint-disable-next-line no-self-assign
element.data = element.data; element.data = element.data;
}
// Remove the warning and show the iframe // Remove the warning and show the iframe
CoreDomUtils.instance.removeElement(element.parentElement, 'div.core-iframe-offline-warning'); CoreDomUtils.instance.removeElement(element.parentElement, 'div.core-iframe-offline-warning');
@ -134,12 +131,14 @@ export class CoreIframeUtilsProvider {
* @param element Element to treat (iframe, embed, ...). * @param element Element to treat (iframe, embed, ...).
* @return Window and Document. * @return Window and Document.
*/ */
getContentWindowAndDocument(element: any): { window: Window, document: Document } { getContentWindowAndDocument(element: CoreFrameElement): { window: Window; document: Document } {
let contentWindow: Window = element.contentWindow; let contentWindow: Window = 'contentWindow' in element ? element.contentWindow : undefined;
let contentDocument: Document; let contentDocument: Document;
try { try {
contentDocument = element.contentDocument || (contentWindow && contentWindow.document); contentDocument = 'contentDocument' in element && element.contentDocument
? element.contentDocument
: contentWindow && contentWindow.document;
} catch (ex) { } catch (ex) {
// Ignore errors. // Ignore errors.
} }
@ -149,7 +148,7 @@ export class CoreIframeUtilsProvider {
contentWindow = contentDocument.defaultView; contentWindow = contentDocument.defaultView;
} }
if (!contentWindow && element.getSVGDocument) { if (!contentWindow && 'getSVGDocument' in element) {
// It's probably an <embed>. Try to get the window and the document. // It's probably an <embed>. Try to get the window and the document.
try { try {
contentDocument = element.getSVGDocument(); contentDocument = element.getSVGDocument();
@ -187,9 +186,6 @@ export class CoreIframeUtilsProvider {
case 'link_clicked': case 'link_clicked':
this.linkClicked(event.data.link); this.linkClicked(event.data.link);
break; break;
default:
break;
} }
} }
@ -202,10 +198,15 @@ export class CoreIframeUtilsProvider {
* @param contentDocument The document of the element contents. * @param contentDocument The document of the element contents.
* @param navCtrl NavController to use if a link can be opened in the app. * @param navCtrl NavController to use if a link can be opened in the app.
*/ */
redefineWindowOpen(element: any, contentWindow: Window, contentDocument: Document, navCtrl?: any): void { redefineWindowOpen(
element: CoreFrameElement,
contentWindow: Window,
contentDocument: Document,
navCtrl?: NavController,
): void {
if (contentWindow) { if (contentWindow) {
// Intercept window.open. // Intercept window.open.
(<any> contentWindow).open = (url: string, name: string): Window => { contentWindow.open = (url: string, name: string) => {
this.windowOpen(url, name, element, navCtrl); this.windowOpen(url, name, element, navCtrl);
return null; return null;
@ -216,7 +217,7 @@ export class CoreIframeUtilsProvider {
// Search sub frames. // Search sub frames.
CoreIframeUtilsProvider.FRAME_TAGS.forEach((tag) => { CoreIframeUtilsProvider.FRAME_TAGS.forEach((tag) => {
const elements = Array.from(contentDocument.querySelectorAll(tag)); const elements = Array.from(contentDocument.querySelectorAll(tag));
elements.forEach((subElement) => { elements.forEach((subElement: CoreFrameElement) => {
this.treatFrame(subElement, true, navCtrl); this.treatFrame(subElement, true, navCtrl);
}); });
}); });
@ -231,7 +232,7 @@ export class CoreIframeUtilsProvider {
* @param isSubframe Whether it's a frame inside another frame. * @param isSubframe Whether it's a frame inside another frame.
* @param navCtrl NavController to use if a link can be opened in the app. * @param navCtrl NavController to use if a link can be opened in the app.
*/ */
treatFrame(element: any, isSubframe?: boolean, navCtrl?: any): void { treatFrame(element: CoreFrameElement, isSubframe?: boolean, navCtrl?: NavController): void {
if (element) { if (element) {
this.checkOnlineFrameInOffline(element, isSubframe); this.checkOnlineFrameInOffline(element, isSubframe);
@ -266,7 +267,7 @@ export class CoreIframeUtilsProvider {
* @param element Element to treat (iframe, embed, ...). * @param element Element to treat (iframe, embed, ...).
* @param contentDocument The document of the element contents. * @param contentDocument The document of the element contents.
*/ */
treatFrameLinks(element: any, contentDocument: Document): void { treatFrameLinks(element: CoreFrameElement, contentDocument: Document): void {
if (!contentDocument) { if (!contentDocument) {
return; return;
} }
@ -292,7 +293,7 @@ export class CoreIframeUtilsProvider {
link.treated = true; link.treated = true;
link.addEventListener('click', this.linkClicked.bind(this, link, element)); link.addEventListener('click', this.linkClicked.bind(this, link, element));
}, { }, {
capture: true // Use capture to fix this listener not called if the element clicked is too deep in the DOM. capture: true, // Use capture to fix this listener not called if the element clicked is too deep in the DOM.
}); });
} }
@ -305,11 +306,13 @@ export class CoreIframeUtilsProvider {
* @param navCtrl NavController to use if a link can be opened in the app. * @param navCtrl NavController to use if a link can be opened in the app.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
protected async windowOpen(url: string, name: string, element?: any, navCtrl?: any): Promise<void> { protected async windowOpen(url: string, name: string, element?: CoreFrameElement, navCtrl?: NavController): Promise<void> {
const scheme = CoreUrlUtils.instance.getUrlScheme(url); const scheme = CoreUrlUtils.instance.getUrlScheme(url);
if (!scheme) { if (!scheme) {
// It's a relative URL, use the frame src to create the full URL. // It's a relative URL, use the frame src to create the full URL.
const src = element && (element.src || element.data); const src = element
? ('src' in element ? element.src : element.data)
: null;
if (src) { if (src) {
const dirAndFile = CoreFile.instance.getFileAndDirectoryFromPath(src); const dirAndFile = CoreFile.instance.getFileAndDirectoryFromPath(src);
if (dirAndFile.directory) { if (dirAndFile.directory) {
@ -372,8 +375,11 @@ export class CoreIframeUtilsProvider {
* @param event Click event. * @param event Click event.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
protected async linkClicked(link: {href: string, target?: string}, element?: HTMLFrameElement | HTMLObjectElement, protected async linkClicked(
event?: Event): Promise<void> { link: {href: string; target?: string},
element?: HTMLFrameElement | HTMLObjectElement,
event?: Event,
): Promise<void> {
if (event && event.defaultPrevented) { if (event && event.defaultPrevented) {
// Event already prevented by some other code. // Event already prevented by some other code.
return; return;
@ -438,6 +444,28 @@ export class CoreIframeUtilsProvider {
} }
} }
} }
/**
* Inject code to the iframes because we cannot access the online ones.
*
* @param userScriptWindow Window.
*/
private injectiOSScripts(userScriptWindow: WKUserScriptWindow) {
const wwwPath = CoreFile.instance.getWWWAbsolutePath();
const linksPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js');
const recaptchaPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js');
userScriptWindow.WKUserScript.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath });
userScriptWindow.WKUserScript.addScript({
id: 'CoreIframeUtilsRecaptchaScript',
file: recaptchaPath,
injectionTime: WKUserScriptInjectionTime.END,
});
// Handle post messages received by iframes.
window.addEventListener('message', this.handleIframeMessage.bind(this));
}
} }
export class CoreIframeUtils extends makeSingleton(CoreIframeUtilsProvider) {} export class CoreIframeUtils extends makeSingleton(CoreIframeUtilsProvider) {}

View File

@ -13,36 +13,55 @@
// limitations under the License. // limitations under the License.
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FileEntry } from '@ionic-native/file';
import { CoreFile } from '@services/file'; import { CoreFile } from '@services/file';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { makeSingleton, Translate, Http } from '@singletons/core.singletons'; import { makeSingleton, Translate, Http } from '@singletons/core.singletons';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { CoreWSExternalFile } from '@services/ws';
import { CoreUtils } from '@services/utils/utils';
interface MimeTypeInfo {
type: string;
icon?: string;
groups?: string[];
// eslint-disable-next-line id-blacklist
string?: string;
}
interface MimeTypeGroupInfo {
mimetypes: string[];
extensions: string[];
}
const EXTENSION_REGEX = /^[a-z0-9]+$/;
/* /*
* "Utils" service with helper functions for mimetypes and extensions. * "Utils" service with helper functions for mimetypes and extensions.
*/ */
@Injectable() @Injectable()
export class CoreMimetypeUtilsProvider { export class CoreMimetypeUtilsProvider {
protected logger: CoreLogger; protected logger: CoreLogger;
protected extToMime = {}; // Object to map extensions -> mimetypes. protected extToMime: Record<string, MimeTypeInfo> = {};
protected mimeToExt = {}; // Object to map mimetypes -> extensions. protected mimeToExt: Record<string, string[]> = {};
protected groupsMimeInfo = {}; // Object to hold extensions and mimetypes that belong to a certain "group" (audio, video, ...). protected groupsMimeInfo: Record<string, MimeTypeGroupInfo> = {};
protected extensionRegex = /^[a-z0-9]+$/;
constructor() { constructor() {
this.logger = CoreLogger.getInstance('CoreMimetypeUtilsProvider'); this.logger = CoreLogger.getInstance('CoreMimetypeUtilsProvider');
Http.instance.get('assets/exttomime.json').subscribe((result) => { Http.instance.get('assets/exttomime.json').subscribe((result: Record<string, MimeTypeInfo>) => {
this.extToMime = result; this.extToMime = result;
}, (err) => { }, () => {
// Error, shouldn't happen. // Error, shouldn't happen.
}); });
Http.instance.get('assets/mimetoext.json').subscribe((result) => { Http.instance.get('assets/mimetoext.json').subscribe((result: Record<string, string[]>) => {
this.mimeToExt = result; this.mimeToExt = result;
}, (err) => { }, () => {
// Error, shouldn't happen. // Error, shouldn't happen
}); });
} }
@ -148,31 +167,38 @@ export class CoreMimetypeUtilsProvider {
* Set the embed type to display an embedded file and mimetype if not found. * Set the embed type to display an embedded file and mimetype if not found.
* *
* @param file File object. * @param file File object.
* @paran path Alternative path that will override fileurl from file object. * @param path Alternative path that will override fileurl from file object.
*/ */
getEmbeddedHtml(file: any, path?: string): string { getEmbeddedHtml(file: CoreWSExternalFile | FileEntry, path?: string): string {
let ext; const filename = CoreUtils.instance.isFileEntry(file) ? (file as FileEntry).name : file.filename;
const filename = file.filename || file.name; const extension = !CoreUtils.instance.isFileEntry(file) && file.mimetype
? this.getExtension(file.mimetype)
: this.getFileExtension(filename);
const mimeType = !CoreUtils.instance.isFileEntry(file) && file.mimetype ? file.mimetype : this.getMimeType(extension);
if (file.mimetype) { // @todo linting: See if this can be removed
ext = this.getExtension(file.mimetype); (file as CoreWSExternalFile).mimetype = mimeType;
} else {
ext = this.getFileExtension(filename);
file.mimetype = this.getMimeType(ext);
}
if (this.canBeEmbedded(ext)) { if (this.canBeEmbedded(extension)) {
file.embedType = this.getExtensionType(ext); const embedType = this.getExtensionType(extension);
path = CoreFile.instance.convertFileSrc(path || file.fileurl || file.url || (file.toURL && file.toURL())); // @todo linting: See if this can be removed
(file as { embedType: string }).embedType = embedType;
if (file.embedType == 'image') { path = CoreFile.instance.convertFileSrc(path ?? (CoreUtils.instance.isFileEntry(file) ? file.toURL() : file.fileurl));
return '<img src="' + path + '">';
} switch (embedType) {
if (file.embedType == 'audio' || file.embedType == 'video') { case 'image':
return '<' + file.embedType + ' controls title="' + filename + '" src="' + path + '">' + return `<img src="${path}">`;
'<source src="' + path + '" type="' + file.mimetype + '">' + case 'audio':
'</' + file.embedType + '>'; case 'video':
return [
`<${embedType} controls title="${filename}" src="${path}">`,
`<source src="${path}" type="${mimeType}">`,
`</${embedType}>`,
].join('');
default:
return '';
} }
} }
@ -290,7 +316,7 @@ export class CoreMimetypeUtilsProvider {
candidate = candidate.substr(0, position); candidate = candidate.substr(0, position);
} }
if (this.extensionRegex.test(candidate)) { if (EXTENSION_REGEX.test(candidate)) {
extension = candidate; extension = candidate;
} }
} }
@ -338,7 +364,7 @@ export class CoreMimetypeUtilsProvider {
* @param field The field to get. If not supplied, all the info will be returned. * @param field The field to get. If not supplied, all the info will be returned.
* @return Info for the group. * @return Info for the group.
*/ */
getGroupMimeInfo(group: string, field?: string): any { getGroupMimeInfo(group: string, field?: string): MimeTypeGroupInfo {
if (typeof this.groupsMimeInfo[group] == 'undefined') { if (typeof this.groupsMimeInfo[group] == 'undefined') {
this.fillGroupMimeInfo(group); this.fillGroupMimeInfo(group);
} }
@ -372,13 +398,13 @@ export class CoreMimetypeUtilsProvider {
* @param capitalise If true, capitalises first character of result. * @param capitalise If true, capitalises first character of result.
* @return Type description. * @return Type description.
*/ */
getMimetypeDescription(obj: any, capitalise?: boolean): string { getMimetypeDescription(obj: FileEntry | { filename: string; mimetype: string } | string, capitalise?: boolean): string {
const langPrefix = 'assets.mimetypes.'; const langPrefix = 'assets.mimetypes.';
let filename = ''; let filename = '';
let mimetype = ''; let mimetype = '';
let extension = ''; let extension = '';
if (typeof obj == 'object' && typeof obj.file == 'function') { if (typeof obj == 'object' && CoreUtils.instance.isFileEntry(obj)) {
// It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable. // It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable.
filename = obj.name; filename = obj.name;
} else if (typeof obj == 'object') { } else if (typeof obj == 'object') {
@ -548,6 +574,7 @@ export class CoreMimetypeUtilsProvider {
return path; return path;
} }
} }
export class CoreMimetypeUtils extends makeSingleton(CoreMimetypeUtilsProvider) {} export class CoreMimetypeUtils extends makeSingleton(CoreMimetypeUtilsProvider) {}

View File

@ -18,6 +18,8 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { CoreApp } from '@services/app'; import { CoreApp } from '@services/app';
import { CoreLang } from '@services/lang'; import { CoreLang } from '@services/lang';
import { makeSingleton, Translate } from '@singletons/core.singletons'; import { makeSingleton, Translate } from '@singletons/core.singletons';
import { CoreWSExternalFile } from '@services/ws';
import { Locutus } from '@singletons/locutus';
/** /**
* Different type of errors the app can treat. * Different type of errors the app can treat.
@ -36,7 +38,7 @@ export type CoreTextErrorObject = {
export class CoreTextUtilsProvider { export class CoreTextUtilsProvider {
// List of regular expressions to convert the old nomenclature to new nomenclature for disabled features. // List of regular expressions to convert the old nomenclature to new nomenclature for disabled features.
protected DISABLED_FEATURES_COMPAT_REGEXPS = [ protected readonly DISABLED_FEATURES_COMPAT_REGEXPS: { old: RegExp; new: string }[] = [
{ old: /\$mmLoginEmailSignup/g, new: 'CoreLoginEmailSignup' }, { old: /\$mmLoginEmailSignup/g, new: 'CoreLoginEmailSignup' },
{ old: /\$mmSideMenuDelegate/g, new: 'CoreMainMenuDelegate' }, { old: /\$mmSideMenuDelegate/g, new: 'CoreMainMenuDelegate' },
{ old: /\$mmCoursesDelegate/g, new: 'CoreCourseOptionsDelegate' }, { old: /\$mmCoursesDelegate/g, new: 'CoreCourseOptionsDelegate' },
@ -82,7 +84,7 @@ export class CoreTextUtilsProvider {
{ old: /remoteAddOn_/g, new: 'sitePlugin_' }, { old: /remoteAddOn_/g, new: 'sitePlugin_' },
]; ];
protected template = document.createElement('template'); // A template element to convert HTML to element. protected template: HTMLTemplateElement = document.createElement('template'); // A template element to convert HTML to element.
constructor(private sanitizer: DomSanitizer) { } constructor(private sanitizer: DomSanitizer) { }
@ -200,7 +202,6 @@ export class CoreTextUtilsProvider {
* @return Size in human readable format. * @return Size in human readable format.
*/ */
bytesToSize(bytes: number, precision: number = 2): string { bytesToSize(bytes: number, precision: number = 2): string {
if (typeof bytes == 'undefined' || bytes === null || bytes < 0) { if (typeof bytes == 'undefined' || bytes === null || bytes < 0) {
return Translate.instance.instant('core.notapplicable'); return Translate.instance.instant('core.notapplicable');
} }
@ -449,9 +450,17 @@ export class CoreTextUtilsProvider {
* @param courseId Course ID the text belongs to. It can be used to improve performance with filters. * @param courseId Course ID the text belongs to. It can be used to improve performance with filters.
* @deprecated since 3.8.3. Please use viewText instead. * @deprecated since 3.8.3. Please use viewText instead.
*/ */
expandText(title: string, text: string, component?: string, componentId?: string | number, files?: any[], expandText(
filter?: boolean, contextLevel?: string, instanceId?: number, courseId?: number): void { title: string,
text: string,
component?: string,
componentId?: string | number,
files?: CoreWSExternalFile[],
filter?: boolean,
contextLevel?: string,
instanceId?: number,
courseId?: number,
): void {
return this.viewText(title, text, { return this.viewText(title, text, {
component, component,
componentId, componentId,
@ -531,12 +540,12 @@ export class CoreTextUtilsProvider {
* @param files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute. * @param files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute.
* @return Pluginfile URL, undefined if no files found. * @return Pluginfile URL, undefined if no files found.
*/ */
getTextPluginfileUrl(files: any[]): string { getTextPluginfileUrl(files: CoreWSExternalFile[]): string {
if (files && files.length) { if (files && files.length) {
const fileURL = files[0].url || files[0].fileurl; const url = files[0].fileurl;
// Remove text after last slash (encoded or not). // Remove text after last slash (encoded or not).
return fileURL.substr(0, Math.max(fileURL.lastIndexOf('/'), fileURL.lastIndexOf('%2F'))); return url.substr(0, Math.max(url.lastIndexOf('/'), url.lastIndexOf('%2F')));
} }
return undefined; return undefined;
@ -610,13 +619,17 @@ export class CoreTextUtilsProvider {
* @param data Object to be checked. * @param data Object to be checked.
* @return If the data has any long Unicode char on it. * @return If the data has any long Unicode char on it.
*/ */
hasUnicodeData(data: object): boolean { hasUnicodeData(data: Record<string, unknown>): boolean {
for (const el in data) { for (const el in data) {
if (typeof data[el] == 'object') { if (typeof data[el] == 'object') {
if (this.hasUnicodeData(data[el])) { if (this.hasUnicodeData(data[el] as Record<string, unknown>)) {
return true; return true;
} }
} else if (typeof data[el] == 'string' && this.hasUnicode(data[el])) {
continue;
}
if (typeof data[el] == 'string' && this.hasUnicode(data[el] as string)) {
return true; return true;
} }
} }
@ -632,13 +645,13 @@ export class CoreTextUtilsProvider {
* @param logErrorFn An error to call with the exception to log the error. If not supplied, no error. * @param logErrorFn An error to call with the exception to log the error. If not supplied, no error.
* @return JSON parsed as object or what it gets. * @return JSON parsed as object or what it gets.
*/ */
parseJSON(json: string, defaultValue?: any, logErrorFn?: (error?: any) => void): any { parseJSON<T>(json: string, defaultValue?: T, logErrorFn?: (error?: Error) => void): T | string {
try { try {
return JSON.parse(json); return JSON.parse(json);
} catch (ex) { } catch (error) {
// Error, log the error if needed. // Error, log the error if needed.
if (logErrorFn) { if (logErrorFn) {
logErrorFn(ex); logErrorFn(error);
} }
} }
@ -675,7 +688,7 @@ export class CoreTextUtilsProvider {
return ''; return '';
} }
return text.replace(/[#:\/\?\\]+/g, '_'); return text.replace(/[#:/?\\]+/g, '_');
} }
/** /**
@ -700,7 +713,7 @@ export class CoreTextUtilsProvider {
* @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. * @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute.
* @return Treated text. * @return Treated text.
*/ */
replacePluginfileUrls(text: string, files: any[]): string { replacePluginfileUrls(text: string, files: CoreWSExternalFile[]): string {
if (text && typeof text == 'string') { if (text && typeof text == 'string') {
const fileURL = this.getTextPluginfileUrl(files); const fileURL = this.getTextPluginfileUrl(files);
if (fileURL) { if (fileURL) {
@ -718,7 +731,7 @@ export class CoreTextUtilsProvider {
* @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. * @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute.
* @return Treated text. * @return Treated text.
*/ */
restorePluginfileUrls(text: string, files: any[]): string { restorePluginfileUrls(text: string, files: CoreWSExternalFile[]): string {
if (text && typeof text == 'string') { if (text && typeof text == 'string') {
const fileURL = this.getTextPluginfileUrl(files); const fileURL = this.getTextPluginfileUrl(files);
if (fileURL) { if (fileURL) {
@ -804,7 +817,6 @@ export class CoreTextUtilsProvider {
/** /**
* Replace text within a portion of a string. Equivalent to PHP's substr_replace. * Replace text within a portion of a string. Equivalent to PHP's substr_replace.
* Credits to http://locutus.io/php/strings/substr_replace/
* *
* @param str The string to treat. * @param str The string to treat.
* @param replace The value to put inside the string. * @param replace The value to put inside the string.
@ -814,22 +826,7 @@ export class CoreTextUtilsProvider {
* @return Treated string. * @return Treated string.
*/ */
substrReplace(str: string, replace: string, start: number, length?: number): string { substrReplace(str: string, replace: string, start: number, length?: number): string {
length = typeof length != 'undefined' ? length : str.length; return Locutus.substrReplace(str, replace, start, length);
if (start < 0) {
start = start + str.length;
}
if (length < 0) {
length = length + str.length - start;
}
return [
str.slice(0, start),
replace.substr(0, length),
replace.slice(length),
str.slice(start + length)
].join('');
} }
/** /**
@ -867,14 +864,14 @@ export class CoreTextUtilsProvider {
return CoreLang.instance.getCurrentLanguage().then((language) => { return CoreLang.instance.getCurrentLanguage().then((language) => {
// Match the current language. // Match the current language.
const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g; const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g;
let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)</(?:lang|span)>', 'g');
if (!text.match(currentLangRegEx)) { if (!text.match(currentLangRegEx)) {
// Current lang not found. Try to find the first language. // Current lang not found. Try to find the first language.
const matches = text.match(anyLangRegEx); const matches = text.match(anyLangRegEx);
if (matches && matches[0]) { if (matches && matches[0]) {
language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1]; language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1];
currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)</(?:lang|span)>', 'g');
} else { } else {
// No multi-lang tag found, stop. // No multi-lang tag found, stop.
return text; return text;
@ -915,221 +912,12 @@ export class CoreTextUtilsProvider {
/** /**
* Unserialize Array from PHP. * Unserialize Array from PHP.
* Taken from: https://github.com/kvz/locutus/blob/master/src/php/var/unserialize.js
* *
* @param data String to unserialize. * @param data String to unserialize.
* @param logErrorFn An error to call with the exception to log the error. If not supplied, no error.
* @return Unserialized data. * @return Unserialized data.
*/ */
unserialize(data: string, logErrorFn?: (error?: string) => void): any { unserialize<T = unknown>(data: string): T {
// Discuss at: http://locutus.io/php/unserialize/ return Locutus.unserialize<T>(data);
// Original by: Arpad Ray (mailto:arpad@php.net)
// Improved by: Pedro Tainha (http://www.pedrotainha.com)
// Improved by: Kevin van Zonneveld (http://kvz.io)
// Improved by: Kevin van Zonneveld (http://kvz.io)
// Improved by: Chris
// Improved by: James
// Improved by: Le Torbi
// Improved by: Eli Skeggs
// Bugfixed by: dptr1988
// Bugfixed by: Kevin van Zonneveld (http://kvz.io)
// Bugfixed by: Brett Zamir (http://brett-zamir.me)
// Bugfixed by: philippsimon (https://github.com/philippsimon/)
// Revised by: d3x
// Input by: Brett Zamir (http://brett-zamir.me)
// Input by: Martin (http://www.erlenwiese.de/)
// Input by: kilops
// Input by: Jaroslaw Czarniak
// Input by: lovasoa (https://github.com/lovasoa/)
// Note 1: We feel the main purpose of this function should be
// Note 1: to ease the transport of data between php & js
// Note 1: Aiming for PHP-compatibility, we have to translate objects to arrays
// Example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}')
// Returns 1: ['Kevin', 'van', 'Zonneveld']
// Example 2: unserialize('a:2:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";}')
// Returns 2: {firstName: 'Kevin', midName: 'van'}
// Example 3: unserialize('a:3:{s:2:"ü";s:2:"ü";s:3:"四";s:3:"四";s:4:"𠜎";s:4:"𠜎";}')
// Returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'}
const utf8Overhead = (str: string): number => {
let s = str.length;
for (let i = str.length - 1; i >= 0; i--) {
const code = str.charCodeAt(i);
if (code > 0x7f && code <= 0x7ff) {
s++;
} else if (code > 0x7ff && code <= 0xffff) {
s += 2;
}
// Trail surrogate.
if (code >= 0xDC00 && code <= 0xDFFF) {
i--;
}
}
return s - 1;
};
const error = (type: string, msg: string): void => {
if (logErrorFn) {
logErrorFn(type + msg);
}
};
const readUntil = (data: string, offset: number, stopchr: string): Array<any> => {
let i = 2;
const buf = [];
let chr = data.slice(offset, offset + 1);
while (chr !== stopchr) {
if ((i + offset) > data.length) {
error('Error', 'Invalid');
}
buf.push(chr);
chr = data.slice(offset + (i - 1), offset + i);
i += 1;
}
return [buf.length, buf.join('')];
};
const readChrs = (data: string, offset: number, length: number): Array<any> => {
let chr;
const buf = [];
for (let i = 0; i < length; i++) {
chr = data.slice(offset + (i - 1), offset + i);
buf.push(chr);
length -= utf8Overhead(chr);
}
return [buf.length, buf.join('')];
};
const _unserialize = (data: string, offset: number): any => {
let dtype,
dataoffset,
keyandchrs,
keys,
contig,
length,
array,
readdata,
readData,
ccount,
stringlength,
i,
key,
kprops,
kchrs,
vprops,
vchrs,
value,
chrs = 0,
typeconvert = (x: any): any => {
return x;
};
if (!offset) {
offset = 0;
}
dtype = (data.slice(offset, offset + 1)).toLowerCase();
dataoffset = offset + 2;
switch (dtype) {
case 'i':
typeconvert = (x: any): number => {
return parseInt(x, 10);
};
readData = readUntil(data, dataoffset, ';');
chrs = readData[0];
readdata = readData[1];
dataoffset += chrs + 1;
break;
case 'b':
typeconvert = (x: any): boolean => {
return parseInt(x, 10) !== 0;
};
readData = readUntil(data, dataoffset, ';');
chrs = readData[0];
readdata = readData[1];
dataoffset += chrs + 1;
break;
case 'd':
typeconvert = (x: any): number => {
return parseFloat(x);
};
readData = readUntil(data, dataoffset, ';');
chrs = readData[0];
readdata = readData[1];
dataoffset += chrs + 1;
break;
case 'n':
readdata = null;
break;
case 's':
ccount = readUntil(data, dataoffset, ':');
chrs = ccount[0];
stringlength = ccount[1];
dataoffset += chrs + 2;
readData = readChrs(data, dataoffset + 1, parseInt(stringlength, 10));
chrs = readData[0];
readdata = readData[1];
dataoffset += chrs + 2;
if (chrs !== parseInt(stringlength, 10) && chrs !== readdata.length) {
error('SyntaxError', 'String length mismatch');
}
break;
case 'a':
readdata = {};
keyandchrs = readUntil(data, dataoffset, ':');
chrs = keyandchrs[0];
keys = keyandchrs[1];
dataoffset += chrs + 2;
length = parseInt(keys, 10);
contig = true;
for (let i = 0; i < length; i++) {
kprops = _unserialize(data, dataoffset);
kchrs = kprops[1];
key = kprops[2];
dataoffset += kchrs;
vprops = _unserialize(data, dataoffset);
vchrs = vprops[1];
value = vprops[2];
dataoffset += vchrs;
if (key !== i) {
contig = false;
}
readdata[key] = value;
}
if (contig) {
array = new Array(length);
for (i = 0; i < length; i++) {
array[i] = readdata[i];
}
readdata = array;
}
dataoffset += 1;
break;
default:
error('SyntaxError', 'Unknown / Unhandled data type(s): ' + dtype);
break;
}
return [dtype, dataoffset - offset, typeconvert(readdata)];
};
return _unserialize((data + ''), 0)[2];
} }
/** /**
@ -1138,16 +926,13 @@ export class CoreTextUtilsProvider {
* @param title Title of the new state. * @param title Title of the new state.
* @param text Content of the text to be expanded. * @param text Content of the text to be expanded.
* @param component Component to link the embedded files to. * @param component Component to link the embedded files to.
* @param componentId An ID to use in conjunction with the component. * @param options Options.
* @param files List of files to display along with the text.
* @param filter Whether the text should be filtered.
* @param contextLevel The context level.
* @param instanceId The instance ID related to the context.
* @param courseId Course ID the text belongs to. It can be used to improve performance with filters.
*/ */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
viewText(title: string, text: string, options?: CoreTextUtilsViewTextOptions): void { viewText(title: string, text: string, options?: CoreTextUtilsViewTextOptions): void {
// @todo // @todo
} }
} }
/** /**
@ -1156,7 +941,7 @@ export class CoreTextUtilsProvider {
export type CoreTextUtilsViewTextOptions = { export type CoreTextUtilsViewTextOptions = {
component?: string; // Component to link the embedded files to. component?: string; // Component to link the embedded files to.
componentId?: string | number; // An ID to use in conjunction with the component. componentId?: string | number; // An ID to use in conjunction with the component.
files?: any[]; // List of files to display along with the text. files?: CoreWSExternalFile[]; // List of files to display along with the text.
filter?: boolean; // Whether the text should be filtered. filter?: boolean; // Whether the text should be filtered.
contextLevel?: string; // The context level. contextLevel?: string; // The context level.
instanceId?: number; // The instance ID related to the context. instanceId?: number; // The instance ID related to the context.

View File

@ -13,44 +13,43 @@
// limitations under the License. // limitations under the License.
import { Injectable, NgZone } from '@angular/core'; import { Injectable, NgZone } from '@angular/core';
import { InAppBrowserObject } from '@ionic-native/in-app-browser'; import { InAppBrowserObject, InAppBrowserOptions } from '@ionic-native/in-app-browser';
import { FileEntry } from '@ionic-native/file';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CoreApp } from '@services/app'; import { CoreApp } from '@services/app';
import { CoreEvents, CoreEventsProvider } from '@services/events'; import { CoreEvents, CoreEventsProvider } from '@services/events';
import { CoreFile } from '@services/file'; import { CoreFile } from '@services/file';
import { CoreLang } from '@services/lang'; import { CoreLang } from '@services/lang';
import { CoreWS, CoreWSError } from '@services/ws'; import { CoreWS, CoreWSError, CoreWSExternalFile } from '@services/ws';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { import {
makeSingleton, Clipboard, InAppBrowser, Platform, FileOpener, WebIntent, QRScanner, Translate makeSingleton, Clipboard, InAppBrowser, Platform, FileOpener, WebIntent, QRScanner, Translate,
} from '@singletons/core.singletons'; } from '@singletons/core.singletons';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
type TreeNode<T> = T & { children: TreeNode<T>[] };
/* /*
* "Utils" service with helper functions. * "Utils" service with helper functions.
*/ */
@Injectable() @Injectable()
export class CoreUtilsProvider { export class CoreUtilsProvider {
protected DONT_CLONE = ['[object FileEntry]', '[object DirectoryEntry]', '[object DOMFileSystem]'];
protected readonly DONT_CLONE = ['[object FileEntry]', '[object DirectoryEntry]', '[object DOMFileSystem]'];
protected logger: CoreLogger; protected logger: CoreLogger;
protected iabInstance: InAppBrowserObject; protected iabInstance: InAppBrowserObject;
protected uniqueIds: {[name: string]: number} = {}; protected uniqueIds: {[name: string]: number} = {};
protected qrScanData: {deferred: PromiseDefer<any>, observable: Subscription}; protected qrScanData: {deferred: PromiseDefer<string>; observable: Subscription};
constructor(protected zone: NgZone) { constructor(protected zone: NgZone) {
this.logger = CoreLogger.getInstance('CoreUtilsProvider'); this.logger = CoreLogger.getInstance('CoreUtilsProvider');
Platform.instance.ready().then(() => { // eslint-disable-next-line promise/catch-or-return
const win = <any> window; Platform.instance.ready().then(() => this.overrideWindowOpen());
if (win.cordova && win.cordova.InAppBrowser) {
// Override the default window.open with the InAppBrowser one.
win.open = win.cordova.InAppBrowser.open;
}
});
} }
/** /**
@ -60,7 +59,7 @@ export class CoreUtilsProvider {
* @param defaultError Message to show if the error is not a string. * @param defaultError Message to show if the error is not a string.
* @return New error message. * @return New error message.
*/ */
addDataNotDownloadedError(error: any, defaultError?: string): string { addDataNotDownloadedError(error: Error | string, defaultError?: string): string {
let errorMessage = error; let errorMessage = error;
if (error && typeof error != 'string') { if (error && typeof error != 'string') {
@ -85,35 +84,25 @@ export class CoreUtilsProvider {
* @param promises Promises. * @param promises Promises.
* @return Promise resolved if all promises are resolved and rejected if at least 1 promise fails. * @return Promise resolved if all promises are resolved and rejected if at least 1 promise fails.
*/ */
allPromises(promises: Promise<unknown>[]): Promise<void> { async allPromises(promises: Promise<unknown>[]): Promise<void> {
if (!promises || !promises.length) { if (!promises || !promises.length) {
return Promise.resolve(); return Promise.resolve();
} }
return new Promise((resolve, reject): void => { const getPromiseError = async (promise): Promise<Error | void> => {
const total = promises.length; try {
let count = 0; await promise;
let hasFailed = false; } catch (error) {
let error; return error;
promises.forEach((promise) => {
promise.catch((err) => {
hasFailed = true;
error = err;
}).finally(() => {
count++;
if (count === total) {
// All promises have finished, reject/resolve.
if (hasFailed) {
reject(error);
} else {
resolve();
} }
};
const errors = await Promise.all(promises.map(getPromiseError));
const error = errors.find(error => !!error);
if (error) {
throw error;
} }
});
});
});
} }
/** /**
@ -126,7 +115,7 @@ export class CoreUtilsProvider {
* @param result Object where to put the properties. If not defined, a new object will be created. * @param result Object where to put the properties. If not defined, a new object will be created.
* @return The object. * @return The object.
*/ */
arrayToObject(array: any[], propertyName?: string, result?: any): any { arrayToObject(array: unknown[], propertyName?: string, result?: unknown): unknown {
result = result || {}; result = result || {};
array.forEach((entry) => { array.forEach((entry) => {
const key = propertyName ? entry[propertyName] : entry; const key = propertyName ? entry[propertyName] : entry;
@ -148,7 +137,13 @@ export class CoreUtilsProvider {
* @param undefinedIsNull True if undefined is equal to null. Defaults to true. * @param undefinedIsNull True if undefined is equal to null. Defaults to true.
* @return Whether both items are equal. * @return Whether both items are equal.
*/ */
basicLeftCompare(itemA: any, itemB: any, maxLevels: number = 0, level: number = 0, undefinedIsNull: boolean = true): boolean { basicLeftCompare(
itemA: any, // eslint-disable-line @typescript-eslint/no-explicit-any
itemB: any, // eslint-disable-line @typescript-eslint/no-explicit-any
maxLevels: number = 0,
level: number = 0,
undefinedIsNull: boolean = true,
): boolean {
if (typeof itemA == 'function' || typeof itemB == 'function') { if (typeof itemA == 'function' || typeof itemB == 'function') {
return true; // Don't compare functions. return true; // Don't compare functions.
} else if (typeof itemA == 'object' && typeof itemB == 'object') { } else if (typeof itemA == 'object' && typeof itemB == 'object') {
@ -190,6 +185,7 @@ export class CoreUtilsProvider {
/** /**
* Blocks leaving a view. * Blocks leaving a view.
*
* @deprecated, use ionViewCanLeave instead. * @deprecated, use ionViewCanLeave instead.
*/ */
blockLeaveView(): void { blockLeaveView(): void {
@ -202,24 +198,26 @@ export class CoreUtilsProvider {
* @param url The URL to check. * @param url The URL to check.
* @return Promise resolved with boolean_ whether there is a redirect. * @return Promise resolved with boolean_ whether there is a redirect.
*/ */
checkRedirect(url: string): Promise<boolean> { async checkRedirect(url: string): Promise<boolean> {
if (window.fetch) { if (!window.fetch) {
const win = <any> window; // Convert to <any> to be able to use AbortController (not supported by our TS version). // Cannot check if there is a redirect, assume it's false.
const initOptions: any = { return false;
redirect: 'follow', }
};
let controller; const initOptions: RequestInit = { redirect: 'follow' };
// Some browsers implement fetch but no AbortController. // Some browsers implement fetch but no AbortController.
if (win.AbortController) { const controller = AbortController ? new AbortController() : false;
controller = new win.AbortController();
if (controller) {
initOptions.signal = controller.signal; initOptions.signal = controller.signal;
} }
return this.timeoutPromise(window.fetch(url, initOptions), CoreWS.instance.getRequestTimeout()) try {
.then((response: Response) => { const response = await this.timeoutPromise(window.fetch(url, initOptions), CoreWS.instance.getRequestTimeout());
return response.redirected; return response.redirected;
}).catch((error) => { } catch (error) {
if (error.timeout && controller) { if (error.timeout && controller) {
// Timeout, abort the request. // Timeout, abort the request.
controller.abort(); controller.abort();
@ -227,10 +225,6 @@ export class CoreUtilsProvider {
// There was a timeout, cannot determine if there's a redirect. Assume it's false. // There was a timeout, cannot determine if there's a redirect. Assume it's false.
return false; return false;
});
} else {
// Cannot check if there is a redirect, assume it's false.
return Promise.resolve(false);
} }
} }
@ -255,7 +249,7 @@ export class CoreUtilsProvider {
* @param level Depth we are right now inside a cloned object. It's used to prevent reaching max call stack size. * @param level Depth we are right now inside a cloned object. It's used to prevent reaching max call stack size.
* @return Cloned variable. * @return Cloned variable.
*/ */
clone(source: any, level: number = 0): any { clone<T>(source: T, level: number = 0): T {
if (level >= 20) { if (level >= 20) {
// Max 20 levels. // Max 20 levels.
this.logger.error('Max depth reached when cloning object.', source); this.logger.error('Max depth reached when cloning object.', source);
@ -265,7 +259,7 @@ export class CoreUtilsProvider {
if (Array.isArray(source)) { if (Array.isArray(source)) {
// Clone the array and all the entries. // Clone the array and all the entries.
const newArray = []; const newArray = [] as unknown as T;
for (let i = 0; i < source.length; i++) { for (let i = 0; i < source.length; i++) {
newArray[i] = this.clone(source[i], level + 1); newArray[i] = this.clone(source[i], level + 1);
} }
@ -279,7 +273,7 @@ export class CoreUtilsProvider {
} }
// Clone the object and all the subproperties. // Clone the object and all the subproperties.
const newObject = {}; const newObject = {} as T;
for (const name in source) { for (const name in source) {
newObject[name] = this.clone(source[name], level + 1); newObject[name] = this.clone(source[name], level + 1);
} }
@ -298,7 +292,7 @@ export class CoreUtilsProvider {
* @param to Object where to store the properties. * @param to Object where to store the properties.
* @param clone Whether the properties should be cloned (so they are different instances). * @param clone Whether the properties should be cloned (so they are different instances).
*/ */
copyProperties(from: any, to: any, clone: boolean = true): void { copyProperties(from: Record<string, unknown>, to: Record<string, unknown>, clone: boolean = true): void {
for (const name in from) { for (const name in from) {
if (clone) { if (clone) {
to[name] = this.clone(from[name]); to[name] = this.clone(from[name]);
@ -314,13 +308,15 @@ export class CoreUtilsProvider {
* @param text Text to be copied * @param text Text to be copied
* @return Promise resolved when text is copied. * @return Promise resolved when text is copied.
*/ */
copyToClipboard(text: string): Promise<any> { async copyToClipboard(text: string): Promise<void> {
return Clipboard.instance.copy(text).then(() => { try {
await Clipboard.instance.copy(text);
// Show toast using ionicLoading. // Show toast using ionicLoading.
return CoreDomUtils.instance.showToast('core.copiedtoclipboard', true); CoreDomUtils.instance.showToast('core.copiedtoclipboard', true);
}).catch(() => { } catch {
// Ignore errors. // Ignore errors.
}); }
} }
/** /**
@ -339,7 +335,7 @@ export class CoreUtilsProvider {
* *
* @param array Array to empty. * @param array Array to empty.
*/ */
emptyArray(array: any[]): void { emptyArray(array: unknown[]): void {
array.length = 0; // Empty array without losing its reference. array.length = 0; // Empty array without losing its reference.
} }
@ -348,9 +344,9 @@ export class CoreUtilsProvider {
* *
* @param object Object to remove the properties. * @param object Object to remove the properties.
*/ */
emptyObject(object: object): void { emptyObject(object: Record<string, unknown>): void {
for (const key in object) { for (const key in object) {
if (object.hasOwnProperty(key)) { if (Object.prototype.hasOwnProperty.call(object, key)) {
delete object[key]; delete object[key];
} }
} }
@ -373,10 +369,8 @@ export class CoreUtilsProvider {
// Execute all the processes in order. // Execute all the processes in order.
for (const i in orderedPromisesData) { for (const i in orderedPromisesData) {
const data = orderedPromisesData[i]; const data = orderedPromisesData[i];
let promise;
// Add the process to the dependency stack. // Add the process to the dependency stack.
promise = dependency.finally(() => { const promise = dependency.finally(() => {
try { try {
return data.function(); return data.function();
} catch (e) { } catch (e) {
@ -406,19 +400,19 @@ export class CoreUtilsProvider {
* @param useDotNotation Whether to use dot notation '.' or square brackets '['. * @param useDotNotation Whether to use dot notation '.' or square brackets '['.
* @return Flattened object. * @return Flattened object.
*/ */
flattenObject(obj: object, useDotNotation?: boolean): object { flattenObject(obj: Record<string, unknown>, useDotNotation?: boolean): Record<string, unknown> {
const toReturn = {}; const toReturn = {};
for (const name in obj) { for (const name in obj) {
if (!obj.hasOwnProperty(name)) { if (!Object.prototype.hasOwnProperty.call(obj, name)) {
continue; continue;
} }
const value = obj[name]; const value = obj[name];
if (typeof value == 'object' && !Array.isArray(value)) { if (typeof value == 'object' && !Array.isArray(value)) {
const flatObject = this.flattenObject(value); const flatObject = this.flattenObject(value as Record<string, unknown>);
for (const subName in flatObject) { for (const subName in flatObject) {
if (!flatObject.hasOwnProperty(subName)) { if (!Object.prototype.hasOwnProperty.call(flatObject, subName)) {
continue; continue;
} }
@ -463,19 +457,24 @@ export class CoreUtilsProvider {
* @param ...args All the params sent after checkAll will be passed to isEnabledFn. * @param ...args All the params sent after checkAll will be passed to isEnabledFn.
* @return Promise resolved with the list of enabled sites. * @return Promise resolved with the list of enabled sites.
*/ */
filterEnabledSites(siteIds: string[], isEnabledFn: (siteId, ...args: any[]) => boolean | Promise<boolean>, checkAll?: boolean, filterEnabledSites<P extends unknown[]>(
...args: any[]): Promise<string[]> { siteIds: string[],
isEnabledFn: (siteId, ...args: P) => boolean | Promise<boolean>,
checkAll?: boolean,
...args: P
): Promise<string[]> {
const promises = []; const promises = [];
const enabledSites = []; const enabledSites = [];
for (const i in siteIds) { for (const i in siteIds) {
const siteId = siteIds[i]; const siteId = siteIds[i];
const pushIfEnabled = enabled => enabled && enabledSites.push(siteId);
if (checkAll || !promises.length) { if (checkAll || !promises.length) {
promises.push(Promise.resolve(isEnabledFn.apply(isEnabledFn, [siteId].concat(args))).then((enabled) => { promises.push(
if (enabled) { Promise
enabledSites.push(siteId); .resolve(isEnabledFn(siteId, ...args))
} .then(pushIfEnabled),
})); );
} }
} }
@ -498,7 +497,7 @@ export class CoreUtilsProvider {
* @param float The float to print. * @param float The float to print.
* @return Locale float. * @return Locale float.
*/ */
formatFloat(float: any): string { formatFloat(float: unknown): string {
if (typeof float == 'undefined' || float === null || typeof float == 'boolean') { if (typeof float == 'undefined' || float === null || typeof float == 'boolean') {
return ''; return '';
} }
@ -506,9 +505,9 @@ export class CoreUtilsProvider {
const localeSeparator = Translate.instance.instant('core.decsep'); const localeSeparator = Translate.instance.instant('core.decsep');
// Convert float to string. // Convert float to string.
float += ''; const floatString = float + '';
return float.replace('.', localeSeparator); return floatString.replace('.', localeSeparator);
} }
/** /**
@ -523,17 +522,20 @@ export class CoreUtilsProvider {
* @param maxDepth Max Depth to convert to tree. Children found will be in the last level of depth. * @param maxDepth Max Depth to convert to tree. Children found will be in the last level of depth.
* @return Array with the formatted tree, children will be on each node under children field. * @return Array with the formatted tree, children will be on each node under children field.
*/ */
formatTree(list: any[], parentFieldName: string = 'parent', idFieldName: string = 'id', rootParentId: number = 0, formatTree<T>(
maxDepth: number = 5): any[] { list: T[],
parentFieldName: string = 'parent',
idFieldName: string = 'id',
rootParentId: number = 0,
maxDepth: number = 5,
): TreeNode<T>[] {
const map = {}; const map = {};
const mapDepth = {}; const mapDepth = {};
const tree = []; const tree: TreeNode<T>[] = [];
let parent;
let id;
list.forEach((node, index): void => { list.forEach((node: TreeNode<T>, index): void => {
id = node[idFieldName]; const id = node[idFieldName];
parent = node[parentFieldName]; const parent = node[parentFieldName];
node.children = []; node.children = [];
if (!id || !parent) { if (!id || !parent) {
@ -543,7 +545,7 @@ export class CoreUtilsProvider {
// Use map to look-up the parents. // Use map to look-up the parents.
map[id] = index; map[id] = index;
if (parent != rootParentId) { if (parent != rootParentId) {
const parentNode = list[map[parent]]; const parentNode = list[map[parent]] as TreeNode<T>;
if (parentNode) { if (parentNode) {
if (mapDepth[parent] == maxDepth) { if (mapDepth[parent] == maxDepth) {
// Reached max level of depth. Proceed with flat order. Find parent object of the current node. // Reached max level of depth. Proceed with flat order. Find parent object of the current node.
@ -551,11 +553,11 @@ export class CoreUtilsProvider {
if (parentOfParent) { if (parentOfParent) {
// This element will be the child of the node that is two levels up the hierarchy // This element will be the child of the node that is two levels up the hierarchy
// (i.e. the child of node.parent.parent). // (i.e. the child of node.parent.parent).
list[map[parentOfParent]].children.push(node); (list[map[parentOfParent]] as TreeNode<T>).children.push(node);
// Assign depth level to the same depth as the parent (i.e. max depth level). // Assign depth level to the same depth as the parent (i.e. max depth level).
mapDepth[id] = mapDepth[parent]; mapDepth[id] = mapDepth[parent];
// Change the parent to be the one that is two levels up the hierarchy. // Change the parent to be the one that is two levels up the hierarchy.
node.parent = parentOfParent; node[parentFieldName] = parentOfParent;
} else { } else {
this.logger.error(`Node parent of parent:${parentOfParent} not found on formatTree`); this.logger.error(`Node parent of parent:${parentOfParent} not found on formatTree`);
} }
@ -596,7 +598,7 @@ export class CoreUtilsProvider {
* *
* @return Promise resolved with the list of countries. * @return Promise resolved with the list of countries.
*/ */
getCountryList(): Promise<any> { getCountryList(): Promise<Record<string, string>> {
// Get the keys of the countries. // Get the keys of the countries.
return this.getCountryKeysList().then((keys) => { return this.getCountryKeysList().then((keys) => {
// Now get the code and the translated name. // Now get the code and the translated name.
@ -618,7 +620,7 @@ export class CoreUtilsProvider {
* *
* @return Promise resolved with the list of countries. * @return Promise resolved with the list of countries.
*/ */
getCountryListSorted(): Promise<any[]> { getCountryListSorted(): Promise<{ code: string; name: string }[]> {
// Get the keys of the countries. // Get the keys of the countries.
return this.getCountryList().then((countries) => { return this.getCountryList().then((countries) => {
// Sort translations. // Sort translations.
@ -647,7 +649,7 @@ export class CoreUtilsProvider {
if (fallbackLang === defaultLang) { if (fallbackLang === defaultLang) {
// Same language, just reject. // Same language, just reject.
return Promise.reject('Countries not found.'); throw new Error('Countries not found.');
} }
return this.getCountryKeysListForLanguage(fallbackLang); return this.getCountryKeysListForLanguage(fallbackLang);
@ -660,9 +662,10 @@ export class CoreUtilsProvider {
* @param lang Language to check. * @param lang Language to check.
* @return Promise resolved with the countries list. Rejected if not translated. * @return Promise resolved with the countries list. Rejected if not translated.
*/ */
protected getCountryKeysListForLanguage(lang: string): Promise<string[]> { protected async getCountryKeysListForLanguage(lang: string): Promise<string[]> {
// Get the translation table for the language. // Get the translation table for the language.
return CoreLang.instance.getTranslationTable(lang).then((table): any => { const table = await CoreLang.instance.getTranslationTable(lang);
// Gather all the keys for countries, // Gather all the keys for countries,
const keys = []; const keys = [];
@ -674,11 +677,10 @@ export class CoreUtilsProvider {
if (keys.length === 0) { if (keys.length === 0) {
// Not translated, reject. // Not translated, reject.
return Promise.reject('Countries not found.'); throw new Error('Countries not found.');
} }
return keys; return keys;
});
} }
/** /**
@ -699,9 +701,7 @@ export class CoreUtilsProvider {
} }
// Can't be guessed, get the remote mimetype. // Can't be guessed, get the remote mimetype.
return CoreWS.instance.getRemoteFileMimeType(url).then((mimetype) => { return CoreWS.instance.getRemoteFileMimeType(url).then(mimetype => mimetype || '');
return mimetype || '';
});
} }
/** /**
@ -718,13 +718,23 @@ export class CoreUtilsProvider {
return ++this.uniqueIds[name]; return ++this.uniqueIds[name];
} }
/**
* Check if a file is a FileEntry
*
* @param file File.
* @return Type guard indicating if the file is a FileEntry.
*/
isFileEntry(file: FileEntry | CoreWSExternalFile): file is FileEntry {
return 'isFile' in file;
}
/** /**
* Given a list of files, check if there are repeated names. * Given a list of files, check if there are repeated names.
* *
* @param files List of files. * @param files List of files.
* @return String with error message if repeated, false if no repeated. * @return String with error message if repeated, false if no repeated.
*/ */
hasRepeatedFilenames(files: any[]): string | boolean { hasRepeatedFilenames(files: (FileEntry | CoreWSExternalFile)[]): string | false {
if (!files || !files.length) { if (!files || !files.length) {
return false; return false;
} }
@ -733,12 +743,14 @@ export class CoreUtilsProvider {
// Check if there are 2 files with the same name. // Check if there are 2 files with the same name.
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const name = files[i].filename || files[i].name; const file = files[i];
const name = this.isFileEntry(file) ? file.name : file.filename;
if (names.indexOf(name) > -1) { if (names.indexOf(name) > -1) {
return Translate.instance.instant('core.filenameexist', { $a: name }); return Translate.instance.instant('core.filenameexist', { $a: name });
} else {
names.push(name);
} }
names.push(name);
} }
return false; return false;
@ -774,6 +786,7 @@ export class CoreUtilsProvider {
* @param value Value to check. * @param value Value to check.
* @return Whether the value is false, 0 or "0". * @return Whether the value is false, 0 or "0".
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isFalseOrZero(value: any): boolean { isFalseOrZero(value: any): boolean {
return typeof value != 'undefined' && (value === false || value === 'false' || parseInt(value, 10) === 0); return typeof value != 'undefined' && (value === false || value === 'false' || parseInt(value, 10) === 0);
} }
@ -784,6 +797,7 @@ export class CoreUtilsProvider {
* @param value Value to check. * @param value Value to check.
* @return Whether the value is true, 1 or "1". * @return Whether the value is true, 1 or "1".
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isTrueOrOne(value: any): boolean { isTrueOrOne(value: any): boolean {
return typeof value != 'undefined' && (value === true || value === 'true' || parseInt(value, 10) === 1); return typeof value != 'undefined' && (value === true || value === 'true' || parseInt(value, 10) === 1);
} }
@ -794,6 +808,7 @@ export class CoreUtilsProvider {
* @param error Error to check. * @param error Error to check.
* @return Whether the error was returned by the WebService. * @return Whether the error was returned by the WebService.
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isWebServiceError(error: any): boolean { isWebServiceError(error: any): boolean {
return error && (typeof error.warningcode != 'undefined' || (typeof error.errorcode != 'undefined' && return error && (typeof error.warningcode != 'undefined' || (typeof error.errorcode != 'undefined' &&
error.errorcode != 'invalidtoken' && error.errorcode != 'userdeleted' && error.errorcode != 'upgraderunning' && error.errorcode != 'invalidtoken' && error.errorcode != 'userdeleted' && error.errorcode != 'upgraderunning' &&
@ -812,19 +827,22 @@ export class CoreUtilsProvider {
* @param defaultValue Element that will become default option value. Default 0. * @param defaultValue Element that will become default option value. Default 0.
* @return The now assembled array * @return The now assembled array
*/ */
makeMenuFromList(list: string, defaultLabel?: string, separator: string = ',', defaultValue?: any): any[] { makeMenuFromList<T>(
list: string,
defaultLabel?: string,
separator: string = ',',
defaultValue?: T,
): { label: string; value: T | number }[] {
// Split and format the list. // Split and format the list.
const split = list.split(separator).map((label, index) => { const split = list.split(separator).map((label, index) => ({
return {
label: label.trim(), label: label.trim(),
value: index + 1 value: index + 1,
}; })) as { label: string; value: T | number }[];
});
if (defaultLabel) { if (defaultLabel) {
split.unshift({ split.unshift({
label: defaultLabel, label: defaultLabel,
value: defaultValue || 0 value: defaultValue || 0,
}); });
} }
@ -839,8 +857,8 @@ export class CoreUtilsProvider {
* @param [key] Key of the property that must be unique. If not specified, the whole entry. * @param [key] Key of the property that must be unique. If not specified, the whole entry.
* @return Merged array. * @return Merged array.
*/ */
mergeArraysWithoutDuplicates(array1: any[], array2: any[], key?: string): any[] { mergeArraysWithoutDuplicates<T>(array1: T[], array2: T[], key?: string): T[] {
return this.uniqueArray(array1.concat(array2), key); return this.uniqueArray(array1.concat(array2), key) as T[];
} }
/** /**
@ -849,6 +867,7 @@ export class CoreUtilsProvider {
* @param value Value to check. * @param value Value to check.
* @return True if not null and not undefined. * @return True if not null and not undefined.
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
notNullOrUndefined(value: any): boolean { notNullOrUndefined(value: any): boolean {
return typeof value != 'undefined' && value !== null; return typeof value != 'undefined' && value !== null;
} }
@ -888,12 +907,10 @@ export class CoreUtilsProvider {
if (!extension || extension.indexOf('/') > -1 || extension.indexOf('\\') > -1) { if (!extension || extension.indexOf('/') > -1 || extension.indexOf('\\') > -1) {
// Extension not found. // Extension not found.
error = Translate.instance.instant('core.erroropenfilenoextension'); throw new Error(Translate.instance.instant('core.erroropenfilenoextension'));
} else {
error = Translate.instance.instant('core.erroropenfilenoapp');
} }
throw error; throw new Error(Translate.instance.instant('core.erroropenfilenoapp'));
} }
} }
@ -905,7 +922,7 @@ export class CoreUtilsProvider {
* @param options Override default options passed to InAppBrowser. * @param options Override default options passed to InAppBrowser.
* @return The opened window. * @return The opened window.
*/ */
openInApp(url: string, options?: any): InAppBrowserObject { openInApp(url: string, options?: InAppBrowserOptions): InAppBrowserObject {
if (!url) { if (!url) {
return; return;
} }
@ -995,36 +1012,32 @@ export class CoreUtilsProvider {
* @param url The URL of the file. * @param url The URL of the file.
* @return Promise resolved when opened. * @return Promise resolved when opened.
*/ */
openOnlineFile(url: string): Promise<void> { async openOnlineFile(url: string): Promise<void> {
if (CoreApp.instance.isAndroid()) { if (CoreApp.instance.isAndroid()) {
// In Android we need the mimetype to open it. // In Android we need the mimetype to open it.
return this.getMimeTypeFromUrl(url).catch(() => { const mimetype = await this.ignoreErrors(this.getMimeTypeFromUrl(url));
// Error getting mimetype, return undefined.
}).then((mimetype) => {
if (!mimetype) { if (!mimetype) {
// Couldn't retrieve mimetype. Return error. // Couldn't retrieve mimetype. Return error.
return Promise.reject(Translate.instance.instant('core.erroropenfilenoextension')); throw new Error(Translate.instance.instant('core.erroropenfilenoextension'));
} }
const options = { const options = {
action: WebIntent.instance.ACTION_VIEW, action: WebIntent.instance.ACTION_VIEW,
url, url,
type: mimetype type: mimetype,
}; };
return WebIntent.instance.startActivity(options).catch((error) => { return WebIntent.instance.startActivity(options).catch((error) => {
this.logger.error('Error opening online file ' + url + ' with mimetype ' + mimetype); this.logger.error('Error opening online file ' + url + ' with mimetype ' + mimetype);
this.logger.error('Error: ', JSON.stringify(error)); this.logger.error('Error: ', JSON.stringify(error));
return Promise.reject(Translate.instance.instant('core.erroropenfilenoapp')); throw new Error(Translate.instance.instant('core.erroropenfilenoapp'));
});
}); });
} }
// In the rest of platforms we need to open them in InAppBrowser. // In the rest of platforms we need to open them in InAppBrowser.
this.openInApp(url); this.openInApp(url);
return Promise.resolve();
} }
/** /**
@ -1033,10 +1046,8 @@ export class CoreUtilsProvider {
* @param obj Object to convert. * @param obj Object to convert.
* @return Array with the values of the object but losing the keys. * @return Array with the values of the object but losing the keys.
*/ */
objectToArray(obj: object): any[] { objectToArray<T>(obj: Record<string, T>): T[] {
return Object.keys(obj).map((key) => { return Object.keys(obj).map((key) => obj[key]);
return obj[key];
});
} }
/** /**
@ -1051,9 +1062,15 @@ export class CoreUtilsProvider {
* @param sortByValue True to sort values alphabetically, false otherwise. * @param sortByValue True to sort values alphabetically, false otherwise.
* @return Array of objects with the name & value of each property. * @return Array of objects with the name & value of each property.
*/ */
objectToArrayOfObjects(obj: object, keyName: string, valueName: string, sortByKey?: boolean, sortByValue?: boolean): any[] { objectToArrayOfObjects(
obj: Record<string, unknown>,
keyName: string,
valueName: string,
sortByKey?: boolean,
sortByValue?: boolean,
): Record<string, unknown>[] {
// Get the entries from an object or primitive value. // Get the entries from an object or primitive value.
const getEntries = (elKey, value): any[] | any => { const getEntries = (elKey: string, value: unknown): Record<string, unknown>[] | unknown => {
if (typeof value == 'undefined' || value == null) { if (typeof value == 'undefined' || value == null) {
// Filter undefined and null values. // Filter undefined and null values.
return; return;
@ -1087,7 +1104,7 @@ export class CoreUtilsProvider {
} }
// "obj" will always be an object, so "entries" will always be an array. // "obj" will always be an object, so "entries" will always be an array.
const entries = <any[]> getEntries('', obj); const entries = getEntries('', obj) as Record<string, unknown>[];
if (sortByKey || sortByValue) { if (sortByKey || sortByValue) {
return entries.sort((a, b) => { return entries.sort((a, b) => {
if (sortByKey) { if (sortByKey) {
@ -1111,7 +1128,12 @@ export class CoreUtilsProvider {
* @param keyPrefix Key prefix if neededs to delete it. * @param keyPrefix Key prefix if neededs to delete it.
* @return Object. * @return Object.
*/ */
objectToKeyValueMap(objects: object[], keyName: string, valueName: string, keyPrefix?: string): {[name: string]: any} { objectToKeyValueMap(
objects: Record<string, unknown>[],
keyName: string,
valueName: string,
keyPrefix?: string,
): {[name: string]: unknown} {
if (!objects) { if (!objects) {
return; return;
} }
@ -1119,7 +1141,8 @@ export class CoreUtilsProvider {
const prefixSubstr = keyPrefix ? keyPrefix.length : 0; const prefixSubstr = keyPrefix ? keyPrefix.length : 0;
const mapped = {}; const mapped = {};
objects.forEach((item) => { objects.forEach((item) => {
const key = prefixSubstr > 0 ? item[keyName].substr(prefixSubstr) : item[keyName]; const keyValue = item[keyName] as string;
const key = prefixSubstr > 0 ? keyValue.substr(prefixSubstr) : keyValue;
mapped[key] = item[valueName]; mapped[key] = item[valueName];
}); });
@ -1133,7 +1156,7 @@ export class CoreUtilsProvider {
* @param removeEmpty Whether to remove params whose value is null/undefined. * @param removeEmpty Whether to remove params whose value is null/undefined.
* @return GET params. * @return GET params.
*/ */
objectToGetParams(object: any, removeEmpty: boolean = true): string { objectToGetParams(object: Record<string, unknown>, removeEmpty: boolean = true): string {
// First of all, flatten the object so all properties are in the first level. // First of all, flatten the object so all properties are in the first level.
const flattened = this.flattenObject(object); const flattened = this.flattenObject(object);
let result = ''; let result = '';
@ -1164,7 +1187,7 @@ export class CoreUtilsProvider {
* @param prefix Prefix to add. * @param prefix Prefix to add.
* @return Prefixed object. * @return Prefixed object.
*/ */
prefixKeys(data: any, prefix: string): any { prefixKeys(data: Record<string, unknown>, prefix: string): Record<string, unknown> {
const newObj = {}; const newObj = {};
const keys = Object.keys(data); const keys = Object.keys(data);
@ -1196,12 +1219,14 @@ export class CoreUtilsProvider {
* @param promise Promise to check * @param promise Promise to check
* @return Promise resolved with boolean: true if the promise is rejected or false if it's resolved. * @return Promise resolved with boolean: true if the promise is rejected or false if it's resolved.
*/ */
promiseFails(promise: Promise<any>): Promise<boolean> { async promiseFails(promise: Promise<unknown>): Promise<boolean> {
return promise.then(() => { try {
await promise;
return false; return false;
}).catch(() => { } catch {
return true; return true;
}); }
} }
/** /**
@ -1210,12 +1235,14 @@ export class CoreUtilsProvider {
* @param promise Promise to check * @param promise Promise to check
* @return Promise resolved with boolean: true if the promise it's resolved or false if it's rejected. * @return Promise resolved with boolean: true if the promise it's resolved or false if it's rejected.
*/ */
promiseWorks(promise: Promise<any>): Promise<boolean> { async promiseWorks(promise: Promise<unknown>): Promise<boolean> {
return promise.then(() => { try {
await promise;
return true; return true;
}).catch(() => { } catch {
return false; return false;
}); }
} }
/** /**
@ -1228,7 +1255,7 @@ export class CoreUtilsProvider {
* @param key Key to check. * @param key Key to check.
* @return Whether the two objects/arrays have the same value (or lack of one) for a given key. * @return Whether the two objects/arrays have the same value (or lack of one) for a given key.
*/ */
sameAtKeyMissingIsBlank(obj1: any, obj2: any, key: string): boolean { sameAtKeyMissingIsBlank(obj1: unknown, obj2: unknown, key: string): boolean {
let value1 = typeof obj1[key] != 'undefined' ? obj1[key] : ''; let value1 = typeof obj1[key] != 'undefined' ? obj1[key] : '';
let value2 = typeof obj2[key] != 'undefined' ? obj2[key] : ''; let value2 = typeof obj2[key] != 'undefined' ? obj2[key] : '';
@ -1249,7 +1276,7 @@ export class CoreUtilsProvider {
* @param obj Object to stringify. * @param obj Object to stringify.
* @return Stringified object. * @return Stringified object.
*/ */
sortAndStringify(obj: object): string { sortAndStringify(obj: Record<string, unknown>): string {
return JSON.stringify(this.sortProperties(obj)); return JSON.stringify(this.sortProperties(obj));
} }
@ -1259,7 +1286,7 @@ export class CoreUtilsProvider {
* @param obj The object to sort. If it isn't an object, the original value will be returned. * @param obj The object to sort. If it isn't an object, the original value will be returned.
* @return Sorted object. * @return Sorted object.
*/ */
sortProperties(obj: object): object { sortProperties<T>(obj: T): T {
if (obj != null && typeof obj == 'object' && !Array.isArray(obj)) { if (obj != null && typeof obj == 'object' && !Array.isArray(obj)) {
// It's an object, sort it. // It's an object, sort it.
return Object.keys(obj).sort().reduce((accumulator, key) => { return Object.keys(obj).sort().reduce((accumulator, key) => {
@ -1267,7 +1294,7 @@ export class CoreUtilsProvider {
accumulator[key] = this.sortProperties(obj[key]); accumulator[key] = this.sortProperties(obj[key]);
return accumulator; return accumulator;
}, {}); }, {} as T);
} else { } else {
return obj; return obj;
} }
@ -1279,12 +1306,12 @@ export class CoreUtilsProvider {
* @param obj The object to sort. If it isn't an object, the original value will be returned. * @param obj The object to sort. If it isn't an object, the original value will be returned.
* @return Sorted object. * @return Sorted object.
*/ */
sortValues(obj: object): object { sortValues<T>(obj: T): T {
if (typeof obj == 'object' && !Array.isArray(obj)) { if (typeof obj == 'object' && !Array.isArray(obj)) {
// It's an object, sort it. Convert it to an array to be able to sort it and then convert it back to object. // It's an object, sort it. Convert it to an array to be able to sort it and then convert it back to object.
const array = this.objectToArrayOfObjects(obj, 'name', 'value', false, true); const array = this.objectToArrayOfObjects(obj as Record<string, unknown>, 'name', 'value', false, true);
return this.objectToKeyValueMap(array, 'name', 'value'); return this.objectToKeyValueMap(array, 'name', 'value') as unknown as T;
} else { } else {
return obj; return obj;
} }
@ -1297,10 +1324,10 @@ export class CoreUtilsProvider {
* @return File size and a boolean to indicate if it is the total size or only partial. * @return File size and a boolean to indicate if it is the total size or only partial.
* @deprecated since 3.8.0. Use CorePluginFileDelegate.getFilesSize instead. * @deprecated since 3.8.0. Use CorePluginFileDelegate.getFilesSize instead.
*/ */
sumFileSizes(files: any[]): { size: number, total: boolean } { sumFileSizes(files: CoreWSExternalFile[]): { size: number; total: boolean } {
const result = { const result = {
size: 0, size: 0,
total: true total: true,
}; };
files.forEach((file) => { files.forEach((file) => {
@ -1325,13 +1352,25 @@ export class CoreUtilsProvider {
*/ */
timeoutPromise<T>(promise: Promise<T>, time: number): Promise<T> { timeoutPromise<T>(promise: Promise<T>, time: number): Promise<T> {
return new Promise((resolve, reject): void => { return new Promise((resolve, reject): void => {
const timeout = setTimeout(() => { let timedOut = false;
const resolveBeforeTimeout = () => {
if (timedOut) {
return;
}
resolve();
};
const timeout = setTimeout(
() => {
reject({ timeout: true }); reject({ timeout: true });
}, time); timedOut = true;
},
time,
);
promise.then(resolve).catch(reject).finally(() => { promise
clearTimeout(timeout); .then(resolveBeforeTimeout)
}); .catch(reject)
.finally(() => clearTimeout(timeout));
}); });
} }
@ -1344,7 +1383,8 @@ export class CoreUtilsProvider {
* @param strict If true, then check the input and return false if it is not a valid number. * @param strict If true, then check the input and return false if it is not a valid number.
* @return False if bad format, empty string if empty value or the parsed float if not. * @return False if bad format, empty string if empty value or the parsed float if not.
*/ */
unformatFloat(localeFloat: any, strict?: boolean): any { // eslint-disable-next-line @typescript-eslint/no-explicit-any
unformatFloat(localeFloat: any, strict?: boolean): false | '' | number {
// Bad format on input type number. // Bad format on input type number.
if (typeof localeFloat == 'undefined') { if (typeof localeFloat == 'undefined') {
return false; return false;
@ -1383,7 +1423,7 @@ export class CoreUtilsProvider {
* @param [key] Key of the property that must be unique. If not specified, the whole entry. * @param [key] Key of the property that must be unique. If not specified, the whole entry.
* @return Array without duplicate values. * @return Array without duplicate values.
*/ */
uniqueArray(array: any[], key?: string): any[] { uniqueArray<T>(array: T[], key?: string): T[] {
const filtered = []; const filtered = [];
const unique = {}; // Use an object to make it faster to check if it's duplicate. const unique = {}; // Use an object to make it faster to check if it's duplicate.
@ -1407,16 +1447,13 @@ export class CoreUtilsProvider {
* @param delay Time that must pass until the function is called. * @param delay Time that must pass until the function is called.
* @return Debounced function. * @return Debounced function.
*/ */
debounce(fn: (...args: any[]) => any, delay: number): (...args: any[]) => void { debounce<T extends unknown[]>(fn: (...args: T) => unknown, delay: number): (...args: T) => void {
let timeoutID: number; let timeoutID: number;
const debounced = (...args: any[]): void => { const debounced = (...args: unknown[]): void => {
clearTimeout(timeoutID); clearTimeout(timeoutID);
timeoutID = window.setTimeout(() => { timeoutID = window.setTimeout(() => fn.apply(null, args), delay);
fn.apply(null, args);
}, delay);
}; };
return debounced; return debounced;
@ -1448,18 +1485,19 @@ export class CoreUtilsProvider {
* *
* @return Promise resolved with the QR string, rejected if error or cancelled. * @return Promise resolved with the QR string, rejected if error or cancelled.
*/ */
startScanQR(): Promise<string> { async startScanQR(): Promise<string> {
if (!CoreApp.instance.isMobile()) { if (!CoreApp.instance.isMobile()) {
return Promise.reject('QRScanner isn\'t available in desktop apps.'); return Promise.reject('QRScanner isn\'t available in desktop apps.');
} }
// Ask the user for permission to use the camera. // Ask the user for permission to use the camera.
// The scan method also does this, but since it returns an Observable we wouldn't be able to detect if the user denied. // The scan method also does this, but since it returns an Observable we wouldn't be able to detect if the user denied.
return QRScanner.instance.prepare().then((status) => { try {
const status = await QRScanner.instance.prepare();
if (!status.authorized) { if (!status.authorized) {
// No access to the camera, reject. In android this shouldn't happen, denying access passes through catch. // No access to the camera, reject. In android this shouldn't happen, denying access passes through catch.
return Promise.reject('The user denied camera access.'); throw new Error('The user denied camera access.');
} }
if (this.qrScanData && this.qrScanData.deferred) { if (this.qrScanData && this.qrScanData.deferred) {
@ -1470,29 +1508,29 @@ export class CoreUtilsProvider {
// Start scanning. // Start scanning.
this.qrScanData = { this.qrScanData = {
deferred: this.promiseDefer(), deferred: this.promiseDefer(),
observable: QRScanner.instance.scan().subscribe((text) => {
// Text received, stop scanning and return the text. // When text is received, stop scanning and return the text.
this.stopScanQR(text, false); observable: QRScanner.instance.scan().subscribe(text => this.stopScanQR(text, false)),
})
}; };
// Show the camera. // Show the camera.
return QRScanner.instance.show().then(() => { try {
await QRScanner.instance.show();
document.body.classList.add('core-scanning-qr'); document.body.classList.add('core-scanning-qr');
return this.qrScanData.deferred.promise; return this.qrScanData.deferred.promise;
}, (err) => { } catch (e) {
this.stopScanQR(err, true); this.stopScanQR(e, true);
return Promise.reject(err); throw e;
}); }
} catch (error) {
// eslint-disable-next-line no-underscore-dangle, @typescript-eslint/naming-convention
error.message = error.message || (error as { _message?: string })._message;
}).catch((err) => { throw error;
err.message = err.message || err._message; }
return Promise.reject(err);
});
} }
/** /**
@ -1501,8 +1539,7 @@ export class CoreUtilsProvider {
* @param data If success, the text of the QR code. If error, the error object or message. Undefined for cancelled. * @param data If success, the text of the QR code. If error, the error object or message. Undefined for cancelled.
* @param error True if the data belongs to an error, false otherwise. * @param error True if the data belongs to an error, false otherwise.
*/ */
stopScanQR(data?: any, error?: boolean): void { stopScanQR(data?: string | Error, error?: boolean): void {
if (!this.qrScanData) { if (!this.qrScanData) {
// Not scanning. // Not scanning.
return; return;
@ -1518,7 +1555,7 @@ export class CoreUtilsProvider {
if (error) { if (error) {
this.qrScanData.deferred.reject(data); this.qrScanData.deferred.reject(data);
} else if (typeof data != 'undefined') { } else if (typeof data != 'undefined') {
this.qrScanData.deferred.resolve(data); this.qrScanData.deferred.resolve(data as string);
} else { } else {
this.qrScanData.deferred.reject(CoreDomUtils.instance.createCanceledError()); this.qrScanData.deferred.reject(CoreDomUtils.instance.createCanceledError());
} }
@ -1549,10 +1586,20 @@ export class CoreUtilsProvider {
* @return Promise resolved after the time has passed. * @return Promise resolved after the time has passed.
*/ */
wait(milliseconds: number): Promise<void> { wait(milliseconds: number): Promise<void> {
return new Promise((resolve, reject): void => { return new Promise(resolve => setTimeout(resolve, milliseconds));
setTimeout(resolve, milliseconds);
});
} }
/**
* Override native window.open with InAppBrowser if available.
*/
private overrideWindowOpen() {
if (!window.cordova?.InAppBrowser) {
return;
}
window.open = window.cordova.InAppBrowser.open;
}
} }
export class CoreUtils extends makeSingleton(CoreUtilsProvider) {} export class CoreUtils extends makeSingleton(CoreUtilsProvider) {}

View File

@ -0,0 +1,447 @@
/* eslint-disable */
/**
* Original code taken from https://github.com/kvz/locutus
*/
function initCache () {
const store = []
// cache only first element, second is length to jump ahead for the parser
const cache = function cache (value) {
store.push(value[0])
return value
}
cache.get = (index) => {
if (index >= store.length) {
throw RangeError(`Can't resolve reference ${index + 1}`)
}
return store[index]
}
return cache
}
function expectType (str, cache) {
const types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g
const type = (types.exec(str) || [])[0]
if (!type) {
throw SyntaxError('Invalid input: ' + str)
}
switch (type) {
case 'N':
return cache([ null, 2 ])
case 'b':
return cache(expectBool(str))
case 'i':
return cache(expectInt(str))
case 'd':
return cache(expectFloat(str))
case 's':
return cache(expectString(str))
case 'S':
return cache(expectEscapedString(str))
case 'a':
return expectArray(str, cache)
case 'O':
return expectObject(str, cache)
case 'C':
return expectClass(str, cache)
case 'r':
case 'R':
return expectReference(str, cache)
default:
throw SyntaxError(`Invalid or unsupported data type: ${type}`)
}
}
function expectBool (str) {
const reBool = /^b:([01]);/
const [ match, boolMatch ] = reBool.exec(str) || []
if (!boolMatch) {
throw SyntaxError('Invalid bool value, expected 0 or 1')
}
return [ boolMatch === '1', match.length ]
}
function expectInt (str) {
const reInt = /^i:([+-]?\d+);/
const [ match, intMatch ] = reInt.exec(str) || []
if (!intMatch) {
throw SyntaxError('Expected an integer value')
}
return [ parseInt(intMatch, 10), match.length ]
}
function expectFloat (str) {
const reFloat = /^d:(NAN|-?INF|(?:\d+\.\d*|\d*\.\d+|\d+)(?:[eE][+-]\d+)?);/
const [ match, floatMatch ] = reFloat.exec(str) || []
if (!floatMatch) {
throw SyntaxError('Expected a float value')
}
let floatValue
switch (floatMatch) {
case 'NAN':
floatValue = Number.NaN
break
case '-INF':
floatValue = Number.NEGATIVE_INFINITY
break
case 'INF':
floatValue = Number.POSITIVE_INFINITY
break
default:
floatValue = parseFloat(floatMatch)
break
}
return [ floatValue, match.length ]
}
function readBytes (str, len, escapedString = false) {
let bytes = 0
let out = ''
let c = 0
const strLen = str.length
let wasHighSurrogate = false
let escapedChars = 0
while (bytes < len && c < strLen) {
let chr = str.charAt(c)
const code = chr.charCodeAt(0)
const isHighSurrogate = code >= 0xd800 && code <= 0xdbff
const isLowSurrogate = code >= 0xdc00 && code <= 0xdfff
if (escapedString && chr === '\\') {
chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 16))
escapedChars++
// each escaped sequence is 3 characters. Go 2 chars ahead.
// third character will be jumped over a few lines later
c += 2
}
c++
bytes += isHighSurrogate || (isLowSurrogate && wasHighSurrogate)
// if high surrogate, count 2 bytes, as expectation is to be followed by low surrogate
// if low surrogate preceded by high surrogate, add 2 bytes
? 2
: code > 0x7ff
// otherwise low surrogate falls into this part
? 3
: code > 0x7f
? 2
: 1
// if high surrogate is not followed by low surrogate, add 1 more byte
bytes += wasHighSurrogate && !isLowSurrogate ? 1 : 0
out += chr
wasHighSurrogate = isHighSurrogate
}
return [ out, bytes, escapedChars ]
}
function expectString (str) {
// PHP strings consist of one-byte characters.
// JS uses 2 bytes with possible surrogate pairs.
// Serialized length of 2 is still 1 JS string character
const reStrLength = /^s:(\d+):"/g // also match the opening " char
const [ match, byteLenMatch ] = reStrLength.exec(str) || []
if (!match) {
throw SyntaxError('Expected a string value')
}
const len = parseInt(byteLenMatch, 10)
str = str.substr(match.length)
let [ strMatch, bytes ] = readBytes(str, len)
if (bytes !== len) {
throw SyntaxError(`Expected string of ${len} bytes, but got ${bytes}`)
}
str = str.substr((strMatch as string).length)
// strict parsing, match closing "; chars
if (!str.startsWith('";')) {
throw SyntaxError('Expected ";')
}
return [ strMatch, match.length + (strMatch as string).length + 2 ] // skip last ";
}
function expectEscapedString (str) {
const reStrLength = /^S:(\d+):"/g // also match the opening " char
const [ match, strLenMatch ] = reStrLength.exec(str) || []
if (!match) {
throw SyntaxError('Expected an escaped string value')
}
const len = parseInt(strLenMatch, 10)
str = str.substr(match.length)
let [ strMatch, bytes, escapedChars ] = readBytes(str, len, true)
if (bytes !== len) {
throw SyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`)
}
str = str.substr((strMatch as string).length + (escapedChars as number) * 2)
// strict parsing, match closing "; chars
if (!str.startsWith('";')) {
throw SyntaxError('Expected ";')
}
return [ strMatch, match.length + (strMatch as string).length + 2 ] // skip last ";
}
function expectKeyOrIndex (str) {
try {
return expectString(str)
} catch (err) {}
try {
return expectEscapedString(str)
} catch (err) {}
try {
return expectInt(str)
} catch (err) {
throw SyntaxError('Expected key or index')
}
}
function expectObject (str, cache) {
// O:<class name length>:"class name":<prop count>:{<props and values>}
// O:8:"stdClass":2:{s:3:"foo";s:3:"bar";s:3:"bar";s:3:"baz";}
const reObjectLiteral = /^O:(\d+):"([^"]+)":(\d+):\{/
const [ objectLiteralBeginMatch, /* classNameLengthMatch */, className, propCountMatch ] = reObjectLiteral.exec(str) || []
if (!objectLiteralBeginMatch) {
throw SyntaxError('Invalid input')
}
if (className !== 'stdClass') {
throw SyntaxError(`Unsupported object type: ${className}`)
}
let totalOffset = objectLiteralBeginMatch.length
const propCount = parseInt(propCountMatch, 10)
const obj = {}
cache([obj])
str = str.substr(totalOffset)
for (let i = 0; i < propCount; i++) {
const prop = expectKeyOrIndex(str)
str = str.substr(prop[1])
totalOffset += prop[1] as number
const value = expectType(str, cache)
str = str.substr(value[1])
totalOffset += value[1]
obj[prop[0]] = value[0]
}
// strict parsing, expect } after object literal
if (str.charAt(0) !== '}') {
throw SyntaxError('Expected }')
}
return [ obj, totalOffset + 1 ] // skip final }
}
function expectClass (str, cache) {
// can't be well supported, because requires calling eval (or similar)
// in order to call serialized constructor name
// which is unsafe
// or assume that constructor is defined in global scope
// but this is too much limiting
throw Error('Not yet implemented')
}
function expectReference (str, cache) {
const reRef = /^[rR]:([1-9]\d*);/
const [ match, refIndex ] = reRef.exec(str) || []
if (!match) {
throw SyntaxError('Expected reference value')
}
return [ cache.get(parseInt(refIndex, 10) - 1), match.length ]
}
function expectArray (str, cache) {
const reArrayLength = /^a:(\d+):{/
const [ arrayLiteralBeginMatch, arrayLengthMatch ] = reArrayLength.exec(str) || []
if (!arrayLengthMatch) {
throw SyntaxError('Expected array length annotation')
}
str = str.substr(arrayLiteralBeginMatch.length)
const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache)
// strict parsing, expect closing } brace after array literal
if (str.charAt(array[1]) !== '}') {
throw SyntaxError('Expected }')
}
return [ array[0], arrayLiteralBeginMatch.length + (array[1] as number) + 1 ] // jump over }
}
function expectArrayItems (str, expectedItems = 0, cache) {
let key
let hasStringKeys = false
let item
let totalOffset = 0
let items = []
cache([items])
for (let i = 0; i < expectedItems; i++) {
key = expectKeyOrIndex(str)
// this is for backward compatibility with previous implementation
if (!hasStringKeys) {
hasStringKeys = (typeof key[0] === 'string')
}
str = str.substr(key[1])
totalOffset += key[1]
// references are resolved immediately, so if duplicate key overwrites previous array index
// the old value is anyway resolved
// fixme: but next time the same reference should point to the new value
item = expectType(str, cache)
str = str.substr(item[1])
totalOffset += item[1]
items[key[0]] = item[0]
}
// this is for backward compatibility with previous implementation
if (hasStringKeys) {
items = Object.assign({}, items)
}
return [ items, totalOffset ]
}
function unserialize (str) {
// discuss at: https://locutus.io/php/unserialize/
// original by: Arpad Ray (mailto:arpad@php.net)
// improved by: Pedro Tainha (https://www.pedrotainha.com)
// improved by: Kevin van Zonneveld (https://kvz.io)
// improved by: Kevin van Zonneveld (https://kvz.io)
// improved by: Chris
// improved by: James
// improved by: Le Torbi
// improved by: Eli Skeggs
// bugfixed by: dptr1988
// bugfixed by: Kevin van Zonneveld (https://kvz.io)
// bugfixed by: Brett Zamir (https://brett-zamir.me)
// bugfixed by: philippsimon (https://github.com/philippsimon/)
// revised by: d3x
// input by: Brett Zamir (https://brett-zamir.me)
// input by: Martin (https://www.erlenwiese.de/)
// input by: kilops
// input by: Jaroslaw Czarniak
// input by: lovasoa (https://github.com/lovasoa/)
// improved by: Rafał Kukawski
// reimplemented by: Rafał Kukawski
// note 1: We feel the main purpose of this function should be
// note 1: to ease the transport of data between php & js
// note 1: Aiming for PHP-compatibility, we have to translate objects to arrays
// example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}')
// returns 1: ['Kevin', 'van', 'Zonneveld']
// example 2: unserialize('a:2:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";}')
// returns 2: {firstName: 'Kevin', midName: 'van'}
// example 3: unserialize('a:3:{s:2:"ü";s:2:"ü";s:3:"四";s:3:"四";s:4:"𠜎";s:4:"𠜎";}')
// returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'}
// example 4: unserialize(undefined)
// returns 4: false
// example 5: unserialize('O:8:"stdClass":1:{s:3:"foo";b:1;}')
// returns 5: { foo: true }
// example 6: unserialize('a:2:{i:0;N;i:1;s:0:"";}')
// returns 6: [null, ""]
// example 7: unserialize('S:7:"\\65\\73\\63\\61\\70\\65\\64";')
// returns 7: 'escaped'
try {
if (typeof str !== 'string') {
return false
}
return expectType(str, initCache())[0]
} catch (err) {
console.error(err)
return false
}
}
function substr_replace (str, replace, start, length) { // eslint-disable-line camelcase
// discuss at: https://locutus.io/php/substr_replace/
// original by: Brett Zamir (https://brett-zamir.me)
// example 1: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 0)
// returns 1: 'bob'
// example 2: var $var = 'ABCDEFGH:/MNRPQR/'
// example 2: substr_replace($var, 'bob', 0, $var.length)
// returns 2: 'bob'
// example 3: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 0, 0)
// returns 3: 'bobABCDEFGH:/MNRPQR/'
// example 4: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 10, -1)
// returns 4: 'ABCDEFGH:/bob/'
// example 5: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', -7, -1)
// returns 5: 'ABCDEFGH:/bob/'
// example 6: substr_replace('ABCDEFGH:/MNRPQR/', '', 10, -1)
// returns 6: 'ABCDEFGH://'
if (start < 0) {
// start position in str
start = start + str.length
}
length = length !== undefined ? length : str.length
if (length < 0) {
length = length + str.length - start
}
return [
str.slice(0, start),
replace.substr(0, length),
replace.slice(length),
str.slice(start + length)
].join('')
}
export class Locutus {
static unserialize<T = unknown>(data: string): T {
return unserialize(data);
}
static substrReplace(str: string, replace: string, start: number, length?: number): string {
return substr_replace(str, replace, start, length);
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,9 @@
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/app", "outDir": "./out-tsc/app",
"types": [ "types": [
"cordova",
"cordova-plugin-file-transfer", "cordova-plugin-file-transfer",
"cordova-plugin-inappbrowser",
"cordova",
"node" "node"
], ],
"paths": { "paths": {

View File

@ -18,6 +18,9 @@
"dom" "dom"
], ],
"types": [ "types": [
"cordova-plugin-file-transfer",
"cordova-plugin-inappbrowser",
"cordova",
"jest", "jest",
"node" "node"
], ],

View File

@ -6,6 +6,9 @@
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"outDir": "./out-tsc/tests", "outDir": "./out-tsc/tests",
"types": [ "types": [
"cordova-plugin-file-transfer",
"cordova-plugin-inappbrowser",
"cordova",
"jest", "jest",
"node" "node"
], ],

View File

@ -1,144 +0,0 @@
{
"extends": "tslint:recommended",
"rules": {
"array-type": false,
"arrow-return-shorthand": true,
"curly": true,
"deprecation": {
"severity": "warning"
},
"component-class-suffix": [true, "Page", "Component"],
"contextual-lifecycle": true,
"directive-class-suffix": true,
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"eofline": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": {
"options": [
"spaces"
]
},
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-angle-bracket-type-assertion": false,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-shadowed-variable": false,
"no-switch-case-fall-through": true,
"no-unused-expression": false,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"prefer-for-of": false,
"quotemark": [
true,
"single"
],
"semicolon": {
"options": [
"always"
]
},
"space-before-function-paren": {
"options": {
"anonymous": "never",
"asyncArrow": "always",
"constructor": "never",
"method": "never",
"named": "never"
}
},
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"variable-name": {
"options": [
"ban-keywords",
"check-format",
"allow-pascal-case",
"allow-leading-underscore"
]
},
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
},
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"object-literal-sort-keys": false,
"forin": false,
"triple-equals": false
},
"rulesDirectory": [
"codelyzer"
]
}