diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..397f572 --- /dev/null +++ b/.env.test @@ -0,0 +1,4 @@ +UNIVERSAL_APP_CLIP_BASE_URL=https://test.network.com +IOS_APP_CLIP_BASE_URL=https://test.apple.com/id?p=test +ANDROID_APP_CLIP_BASE_URL=https://test.android.com +WATSON_URL=https://test.com/public/gql \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..cce6a5c --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +src/tests \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..94b4e58 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,31 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.js', 'codegen.ts', 'jest.config.js', 'rollup.config.js', 'tests', 'examples'], + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'prettier/prettier': [ + 'error', + { + "endOfLine": "auto", + } + ] + }, +}; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..dcb7279 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/examples/example.ts b/examples/example.ts index d1f90db..935be63 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -1,8 +1,14 @@ import Connect from "../src/connect"; +import { ActivityType, InputData, TraitLabel } from "../src/types"; const publicKey = "0x0297bb4f88a65b82c08fd20afb1259b7027dc996c8941e0c5917a452d538cd0da9"; const redirectURL = "https://example.com" -const services = {"NETFLIX": true} +const services: InputData = { + uber: { + traits: [TraitLabel.Plan], + activities: [ActivityType.Trip], + }, +} async function example() { const connect = new Connect({publicKey, redirectURL, services}) diff --git a/jest.config.js b/jest.config.js index 3accda7..9a6b601 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,6 +9,7 @@ module.exports = { '^.+\\.tsx?$': 'ts-jest' }, testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + setupFiles: ['/tests/setupEnv.ts'], }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index caf5665..73623aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gandalf-network/connect", - "version": "1.0.5", + "version": "1.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gandalf-network/connect", - "version": "1.0.5", + "version": "1.0.6", "license": "MIT", "dependencies": { "graphql": "^16.8.1", @@ -24,7 +24,12 @@ "@rollup/plugin-replace": "^5.0.5", "@types/babel__core": "^7.20.5", "@types/jest": "^29.5.12", + "@typescript-eslint/eslint-plugin": "^7.9.0", + "@typescript-eslint/parser": "^7.9.0", "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "rimraf": "^5.0.5", @@ -32,7 +37,7 @@ "rollup-plugin-typescript2": "^0.36.0", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", - "typescript": "^5.3.3" + "typescript": "^5.4.5" }, "engines": { "node": ">=18" @@ -2117,6 +2122,107 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@graphql-codegen/add": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-5.0.2.tgz", @@ -3108,6 +3214,39 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3700,6 +3839,18 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@repeaterjs/repeater": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.5.tgz", @@ -4275,6 +4426,221 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.9.0.tgz", + "integrity": "sha512-6e+X0X3sFe/G/54aC3jt0txuMTURqLyekmEHViqyA2VnxhLMpvA6nqmcjIy+Cr9tLDHPssA74BP5Mx9HQIxBEA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.9.0", + "@typescript-eslint/type-utils": "7.9.0", + "@typescript-eslint/utils": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.9.0.tgz", + "integrity": "sha512-qHMJfkL5qvgQB2aLvhUSXxbK7OLnDkwPzFalg458pxQgfxKDfT1ZDbHQM/I6mDIf/svlMkj21kzKuQ2ixJlatQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.9.0", + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/typescript-estree": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.9.0.tgz", + "integrity": "sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.9.0.tgz", + "integrity": "sha512-6Qy8dfut0PFrFRAZsGzuLoM4hre4gjzWJB6sUvdunCYZsYemTkzZNwF1rnGea326PHPT3zn5Lmg32M/xfJfByA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.9.0", + "@typescript-eslint/utils": "7.9.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.9.0.tgz", + "integrity": "sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.9.0.tgz", + "integrity": "sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.9.0.tgz", + "integrity": "sha512-5KVRQCzZajmT4Ep+NEgjXCvjuypVvYHUW7RHlXzNPuak2oWpVoD1jf5xCP0dPAuNIchjC7uQyvbdaSTFaLqSdA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.9.0", + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/typescript-estree": "7.9.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz", + "integrity": "sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@whatwg-node/events": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.0.3.tgz", @@ -4336,6 +4702,15 @@ "acorn-walk": "^8.0.2" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", @@ -4370,6 +4745,22 @@ "node": ">=8" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -5432,6 +5823,12 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -5536,6 +5933,18 @@ "node": ">=8" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -5691,27 +6100,323 @@ "source-map": "~0.6.1" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, - "engines": { - "node": ">=4.0" - } + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } }, "node_modules/estree-walker": { "version": "2.0.2", @@ -5820,6 +6525,18 @@ "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", "dev": true }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -5842,6 +6559,12 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, "node_modules/fast-querystring": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", @@ -5929,6 +6652,18 @@ "node": ">=0.8.0" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5995,6 +6730,41 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -6226,6 +6996,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/graphql": { "version": "16.8.1", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", @@ -6362,9 +7138,9 @@ } }, "node_modules/hasown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", - "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -6744,6 +7520,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -7673,12 +8458,24 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/json-stable-stringify": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", @@ -7697,6 +8494,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json-to-pretty-yaml": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/json-to-pretty-yaml/-/json-to-pretty-yaml-1.2.2.tgz", @@ -7752,6 +8555,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -7770,6 +8582,19 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7833,6 +8658,12 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -8246,6 +9077,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -8542,6 +9390,43 @@ "node": ">=8" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -9444,6 +10329,22 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -9458,6 +10359,12 @@ "node": ">=8" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -9539,6 +10446,18 @@ "node": ">=12" } }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-jest": { "version": "29.1.2", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", @@ -9637,6 +10556,18 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -9659,9 +10590,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -9830,6 +10761,15 @@ "tslib": "^2.0.3" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -10007,6 +10947,15 @@ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 0dcbfa3..5794aba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gandalf-network/connect", - "version": "1.0.5", + "version": "1.0.6", "description": "", "main": "dist/index.js", "module": "dist/index.mjs", @@ -54,7 +54,12 @@ "@rollup/plugin-replace": "^5.0.5", "@types/babel__core": "^7.20.5", "@types/jest": "^29.5.12", + "@typescript-eslint/eslint-plugin": "^7.9.0", + "@typescript-eslint/parser": "^7.9.0", "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "rimraf": "^5.0.5", @@ -62,6 +67,6 @@ "rollup-plugin-typescript2": "^0.36.0", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", - "typescript": "^5.3.3" + "typescript": "^5.4.5" } -} \ No newline at end of file +} diff --git a/rollup.config.js b/rollup.config.js index c60042a..fcd63c2 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -12,26 +12,34 @@ const PLUGINS = [ }), replace({ preventAssignment: true, - 'process.env.APP_CLIP_BASE_URL': JSON.stringify(process.env.APP_CLIP_BASE_URL), + 'process.env.UNIVERSAL_APP_CLIP_BASE_URL': JSON.stringify( + process.env.UNIVERSAL_APP_CLIP_BASE_URL, + ), + 'process.env.IOS_APP_CLIP_BASE_URL': JSON.stringify( + process.env.IOS_APP_CLIP_BASE_URL, + ), + 'process.env.ANDROID_APP_CLIP_BASE_URL': JSON.stringify( + process.env.ANDROID_APP_CLIP_BASE_URL, + ), 'process.env.WATSON_URL': JSON.stringify(process.env.WATSON_URL), }), ]; export default [ - { - input: 'src/index.ts', - output: [ - {file: 'dist/index.js', format: 'cjs'}, - {file: 'dist/index.mjs', format: 'es'}, - ], - plugins: PLUGINS, - }, - { - input: 'src/components.ts', - output: [ - {file: 'dist/components.js', format: 'cjs'}, - {file: 'dist/components.mjs', format: 'es'}, - ], - plugins: PLUGINS, - }, - ]; + { + input: 'src/index.ts', + output: [ + { file: 'dist/index.js', format: 'cjs' }, + { file: 'dist/index.mjs', format: 'es' }, + ], + plugins: PLUGINS, + }, + { + input: 'src/types/index.ts', + output: [ + { file: 'dist/components.js', format: 'cjs' }, + { file: 'dist/components.mjs', format: 'es' }, + ], + plugins: PLUGINS, + }, +]; diff --git a/src/api/__generated__/gql.ts b/src/api/__generated__/gql.ts index 5ff3bcf..24803f4 100644 --- a/src/api/__generated__/gql.ts +++ b/src/api/__generated__/gql.ts @@ -14,7 +14,7 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ */ const documents = { "\n query GetAppByPublicKey($publicKey: String!) {\n getAppByPublicKey(publicKey: $publicKey) {\n appName\n gandalfID\n }\n }\n": types.GetAppByPublicKeyDocument, - "\n query GetSupportedServices {\n __type(name: \"Source\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n }\n": types.GetSupportedServicesDocument, + "\n query GetSupportedServices {\n __sourceType: __type(name: \"Source\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n __traitType: __type(name: \"TraitLabel\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n __activityType: __type(name: \"ActivityType\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n }\n": types.GetSupportedServicesDocument, }; /** @@ -38,7 +38,7 @@ export function gql(source: "\n query GetAppByPublicKey($publicKey: String!) {\ /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "\n query GetSupportedServices {\n __type(name: \"Source\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n }\n"): (typeof documents)["\n query GetSupportedServices {\n __type(name: \"Source\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n }\n"]; +export function gql(source: "\n query GetSupportedServices {\n __sourceType: __type(name: \"Source\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n __traitType: __type(name: \"TraitLabel\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n __activityType: __type(name: \"ActivityType\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n }\n"): (typeof documents)["\n query GetSupportedServices {\n __sourceType: __type(name: \"Source\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n __traitType: __type(name: \"TraitLabel\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n __activityType: __type(name: \"ActivityType\") {\n name\n enumValues(includeDeprecated: false) {\n name\n }\n }\n }\n"]; export function gql(source: string) { return (documents as any)[source] ?? {}; diff --git a/src/api/__generated__/graphql.ts b/src/api/__generated__/graphql.ts index fbdb006..00f02c9 100644 --- a/src/api/__generated__/graphql.ts +++ b/src/api/__generated__/graphql.ts @@ -49,6 +49,14 @@ export type ActivityResponse = { total: Scalars['Int64']['output']; }; +export enum ActivityType { + Play = 'PLAY', + Shop = 'SHOP', + Stay = 'STAY', + Trip = 'TRIP', + Watch = 'WATCH' +} + export type AmazonActivityMetadata = ActivityMetadata & { __typename?: 'AmazonActivityMetadata'; /** Date indicating when the activity occurred. */ @@ -87,6 +95,36 @@ export type Application = { publicKey: Scalars['String']['output']; }; +export type BookingActivityMetadata = ActivityMetadata & { + __typename?: 'BookingActivityMetadata'; + /** Reference identifying a booking */ + bookingID: Scalars['String']['output']; + /** An array of bookings in a single purchase */ + bookings: Array; + /** The ticket or bill of a trip or stay */ + price: Scalars['String']['output']; + /** List of identifiers associated with the activity's subject. */ + subject?: Maybe>>; +}; + +export type BookingItem = { + __typename?: 'BookingItem'; + /** Activity type of the returned data source */ + activityType: ActivityType; + /** The location of a trip */ + address: Scalars['String']['output']; + /** The location of arrival of a booking flight */ + arrivalLocation: Scalars['String']['output']; + /** The location where a booking flight takes off from */ + depatureLocation: Scalars['String']['output']; + /** CheckOut time of a booking */ + endDateTime: Scalars['Time']['output']; + /** An string listing the stops of a flight between its depature and arrival locations */ + layoverLocations: Array>; + /** CheckIn time of a booking */ + startDateTime: Scalars['Time']['output']; +}; + export enum ContentType { Music = 'MUSIC', Shorts = 'SHORTS', @@ -103,20 +141,71 @@ export type Identifier = { export enum IdentifierType { Asin = 'ASIN', + Booking = 'BOOKING', Igdb = 'IGDB', Imdb = 'IMDB', + Instacart = 'INSTACART', Moby = 'MOBY', Playstation = 'PLAYSTATION', Rawg = 'RAWG', Tvdb = 'TVDB', Tvmaze = 'TVMAZE', + Uber = 'UBER', + Ubereats = 'UBEREATS', Youtube = 'YOUTUBE' } +export type InstacartActivityMetadata = ActivityMetadata & { + __typename?: 'InstacartActivityMetadata'; + /** The date the order was delivered */ + dateDelivered: Scalars['Date']['output']; + /** The date the order was placed */ + dateOrdered: Scalars['Date']['output']; + /** List of items ordered. */ + items: Array; + /** The name of the ratailer that handled the order. */ + retailer: Scalars['String']['output']; + /** String indicating the status of the order */ + statusString: Scalars['String']['output']; + /** List of identifiers associated with the activity's subject. */ + subject?: Maybe>>; + /** The total amount spent on this order. */ + totalOrderAmountSpent: Scalars['String']['output']; +}; + +export enum InstacartItemStatus { + Found = 'FOUND', + Replaced = 'REPLACED', + Torefund = 'TOREFUND' +} + +export type InstacartOrderItem = { + __typename?: 'InstacartOrderItem'; + /** The Instacart ID of the item */ + itemID: Scalars['String']['output']; + /** The name of the ordered item. */ + productName: Scalars['String']['output']; + /** The quantity purchased. */ + quantityPurchased: Scalars['Int64']['output']; + /** Enum indicating the status of the ordered item e.g found */ + status: InstacartItemStatus; + /** The price per unit e.g (1.39 • 1 gal). */ + unitPrice: Scalars['String']['output']; +}; + +export enum InstacartOrderStatus { + Complete = 'COMPLETE' +} + export type NetflixActivityMetadata = ActivityMetadata & { __typename?: 'NetflixActivityMetadata'; - /** Date indicating when the activity occurred , formatted as (DD/MM/YYYY). */ + /** + * Date indicating when the activity occurred , formatted as (DD/MM/YYYY). + * @deprecated use lastPlayedAt + */ date?: Maybe; + /** Date indicating when the activity was last played , formatted as (DD/MM/YYYY). */ + lastPlayedAt?: Maybe; /** List of identifiers associated with the activity's subject. */ subject?: Maybe>>; /** The title of the Netflix activity */ @@ -147,16 +236,29 @@ export type Query = { * Returns: An Application object that includes detailed information about the requested application. */ getAppByPublicKey: Application; + /** + * get user traits for a specific source by datakey + * + * Returns: A response object containing a list of traits. + */ + getTraits: Array>; /** * Looks up a specific activity by its unique identifier (ID) and a data key. * * Returns: An Activity object containing detailed information about the requested activity. */ lookupActivity: Activity; + /** + * Looks up a specific trait by its unique identifier (ID) and a data key. + * + * Returns: A trait object containing detailed information about the requested trait. + */ + lookupTrait: Trait; }; export type QueryGetActivityArgs = { + activityType?: InputMaybe>>; dataKey: Scalars['String']['input']; limit: Scalars['Int64']['input']; page: Scalars['Int64']['input']; @@ -169,18 +271,150 @@ export type QueryGetAppByPublicKeyArgs = { }; +export type QueryGetTraitsArgs = { + dataKey: Scalars['String']['input']; + labels: Array>; + source: Source; +}; + + export type QueryLookupActivityArgs = { activityId: Scalars['UUID']['input']; dataKey: Scalars['String']['input']; }; + +export type QueryLookupTraitArgs = { + dataKey: Scalars['String']['input']; + traitId: Scalars['UUID']['input']; +}; + export enum Source { Amazon = 'AMAZON', + Booking = 'BOOKING', + Instacart = 'INSTACART', + Instagram = 'INSTAGRAM', Netflix = 'NETFLIX', Playstation = 'PLAYSTATION', + Uber = 'UBER', + Ubereats = 'UBEREATS', + X = 'X', Youtube = 'YOUTUBE' } +/** Represents a User Trait. */ +export type Trait = { + __typename?: 'Trait'; + /** Unique identifier for the activity. */ + id: Scalars['UUID']['output']; + /** The label of the trait. */ + label: TraitLabel; + /** The source of the trait. */ + source: Source; + /** The timestamp when the trait was recorded. */ + timestamp: Scalars['Time']['output']; + /** The value of the trait. */ + value: Scalars['String']['output']; +}; + +/** Represents the labels for different user traits. */ +export enum TraitLabel { + /** Date when the user account was created. */ + AccountCreatedOn = 'ACCOUNT_CREATED_ON', + /** The number of account following the user. */ + FollowerCount = 'FOLLOWER_COUNT', + /** The number of accounts followed by the user. */ + FollowingCount = 'FOLLOWING_COUNT', + /** User's genius level. */ + GeniusLevel = 'GENIUS_LEVEL', + /** The number of orders the user has made on the platform. */ + OrderCount = 'ORDER_COUNT', + /** User plan. */ + Plan = 'PLAN', + /** The number of posts the user has made on the platform. */ + PostCount = 'POST_COUNT', + /** Indicates if the user is a prime subscriber. */ + PrimeSubscriber = 'PRIME_SUBSCRIBER', + /** User rating. */ + Rating = 'RATING', + /** Number of trips taken by the user. */ + TripCount = 'TRIP_COUNT', + /** The users profile name. */ + Username = 'USERNAME' +} + +export enum TripStatus { + Canceled = 'CANCELED', + Completed = 'COMPLETED', + Unfulfilled = 'UNFULFILLED' +} + +export type UberActivityMetadata = ActivityMetadata & { + __typename?: 'UberActivityMetadata'; + /** This indicates the start time of the trip */ + beginTripTime: Scalars['Time']['output']; + /** A string indicating the city the trip originated from */ + city: Scalars['String']['output']; + /** A string indicating the cost of the trip */ + cost: Scalars['String']['output']; + /** Distance covered from pickup to dropoff location */ + distance: Scalars['String']['output']; + /** This indicates the end time of the trip */ + dropoffTime?: Maybe; + /** Enum indicating the status of a trip */ + status: TripStatus; + /** List of identifiers associated with the activity's subject. */ + subject?: Maybe>>; +}; + +export type UberEatsActivityMetadata = ActivityMetadata & { + __typename?: 'UberEatsActivityMetadata'; + /** The currency the order was priced in. */ + currency: Scalars['String']['output']; + /** Date indicating when the order was made. */ + date?: Maybe; + /** List of items ordered. */ + items: Array; + /** The name of the restaurant that handled the order. */ + restaurant: Scalars['String']['output']; + /** Enum indicating the status of the order */ + status: UberEatsOrderStatus; + /** List of identifiers associated with the activity's subject. */ + subject?: Maybe>>; + /** The total amount spent on this order. */ + totalPrice: Scalars['Float']['output']; +}; + +export type UberEatsOrderItem = { + __typename?: 'UberEatsOrderItem'; + /** Order customizations. */ + customizations: Array>; + /** The name of the ordered item. */ + name: Scalars['String']['output']; + /** The price per unit. */ + price: Scalars['String']['output']; + /** The quantity purchased. */ + quantityPurchased: Scalars['Int64']['output']; +}; + +export type UberEatsOrderItemCustomizations = { + __typename?: 'UberEatsOrderItemCustomizations'; + /** The customization tag. */ + customization: Scalars['String']['output']; + /** The customization quantity requested. */ + quantity: Scalars['Int64']['output']; + /** The customization chosen. */ + value: Scalars['String']['output']; +}; + +export enum UberEatsOrderStatus { + EaterCancelled = 'EATER_CANCELLED', + RestaurantCancelled = 'RESTAURANT_CANCELLED', + RestaurantUnfulfilled = 'RESTAURANT_UNFULFILLED', + Success = 'SUCCESS', + Unknown = 'UNKNOWN' +} + export type YoutubeActivityMetadata = ActivityMetadata & { __typename?: 'YoutubeActivityMetadata'; /** Enum denoting the type of the youtube activity */ @@ -312,8 +546,8 @@ export type GetAppByPublicKeyQuery = { __typename?: 'Query', getAppByPublicKey: export type GetSupportedServicesQueryVariables = Exact<{ [key: string]: never; }>; -export type GetSupportedServicesQuery = { __typename?: 'Query', __type?: { __typename?: '__Type', name?: string | null, enumValues?: Array<{ __typename?: '__EnumValue', name: string }> | null } | null }; +export type GetSupportedServicesQuery = { __typename?: 'Query', __sourceType?: { __typename?: '__Type', name?: string | null, enumValues?: Array<{ __typename?: '__EnumValue', name: string }> | null } | null, __traitType?: { __typename?: '__Type', name?: string | null, enumValues?: Array<{ __typename?: '__EnumValue', name: string }> | null } | null, __activityType?: { __typename?: '__Type', name?: string | null, enumValues?: Array<{ __typename?: '__EnumValue', name: string }> | null } | null }; export const GetAppByPublicKeyDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAppByPublicKey"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"publicKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAppByPublicKey"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"publicKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"publicKey"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appName"}},{"kind":"Field","name":{"kind":"Name","value":"gandalfID"}}]}}]}}]} as unknown as DocumentNode; -export const GetSupportedServicesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSupportedServices"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__type"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"StringValue","value":"Source","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"enumValues"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"includeDeprecated"},"value":{"kind":"BooleanValue","value":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const GetSupportedServicesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSupportedServices"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"__sourceType"},"name":{"kind":"Name","value":"__type"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"StringValue","value":"Source","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"enumValues"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"includeDeprecated"},"value":{"kind":"BooleanValue","value":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","alias":{"kind":"Name","value":"__traitType"},"name":{"kind":"Name","value":"__type"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"StringValue","value":"TraitLabel","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"enumValues"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"includeDeprecated"},"value":{"kind":"BooleanValue","value":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","alias":{"kind":"Name","value":"__activityType"},"name":{"kind":"Name","value":"__type"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"StringValue","value":"ActivityType","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"enumValues"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"includeDeprecated"},"value":{"kind":"BooleanValue","value":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/src/api/gql/queries.ts b/src/api/gql/queries.ts index 9ec1a2f..bf126b5 100644 --- a/src/api/gql/queries.ts +++ b/src/api/gql/queries.ts @@ -1,4 +1,4 @@ -import { gql } from "../__generated__"; +import { gql } from '../__generated__'; export const GET_APP_BY_PUBLIC_KEY = gql(` query GetAppByPublicKey($publicKey: String!) { @@ -9,13 +9,25 @@ export const GET_APP_BY_PUBLIC_KEY = gql(` } `); -export const GET_SUPORTED_SERVICES = gql(` +export const GET_SUPORTED_SERVICES_AND_TRAITS = gql(` query GetSupportedServices { - __type(name: "Source") { + __sourceType: __type(name: "Source") { + name + enumValues(includeDeprecated: false) { + name + } + } + __traitType: __type(name: "TraitLabel") { + name + enumValues(includeDeprecated: false) { + name + } + } + __activityType: __type(name: "ActivityType") { name enumValues(includeDeprecated: false) { name } } } -`); \ No newline at end of file +`); diff --git a/src/api/publicKey.ts b/src/api/publicKey.ts index 5603fa3..95906e8 100644 --- a/src/api/publicKey.ts +++ b/src/api/publicKey.ts @@ -1,15 +1,17 @@ -import { GraphQLClient } from "graphql-request"; -import { GET_APP_BY_PUBLIC_KEY } from "./gql/queries"; -import { WATSON_URL } from "../lib/constants"; -import { GetAppByPublicKeyQueryVariables } from "./__generated__/graphql"; +import { GraphQLClient } from 'graphql-request'; +import { GET_APP_BY_PUBLIC_KEY } from './gql/queries'; +import { WATSON_URL } from '../lib/constants'; +import { GetAppByPublicKeyQueryVariables } from './__generated__/graphql'; -export const verifyPublicKey = async (variables: GetAppByPublicKeyQueryVariables): Promise => { - try { - const client = new GraphQLClient(WATSON_URL); - const data = await client.request(GET_APP_BY_PUBLIC_KEY, variables); +export const verifyPublicKey = async ( + variables: GetAppByPublicKeyQueryVariables, +): Promise => { + try { + const client = new GraphQLClient(WATSON_URL); + const data = await client.request(GET_APP_BY_PUBLIC_KEY, variables); - return data.getAppByPublicKey.gandalfID > 0 - } catch (error) { - return false - } -} \ No newline at end of file + return data.getAppByPublicKey.gandalfID > 0; + } catch (error) { + return false; + } +}; diff --git a/src/api/supportedServices.ts b/src/api/supportedServices.ts index 9aec7bd..e756a8d 100644 --- a/src/api/supportedServices.ts +++ b/src/api/supportedServices.ts @@ -1,20 +1,41 @@ -import { GraphQLClient } from "graphql-request"; -import { GET_SUPORTED_SERVICES } from "./gql/queries"; -import { WATSON_URL } from "../lib/constants"; +import { GraphQLClient } from 'graphql-request'; +import { GET_SUPORTED_SERVICES_AND_TRAITS } from './gql/queries'; +import { WATSON_URL } from '../lib/constants'; -export const getSupportedServices = async (): Promise => { - let services: string[] = [] - try { - const client = new GraphQLClient(WATSON_URL); - const data = await client.request(GET_SUPORTED_SERVICES); +export type SupportedServicesAndTraits = { + services: string[]; + traits: string[]; + activities: string[]; +}; - if (data.__type?.enumValues) { - for (let i of data.__type.enumValues) { - services = [...services, i.name.toLowerCase()] - } - } - return services - } catch (error) { - return services +export const getSupportedServicesAndTraits = async () => { + const res: SupportedServicesAndTraits = { + services: [], + traits: [], + activities: [], + }; + + try { + const client = new GraphQLClient(WATSON_URL); + const data = await client.request(GET_SUPORTED_SERVICES_AND_TRAITS); + + if (data.__sourceType?.enumValues) { + for (const i of data.__sourceType.enumValues) { + res.services = [...res.services, i.name.toLowerCase()]; + } + } + if (data.__traitType?.enumValues) { + for (const i of data.__traitType.enumValues) { + res.traits = [...res.traits, i.name.toLowerCase()]; + } + } + if (data.__activityType?.enumValues) { + for (const i of data.__activityType.enumValues) { + res.activities = [...res.activities, i.name.toLowerCase()]; + } } -} \ No newline at end of file + return res; + } catch (error) { + return res; + } +}; diff --git a/src/components.ts b/src/components.ts deleted file mode 100644 index b6ea9e8..0000000 --- a/src/components.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Services } from "./connect"; -import { GandalfErrorCode } from "./lib/errors"; - -export { Services, GandalfErrorCode } diff --git a/src/connect.ts b/src/connect.ts index bff7087..55af3f9 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -1,6 +1,24 @@ import { verifyPublicKey } from './api/publicKey'; -import { APP_CLIP_BASE_URL } from "./lib/constants"; -import { qrCodeStyle } from "./lib/qrCode-style"; +import { + ANDROID_APP_CLIP_BASE_URL, + IOS_APP_CLIP_BASE_URL, + UNIVERSAL_APP_CLIP_BASE_URL, +} from './lib/constants'; +import qrCodeStyle from './lib/qrCode-style'; + +import { Source } from './api/__generated__/graphql'; +import { + getSupportedServicesAndTraits, + SupportedServicesAndTraits, +} from './api/supportedServices'; +import GandalfError from './lib/errors'; +import { + Platform, + ConnectInput, + GandalfErrorCode, + InputData, + Service, +} from './types'; let QRCodeStyling: any; @@ -10,138 +28,242 @@ if (typeof window !== 'undefined') { }); } -import { Source } from './api/__generated__/graphql'; -import { getSupportedServices } from './api/supportedServices'; -import { GandalfError, GandalfErrorCode } from './lib/errors'; +class Connect { + publicKey: string; + redirectURL: string; + data: InputData; + platform: Platform = Platform.ios; + verificationComplete: boolean = false; -export type Services = { - [key: string]: boolean; -} + constructor(input: ConnectInput) { + if (input.redirectURL.endsWith('/')) { + input.redirectURL = input.redirectURL.slice(0, -1); + } + this.publicKey = input.publicKey; + this.redirectURL = input.redirectURL; + this.data = input.services; + this.platform = input.platform ? input.platform : Platform.ios; + } -export type ConnectInput = { - publicKey: string; - redirectURL: string; - services: Services; -} + async generateURL(): Promise { + await this.allValidations(this.publicKey, this.redirectURL, this.data); + const data = JSON.stringify(this.data); + const appClipURL = this.encodeComponents( + data, + this.redirectURL, + this.publicKey, + ); + return appClipURL; + } -class Connect { - publicKey: string; - redirectURL: string; - services: Services; - verificationComplete: boolean = false; + async generateQRCode(): Promise { + if (typeof window === 'undefined') { + throw new GandalfError( + 'QrCode generation only works in browsers', + GandalfErrorCode.QRCodeGenNotSupported, + ); + } - constructor(input: ConnectInput) { - if (input.redirectURL.endsWith('/')) { - input.redirectURL = input.redirectURL.slice(0, -1) - } - this.publicKey = input.publicKey; - this.redirectURL = input.redirectURL; - this.services = input.services; - } - - async generateURL(): Promise { - await this.allValidations(this.publicKey, this.redirectURL, this.services); - const services = JSON.stringify(this.services) - const appClipURL = this.encodeComponents(services, this.redirectURL, this.publicKey); - return appClipURL; - } - - async generateQRCode(): Promise { - if (typeof window == 'undefined') { - throw new GandalfError("QrCode generation only works in browsers", GandalfErrorCode.QRCodeGenNotSupported) - } - await this.allValidations(this.publicKey, this.redirectURL, this.services); - const services = JSON.stringify(this.services) - const appClipURL = this.encodeComponents(services, this.redirectURL, this.publicKey); - const qrCode = new QRCodeStyling(qrCodeStyle(appClipURL)); - try { - const qrCodeBlob = await qrCode.getRawData('webp') - if (!qrCodeBlob) { - throw new GandalfError("QRCode Generation Error", GandalfErrorCode.QRCodeNotGenerated) - } - const qrCodeURL = URL.createObjectURL(qrCodeBlob); - return qrCodeURL - } catch (error: any) { - throw new GandalfError(error.message, GandalfErrorCode.QRCodeNotGenerated) - } - } - - static async getSupportedServices(): Promise { - const services = await getSupportedServices(); - return services; - } - - static getDataKeyFromURL(redirectURL: string): string { - Connect.validateRedirectURL(redirectURL); - const url = new URL(redirectURL); - const dataKey = url.searchParams.get('dataKey'); - - if (!dataKey) { - throw new GandalfError(`Datakey not found in the URL ${redirectURL}`, GandalfErrorCode.DataKeyNotFound) - } - return dataKey - } - - private encodeComponents(services: string, redirectURL: string, publicKey: string): string { - const encodedServices = encodeURIComponent(services) - const encodedRedirectURL = encodeURIComponent(redirectURL) - const encodedPublicKey = encodeURIComponent(publicKey) - return `${APP_CLIP_BASE_URL}&services=${encodedServices}&redirectUrl=${encodedRedirectURL}&publicKey=${encodedPublicKey}` - } - - private async allValidations(publicKey: string, redirectURL: string, services: Services): Promise { - if (!this.verificationComplete) { - await Connect.validatePublicKey(publicKey); - Connect.validateRedirectURL(redirectURL); - const cleanServices = await Connect.validateInputServices(services); - this.services = cleanServices; - } - - this.verificationComplete = true; - } - - private static async validatePublicKey(publicKey: string): Promise { - const isValidPublicKey = await verifyPublicKey({publicKey}); - if (!isValidPublicKey) { - throw new GandalfError('Public key does not exist', GandalfErrorCode.InvalidPublicKey); - } - } - - private static async validateInputServices(input: Services): Promise { - const services = await getSupportedServices(); - const cleanServices: Services = {} - - let unsupportedServices:string[] = [] - let requiredServices = 0 - for (const key in input) { - if (!services.includes(key.toLowerCase() as Source)) { - unsupportedServices = [...unsupportedServices, key] - continue - } - if (input[key as Source]) requiredServices++ - cleanServices[key.toLocaleLowerCase()] = input[key as Source] - } - - if (unsupportedServices.length > 0) { - throw new GandalfError( - `These services ${unsupportedServices.join(' ')} are unsupported`, - GandalfErrorCode.InvalidService - ) - } - - if (requiredServices < 1) { - throw new GandalfError("At least one service has to be required", GandalfErrorCode.InvalidService) - } - return cleanServices - } - - private static validateRedirectURL(url: string): void { - try { - Boolean(new URL(url)); - } catch (e) { - throw new GandalfError('Invalid redirectURL', GandalfErrorCode.InvalidRedirectURL); - } - } + const appClipURL = await this.generateURL(); + const qrCode = new QRCodeStyling(qrCodeStyle(appClipURL)); + try { + const qrCodeBlob = await qrCode.getRawData('webp'); + if (!qrCodeBlob) { + throw new GandalfError( + 'QRCode Generation Error', + GandalfErrorCode.QRCodeNotGenerated, + ); + } + const qrCodeURL = URL.createObjectURL(qrCodeBlob); + return qrCodeURL; + } catch (error: any) { + throw new GandalfError( + error.message, + GandalfErrorCode.QRCodeNotGenerated, + ); + } + } + + static async getSupportedServicesAndTraits() { + return await getSupportedServicesAndTraits(); + } + + static getDataKeyFromURL(redirectURL: string): string { + Connect.validateRedirectURL(redirectURL); + const url = new URL(redirectURL); + const dataKey = url.searchParams.get('dataKey'); + + if (!dataKey) { + throw new GandalfError( + `Datakey not found in the URL ${redirectURL}`, + GandalfErrorCode.DataKeyNotFound, + ); + } + return dataKey; + } + + private encodeComponents( + data: string, + redirectUrl: string, + publicKey: string, + ): string { + let BASE_URL = IOS_APP_CLIP_BASE_URL; + switch (this.platform) { + case Platform.android: + BASE_URL = ANDROID_APP_CLIP_BASE_URL; + break; + case Platform.universal: + BASE_URL = UNIVERSAL_APP_CLIP_BASE_URL; + break; + } + + const base64Data = btoa(data); + const url = new URL(BASE_URL); + const params: Record = { + publicKey, + redirectUrl, + data: base64Data, + }; + + Object.keys(params).forEach((key) => + url.searchParams.append(key, params[key]), + ); + + return url.toString(); + } + + private async allValidations( + publicKey: string, + redirectURL: string, + data: InputData, + ): Promise { + if (!this.verificationComplete) { + await Connect.validatePublicKey(publicKey); + Connect.validateRedirectURL(redirectURL); + const cleanServices = await Connect.validateInputData(data); + this.data = cleanServices; + } + + this.verificationComplete = true; + } + + private static async validatePublicKey(publicKey: string): Promise { + const isValidPublicKey = await verifyPublicKey({ publicKey }); + if (!isValidPublicKey) { + throw new GandalfError( + 'Public key does not exist', + GandalfErrorCode.InvalidPublicKey, + ); + } + } + + private static async validateInputData(input: InputData): Promise { + const supportedServicesAndTraits = await getSupportedServicesAndTraits(); + const cleanServices: InputData = {}; + + let unsupportedServices: string[] = []; + + const keys = Object.keys(input).map((key) => key.toLowerCase()); + + if (keys.length > 1) { + throw new GandalfError( + `Only one service is supported per Connect URL`, + GandalfErrorCode.InvalidService, + ); + } + + for (const key of keys) { + if (!supportedServicesAndTraits.services.includes(key as Source)) { + unsupportedServices = [...unsupportedServices, key]; + continue; + } + + const service = input[key]; + if (typeof service === 'boolean') { + if (!service) + throw new GandalfError( + 'At least one service has to be required', + GandalfErrorCode.InvalidService, + ); + cleanServices[key.toLowerCase()] = input[key as Source]; + } else { + this.validateInputService(service, supportedServicesAndTraits); + cleanServices[key.toLowerCase()] = input[key as Source]; + } + } + + if (unsupportedServices.length > 0) { + throw new GandalfError( + `These services [ ${unsupportedServices.join(', ')} ] are unsupported`, + GandalfErrorCode.InvalidService, + ); + } + + return cleanServices; + } + + private static validateInputService( + input: Service, + supportedServicesAndTraits: SupportedServicesAndTraits, + ): void { + if ( + (input.activities?.length ?? 0) < 1 && + (input.traits?.length ?? 0) < 1 + ) { + throw new GandalfError( + 'At least one trait or activity is required', + GandalfErrorCode.InvalidService, + ); + } + + let unsupportedActivities: string[] = []; + let unsupportedTraits: string[] = []; + + if (input.activities) { + for (const key of input.activities) { + if ( + !supportedServicesAndTraits.activities.includes(key.toLowerCase()) + ) { + unsupportedActivities = [...unsupportedActivities, key]; + continue; + } + } + } + + if (input.traits) { + for (const key of input.traits) { + if (!supportedServicesAndTraits.traits.includes(key.toLowerCase())) { + unsupportedTraits = [...unsupportedTraits, key]; + continue; + } + } + } + + if (unsupportedActivities.length > 0) { + throw new GandalfError( + `These activities [ ${unsupportedActivities.join(', ')} ] are unsupported`, + GandalfErrorCode.InvalidService, + ); + } + + if (unsupportedTraits.length > 0) { + throw new GandalfError( + `These traits [ ${unsupportedTraits.join(', ')} ] are unsupported`, + GandalfErrorCode.InvalidService, + ); + } + } + + private static validateRedirectURL(url: string): void { + try { + Boolean(new URL(url)); + } catch (e) { + throw new GandalfError( + 'Invalid redirectURL', + GandalfErrorCode.InvalidRedirectURL, + ); + } + } } -export default Connect \ No newline at end of file +export default Connect; diff --git a/src/index.ts b/src/index.ts index fa60535..80df8d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,3 @@ -import Connect from "./connect"; +import Connect from './connect'; -export default Connect; \ No newline at end of file +export default Connect; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 3fdf71f..7b257f4 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,2 +1,10 @@ +import dotenv from 'dotenv'; +dotenv.config(); + export const WATSON_URL = process.env.WATSON_URL as string; -export const APP_CLIP_BASE_URL = process.env.APP_CLIP_BASE_URL as string; \ No newline at end of file +export const UNIVERSAL_APP_CLIP_BASE_URL = process.env + .UNIVERSAL_APP_CLIP_BASE_URL as string; +export const IOS_APP_CLIP_BASE_URL = process.env + .IOS_APP_CLIP_BASE_URL as string; +export const ANDROID_APP_CLIP_BASE_URL = process.env + .ANDROID_APP_CLIP_BASE_URL as string; diff --git a/src/lib/errors.ts b/src/lib/errors.ts index e8d859a..4a2688d 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -1,21 +1,15 @@ -enum GandalfErrorCode { - InvalidPublicKey = 'INVALID_PUBLIC_KEY', - InvalidRedirectURL = 'INVALID_REDIRECT_URL', - InvalidService = 'INVALID_SERVICE', - DataKeyNotFound = 'DATA_KEY_NOT_FOUND', - QRCodeNotGenerated = 'QR_CODE_NOT_GENERATED', - QRCodeGenNotSupported = 'QR_CODE_GEN_NOT_SUPPORTED' -} - +import { GandalfErrorCode } from '../types'; + class GandalfError extends Error { - code: GandalfErrorCode + code: GandalfErrorCode; + constructor(message: string, code: GandalfErrorCode) { super(message); Object.setPrototypeOf(this, GandalfError.prototype); - this.name = "GandalfError"; + this.name = 'GandalfError'; this.code = code; } } - -export { GandalfError, GandalfErrorCode }; \ No newline at end of file + +export default GandalfError; diff --git a/src/lib/qrCode-style.ts b/src/lib/qrCode-style.ts index 8cbc2ba..82910fd 100644 --- a/src/lib/qrCode-style.ts +++ b/src/lib/qrCode-style.ts @@ -1,38 +1,37 @@ import { Options } from 'qr-code-styling'; -const base64EncodedIcon = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIj4KICAgIDxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMzNfMjgzOTYpIj4KICAgICAgICA8cGF0aCBkPSJNMjQgMTJDMjQgMTguNjI3NCAxOC42Mjc0IDI0IDEyIDI0QzUuMzcyNTggMjQgMCAxOC42Mjc0IDAgMTJDMCA1LjM3MjU4IDUuMzcyNTggMCAxMiAwQzE4LjYyNzQgMCAyNCA1LjM3MjU4IDI0IDEyWiIgZmlsbD0iIzY2MTBGMiI+PC9wYXRoPgogICAgICAgIDxwYXRoIGQ9Ik05LjIxODIzIDI5LjM4OTRMOC44OTYyMSAzMy43NDc0TDkuNDM2MzMgMzMuODIxNUwxMC4yOTg1IDI5LjUzNzVMOS4yMTgyMyAyOS4zODk0WiIgZmlsbD0id2hpdGUiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNOS4yNTUzNiAyOS4xMTkzTDEwLjI4MDUgMTkuNjMwMUwxMS45MDA5IDE5Ljg1MjFMMTAuMzM1NiAyOS4yNjczTDkuMjU1MzYgMjkuMTE5M1oiIGZpbGw9IndoaXRlIj48L3BhdGg+CiAgICAgICAgPHBhdGggZD0iTTExLjkzNzggMTkuNTgyMUwxMC4zMTc0IDE5LjM2MDFMMTAuNDgwNCAxNC4xNTQ5TDE0LjY2ODMgMTEuNzAyM0wxMy41NzcxIDkuNjI2NzlMMTAuOTk4NSAxMC4zNzRMMTAuOTI0NSAxMC45MTQxTDExLjQ2NDYgMTAuOTg4MUwxMS45MzA3IDExLjYwMjNMMTAuODk4NSAxMy4xMTE2TDkuMDgyMDUgMTIuMzEyNUw5LjI5MzA3IDguNzY0NjNMMTQuMTgwMiA3LjIzMzIyTDE2LjgyODggMTEuOTk4M0wxMi40OTI5IDE1LjUzMTJMMTEuOTM3OCAxOS41ODIxWiIgZmlsbD0id2hpdGUiPjwvcGF0aD4KICAgICAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIyLjY5MzNDMTcuOTA1NyAyMi42OTMzIDIyLjY5MzMgMTcuOTA1NyAyMi42OTMzIDEyQzIyLjY5MzMgNi4wOTQyNSAxNy45MDU3IDEuMzA2NyAxMiAxLjMwNjdDNi4wOTQyNSAxLjMwNjcgMS4zMDY3IDYuMDk0MjUgMS4zMDY3IDEyQzEuMzA2NyAxNy45MDU3IDYuMDk0MjUgMjIuNjkzMyAxMiAyMi42OTMzWk0xMiAyNEMxOC42Mjc0IDI0IDI0IDE4LjYyNzQgMjQgMTJDMjQgNS4zNzI1OCAxOC42Mjc0IDAgMTIgMEM1LjM3MjU4IDAgMCA1LjM3MjU4IDAgMTJDMCAxOC42Mjc0IDUuMzcyNTggMjQgMTIgMjRaIiBmaWxsPSJ3aGl0ZSI+PC9wYXRoPgogICAgPC9nPgogICAgPGRlZnM+CiAgICAgICAgPGNsaXBQYXRoIGlkPSJjbGlwMF8xMzNfMjgzOTYiPgogICAgICAgICAgICA8cmVjdCB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHJ4PSIxMCIgZmlsbD0id2hpdGUiPjwvcmVjdD4KICAgICAgICA8L2NsaXBQYXRoPgogICAgPC9kZWZzPgo8L3N2Zz4K' -export const qrCodeStyle = (url: string): Partial => { - return ( - { - width: 300, - height: 300, - data: url, - margin: 0, - qrOptions: { - typeNumber: 0, - mode: "Byte", - errorCorrectionLevel: "Q", - }, - imageOptions: { - hideBackgroundDots: true, - imageSize: 0.5, - margin: 4, - }, - dotsOptions: { - type: "classy", - color: "#6610f3", - }, - backgroundOptions: { - color: "#ffffff", - }, - image: base64EncodedIcon, - cornersDotOptions: { - color: "#6a0ff3", - }, - cornersSquareOptions: { - type: "extra-rounded", - color: "#6610f3", - }, - } - ) -} \ No newline at end of file +const base64EncodedIcon = + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIj4KICAgIDxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMzNfMjgzOTYpIj4KICAgICAgICA8cGF0aCBkPSJNMjQgMTJDMjQgMTguNjI3NCAxOC42Mjc0IDI0IDEyIDI0QzUuMzcyNTggMjQgMCAxOC42Mjc0IDAgMTJDMCA1LjM3MjU4IDUuMzcyNTggMCAxMiAwQzE4LjYyNzQgMCAyNCA1LjM3MjU4IDI0IDEyWiIgZmlsbD0iIzY2MTBGMiI+PC9wYXRoPgogICAgICAgIDxwYXRoIGQ9Ik05LjIxODIzIDI5LjM4OTRMOC44OTYyMSAzMy43NDc0TDkuNDM2MzMgMzMuODIxNUwxMC4yOTg1IDI5LjUzNzVMOS4yMTgyMyAyOS4zODk0WiIgZmlsbD0id2hpdGUiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNOS4yNTUzNiAyOS4xMTkzTDEwLjI4MDUgMTkuNjMwMUwxMS45MDA5IDE5Ljg1MjFMMTAuMzM1NiAyOS4yNjczTDkuMjU1MzYgMjkuMTE5M1oiIGZpbGw9IndoaXRlIj48L3BhdGg+CiAgICAgICAgPHBhdGggZD0iTTExLjkzNzggMTkuNTgyMUwxMC4zMTc0IDE5LjM2MDFMMTAuNDgwNCAxNC4xNTQ5TDE0LjY2ODMgMTEuNzAyM0wxMy41NzcxIDkuNjI2NzlMMTAuOTk4NSAxMC4zNzRMMTAuOTI0NSAxMC45MTQxTDExLjQ2NDYgMTAuOTg4MUwxMS45MzA3IDExLjYwMjNMMTAuODk4NSAxMy4xMTE2TDkuMDgyMDUgMTIuMzEyNUw5LjI5MzA3IDguNzY0NjNMMTQuMTgwMiA3LjIzMzIyTDE2LjgyODggMTEuOTk4M0wxMi40OTI5IDE1LjUzMTJMMTEuOTM3OCAxOS41ODIxWiIgZmlsbD0id2hpdGUiPjwvcGF0aD4KICAgICAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIyLjY5MzNDMTcuOTA1NyAyMi42OTMzIDIyLjY5MzMgMTcuOTA1NyAyMi42OTMzIDEyQzIyLjY5MzMgNi4wOTQyNSAxNy45MDU3IDEuMzA2NyAxMiAxLjMwNjdDNi4wOTQyNSAxLjMwNjcgMS4zMDY3IDYuMDk0MjUgMS4zMDY3IDEyQzEuMzA2NyAxNy45MDU3IDYuMDk0MjUgMjIuNjkzMyAxMiAyMi42OTMzWk0xMiAyNEMxOC42Mjc0IDI0IDI0IDE4LjYyNzQgMjQgMTJDMjQgNS4zNzI1OCAxOC42Mjc0IDAgMTIgMEM1LjM3MjU4IDAgMCA1LjM3MjU4IDAgMTJDMCAxOC42Mjc0IDUuMzcyNTggMjQgMTIgMjRaIiBmaWxsPSJ3aGl0ZSI+PC9wYXRoPgogICAgPC9nPgogICAgPGRlZnM+CiAgICAgICAgPGNsaXBQYXRoIGlkPSJjbGlwMF8xMzNfMjgzOTYiPgogICAgICAgICAgICA8cmVjdCB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHJ4PSIxMCIgZmlsbD0id2hpdGUiPjwvcmVjdD4KICAgICAgICA8L2NsaXBQYXRoPgogICAgPC9kZWZzPgo8L3N2Zz4K'; +const qrCodeStyle = (url: string): Partial => ({ + width: 300, + height: 300, + data: url, + margin: 0, + qrOptions: { + typeNumber: 0, + mode: 'Byte', + errorCorrectionLevel: 'Q', + }, + imageOptions: { + hideBackgroundDots: true, + imageSize: 0.5, + margin: 4, + }, + dotsOptions: { + type: 'classy', + color: '#6610f3', + }, + backgroundOptions: { + color: '#ffffff', + }, + image: base64EncodedIcon, + cornersDotOptions: { + color: '#6a0ff3', + }, + cornersSquareOptions: { + type: 'extra-rounded', + color: '#6610f3', + }, +}); + +export default qrCodeStyle; diff --git a/src/tests/connect.spec.ts b/src/tests/connect.spec.ts deleted file mode 100644 index 70592b5..0000000 --- a/src/tests/connect.spec.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { verifyPublicKey } from "../api/publicKey"; -import { getSupportedServices } from "../api/supportedServices"; -import { Services } from "../connect"; -import Connect from "../index"; -import { APP_CLIP_BASE_URL } from "../lib/constants"; -import QRCodeStyling from "qr-code-styling"; - -jest.mock('../api/publicKey', () => ({ - verifyPublicKey: jest.fn() -})); - -jest.mock('../api/supportedServices', () => ({ - getSupportedServices: jest.fn() -})); - -jest.mock('qr-code-styling', () => { - return jest.fn().mockImplementation(() => { - return { - getRawData: jest.fn().mockReturnValue(new Blob()), - }; - }); -}); - -describe('Connect SDK', () => { - const publicKey = 'examplePublicKey'; - const redirectURL = 'https://example.com'; - const services = {"netflix": true} - const stringServices = JSON.stringify(services) - - beforeEach(() => { - (verifyPublicKey as jest.Mock).mockResolvedValue(true); - (getSupportedServices as jest.Mock).mockResolvedValue(["netflix"]); - global.URL.createObjectURL = jest.fn(() => "mocked-object-url"); - }) - - afterEach(() => { - (getSupportedServices as jest.Mock).mockClear(); - }); - - describe('Constructor', () => { - it('should initialize publicKey and redirectURL properly', async () => { - const connect = new Connect({publicKey, redirectURL, services}); - - expect(connect.publicKey).toEqual(publicKey); - expect(connect.redirectURL).toEqual(redirectURL); - }); - - it('should strip the redirect url of trailing slashes', async () => { - const redirectURL = 'https://example.com/'; - const connect = new Connect({publicKey, redirectURL, services}); - - expect(connect.redirectURL).toEqual('https://example.com') - }); - }); - - describe('allValidations', () => { - it('should pass all validations and call verifyPublicKey only once', async () => { - - const connect = new Connect({publicKey, redirectURL, services}); - await connect.generateURL(); - - expect(connect.verificationComplete).toEqual(true) - expect(verifyPublicKey as jest.Mock).toHaveBeenCalledTimes(1) - }); - - it('should throw error if publicKey is invalid', async () => { - const publicKey = 'invalidPublicKey'; - const connect = new Connect({publicKey, redirectURL, services}); - (verifyPublicKey as jest.Mock).mockResolvedValue(false); - - await expect(connect.generateURL()).rejects.toThrow('Public key does not exist'); - expect(connect.verificationComplete).toEqual(false); - }); - - it('should throw error if redirectURL is invalid', async () => { - const invalidRedirectURL = 'not a valid URL'; - const connect = new Connect({publicKey, redirectURL: invalidRedirectURL, services}); - - await expect(connect.generateURL()).rejects.toThrow('Invalid redirectURL'); - expect(connect.verificationComplete).toEqual(false); - }); - - it('should throw error if invalid service is passed', async () => { - const invalidServices = {"twitter": true, "showmax": false} as Services - const connect = new Connect({publicKey, redirectURL, services: invalidServices}); - - await expect(connect.generateURL()).rejects.toThrow( - `These services ${Object.keys(invalidServices).join(' ')} are unsupported` - ); - expect(connect.verificationComplete).toEqual(false); - }); - - it('should throw error if no required service is passed', async () => { - const invalidServices = {"NETFLIX": false} - const connect = new Connect({publicKey, redirectURL, services: invalidServices}); - - await expect(connect.generateURL()).rejects.toThrow('At least one service has to be required'); - expect(connect.verificationComplete).toEqual(false); - }); - }); - - describe('generateURL', () => { - it('should generate the correct URL', async () => { - const connect = new Connect({publicKey, redirectURL, services}); - const encodedServices = encodeURIComponent(stringServices) - const encodedRedirectURL = encodeURIComponent(redirectURL) - const encodedPublicKey = encodeURIComponent(publicKey) - const generatedURL = await connect.generateURL(); - expect(generatedURL).toEqual(`${APP_CLIP_BASE_URL}&services=${encodedServices}&redirectUrl=${encodedRedirectURL}&publicKey=${encodedPublicKey}`); - }); - }); - - describe('generateQRCode', () => { - it('should create QR code with correct options', async () => { - const connect = new Connect({publicKey, redirectURL, services}); - const qrCodeUrl = await connect.generateQRCode(); - expect(qrCodeUrl).toBeTruthy() - }); - }); - - describe('getDataKeyFromURL', () => { - it('should get the data key from the url', () => { - const url = 'https://dashboard.doppler.com/connect=success?dataKey=11221122' - const dataKey = Connect.getDataKeyFromURL(url); - expect(dataKey).toEqual('11221122') - }); - - it('should throw data key not found error', () => { - const url = 'https://dashboard.doppler.com/connect=success?noDataKey=11221122' - expect(() => Connect.getDataKeyFromURL(url)).toThrow(`Datakey not found in the URL ${url}`); - }); - - it('should throw Invalid redirect url error', () => { - const url = 'https://dashboard.doppler.com/connect=success?noDataKey=11221122' - expect(() => Connect.getDataKeyFromURL(url)).toThrow(`Datakey not found in the URL ${url}`); - }); - }); - - describe('getSupportedServices', () => { - it('should get the supported services', async () => { - await Connect.getSupportedServices(); - expect(getSupportedServices as jest.Mock).toHaveBeenCalledTimes(1) - }); - }); -}); diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..2e095fe --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,34 @@ +import { ActivityType, Source, TraitLabel } from '../api/__generated__/graphql'; + +export type InputData = { + [key: string]: boolean | Service; +}; + +export { Source, TraitLabel, ActivityType }; + +export type Service = { + traits?: string[]; + activities?: string[]; +}; + +export enum Platform { + universal = 'UNIVERSAL', + ios = 'IOS', + android = 'ANDROID', +} + +export type ConnectInput = { + publicKey: string; + redirectURL: string; + services: InputData; + platform?: Platform; +}; + +export enum GandalfErrorCode { + InvalidPublicKey = 'INVALID_PUBLIC_KEY', + InvalidRedirectURL = 'INVALID_REDIRECT_URL', + InvalidService = 'INVALID_SERVICE', + DataKeyNotFound = 'DATA_KEY_NOT_FOUND', + QRCodeNotGenerated = 'QR_CODE_NOT_GENERATED', + QRCodeGenNotSupported = 'QR_CODE_GEN_NOT_SUPPORTED', +} diff --git a/tests/connect.spec.ts b/tests/connect.spec.ts new file mode 100644 index 0000000..cd62ec4 --- /dev/null +++ b/tests/connect.spec.ts @@ -0,0 +1,318 @@ +import { verifyPublicKey } from "../src/api/publicKey"; +import { getSupportedServicesAndTraits } from "../src/api/supportedServices"; +import Connect from "../src/index"; +import { ANDROID_APP_CLIP_BASE_URL, IOS_APP_CLIP_BASE_URL, UNIVERSAL_APP_CLIP_BASE_URL } from "../src/lib/constants"; +import { InputData, Platform } from "../src/types"; + +jest.mock("../src/api/publicKey", () => ({ + verifyPublicKey: jest.fn(), +})); + +jest.mock("../src/api/supportedServices", () => ({ + getSupportedServicesAndTraits: jest.fn(), +})); + +jest.mock("qr-code-styling", () => + jest.fn().mockImplementation(() => ({ + getRawData: jest.fn().mockReturnValue(new Blob()), + })), +); + +describe("Connect SDK", () => { + const publicKey = "examplePublicKey"; + const redirectURL = "https://example.com"; + const services: InputData = { + uber: { + traits: ["rating"], + activities: ["trip"], + }, + } + const stringData = JSON.stringify(services); + + beforeEach(() => { + (verifyPublicKey as jest.Mock).mockResolvedValue(true); + (getSupportedServicesAndTraits as jest.Mock).mockResolvedValue( + { + services: ["netflix", "uber", "instacart", "gandalf"], + activities: ["trip", "watch", "shop"], + traits: ["email", "post_count", "follower_count", "rating", "plan"], + } + ); + global.URL.createObjectURL = jest.fn(() => "mocked-object-url"); + }); + + afterEach(() => { + (getSupportedServicesAndTraits as jest.Mock).mockClear(); + }); + + describe("Constructor", () => { + it("should initialize publicKey and redirectURL properly", async () => { + const connect = new Connect({ publicKey, redirectURL, services }); + + expect(connect.publicKey).toEqual(publicKey); + expect(connect.redirectURL).toEqual(redirectURL); + expect(connect.platform).toEqual(Platform.ios); + }); + + it("should strip the redirect url of trailing slashes", async () => { + const redirectURL = "https://example.com/"; + const connect = new Connect({ publicKey, redirectURL, services }); + + expect(connect.redirectURL).toEqual("https://example.com"); + }); + + it("should set the platform", async () => { + const redirectURL = "https://example.com/"; + const connect = new Connect({ publicKey, redirectURL, services, platform: Platform.universal }); + + expect(connect.platform).toEqual(Platform.universal); + }); + }); + + describe("allValidations", () => { + it("should pass all validations and call verifyPublicKey only once", async () => { + const connect = new Connect({ publicKey, redirectURL, services }); + await connect.generateURL(); + + expect(connect.verificationComplete).toEqual(true); + expect(verifyPublicKey as jest.Mock).toHaveBeenCalledTimes(1); + }); + + it("should throw error if publicKey is invalid", async () => { + const publicKey = "invalidPublicKey"; + const connect = new Connect({ publicKey, redirectURL, services }); + (verifyPublicKey as jest.Mock).mockResolvedValue(false); + + await expect(connect.generateURL()).rejects.toThrow( + "Public key does not exist", + ); + expect(connect.verificationComplete).toEqual(false); + }); + + it("should throw error if redirectURL is invalid", async () => { + const invalidRedirectURL = "not a valid URL"; + const connect = new Connect({ + publicKey, + redirectURL: invalidRedirectURL, + services, + }); + + await expect(connect.generateURL()).rejects.toThrow( + "Invalid redirectURL", + ); + expect(connect.verificationComplete).toEqual(false); + }); + + it("should throw error if invalid service is passed", async () => { + const invalidDataServices = { + facebook: { + traits: ["plan"], + activities: ["watch"] + }, + } as InputData; + + const connect = new Connect({ + publicKey, + redirectURL, + services: invalidDataServices, + }); + + await expect(connect.generateURL()).rejects.toThrow( + `These services [ ${Object.keys(invalidDataServices).join(", ")} ] are unsupported`, + ); + expect(connect.verificationComplete).toEqual(false); + }); + + it("should throw error if invalid trait is passed", async () => { + const invalidTraits = ["age", "color"] + const invalidDataServices = { + netflix: { + traits: invalidTraits, + activities: ["watch"] + }, + } as InputData; + + const connect = new Connect({ + publicKey, + redirectURL, + services: invalidDataServices, + }); + + await expect(connect.generateURL()).rejects.toThrow( + `These traits [ ${invalidTraits.join(", ")} ] are unsupported`, + ); + expect(connect.verificationComplete).toEqual(false); + }); + + it("should throw error if invalid activity is passed", async () => { + const invalidActivities = ["read", "dance"] + const invalidDataServices = { + netflix: { + activities: invalidActivities + }, + } as InputData; + + const connect = new Connect({ + publicKey, + redirectURL, + services: invalidDataServices, + }); + + await expect(connect.generateURL()).rejects.toThrow( + `These activities [ ${invalidActivities.join(", ")} ] are unsupported`, + ); + expect(connect.verificationComplete).toEqual(false); + }); + + it("should throw error if no required trait or activity is passed", async () => { + const invalidDataServices = { + netflix: { + traits: [], + activities: [] + }, + } as InputData; + + const connect = new Connect({ + publicKey, + redirectURL, + services: invalidDataServices, + }); + + await expect(connect.generateURL()).rejects.toThrow( + "At least one trait or activity is required", + ); + expect(connect.verificationComplete).toEqual(false); + }); + + it("should throw an error if more than one service is passsed", async () => { + const invalidDataServices: InputData = { + uber: { + traits: ["rating"], + activities: ["trip"], + }, + netflix: { + traits: ["plan"], + activities: ["watch"] + }, + instacart: { + activities: ["shop"] + } + } as InputData; + + const connect = new Connect({ + publicKey, + redirectURL, + services: invalidDataServices, + }); + + await expect(connect.generateURL()).rejects.toThrow( + "Only one service is supported per Connect URL", + ); + expect(connect.verificationComplete).toEqual(false); + }); + }); + + describe("generateURL", () => { + const encodedData = encodeURIComponent(btoa(stringData)); + const encodedRedirectURL = encodeURIComponent(redirectURL); + const encodedPublicKey = encodeURIComponent(publicKey); + + it("should generate an IOS connect URL", async () => { + const connect = new Connect({ publicKey, redirectURL, services }); + const generatedURL = await connect.generateURL(); + expect(generatedURL).toEqual( + `${IOS_APP_CLIP_BASE_URL}&publicKey=${encodedPublicKey}&redirectUrl=${encodedRedirectURL}&data=${encodedData}`, + ); + }); + + it("should generate a universal connect URL", async () => { + const connect = new Connect({ publicKey, redirectURL, services, platform: Platform.universal }); + const generatedURL = await connect.generateURL(); + expect(generatedURL).toEqual( + `${UNIVERSAL_APP_CLIP_BASE_URL}/?publicKey=${encodedPublicKey}&redirectUrl=${encodedRedirectURL}&data=${encodedData}`, + ); + }); + + it("should generate an android connect URL", async () => { + const connect = new Connect({ publicKey, redirectURL, services, platform: Platform.android }); + const generatedURL = await connect.generateURL(); + expect(generatedURL).toEqual( + `${ANDROID_APP_CLIP_BASE_URL}/?publicKey=${encodedPublicKey}&redirectUrl=${encodedRedirectURL}&data=${encodedData}`, + ); + }); + }); + + describe("generateQRCode", () => { + it("should create QR code with correct options", async () => { + const connect = new Connect({ publicKey, redirectURL, services }); + const qrCodeUrl = await connect.generateQRCode(); + expect(qrCodeUrl).toBeTruthy(); + }); + }); + + describe("getDataKeyFromURL", () => { + it("should get the data key from the url", () => { + const url = + "https://dashboard.doppler.com/connect=success?dataKey=11221122"; + const dataKey = Connect.getDataKeyFromURL(url); + expect(dataKey).toEqual("11221122"); + }); + + it("should throw data key not found error", () => { + const url = + "https://dashboard.doppler.com/connect=success?noDataKey=11221122"; + expect(() => Connect.getDataKeyFromURL(url)).toThrow( + `Datakey not found in the URL ${url}`, + ); + }); + + it("should throw Invalid redirect url error", () => { + const url = + "https://dashboard.doppler.com/connect=success?noDataKey=11221122"; + expect(() => Connect.getDataKeyFromURL(url)).toThrow( + `Datakey not found in the URL ${url}`, + ); + }); + }); + + describe("getSupportedServices", () => { + it("should get the supported services", async () => { + await Connect.getSupportedServicesAndTraits(); + expect(getSupportedServicesAndTraits as jest.Mock).toHaveBeenCalledTimes(1); + }); + }); + + describe('backwardCompatibility', () => { + it('should throw error if invalid service is passed', async () => { + const invalidServices = {"twitter": true} as InputData + const connect = new Connect({publicKey, redirectURL, services: invalidServices}); + + await expect(connect.generateURL()).rejects.toThrow( + `These services [ ${Object.keys(invalidServices).join(' ')} ] are unsupported` + ); + expect(connect.verificationComplete).toEqual(false); + }); + + it('should throw error if no required service is passed', async () => { + const invalidServices = {"netflix": false} + const connect = new Connect({publicKey, redirectURL, services: invalidServices}); + + await expect(connect.generateURL()).rejects.toThrow('At least one service has to be required'); + expect(connect.verificationComplete).toEqual(false); + }); + + it("should generate a universal connect URL", async () => { + const services = {"netflix": true}; + const stringData = JSON.stringify(services); + const encodedData = encodeURIComponent(btoa(stringData)); + const encodedRedirectURL = encodeURIComponent(redirectURL); + const encodedPublicKey = encodeURIComponent(publicKey); + + const connect = new Connect({ publicKey, redirectURL, services, platform: Platform.universal }); + const generatedURL = await connect.generateURL(); + expect(generatedURL).toEqual( + `${UNIVERSAL_APP_CLIP_BASE_URL}/?publicKey=${encodedPublicKey}&redirectUrl=${encodedRedirectURL}&data=${encodedData}`, + ); + }); + }); +}); \ No newline at end of file diff --git a/tests/setupEnv.ts b/tests/setupEnv.ts new file mode 100644 index 0000000..f56a303 --- /dev/null +++ b/tests/setupEnv.ts @@ -0,0 +1,3 @@ +import { config } from 'dotenv'; + +config({ path: '.env.test' }); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 6990216..97244b5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,6 @@ "allowJs": true, "emitDeclarationOnly": false }, - "exclude": ["**/*.spec.ts", "**/*.test.ts", "tests", "examples"], + "exclude": ["tests", "examples"], "include": ["src"] }