diff --git a/index.js b/index.js index def95a5c527180b860f64b431caa52d54666c380..5e11de559be0aac0f16709cf0f694408d4d62342 100644 --- a/index.js +++ b/index.js @@ -98,7 +98,7 @@ const { } = __webpack_require__(1) const getAccountId = __webpack_require__(1767) const getDataGenericErrors = __webpack_require__(1768) -const { isAlpha } = __webpack_require__(1769) +const { isDev } = __webpack_require__(1769) const moment = __webpack_require__(1770) __webpack_require__(1907) moment.locale('fr') // set the language @@ -118,14 +118,34 @@ Sentry.init({ // We recommend adjusting this value in production tracesSampleRate: 1.0, release: version, - environment: isAlpha() ? 'development' : 'production', - debug: isAlpha(), + environment: isDev() ? 'development' : 'production', + debug: isDev(), integrations: [ // enable HTTP calls tracing new Sentry.Integrations.Http({ tracing: true }) ] }) +async function standaloneStart(token, pce) { + try { + const grdfData = await getData(token, pce) + if (!grdfData) { + log('debug', 'No consent or data for load curve') + return + } + log('debug', 'Process grdf daily data') + const processedLoadData = await processData( + grdfData, + 'com.grandlyon.grdf.day', + ['year', 'month', 'day'] + ) + log('debug', 'Aggregate grdf load data for month and year') + await aggregateMonthAndYearData(processedLoadData) + } catch (error) { + log('error', 'Standalone failed') + } +} + const manualExecution = process.env.COZY_JOB_MANUAL_EXECUTION === 'true' ? true : false const startDate = manualExecution @@ -211,9 +231,15 @@ async function start(fields) { } } +function buildGetDataUrl(idPCE, startDate, endDate) { + const baseUrl = 'https://api.grdf.fr/adict/v2/pce' + const queryParams = `date_debut=${startDate}&date_fin=${endDate}` + return `${baseUrl}/${idPCE}/donnees_consos_informatives?${queryParams}` +} + async function getData(token, idPCE) { const url = buildGetDataUrl(idPCE, startDate, endDate) - const rep = await fetch(url, { + const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/x-ndjson', @@ -267,18 +293,12 @@ async function getData(token, idPCE) { log('debug', 'Error from getData') throw error }) - const filteredRep = rep.filter(function(el) { + const filteredRep = response.filter(function(el) { return el.energie != null || el.volume_brut != null }) return filteredRep } -function buildGetDataUrl(idPCE, startDate, endDate) { - const baseUrl = 'https://api.grdf.fr/adict/v2/pce' - const queryParams = `date_debut=${startDate}&date_fin=${endDate}` - return `${baseUrl}/${idPCE}/donnees_consos_informatives?${queryParams}` -} - /** * Parse data * Remove existing data from DB using hydrateAndFilter @@ -151714,7 +151734,7 @@ const fs = __webpack_require__(167); const path = __webpack_require__(160); -let manifest = typeof {"version":"1.2.1","name":"GRDF","type":"konnector","language":"node","icon":"icon.png","slug":"grdfgrandlyon","source":"https://forge.grandlyon.com/web-et-numerique/llle_project/grdf-konnector.git","editor":"Métropole de Lyon","vendor_link":"https://www.grdf.fr/","categories":["energy"],"frequency":"daily","fields":{"access_token":{"type":"hidden"},"refresh_token":{"type":"hidden"}},"oauth":{},"data_types":[],"screenshots":[],"permissions":{"accounts":{"type":"io.cozy.accounts"},"grdf data":{"type":"com.grandlyon.grdf.*"}},"developer":{"name":"Métropole de Lyon","url":"https://www.grandlyon.com/"},"langs":["fr"],"locales":{"fr":{"short_description":"Récupère vos donnéees de courbe de charge depuis l'API GRDF ADICT","long_description":"Ce connecteur récupère la courbe d'energie en kWh enregistrée par un compteur Gazpar","permissions":{"grdf data":{"description":"Requises pour accéder et stocker les données collectées par le compteur Gazpar et exposées par les API GRDF (consommations de gaz au jour, mois et année)."},"accounts":{"description":"Utilisé pour accéder à vos données de consommation."}}},"en":{"short_description":"Gas consumption data fetched from GRDF ADICT API","long_description":"This konnector fetches the data curve in kWh gathered by Gaspar device.","permissions":{"grdf data":{"description":"Required to access and store the data collected by the Gazpar meter and exposed by GRDF APIs (daily, monthly and yearly consumption)."},"accounts":{"description":"Used to access your consumption data."}}}},"manifest_version":"2","on_delete_account":"onDeleteAccount.js"} === 'undefined' ? {} : {"version":"1.2.1","name":"GRDF","type":"konnector","language":"node","icon":"icon.png","slug":"grdfgrandlyon","source":"https://forge.grandlyon.com/web-et-numerique/llle_project/grdf-konnector.git","editor":"Métropole de Lyon","vendor_link":"https://www.grdf.fr/","categories":["energy"],"frequency":"daily","fields":{"access_token":{"type":"hidden"},"refresh_token":{"type":"hidden"}},"oauth":{},"data_types":[],"screenshots":[],"permissions":{"accounts":{"type":"io.cozy.accounts"},"grdf data":{"type":"com.grandlyon.grdf.*"}},"developer":{"name":"Métropole de Lyon","url":"https://www.grandlyon.com/"},"langs":["fr"],"locales":{"fr":{"short_description":"Récupère vos donnéees de courbe de charge depuis l'API GRDF ADICT","long_description":"Ce connecteur récupère la courbe d'energie en kWh enregistrée par un compteur Gazpar","permissions":{"grdf data":{"description":"Requises pour accéder et stocker les données collectées par le compteur Gazpar et exposées par les API GRDF (consommations de gaz au jour, mois et année)."},"accounts":{"description":"Utilisé pour accéder à vos données de consommation."}}},"en":{"short_description":"Gas consumption data fetched from GRDF ADICT API","long_description":"This konnector fetches the data curve in kWh gathered by Gaspar device.","permissions":{"grdf data":{"description":"Required to access and store the data collected by the Gazpar meter and exposed by GRDF APIs (daily, monthly and yearly consumption)."},"accounts":{"description":"Used to access your consumption data."}}}},"manifest_version":"2","on_delete_account":"onDeleteAccount.js"}; +let manifest = typeof {"version":"1.2.0","name":"GRDF","type":"konnector","language":"node","icon":"icon.png","slug":"grdfgrandlyon","source":"https://forge.grandlyon.com/web-et-numerique/llle_project/grdf-konnector.git","editor":"Métropole de Lyon","vendor_link":"https://www.grdf.fr/","categories":["energy"],"frequency":"daily","fields":{"access_token":{"type":"hidden"},"refresh_token":{"type":"hidden"}},"oauth":{},"data_types":[],"screenshots":[],"permissions":{"accounts":{"type":"io.cozy.accounts"},"grdf data":{"type":"com.grandlyon.grdf.*"}},"developer":{"name":"Métropole de Lyon","url":"https://www.grandlyon.com/"},"langs":["fr"],"locales":{"fr":{"short_description":"Récupère vos donnéees de courbe de charge depuis l'API GRDF ADICT","long_description":"Ce connecteur récupère la courbe d'energie en kWh enregistrée par un compteur Gazpar","permissions":{"grdf data":{"description":"Requises pour accéder et stocker les données collectées par le compteur Gazpar et exposées par les API GRDF (consommations de gaz au jour, mois et année)."},"accounts":{"description":"Utilisé pour accéder à vos données de consommation."}}},"en":{"short_description":"Gas consumption data fetched from GRDF ADICT API","long_description":"This konnector fetches the data curve in kWh gathered by Gaspar device.","permissions":{"grdf data":{"description":"Required to access and store the data collected by the Gazpar meter and exposed by GRDF APIs (daily, monthly and yearly consumption)."},"accounts":{"description":"Used to access your consumption data."}}}},"manifest_version":"2","on_delete_account":"onDeleteAccount.js"} === 'undefined' ? {} : {"version":"1.2.0","name":"GRDF","type":"konnector","language":"node","icon":"icon.png","slug":"grdfgrandlyon","source":"https://forge.grandlyon.com/web-et-numerique/llle_project/grdf-konnector.git","editor":"Métropole de Lyon","vendor_link":"https://www.grdf.fr/","categories":["energy"],"frequency":"daily","fields":{"access_token":{"type":"hidden"},"refresh_token":{"type":"hidden"}},"oauth":{},"data_types":[],"screenshots":[],"permissions":{"accounts":{"type":"io.cozy.accounts"},"grdf data":{"type":"com.grandlyon.grdf.*"}},"developer":{"name":"Métropole de Lyon","url":"https://www.grandlyon.com/"},"langs":["fr"],"locales":{"fr":{"short_description":"Récupère vos donnéees de courbe de charge depuis l'API GRDF ADICT","long_description":"Ce connecteur récupère la courbe d'energie en kWh enregistrée par un compteur Gazpar","permissions":{"grdf data":{"description":"Requises pour accéder et stocker les données collectées par le compteur Gazpar et exposées par les API GRDF (consommations de gaz au jour, mois et année)."},"accounts":{"description":"Utilisé pour accéder à vos données de consommation."}}},"en":{"short_description":"Gas consumption data fetched from GRDF ADICT API","long_description":"This konnector fetches the data curve in kWh gathered by Gaspar device.","permissions":{"grdf data":{"description":"Required to access and store the data collected by the Gazpar meter and exposed by GRDF APIs (daily, monthly and yearly consumption)."},"accounts":{"description":"Used to access your consumption data."}}}},"manifest_version":"2","on_delete_account":"onDeleteAccount.js"}; if (false) {} @@ -234712,9 +234732,10 @@ module.exports = getDataGenericErrors /* 1769 */ /***/ (function(module, exports, __webpack_require__) { -function isDev() { +function isLocal() { return ( - false || "none" === 'test' + false || + "none" === 'standalone' ) } @@ -234722,14 +234743,14 @@ function isDev() { * Verify if it's an alpha URL * @returns {boolean} */ -function isAlpha() { +function isDev() { return ( process.env.COZY_URL.includes('alpha') || process.env.COZY_URL.includes('cozy.tools') ) } -module.exports = { isDev, isAlpha } +module.exports = { isLocal, isDev } /***/ }), @@ -276100,7 +276121,7 @@ var SpanStatus; (function (SpanStatus) { /* 2047 */ /***/ (function(module) { -module.exports = JSON.parse("{\"name\":\"grdf\",\"version\":\"1.2.1\",\"description\":\"\",\"repository\":{\"type\":\"git\",\"url\":\"https://forge.grandlyon.com/web-et-numerique/llle_project/grdf-konnector.git\"},\"keywords\":[],\"author\":\"Grand Lyon\",\"license\":\"AGPL-3.0\",\"main\":\"./src/index.js\",\"eslintConfig\":{\"extends\":[\"cozy-app\"]},\"eslintIgnore\":[\"build\",\"data\"],\"husky\":{\"hooks\":{\"pre-commit\":\"yarn lint\"}},\"jest\":{\"setupFiles\":[\"./setupTests.js\"]},\"scripts\":{\"start\":\"node ./src/index.js\",\"dev\":\"cozy-konnector-dev\",\"standalone\":\"cozy-konnector-standalone\",\"onDeleteAccount\":\"cozy-konnector-dev src/onDeleteAccount.js\",\"pretest\":\"npm run clean\",\"test:cov\":\"jest --coverage\",\"test\":\"jest\",\"release\":\"standard-version --no-verify\",\"clean\":\"rm -rf ./data\",\"build\":\"webpack\",\"lint\":\"eslint --fix .\",\"deploy\":\"git-directory-deploy --directory build/ --branch ${DEPLOY_BRANCH:-build}\",\"deploy-dev\":\"git-directory-deploy --directory build/ --branch ${DEPLOY_BRANCH:-build-dev}\",\"deploy-test\":\"git-directory-deploy --directory build/ --branch ${DEPLOY_BRANCH:-build-test}\",\"cozyPublish\":\"cozy-app-publish --token $REGISTRY_TOKEN --build-commit $(git rev-parse ${DEPLOY_BRANCH:-build})\",\"travisDeployKey\":\"./bin/generate_travis_deploy_key\"},\"dependencies\":{\"@sentry/node\":\"^7.28.1\",\"@sentry/tracing\":\"^7.28.1\",\"axios\":\"^0.20.0\",\"cozy-client\":\"23.22.0\",\"cozy-konnector-libs\":\"4.34.5\",\"husky\":\"4.3.0\",\"jest\":\"^28.1.3\",\"jest-fetch-mock\":\"^3.0.3\",\"jsonwebtoken\":\"^8.5.1\",\"moment\":\"^2.29.0\",\"moment-timezone\":\"^0.5.31\"},\"devDependencies\":{\"copy-webpack-plugin\":\"6.1.1\",\"cozy-app-publish\":\"0.25.0\",\"cozy-jobs-cli\":\"1.13.6\",\"eslint-config-cozy-app\":\"1.6.0\",\"git-directory-deploy\":\"1.5.1\",\"husky\":\"4.3.0\",\"jest-junit\":\"^14.0.0\",\"standard-version\":\"^9.5.0\",\"svgo\":\"1.3.2\",\"webpack\":\"4.44.2\",\"webpack-cli\":\"3.3.12\"}}"); +module.exports = JSON.parse("{\"name\":\"grdf\",\"version\":\"1.2.0\",\"description\":\"\",\"repository\":{\"type\":\"git\",\"url\":\"https://forge.grandlyon.com/web-et-numerique/llle_project/grdf-konnector.git\"},\"keywords\":[],\"author\":\"Grand Lyon\",\"license\":\"AGPL-3.0\",\"main\":\"./src/index.js\",\"eslintConfig\":{\"extends\":[\"cozy-app\"]},\"eslintIgnore\":[\"build\",\"data\"],\"husky\":{\"hooks\":{\"pre-commit\":\"yarn lint\"}},\"jest\":{\"setupFiles\":[\"./setupTests.js\"]},\"scripts\":{\"start\":\"node ./src/index.js\",\"dev\":\"cozy-konnector-dev\",\"standalone\":\"cozy-konnector-standalone\",\"onDeleteAccount:standalone\":\"cozy-konnector-standalone src/onDeleteAccount.js\",\"onDeleteAccount\":\"cozy-konnector-dev src/onDeleteAccount.js\",\"pretest\":\"npm run clean\",\"test:cov\":\"jest --coverage\",\"test\":\"jest\",\"release\":\"standard-version --no-verify\",\"clean\":\"rm -rf ./data\",\"build\":\"webpack\",\"lint\":\"eslint --fix .\",\"deploy\":\"git-directory-deploy --directory build/ --branch ${DEPLOY_BRANCH:-build}\",\"deploy-dev\":\"git-directory-deploy --directory build/ --branch ${DEPLOY_BRANCH:-build-dev}\",\"deploy-test\":\"git-directory-deploy --directory build/ --branch ${DEPLOY_BRANCH:-build-test}\",\"cozyPublish\":\"cozy-app-publish --token $REGISTRY_TOKEN --build-commit $(git rev-parse ${DEPLOY_BRANCH:-build})\",\"travisDeployKey\":\"./bin/generate_travis_deploy_key\"},\"dependencies\":{\"@sentry/node\":\"^7.28.1\",\"@sentry/tracing\":\"^7.28.1\",\"axios\":\"^0.20.0\",\"cozy-client\":\"23.22.0\",\"cozy-konnector-libs\":\"4.34.5\",\"husky\":\"4.3.0\",\"jest\":\"^28.1.3\",\"jsonwebtoken\":\"^8.5.1\",\"moment\":\"^2.29.0\",\"moment-timezone\":\"^0.5.31\"},\"devDependencies\":{\"copy-webpack-plugin\":\"6.1.1\",\"cozy-app-publish\":\"0.25.0\",\"cozy-jobs-cli\":\"1.13.6\",\"eslint-config-cozy-app\":\"1.6.0\",\"git-directory-deploy\":\"1.5.1\",\"husky\":\"4.3.0\",\"jest-junit\":\"^14.0.0\",\"standard-version\":\"^9.5.0\",\"svgo\":\"1.3.2\",\"webpack\":\"4.44.2\",\"webpack-cli\":\"3.3.12\"}}"); /***/ }) /******/ ]); \ No newline at end of file diff --git a/manifest.konnector b/manifest.konnector index 75e5a9da53156a72dd0ad0058122c84e283a5dd8..a5a3348aaad852260f7d6839256edcb717a4a72e 100644 --- a/manifest.konnector +++ b/manifest.konnector @@ -1,5 +1,5 @@ { - "version": "1.2.1", + "version": "1.2.0", "name": "GRDF", "type": "konnector", "language": "node", diff --git a/onDeleteAccount.js b/onDeleteAccount.js index 912dbde6bfd168d68c269d5cd179d6718ee1cd2e..dbb4085640ebbc749fe4d8420f867c5288954d9a 100644 --- a/onDeleteAccount.js +++ b/onDeleteAccount.js @@ -151333,7 +151333,7 @@ const fs = __webpack_require__(167); const path = __webpack_require__(160); -let manifest = typeof {"version":"1.2.1","name":"GRDF","type":"konnector","language":"node","icon":"icon.png","slug":"grdfgrandlyon","source":"https://forge.grandlyon.com/web-et-numerique/llle_project/grdf-konnector.git","editor":"Métropole de Lyon","vendor_link":"https://www.grdf.fr/","categories":["energy"],"frequency":"daily","fields":{"access_token":{"type":"hidden"},"refresh_token":{"type":"hidden"}},"oauth":{},"data_types":[],"screenshots":[],"permissions":{"accounts":{"type":"io.cozy.accounts"},"grdf data":{"type":"com.grandlyon.grdf.*"}},"developer":{"name":"Métropole de Lyon","url":"https://www.grandlyon.com/"},"langs":["fr"],"locales":{"fr":{"short_description":"Récupère vos donnéees de courbe de charge depuis l'API GRDF ADICT","long_description":"Ce connecteur récupère la courbe d'energie en kWh enregistrée par un compteur Gazpar","permissions":{"grdf data":{"description":"Requises pour accéder et stocker les données collectées par le compteur Gazpar et exposées par les API GRDF (consommations de gaz au jour, mois et année)."},"accounts":{"description":"Utilisé pour accéder à vos données de consommation."}}},"en":{"short_description":"Gas consumption data fetched from GRDF ADICT API","long_description":"This konnector fetches the data curve in kWh gathered by Gaspar device.","permissions":{"grdf data":{"description":"Required to access and store the data collected by the Gazpar meter and exposed by GRDF APIs (daily, monthly and yearly consumption)."},"accounts":{"description":"Used to access your consumption data."}}}},"manifest_version":"2","on_delete_account":"onDeleteAccount.js"} === 'undefined' ? {} : {"version":"1.2.1","name":"GRDF","type":"konnector","language":"node","icon":"icon.png","slug":"grdfgrandlyon","source":"https://forge.grandlyon.com/web-et-numerique/llle_project/grdf-konnector.git","editor":"Métropole de Lyon","vendor_link":"https://www.grdf.fr/","categories":["energy"],"frequency":"daily","fields":{"access_token":{"type":"hidden"},"refresh_token":{"type":"hidden"}},"oauth":{},"data_types":[],"screenshots":[],"permissions":{"accounts":{"type":"io.cozy.accounts"},"grdf data":{"type":"com.grandlyon.grdf.*"}},"developer":{"name":"Métropole de Lyon","url":"https://www.grandlyon.com/"},"langs":["fr"],"locales":{"fr":{"short_description":"Récupère vos donnéees de courbe de charge depuis l'API GRDF ADICT","long_description":"Ce connecteur récupère la courbe d'energie en kWh enregistrée par un compteur Gazpar","permissions":{"grdf data":{"description":"Requises pour accéder et stocker les données collectées par le compteur Gazpar et exposées par les API GRDF (consommations de gaz au jour, mois et année)."},"accounts":{"description":"Utilisé pour accéder à vos données de consommation."}}},"en":{"short_description":"Gas consumption data fetched from GRDF ADICT API","long_description":"This konnector fetches the data curve in kWh gathered by Gaspar device.","permissions":{"grdf data":{"description":"Required to access and store the data collected by the Gazpar meter and exposed by GRDF APIs (daily, monthly and yearly consumption)."},"accounts":{"description":"Used to access your consumption data."}}}},"manifest_version":"2","on_delete_account":"onDeleteAccount.js"}; +let manifest = typeof {"version":"1.2.0","name":"GRDF","type":"konnector","language":"node","icon":"icon.png","slug":"grdfgrandlyon","source":"https://forge.grandlyon.com/web-et-numerique/llle_project/grdf-konnector.git","editor":"Métropole de Lyon","vendor_link":"https://www.grdf.fr/","categories":["energy"],"frequency":"daily","fields":{"access_token":{"type":"hidden"},"refresh_token":{"type":"hidden"}},"oauth":{},"data_types":[],"screenshots":[],"permissions":{"accounts":{"type":"io.cozy.accounts"},"grdf data":{"type":"com.grandlyon.grdf.*"}},"developer":{"name":"Métropole de Lyon","url":"https://www.grandlyon.com/"},"langs":["fr"],"locales":{"fr":{"short_description":"Récupère vos donnéees de courbe de charge depuis l'API GRDF ADICT","long_description":"Ce connecteur récupère la courbe d'energie en kWh enregistrée par un compteur Gazpar","permissions":{"grdf data":{"description":"Requises pour accéder et stocker les données collectées par le compteur Gazpar et exposées par les API GRDF (consommations de gaz au jour, mois et année)."},"accounts":{"description":"Utilisé pour accéder à vos données de consommation."}}},"en":{"short_description":"Gas consumption data fetched from GRDF ADICT API","long_description":"This konnector fetches the data curve in kWh gathered by Gaspar device.","permissions":{"grdf data":{"description":"Required to access and store the data collected by the Gazpar meter and exposed by GRDF APIs (daily, monthly and yearly consumption)."},"accounts":{"description":"Used to access your consumption data."}}}},"manifest_version":"2","on_delete_account":"onDeleteAccount.js"} === 'undefined' ? {} : {"version":"1.2.0","name":"GRDF","type":"konnector","language":"node","icon":"icon.png","slug":"grdfgrandlyon","source":"https://forge.grandlyon.com/web-et-numerique/llle_project/grdf-konnector.git","editor":"Métropole de Lyon","vendor_link":"https://www.grdf.fr/","categories":["energy"],"frequency":"daily","fields":{"access_token":{"type":"hidden"},"refresh_token":{"type":"hidden"}},"oauth":{},"data_types":[],"screenshots":[],"permissions":{"accounts":{"type":"io.cozy.accounts"},"grdf data":{"type":"com.grandlyon.grdf.*"}},"developer":{"name":"Métropole de Lyon","url":"https://www.grandlyon.com/"},"langs":["fr"],"locales":{"fr":{"short_description":"Récupère vos donnéees de courbe de charge depuis l'API GRDF ADICT","long_description":"Ce connecteur récupère la courbe d'energie en kWh enregistrée par un compteur Gazpar","permissions":{"grdf data":{"description":"Requises pour accéder et stocker les données collectées par le compteur Gazpar et exposées par les API GRDF (consommations de gaz au jour, mois et année)."},"accounts":{"description":"Utilisé pour accéder à vos données de consommation."}}},"en":{"short_description":"Gas consumption data fetched from GRDF ADICT API","long_description":"This konnector fetches the data curve in kWh gathered by Gaspar device.","permissions":{"grdf data":{"description":"Required to access and store the data collected by the Gazpar meter and exposed by GRDF APIs (daily, monthly and yearly consumption)."},"accounts":{"description":"Used to access your consumption data."}}}},"manifest_version":"2","on_delete_account":"onDeleteAccount.js"}; if (false) {} @@ -234304,7 +234304,31 @@ module.exports = getAccountId /***/ }), /* 1768 */, -/* 1769 */, +/* 1769 */ +/***/ (function(module, exports, __webpack_require__) { + +function isLocal() { + return ( + false || + "none" === 'standalone' + ) +} + +/** + * Verify if it's an alpha URL + * @returns {boolean} + */ +function isDev() { + return ( + process.env.COZY_URL.includes('alpha') || + process.env.COZY_URL.includes('cozy.tools') + ) +} + +module.exports = { isLocal, isDev } + + +/***/ }), /* 1770 */ /***/ (function(module, exports, __webpack_require__) { @@ -275683,165 +275707,179 @@ moment.locale('fr') // set the language moment.tz.setDefault('Europe/Paris') // set the timezone const Sentry = __webpack_require__(1910) // eslint-disable-next-line -const Tracing = __webpack_require__(2011) // Needed for tracking performance in Sentry +const Tracing = __webpack_require__(2011) +const MAX_RETRIES = 5 // Maximum number of retries +const INITIAL_BACKOFF = 1000 // Initial backoff interval in ms + +async function getAccessToken(accountId, accountRev) { + log('info', `getAccessToken: ${accountId} - ${accountRev}`) + let body = await cozyClient.fetchJSON( + 'GET', + `/data/io.cozy.accounts/${accountId}?rev=${accountRev}` + ) + if (body.oauth.access_token) { + return body + } else { + throw new Error('cozyClient.fetchJson account_rev has encountered an error') + } +} + +async function fetchNewAccessToken(accountSecret) { + log( + 'info', + `fetchNewAccessToken: ${accountSecret} - ${accountSecret.client_id}` + ) + + let url = + 'https://sofit-sso-oidc.grdf.fr/openam/oauth2/realms/externeGrdf/access_token' + return fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: new URLSearchParams({ + grant_type: 'client_credentials', + client_id: `${accountSecret.client_id}`, + client_secret: `${accountSecret.client_secret}`, + scope: '/adict/v2' + }), + redirect: 'follow' + }) + .then(async response => { + if (response.status !== 200) { + throw new Error(response.status + ' - ' + response.statusText) + } + return response.text() + }) + .then(result => { + // split the result into an array of strings containing a single json object + // then check if the resulting json has an access_token field + // return it if it does or return undefined + return result.match(/.+/g).map(jsonString => { + const jsonObject = JSON.parse(jsonString) + if (jsonObject.access_token) { + return jsonObject.access_token + } + }) + }) + .catch(error => { + log('debug', 'Error from get access_token [onDeleteAccount]') + throw error + }) +} + +async function fetchDroitAccessGrdf(token, pce) { + return fetch('https://api.grdf.fr/adict/v2/droits_acces', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/x-ndjson', + Authorization: 'Bearer ' + token + }, + body: JSON.stringify({ + role_tiers: ['AUTORISE_CONTRAT_FOURNITURE'], + etat_droit_acces: ['Active'], + id_pce: [pce] + }), + redirect: 'follow' + }) + .then(async response => { + if (response.status !== 200) { + throw new Error(response.status + ' - ' + response.statusText) + } + return response.text() + }) + .then(response => { + // Split the response into an array of strings, each containing a single JSON object. + // Check if each JSON object has an "id_droit_acces" field. + // Return an array of "id_droit_acces" values, or undefined if the field is missing. + return response.match(/.+/g).map(jsonString => { + const jsonObject = JSON.parse(jsonString) + if (jsonObject.id_droit_acces !== null) { + return jsonObject.id_droit_acces + } + }) + }) + .catch(error => { + log('debug', 'Error from get droits_access') + throw error + }) +} + +async function deleteUserConsent(token, access) { + let url = 'https://api.grdf.fr/adict/v2/droit_acces/' + access + try { + const response = await fetch(url, { + method: 'PATCH', + headers: { + 'Cache-Control': 'no-cache', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + token + }, + body: JSON.stringify({}), + redirect: 'follow' + }) + if (response.status !== 200) { + throw new Error(response.status + ' - ' + response.statusText) + } + log('debug', 'Active consent was successfully removed') + return true + } catch (error) { + log('debug', 'Error from delete droits_access') + throw error + } +} async function onDeleteAccount(accountId) { - const MAX_RETRIES = 5 // Maximum number of retries - const INITIAL_BACKOFF = 1000 // Initial backoff interval in ms let retries = 0 let backoff = INITIAL_BACKOFF let accountDeleted = false - + log('info', 'Deleting account ...') while (retries < MAX_RETRIES && !accountDeleted) { try { const accountRev = getAccountRev() - if (accountRev) { - let body = '' - let access_token = '' - body = await cozyClient.fetchJSON( - 'GET', - `/data/io.cozy.accounts/${accountId}?rev=${accountRev}` + if (!accountRev) { + throw new Error( + 'No account revision was found, something went wrong during the deletion of said account' ) - if (body.oauth.access_token) { - access_token = body.oauth.access_token - } else { - throw new Error( - 'cozyClient.fetchJson account_rev has encountered an error' - ) - } - if (moment().diff(body.oauth.expires_at) > 0) { - // token is expired, need a new one. grdf does not provide a refresh token - // so we request a new one from a client_credentials query - // first we fetch credentials secrets from account-type - const accountSecret = getAccountSecret() - if (accountSecret) { - var myTokenHeaders = new Headers() - myTokenHeaders.append( - 'Content-Type', - 'application/x-www-form-urlencoded' - ) - var urlencoded = new URLSearchParams() - urlencoded.append('grant_type', 'client_credentials') - urlencoded.append('client_id', accountSecret.client_id) - urlencoded.append('client_secret', accountSecret.client_secret) - urlencoded.append('scope', '/adict/v2') - - var requestOptionsToken = { - method: 'POST', - headers: myTokenHeaders, - body: urlencoded, - redirect: 'follow' - } + } - access_token = await fetch( - 'https://sofit-sso-oidc.grdf.fr/openam/oauth2/realms/externeGrdf/access_token', - requestOptionsToken - ) - .then(async response => { - if (response.status !== 200) { - throw new Error(response.status + ' - ' + response.statusText) - } - return response.text() - }) - .then(result => { - return result.match(/.+/g).map(s => { - result = JSON.parse(s) - if (result.access_token) { - return result.access_token - } - }) - }) - .catch(error => { - log('debug', 'Error from get access_token [onDeleteAccount]') - throw error - }) - } else { - throw new Error( - 'Access Token is expired and konnector failed to get a new one' - ) - } - } + const oauthAccount = await getAccessToken(accountId, accountRev) + let fetchedNewAccessToken = oauthAccount.oauth.access_token - var myHeaders = new Headers() - myHeaders.append('Content-Type', 'application/json') - myHeaders.append('Accept', 'application/x-ndjson') - myHeaders.append('Authorization', 'Bearer ' + access_token) - var raw = JSON.stringify({ - role_tiers: ['AUTORISE_CONTRAT_FOURNITURE'], - etat_droit_acces: ['Active'], - id_pce: [body.oauth_callback_results.pce] - }) - var requestOptions = { - method: 'POST', - headers: myHeaders, - body: raw, - redirect: 'follow' + if (moment().diff(oauthAccount.oauth.expires_at) <= 0) { + log('info', 'Token still valid, no need to fetch a new one') + } else { + log('info', 'expired token, need a new one') + // token is expired, need a new one. + // we request a new one from a client_credentials query + // with credentials secrets from account-type + const accountSecret = getAccountSecret() + if (!accountSecret) { + throw new Error( + 'Access Token is expired and konnector failed to get a new one' + ) } + fetchedNewAccessToken = await fetchNewAccessToken(accountSecret) + } - let accessRights = await fetch( - 'https://api.grdf.fr/adict/v2/droits_acces', - requestOptions - ) - .then(async response => { - if (response.status !== 200) { - throw new Error(response.status + ' - ' + response.statusText) - } - return response.text() - }) - .then(result => { - return result.match(/.+/g).map(s => { - result = JSON.parse(s) - if (result.id_droit_acces !== null) { - return result.id_droit_acces - } - }) - }) - .catch(error => { - log('debug', 'Error from get droits_access') - throw error - }) - - // remove unwanted commas - accessRights = accessRights.toString().replace(/,\s*$/, '') - if (accessRights) { - var myDeleteHeaders = new Headers() - myDeleteHeaders.append('Cache-Control', 'no-cache') - myDeleteHeaders.append('Content-Type', 'application/json') - myDeleteHeaders.append('Authorization', 'Bearer ' + access_token) - - var deleteRaw = JSON.stringify({}) - - var deleteRequestOptions = { - method: 'PATCH', - headers: myDeleteHeaders, - body: deleteRaw, - redirect: 'follow' - } + let accessRights = await fetchDroitAccessGrdf( + fetchedNewAccessToken, + oauthAccount.oauth_callback_results.pce + ) - var url = 'https://api.grdf.fr/adict/v2/droit_acces/' + accessRights + // remove unwanted commas + accessRights = accessRights.toString().replace(/,\s*$/, '') - await fetch(url, deleteRequestOptions) - .then(async response => { - if (response.status !== 200) { - throw new Error(response.status + ' - ' + response.statusText) - } - log('debug', 'Active consent was successfully removed') - accountDeleted = true - return 'Account deleted successfully' - }) - .catch(error => { - log('debug', 'Error from delete droits_access') - throw error - }) - } else { - log('debug', 'No active consent') - throw new Error('No active access right was found for given user') - } - } else { - throw new Error( - 'No account revision was found, something went wrong during the deletion of said account' - ) + if (!accessRights) { + log('debug', 'No active consent') + throw new Error('No active access right was found for given user') } + + accountDeleted = await deleteUserConsent( + fetchedNewAccessToken, + accessRights + ) } catch (error) { // If the API call fails, log the error and retry with an increased backoff interval log( @@ -275868,14 +275906,13 @@ onDeleteAccount(accountId).then( () => { log( 'info', - `onDeleteAccount: Successfully retrieved grdf account from account doctype.` + `onDeleteAccount GRDF: Successfully delete consent and account.` ) }, err => { - log( - 'error', - `onDeleteAccount: An error occurred during getGrdfAccountInfos script: ${err.message}` - ) + const errorMessage = `onDeleteAccount GRDF: An error occurred during script: ${err.message}` + log('error', errorMessage) + Sentry.captureException(errorMessage) } ) @@ -275886,13 +275923,20 @@ module.exports = { onDeleteAccount } /* 2049 */ /***/ (function(module, exports, __webpack_require__) { +const { log } = __webpack_require__(1) +const { isLocal } = __webpack_require__(1769) +const Sentry = __webpack_require__(1910) + function getAccountRev() { + log('info', `getAccountRev: ${JSON.stringify(process.env.COZY_FIELDS)}`) try { - return false - ? undefined + return isLocal() + ? 'fakeAccountRev' : JSON.parse(process.env.COZY_FIELDS).account_rev } catch (err) { - throw new Error(`You must provide 'account' in COZY_FIELDS: ${err.message}`) + const errorMessage = `You must provide 'account' in COZY_FIELDS: ${err.message}` + Sentry.captureException(errorMessage) + throw new Error(errorMessage) } } @@ -275903,15 +275947,20 @@ module.exports = getAccountRev /* 2050 */ /***/ (function(module, exports, __webpack_require__) { +const { log } = __webpack_require__(1) +const { isLocal } = __webpack_require__(1769) +const Sentry = __webpack_require__(1910) + function getAccountSecret() { + log('info', `getAccountSecret`) try { - return false - ? undefined + return isLocal() + ? JSON.parse(process.env.COZY_FIELDS) : JSON.parse(process.env.COZY_PARAMETERS).secret } catch (err) { - throw new Error( - `You must provide 'account-types' in COZY_PARAMETERS: ${err.message}` - ) + const errorMessage = `You must provide 'account-types' in COZY_PARAMETERS: ${err.message}` + Sentry.captureException(errorMessage) + throw new Error(errorMessage) } } diff --git a/package.json b/package.json index b8483318b5ffaab5280769fded78a777a084aaeb..7d8f1d826bc16d472cbccfff6b99a24861361461 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grdf", - "version": "1.2.1", + "version": "1.2.0", "description": "", "repository": { "type": "git", @@ -33,6 +33,7 @@ "start": "node ./src/index.js", "dev": "cozy-konnector-dev", "standalone": "cozy-konnector-standalone", + "onDeleteAccount:standalone": "cozy-konnector-standalone src/onDeleteAccount.js", "onDeleteAccount": "cozy-konnector-dev src/onDeleteAccount.js", "pretest": "npm run clean", "test:cov": "jest --coverage", @@ -55,7 +56,6 @@ "cozy-konnector-libs": "4.34.5", "husky": "4.3.0", "jest": "^28.1.3", - "jest-fetch-mock": "^3.0.3", "jsonwebtoken": "^8.5.1", "moment": "^2.29.0", "moment-timezone": "^0.5.31"