diff --git a/manifest.konnector b/manifest.konnector
index 65f6526..8a8f2cf 100644
--- a/manifest.konnector
+++ b/manifest.konnector
@@ -11,17 +11,8 @@
"categories": [
"education"
],
- "fields": {
- "pronote_url": {
- "type": "text"
- },
- "login": {
- "type": "text"
- },
- "password": {
- "type": "password"
- }
- },
+ "fields": {},
+ "clientSide": true,
"folders": [
{
"defaultDir": "$administrative/$konnector/$account"
@@ -32,7 +23,6 @@
"event",
"document"
],
- "screenshots": [],
"permissions": {
"files": {
"type": "io.cozy.files"
@@ -57,17 +47,8 @@
"langs": [
"fr"
],
- "frequency": "daily",
"locales": {
"fr": {
- "fields": {
- "login": {
- "label": "Identifiant de l'élève"
- },
- "pronote_url": {
- "label": "URL PRONOTE de l'établissement"
- }
- },
"short_description": "Accédez à l'ensemble de vos données scolaires, et conservez-les pour toujours.",
"long_description": "Ce connecteur **sauvegarde vos données d'élève** depuis le service Pronote à l'aide de vos identifiants d'élève et les sécurise **pour votre usage personnel immédiat ou futur.**\n\n
Vous pourrez visualiser ces données **avec votre mobile ou votre ordinateur**, dans votre Cozy, notamment au travers d'applications comme Drive, Papillon ou MesPapiers.\n\n
Vos données suivantes sont ainsi regroupées et **sauvegardées automatiquement** :\n\n- Emploi du temps\n- Devoirs & travaux rendus\n- Notes et corrections\n- Bulletins scolaires\n- Notifications de retard ou d'absence\n- ... Et même votre inénarrable photo de profil 🙂 !",
"permissions": {
@@ -89,14 +70,6 @@
}
},
"en": {
- "fields": {
- "login": {
- "label": "Student identifier"
- },
- "pronote_url": {
- "label": "School's Pronote URL "
- }
- },
"short_description": "Access all your school data and keep it forever.",
"long_description": "This connector **saves your student data** from the Pronote service using your student identifiers and secures it **for your immediate or future personal use.**\n\n
You can view these data **on your mobile or computer**, in your Cozy, particularly through applications like Drive, Papillon, or MesPapiers.\n\n
These data are **automatically gathered:**\n\n- Timetable\n- Homework & assignments submitted\n- Grades and corrections\n- School report cards\n- Notifications of delay or absence\n- ... And even your unforgettable profile picture 🙂!",
"permissions": {
diff --git a/package.json b/package.json
index 26b12e3..df92723 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "name": "cozy-konnector-pronote",
+ "name": "pronote",
"version": "2.10.0",
"description": "",
"repository": {
@@ -27,20 +27,23 @@
"start": "node ./src/index.js",
"dev": "cozy-konnector-dev",
"standalone": "cozy-konnector-standalone",
- "pretest": "npm run clean",
"clean": "rm -rf ./data",
- "build": "webpack",
+ "build:client": "webpack --config ./webpack.config.client.js",
+ "build:server": "webpack --config ./webpack.config.server.js",
+ "build": "yarn build:client && yarn build:server",
"lint": "eslint --fix .",
"deploy": "git-directory-deploy --directory build/ --branch ${DEPLOY_BRANCH:-build} --repo=${DEPLOY_REPOSITORY:-$npm_package_repository_url}",
"cozyPublish": "cozy-app-publish --token $REGISTRY_TOKEN --build-commit $(git rev-parse ${DEPLOY_BRANCH:-build})",
"travisDeployKey": "./bin/generate_travis_deploy_key"
},
"dependencies": {
+ "@cozy/minilog": "1.0.0",
+ "cozy-clisk": "^0.38.0",
"cozy-client": "^48.8.0",
"cozy-flags": "^4.0.0",
"cozy-konnector-libs": "5.11.0",
"globals": "^15.8.0",
- "pawnote": "^0.22.1",
+ "pawnote": "^1.2.2",
"pronote-api-maintained": "^3.1.0"
},
"devDependencies": {
diff --git a/src/client.js b/src/client.js
new file mode 100644
index 0000000..5fccbf7
--- /dev/null
+++ b/src/client.js
@@ -0,0 +1,208 @@
+import { ContentScript } from 'cozy-clisk/dist/contentscript'
+import Minilog from '@cozy/minilog'
+import waitFor, { TimeoutError } from 'p-wait-for'
+const log = Minilog('ContentScript')
+Minilog.enable()
+
+const UUID = uuid()
+
+monkeyPatch(UUID)
+
+class PronoteContentScript extends ContentScript {
+ async ensureAuthenticated({ account, trigger }) {
+ this.log('info', '🤖 ensureAuthenticated')
+ const isLastJobError =
+ trigger?.current_state?.last_failure >
+ trigger?.current_state?.last_success
+ this.log('debug', 'isLastJobError: ' + isLastJobError)
+ const lastJobError = trigger?.current_state?.last_error
+ this.log('debug', 'lastJobError: ' + lastJobError)
+
+ await this.setWorkerState({ incognito: true })
+ let url = account?.data?.url
+ this.log('debug', 'url: ' + url)
+ if (!url || (isLastJobError && lastJobError === 'LOGIN_FAILED')) {
+ await this.setWorkerState({ visible: true })
+ await this.goto(
+ 'https://demo.index-education.net/pronote/mobile.eleve.html'
+ )
+ await this.waitForElementInWorker('nav')
+ url = await this.evaluateInWorker(getUrlFromUser)
+ await this.setWorkerState({ visible: false })
+
+ await this.goto(
+ url + '/infoMobileApp.json?id=0D264427-EEFC-4810-A9E9-346942A862A4'
+ )
+ await new Promise(resolve => window.setTimeout(resolve, 2000))
+ await this.evaluateInWorker(function (UUID) {
+ const PRONOTE_COOKIE_EXPIRED = new Date(0).toUTCString()
+ const PRONOTE_COOKIE_VALIDATION_EXPIRES = new Date(
+ new Date().getTime() + 5 * 60 * 1000
+ ).toUTCString()
+ const PRONOTE_COOKIE_LANGUAGE_EXPIRES = new Date(
+ new Date().getTime() + 365 * 24 * 60 * 60 * 1000
+ ).toUTCString()
+ const json = JSON.parse(document.body.innerText)
+ const lJetonCas = !!json && !!json.CAS && json.CAS.jetonCAS
+ document.cookie = `appliMobile=; expires=${PRONOTE_COOKIE_EXPIRED}`
+
+ if (lJetonCas) {
+ document.cookie = `validationAppliMobile=${lJetonCas}; expires=${PRONOTE_COOKIE_VALIDATION_EXPIRES}`
+ document.cookie = `uuidAppliMobile=${UUID}; expires=${PRONOTE_COOKIE_VALIDATION_EXPIRES}`
+ document.cookie = `ielang=1036; expires=${PRONOTE_COOKIE_LANGUAGE_EXPIRES}`
+ }
+ }, UUID)
+ await this.goto(`${url}/mobile.eleve.html?fd=1`)
+
+ await this.setWorkerState({ visible: true })
+ await this.runInWorkerUntilTrue({
+ method: 'waitForLoginState'
+ })
+ await this.setWorkerState({ visible: false })
+ const loginState = await this.evaluateInWorker(() => window.loginState)
+
+ const loginTokenParams = {
+ url,
+ kind: 6,
+ login: loginState.login,
+ token: loginState.mdp,
+ deviceUUID: UUID
+ }
+ this.store = loginTokenParams
+ } else {
+ this.store = account?.data
+ }
+
+ return true
+ }
+
+ async ensureNotAuthenticated() {
+ // always true in incognito mode
+ return true
+ }
+
+ async getUserDataFromWebsite() {
+ this.log('info', '🤖 getUserDataFromWebsite')
+ return {
+ sourceAccountIdentifier: this.store.login
+ }
+ }
+
+ async fetch({ account }) {
+ this.log('info', '🤖 fetch')
+ if (!this.bridge) {
+ throw new Error(
+ 'No bridge is defined, you should call ContentScript.init before using this method'
+ )
+ }
+
+ await this.bridge.call('saveAccountData', this.store)
+ const jobResult = await this.bridge.call('runServerJob', {
+ mode: 'pronote-server',
+ account: account._id
+ })
+ if (jobResult.error) {
+ throw new Error(jobResult.error)
+ }
+ }
+
+ async waitForLoginState() {
+ this.log('debug', '🔧 waitForLoginState')
+ await waitFor(
+ () => {
+ return Boolean(window.loginState)
+ },
+ {
+ interval: 1000,
+ timeout: {
+ milliseconds: 60 * 1000,
+ message: new TimeoutError(
+ `waitForLoginState timed out after ${60 * 1000}ms`
+ )
+ }
+ }
+ )
+ return true
+ }
+}
+
+const connector = new PronoteContentScript()
+connector
+ .init({ additionalExposedMethodsNames: ['waitForLoginState'] })
+ .catch(err => {
+ log.warn(err)
+ })
+
+function getUrlFromUser() {
+ document.querySelector('nav').remove()
+ document.querySelector('form').remove()
+ document.querySelector('main').innerHTML = `
+
+`
+
+ function cleanURL(url) {
+ let pronoteURL = url
+ if (
+ !pronoteURL.startsWith('https://') &&
+ !pronoteURL.startsWith('http://')
+ ) {
+ pronoteURL = `https://${pronoteURL}`
+ }
+
+ pronoteURL = new URL(pronoteURL)
+ // Clean any unwanted data from URL.
+ pronoteURL = new URL(
+ `${pronoteURL.protocol}//${pronoteURL.host}${pronoteURL.pathname}`
+ )
+
+ // Clear the last path if we're not main selection menu.
+ const paths = pronoteURL.pathname.split('/')
+ if (paths[paths.length - 1].includes('.html')) {
+ paths.pop()
+ }
+
+ // Rebuild URL with cleaned paths.
+ pronoteURL.pathname = paths.join('/')
+
+ // Return rebuilt URL without trailing slash.
+ return pronoteURL.href.endsWith('/')
+ ? pronoteURL.href.slice(0, -1)
+ : pronoteURL.href
+ }
+ return new Promise(resolve => {
+ const button = document.querySelector('#submitButton')
+ button.addEventListener('click', () => {
+ const url = cleanURL(document.querySelector('#url').value)
+ resolve(url)
+ })
+ })
+}
+
+function monkeyPatch(uuid) {
+ window.hookAccesDepuisAppli = function () {
+ this.passerEnModeValidationAppliMobile('', uuid)
+ }
+}
+
+function uuid() {
+ let dateTime = new Date().getTime()
+
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
+ const r = (dateTime + Math.random() * 16) % 16 | 0
+ dateTime = Math.floor(dateTime / 16)
+ return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
+ })
+
+ return uuid
+}
diff --git a/src/cozy/index.js b/src/cozy/index.js
index b063af9..383dd71 100644
--- a/src/cozy/index.js
+++ b/src/cozy/index.js
@@ -2,30 +2,26 @@ const identity = require('./pronote/identity')
const timetable = require('./pronote/timetable')
const homeworks = require('./pronote/homeworks')
const grades = require('./pronote/grades')
-const presence = require('./pronote/presence')
+// const presence = require('./pronote/presence')
const { log } = require('cozy-konnector-libs')
const handlers = {
identity,
timetable,
homeworks,
- grades,
- presence
+ grades
+ // presence
}
-async function cozy_save(type, pronote, fields, options = {}) {
- try {
- log('info', `🔁 Saving ${type}`)
+async function cozy_save(type, session, fields, options = {}) {
+ log('info', `🔁 Saving ${type}`)
- const handler = handlers[type]
- if (handler) {
- return handler(pronote, fields, options)
- }
-
- return false
- } catch (err) {
- throw new Error(err.message)
+ const handler = handlers[type]
+ if (handler) {
+ return handler(session, fields, options)
}
+
+ return false
}
module.exports = {
diff --git a/src/cozy/pronote/grades.js b/src/cozy/pronote/grades.js
index 3406ba5..afb3b0f 100644
--- a/src/cozy/pronote/grades.js
+++ b/src/cozy/pronote/grades.js
@@ -1,4 +1,5 @@
const { saveFiles, updateOrCreate, log } = require('cozy-konnector-libs')
+const { gradesOverview: getGradesOverview, gradebookPDF } = require('pawnote')
const {
DOCTYPE_GRADE,
@@ -11,16 +12,16 @@ const findObjectByPronoteString = require('../../utils/format/format_cours_name'
const preprocessDoctype = require('../../utils/format/preprocess_doctype')
const { queryAllGrades } = require('../../queries')
-async function get_grades(pronote) {
+async function get_grades(session) {
const allGrades = []
// Get all periods (trimesters, semesters, etc.)
- const periods = pronote.periods
+ const periods = session.instance.periods
// For each period, get all grades
for (const period of periods) {
// Get all grades for each period
- const gradesOverview = await pronote.getGradesOverview(period)
+ const gradesOverview = await getGradesOverview(session, period)
// For each grade, get the subject and add it to the list
for (const grade of gradesOverview.grades) {
@@ -46,13 +47,14 @@ async function get_grades(pronote) {
}
// For each average, get the subject and add it to the list
- for (const average of gradesOverview.averages) {
+ for (const averageKey of ['classAverage', 'overallAveragegradesOverview']) {
+ const average = gradesOverview[averageKey]
// Get the subject of the average
- const subject = average.subject
+ const subject = average?.subject
// Find the subject in the list of all subjects
const subjectIndex = allGrades.findIndex(
- item => item.subject.name === subject.name && item.period === period
+ item => item.subject?.name === subject?.name && item?.period === period
)
if (subjectIndex === -1) {
allGrades.push({
@@ -71,14 +73,14 @@ async function get_grades(pronote) {
return allGrades
}
-async function getReports(pronote) {
+async function getReports(session) {
const allReports = []
// Get all reports
- const reportPeriods = pronote.readPeriodsForGradesReport()
- for (const period of reportPeriods) {
+ const periods = session.instance.periods
+ for (const period of periods) {
try {
- const reportURL = await pronote.generateGradesReportPDF(period)
+ const reportURL = await gradebookPDF(session, period)
allReports.push({
period: period.name,
url: reportURL
@@ -112,7 +114,7 @@ async function saveReports(pronote, fields) {
}
const data = await saveFiles(filesToDownload, fields, {
- sourceAccount: this.accountId,
+ sourceAccount: fields.account,
sourceAccountIdentifier: fields.login,
concurrency: 3,
qualificationLabel: 'gradebook', // Grade report
@@ -122,9 +124,9 @@ async function saveReports(pronote, fields) {
return data
}
-async function createGrades(pronote, fields, options) {
+async function createGrades(session, fields, options) {
// Get all grades
- const grades = await get_grades(pronote, fields, options)
+ const grades = await get_grades(session, fields, options)
const data = []
// Get options
@@ -190,7 +192,7 @@ async function createGrades(pronote, fields, options) {
})
const data = await saveFiles(filesToDownload, fields, {
- sourceAccount: this.accountId,
+ sourceAccount: fields.account,
sourceAccountIdentifier: fields.login,
concurrency: 3,
qualificationLabel: 'other_work_document', // Given subject
@@ -241,7 +243,7 @@ async function createGrades(pronote, fields, options) {
})
const data = await saveFiles(filesToDownload, fields, {
- sourceAccount: this.accountId,
+ sourceAccount: fields.account,
sourceAccountIdentifier: fields.login,
concurrency: 3,
qualificationLabel: 'other_work_document', // Corrected subject
@@ -292,7 +294,7 @@ async function createGrades(pronote, fields, options) {
const scaleMult = 20 / evals[0].value.outOf // Necessary to normalise grades not on /20
avgGrades = evals[0].value.student * scaleMult
} else {
- avgGrades = grade.averages.student
+ avgGrades = grade.averages?.student
}
// Create the doctype
@@ -300,13 +302,13 @@ async function createGrades(pronote, fields, options) {
subject: processedCoursName,
sourceSubject: grade.subject?.name || 'Cours',
title: grade.period.name,
- startDate: new Date(grade.period.start).toISOString(),
- endDate: new Date(grade.period.end).toISOString(),
+ startDate: new Date(grade.period.startDate).toISOString(),
+ endDate: new Date(grade.period.endDate).toISOString(),
aggregation: {
avgGrades: avgGrades,
- avgClass: grade.averages.class_average,
- maxClass: grade.averages.max,
- minClass: grade.averages.min
+ avgClass: grade.averages?.class_average,
+ maxClass: grade.averages?.max,
+ minClass: grade.averages?.min
},
series: evals,
relationships:
@@ -328,45 +330,41 @@ async function createGrades(pronote, fields, options) {
return data
}
-async function init(pronote, fields, options) {
- try {
- let files = await createGrades(pronote, fields, options)
+async function init(session, fields, options) {
+ let files = await createGrades(session, fields, options)
- /*
+ /*
[Strategy] : don't update grades, they stay the same
*/
- const existing = await queryAllGrades()
-
- // remove duplicates in files
- const filtered = files.filter(file => {
- const found = existing.find(item => {
- return (
- item.series.length === file.series.length &&
- item.startDate === file.startDate &&
- item.subject === file.subject
- )
- })
+ const existing = await queryAllGrades()
- return !found
+ // remove duplicates in files
+ const filtered = files.filter(file => {
+ const found = existing.find(item => {
+ return (
+ item?.series?.length === file?.series?.length &&
+ item?.startDate === file?.startDate &&
+ item?.subject === file?.subject
+ )
})
- const res = await updateOrCreate(
- filtered,
- DOCTYPE_GRADE,
- ['startDate', 'subject'],
- {
- sourceAccount: this.accountId,
- sourceAccountIdentifier: fields.login
- }
- )
+ return !found
+ })
- await saveReports(pronote, fields, options)
+ const res = await updateOrCreate(
+ filtered,
+ DOCTYPE_GRADE,
+ ['startDate', 'subject'],
+ {
+ sourceAccount: fields.account,
+ sourceAccountIdentifier: fields.login
+ }
+ )
- return res
- } catch (error) {
- throw new Error(error)
- }
+ await saveReports(session, fields, options)
+
+ return res
}
module.exports = init
diff --git a/src/cozy/pronote/homeworks.js b/src/cozy/pronote/homeworks.js
index b0420d5..3a6dab1 100644
--- a/src/cozy/pronote/homeworks.js
+++ b/src/cozy/pronote/homeworks.js
@@ -1,4 +1,5 @@
const { saveFiles, updateOrCreate, log } = require('cozy-konnector-libs')
+const { resourcesFromIntervals } = require('pawnote')
const {
DOCTYPE_HOMEWORK,
@@ -13,17 +14,17 @@ const { createDates, getIcalDate } = require('../../utils/misc/createDates')
const save_resources = require('../../utils/stack/save_resources')
const { queryFilesByName, queryHomeworksByDate } = require('../../queries')
-async function get_homeworks(pronote, fields, options) {
+async function get_homeworks(session, fields, options) {
const dates = createDates(options)
- const overview = await pronote.getHomeworkForInterval(dates.from, dates.to)
+ const overview = await resourcesFromIntervals(session, dates.from, dates.to)
return {
homeworks: overview
}
}
-async function createHomeworks(pronote, fields, options) {
- const interval = await get_homeworks(pronote, fields, options)
+async function createHomeworks(session, fields, options) {
+ const interval = await get_homeworks(session, fields, options)
const homeworks = interval.homeworks
const data = []
@@ -94,7 +95,7 @@ async function createHomeworks(pronote, fields, options) {
})
const data = await saveFiles(filesToDownload, fields, {
- sourceAccount: this.accountId,
+ sourceAccount: fields.account,
sourceAccountIdentifier: fields.login,
concurrency: 3,
qualificationLabel: 'other_work_document', // Homework subject
@@ -142,74 +143,67 @@ async function createHomeworks(pronote, fields, options) {
return data
}
-async function init(pronote, fields, options) {
- try {
- let files = await createHomeworks(pronote, fields, options)
+async function init(session, fields, options) {
+ let files = await createHomeworks(session, fields, options)
- /*
+ /*
[Strategy] : don't update past homeworks, only update future homeworks
*/
- // get existing homeworks
- const existing = await queryHomeworksByDate(
- fields,
- options.dateFrom,
- options.dateTo
- )
-
- // remove duplicates in files
- const filtered = files.filter(file => {
- const found = existing.find(item => {
- // if item.cozyMetadata.updatedAt is less than today
- const updated = new Date(item.cozyMetadata.updatedAt)
- const today = new Date()
- today.setHours(0, 0, 0, 0)
- const updatedRecently = updated.getTime() > today.getTime()
-
- // if item is more recent than today
- if (new Date(item.dueDate) > new Date()) {
- if (!updatedRecently) {
- return false
- }
- }
+ // get existing homeworks
+ const existing = await queryHomeworksByDate(
+ fields,
+ options.dateFrom,
+ options.dateTo
+ )
- return item.label === file.label && item.start === file.start
- })
+ // remove duplicates in files
+ const filtered = files.filter(file => {
+ const found = existing.find(item => {
+ // if item.cozyMetadata.updatedAt is less than today
+ const updated = new Date(item.cozyMetadata.updatedAt)
+ const today = new Date()
+ today.setHours(0, 0, 0, 0)
+ const updatedRecently = updated.getTime() > today.getTime()
+
+ // if item is more recent than today
+ if (new Date(item.dueDate) > new Date()) {
+ if (!updatedRecently) {
+ return false
+ }
+ }
- return !found
+ return item.label === file.label && item.start === file.start
})
- // for existing files, add their _id and _rev
- for (const file of filtered) {
- const found = existing.find(item => {
- return item.label === file.label && item.start === file.start
- })
+ return !found
+ })
- if (found) {
- file._id = found._id
- file._rev = found._rev
- }
+ // for existing files, add their _id and _rev
+ for (const file of filtered) {
+ const found = existing.find(item => {
+ return item.label === file.label && item.start === file.start
+ })
+
+ if (found) {
+ file._id = found._id
+ file._rev = found._rev
}
+ }
- log(
- 'info',
- `${filtered.length} new homeworks to save out of ${files.length}`
- )
-
- const res = await updateOrCreate(
- filtered,
- DOCTYPE_HOMEWORK,
- ['start', 'label'],
- {
- sourceAccount: this.accountId,
- sourceAccountIdentifier: fields.login
- }
- )
+ log('info', `${filtered.length} new homeworks to save out of ${files.length}`)
- return res
- } catch (error) {
- throw new Error(error)
- }
+ const res = await updateOrCreate(
+ filtered,
+ DOCTYPE_HOMEWORK,
+ ['start', 'label'],
+ {
+ sourceAccount: fields.account,
+ sourceAccountIdentifier: fields.login
+ }
+ )
+
+ return res
}
module.exports = init
diff --git a/src/cozy/pronote/identity.js b/src/cozy/pronote/identity.js
index 03ced8e..a81eb07 100644
--- a/src/cozy/pronote/identity.js
+++ b/src/cozy/pronote/identity.js
@@ -1,28 +1,26 @@
const { saveFiles, log, saveIdentity } = require('cozy-konnector-libs')
+const { account } = require('pawnote')
const { PATH_IDENTITY_PROFILE_PIC } = require('../../constants')
const extract_pronote_name = require('../../utils/format/extract_pronote_name')
const gen_pronoteIdentifier = require('../../utils/format/gen_pronoteIdentifier')
-async function createIdentity(pronote, fields) {
+async function createIdentity(session, fields) {
// Getting personal information
- const information = await pronote.getPersonalInformation()
+ const information = await account(session)
// Getting profile picture
- const profile_pic = await save_profile_picture(pronote, fields)
+ const profile_pic = await save_profile_picture(session, fields)
// Formatting the JSON
- const json = await format_json(pronote, information, profile_pic)
+ const json = await format_json(session, information, profile_pic)
// Returning the identity
return json
}
-async function format_json(pronote, information, profile_pic) {
- const etabInfo = pronote.user?.listeInformationsEtablissements['V'][0]
- const scAdress = etabInfo['Coordonnees']
-
+async function format_json(session, information, profile_pic) {
const address = []
if (information.city && information.city.trim() !== '') {
@@ -38,39 +36,18 @@ async function format_json(pronote, information, profile_pic) {
})
}
- if (scAdress && scAdress['LibelleVille']) {
- address.push({
- type: 'School',
- label: 'work',
- city: scAdress['LibelleVille'],
- region: scAdress['Province'],
- street: scAdress['Adresse1'],
- country: scAdress['Pays'],
- code: scAdress['CodePostal'],
- formattedAddress:
- scAdress['Adresse1'] +
- ', ' +
- scAdress['CodePostal'] +
- ' ' +
- scAdress['LibelleVille'] +
- ', ' +
- scAdress['Pays']
- })
- }
-
- const identifier = gen_pronoteIdentifier(pronote)
+ const identifier = gen_pronoteIdentifier(session)
const identity = {
- // _id: genUUID(),
source: 'connector',
identifier: identifier,
contact: {
- fullname: pronote.studentName && pronote.studentName,
- name: pronote.studentName && extract_pronote_name(pronote.studentName),
+ fullname: session.user.name && session.user.name,
+ name: session.user.name && extract_pronote_name(session.user.name),
email: information.email && [
{
address: information.email,
- type: 'Profressional',
+ type: 'Professional',
label: 'work',
primary: true
}
@@ -84,13 +61,13 @@ async function format_json(pronote, information, profile_pic) {
}
],
address: address,
- company: pronote.schoolName,
- jobTitle: 'Élève de ' + pronote.studentClass
+ company: session.user.resources?.[0].establishmentName,
+ jobTitle: 'Élève de ' + session.user.resources?.[0].className
},
student: {
ine: information.INE,
- class: pronote.studentClass,
- school: pronote.schoolName
+ class: session.user.resources?.[0].className,
+ school: session.user.resources?.[0].establishmentName
},
relationships: profile_pic &&
profile_pic['_id'] && {
@@ -104,20 +81,20 @@ async function format_json(pronote, information, profile_pic) {
return identity
}
-async function save_profile_picture(pronote, fields) {
+async function save_profile_picture(session, fields) {
log('info', `🖼️ Saving profile picture at ' + ${PATH_IDENTITY_PROFILE_PIC}`)
const documents = [
{
filename: 'Photo de classe.jpg',
- fileurl: pronote.studentProfilePictureURL,
+ fileurl: session.user.resources[0].profilePicture.url,
shouldReplaceFile: false,
subPath: PATH_IDENTITY_PROFILE_PIC
}
]
const files = await saveFiles(documents, fields, {
- sourceAccount: this.accountId,
+ sourceAccount: fields.account,
sourceAccountIdentifier: fields.login,
concurrency: 3,
qualificationLabel: 'identity_photo', // Class photo
@@ -128,9 +105,9 @@ async function save_profile_picture(pronote, fields) {
return meta || null
}
-async function init(pronote, fields) {
+async function init(session, fields) {
try {
- let identity = await createIdentity(pronote, fields)
+ let identity = await createIdentity(session, fields)
log('info', '🗣️ Saving identity for ' + identity.identifier)
return saveIdentity(identity, fields.login)
} catch (error) {
diff --git a/src/cozy/pronote/presence.js b/src/cozy/pronote/presence.js
index f3da4cc..ed1af4b 100644
--- a/src/cozy/pronote/presence.js
+++ b/src/cozy/pronote/presence.js
@@ -73,32 +73,28 @@ async function createPresence(pronote, fields, options) {
}
async function init(pronote, fields, options) {
- try {
- let files = await createPresence(pronote, fields, options)
+ let files = await createPresence(pronote, fields, options)
- /*
+ /*
[Strategy] : only update events that are NOT justified yet
*/
- // remove all justified absences
- const filtered = files.filter(file => {
- return file.xJustified === false
- })
+ // remove all justified absences
+ const filtered = files.filter(file => {
+ return file.xJustified === false
+ })
- const res = await updateOrCreate(
- filtered,
- DOCTYPE_ATTENDANCE,
- ['label', 'start'],
- {
- sourceAccount: this.accountId,
- sourceAccountIdentifier: fields.login
- }
- )
+ const res = await updateOrCreate(
+ filtered,
+ DOCTYPE_ATTENDANCE,
+ ['label', 'start'],
+ {
+ sourceAccount: fields.account,
+ sourceAccountIdentifier: fields.login
+ }
+ )
- return res
- } catch (error) {
- throw new Error(error)
- }
+ return res
}
module.exports = init
diff --git a/src/cozy/pronote/timetable.js b/src/cozy/pronote/timetable.js
index 5b9c672..22bf05f 100644
--- a/src/cozy/pronote/timetable.js
+++ b/src/cozy/pronote/timetable.js
@@ -1,4 +1,5 @@
const { updateOrCreate, log } = require('cozy-konnector-libs')
+const { timetableFromIntervals, parseTimetable } = require('pawnote')
const {
DOCTYPE_TIMETABLE_LESSON,
@@ -13,28 +14,26 @@ const save_resources = require('../../utils/stack/save_resources')
const { queryLessonsByDate } = require('../../queries')
// Obtains timetable from Pronote
-async function get_timetable(pronote, fields, options) {
+async function get_timetable(session, fields, options) {
// Generate dates if needed (to get the full week or year)
const dates = createDates(options)
// Send request to get timetable
- const overview = await pronote.getTimetableOverviewForInterval(
- dates.from,
- dates.to
- )
+ const timetable = await timetableFromIntervals(session, dates.from, dates.to)
// Parse timetable response with settings
- return overview.parse({
+ parseTimetable(session, timetable, {
withSuperposedCanceledClasses: false,
withCanceledClasses: true,
withPlannedClasses: true
})
+ return timetable
}
// Process timetable and create doctypes
-async function createTimetable(pronote, fields, options) {
+async function createTimetable(session, fields, options) {
// Get timetable from Pronote
- const timetable = await get_timetable(pronote, fields, options)
+ const timetable = await get_timetable(session, fields, options)
// Empty array to store doctypes
const data = []
@@ -59,7 +58,8 @@ async function createTimetable(pronote, fields, options) {
`[Timetable] : 📕 Content ${shouldGetContent ? 'saved' : 'ignored'}`
)
- for (const lesson of timetable) {
+ for (const lesson of timetable.classes) {
+ if (lesson.is !== 'lesson') continue
// Get the formatted Cozy name
const pronoteString = findObjectByPronoteString(lesson.subject?.name)
const processedCoursName = pronoteString.label
@@ -139,70 +139,66 @@ async function createTimetable(pronote, fields, options) {
return data
}
-async function init(pronote, fields, options) {
- try {
- const files = await createTimetable(pronote, fields, options)
+async function init(session, fields, options) {
+ const files = await createTimetable(session, fields, options)
- /*
+ /*
[Strategy] : don't update past lessons, only update future lessons
+ don't update lessons that have been updated today
Why ? : past lessons never update, only future ones can be edited / cancelled
*/
- // query all lessons from dateFrom to dateTo
- const existing = await queryLessonsByDate(
- fields,
- options.dateFrom,
- options.dateTo
- )
-
- // filtered contains only events to update
- const filtered = files.filter(file => {
- // found returns true if the event is already in the database and doesn't need to be updated
- const found = existing.find(item => {
- // get the last update date
- const updated = new Date(item.cozyMetadata.updatedAt)
-
- // get today's date
- const today = new Date()
- today.setHours(0, 0, 0, 0)
-
- // has the item been updated today ?
- const updatedRecently = updated.getTime() > today.getTime()
-
- // if the lesson is in the future, it can be updated
- if (new Date(item.start) > new Date()) {
- // only update if the lesson has not been updated
- if (!updatedRecently) {
- // needs an update since it's in the future and hasn't been updated today
- return false
- }
- }
+ // query all lessons from dateFrom to dateTo
+ const existing = await queryLessonsByDate(
+ fields,
+ options.dateFrom,
+ options.dateTo
+ )
- // else, match the label and start date to know if the event is already in the database
- return item.label === file.label && item.start === file.start
- })
+ // filtered contains only events to update
+ const filtered = files.filter(file => {
+ // found returns true if the event is already in the database and doesn't need to be updated
+ const found = existing.find(item => {
+ // get the last update date
+ const updated = new Date(item.cozyMetadata.updatedAt)
+
+ // get today's date
+ const today = new Date()
+ today.setHours(0, 0, 0, 0)
+
+ // has the item been updated today ?
+ const updatedRecently = updated.getTime() > today.getTime()
+
+ // if the lesson is in the future, it can be updated
+ if (new Date(item.start) > new Date()) {
+ // only update if the lesson has not been updated
+ if (!updatedRecently) {
+ // needs an update since it's in the future and hasn't been updated today
+ return false
+ }
+ }
- // only return files that are not found or that needs an update (returned false)
- return !found
+ // else, match the label and start date to know if the event is already in the database
+ return item.label === file.label && item.start === file.start
})
- log('info', `${filtered.length} new events to save out of ${files.length}`)
+ // only return files that are not found or that needs an update (returned false)
+ return !found
+ })
- const res = await updateOrCreate(
- filtered,
- DOCTYPE_TIMETABLE_LESSON,
- ['start', 'label'],
- {
- sourceAccount: this.accountId,
- sourceAccountIdentifier: fields.login
- }
- )
+ log('info', `${filtered.length} new events to save out of ${files.length}`)
- return res
- } catch (error) {
- throw new Error(error)
- }
+ const res = await updateOrCreate(
+ filtered,
+ DOCTYPE_TIMETABLE_LESSON,
+ ['start', 'label'],
+ {
+ sourceAccount: fields.account,
+ sourceAccountIdentifier: fields.login
+ }
+ )
+
+ return res
}
module.exports = init
diff --git a/src/fetch/session.js b/src/fetch/session.js
index e941041..6456fbd 100644
--- a/src/fetch/session.js
+++ b/src/fetch/session.js
@@ -1,62 +1,17 @@
// Librairie Pawnote
-const {
- authenticatePronoteCredentials,
- PronoteApiAccountId,
- getPronoteInstanceInformation,
- defaultPawnoteFetcher
-} = require('pawnote')
-const uuid = require('../utils/misc/uuid')
-const { log } = require('cozy-konnector-libs')
-const { ENTPronoteLogin, getCasName } = require('./ENT')
+const { loginToken, createSessionHandle } = require('pawnote')
// creates a Pawnote session using the provided credentials
-async function Pronote({ url, login, password }) {
- try {
- // Remove everything after /pronote/ in the URL
- const newURL = url.split('/pronote')[0] + '/pronote/'
-
- // Get data from infoMobileApp.json (contains info about the instance including ENT redirection)
- const info = await getPronoteInstanceInformation(defaultPawnoteFetcher, {
- pronoteURL: newURL
- })
-
- // Get the URL of the instance (with a trailing slash to add the mobile.eleve.html endpoint)
- const pronoteURL = info.pronoteRootURL + '/'
-
- // Asks instance information to Pawnote to check if it's a Toutatice instance
- const casName = await getCasName(info)
- if (casName) {
- log('debug', `Found a CAS name : ${casName}`)
- }
-
- // Check if the URL uses the login=true parameter (bypasses ENT redirection)
- const usesLoginTrue = url.includes('login=true')
-
- if (casName && !usesLoginTrue) {
- // use ENT function to authenticate using retrived tokens
- return ENTPronoteLogin({ url: pronoteURL, login, password, casName })
- }
-
- // creates a Pawnote session using the provided credentials
- const pronote = await authenticatePronoteCredentials(pronoteURL, {
- // account type (student by default)
- accountTypeID: PronoteApiAccountId.Student,
- // provided credentials
- username: login,
- password: password,
- // generate a random UUID for the device
- deviceUUID: uuid()
- })
-
- log(
- 'info',
- `Pronote session created [${pronote.username} : ${pronote.studentName}]`
- )
-
- return pronote
- } catch (error) {
- throw new Error(error)
- }
+async function Pronote({ url, login, token, deviceUUID }) {
+ const session = createSessionHandle()
+ const loginResult = await loginToken(session, {
+ url,
+ username: login,
+ token,
+ deviceUUID,
+ kind: 6
+ })
+ return { session, loginResult }
}
module.exports = {
diff --git a/src/index.js b/src/server.js
similarity index 68%
rename from src/index.js
rename to src/server.js
index e7263f9..99cf70a 100644
--- a/src/index.js
+++ b/src/server.js
@@ -17,7 +17,12 @@ module.exports = new BaseKonnector(start)
// Variable globale pour savoir si on doit sauvegarder les fichiers
const SHOULD_SAVE = true
const SHOULD_GET_LESSON_CONTENT = false // ONLY for small requests, sends a request per course to get the content of the lesson
-const SAVES = ['timetable', 'homeworks', 'grades', 'presence']
+const SAVES = [
+ 'timetable',
+ 'homeworks',
+ 'grades'
+ // 'presence'
+]
// Fonction start qui va être exportée
async function start(fields) {
@@ -26,30 +31,45 @@ async function start(fields) {
// Initialisation de la session Pronote
await this.deactivateAutoSuccessfulLogin()
- const pronote = await Pronote({
- url: fields.pronote_url,
- login: fields.login,
- password: fields.password
+ const accountData = this.getAccountData()
+ const { session, loginResult } = await Pronote(accountData)
+ await this.saveAccountData({
+ ...accountData,
+ token: loginResult.token,
+ navigatorIdentifier: loginResult.navigatorIdentifier
})
await this.notifySuccessfulLogin()
- log('info', 'Pronote session initialized successfully : ' + pronote)
+ log(
+ 'info',
+ 'Pronote session initialized successfully : ' + session.instance
+ )
// Gets school year dates
- let dateFrom = new Date(pronote.firstDate)
+ let dateFrom = new Date(session.instance.firstDate)
let dateToday = new Date()
- const dateTo = new Date(pronote.lastDate)
+ const dateTo = new Date(session.instance.lastDate)
// Saves user identity
- await cozy_save('identity', pronote, fields)
+ const envFields = JSON.parse(process.env.COZY_FIELDS || '{}')
+ await cozy_save('identity', session, {
+ ...fields,
+ ...accountData,
+ ...envFields
+ })
SAVES.forEach(async save => {
- await cozy_save(save, pronote, fields, {
- dateFrom: SAVES === 'homeworks' ? dateToday : dateFrom,
- dateTo: dateTo,
- saveFiles: SHOULD_SAVE && true,
- getLessonContent: SHOULD_GET_LESSON_CONTENT
- })
+ await cozy_save(
+ save,
+ session,
+ { ...fields, ...accountData, ...envFields },
+ {
+ dateFrom: SAVES === 'homeworks' ? dateToday : dateFrom,
+ dateTo: dateTo,
+ saveFiles: SHOULD_SAVE && true,
+ getLessonContent: SHOULD_GET_LESSON_CONTENT
+ }
+ )
})
} catch (err) {
const error = err.toString()
diff --git a/src/utils/format/gen_pronoteIdentifier.js b/src/utils/format/gen_pronoteIdentifier.js
index 314181a..f533ae0 100644
--- a/src/utils/format/gen_pronoteIdentifier.js
+++ b/src/utils/format/gen_pronoteIdentifier.js
@@ -1,21 +1,25 @@
-const gen_pronoteIdentifier = pronote => {
+const gen_pronoteIdentifier = session => {
// Student name
- let name = pronote.studentName.toLowerCase().replace(/ /g, '')
+ let name = session.user.name.toLowerCase().replace(/ /g, '')
name = name.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
name = name.replace(/[^a-zA-Z0-9]/g, '')
// School name
- let school = pronote.schoolName.toLowerCase().replace(/ /g, '')
+ let school = session.user.resources?.[0].establishmentName
+ .toLowerCase()
+ .replace(/ /g, '')
school = school.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
school = school.replace(/[^a-zA-Z0-9]/g, '')
// Student class
- let studentClass = pronote.studentClass.toLowerCase().replace(/ /g, '')
+ let studentClass = session.user.resources?.[0].className
+ .toLowerCase()
+ .replace(/ /g, '')
studentClass = studentClass.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
studentClass = studentClass.replace(/[^a-zA-Z0-9]/g, '')
// School year end
- const end = new Date(pronote.lastDate).getFullYear()
+ const end = new Date(session.instance.lastDate).getFullYear()
// Return the identifier
return `${name}-${school}-${studentClass}-${end}`
diff --git a/src/utils/format/remove_html.js b/src/utils/format/remove_html.js
index d984eb9..273ffe6 100644
--- a/src/utils/format/remove_html.js
+++ b/src/utils/format/remove_html.js
@@ -1,4 +1,5 @@
const remove_html = entryHtml => {
+ if (!entryHtml) return entryHtml
// Remove HTML tags
let html = entryHtml
html = html.replace(/<[^>]*>?/gm, '')
diff --git a/src/utils/misc/createDates.js b/src/utils/misc/createDates.js
index f2edc1b..2c8ba6f 100644
--- a/src/utils/misc/createDates.js
+++ b/src/utils/misc/createDates.js
@@ -21,6 +21,7 @@ function createDates(options) {
}
function getIcalDate(date) {
+ if (!date) return date
return (
date.toISOString().replace(/-/g, '').replace(/:/g, '').replace(/\..+/, '') +
'Z'
diff --git a/src/utils/stack/save_resources.js b/src/utils/stack/save_resources.js
index 36e1475..b2a9dbb 100644
--- a/src/utils/stack/save_resources.js
+++ b/src/utils/stack/save_resources.js
@@ -79,7 +79,7 @@ URL=${file.url}`.trim()
if (filesToDownload.length > 0) {
const data = await saveFiles(filesToDownload, fields, {
- sourceAccount: this.accountId,
+ sourceAccount: fields.account,
sourceAccountIdentifier: fields.login,
concurrency: 3,
validateFile: () => true
diff --git a/webpack.config.client.js b/webpack.config.client.js
new file mode 100644
index 0000000..8eaf8e7
--- /dev/null
+++ b/webpack.config.client.js
@@ -0,0 +1,9 @@
+var path = require('path')
+module.exports = {
+ ...require('cozy-konnector-build/webpack.config.clisk'),
+ entry: './src/client.js',
+ output: {
+ path: path.join(process.cwd(), 'build'),
+ filename: 'main.js'
+ }
+}
diff --git a/webpack.config.js b/webpack.config.js
deleted file mode 100644
index ea392d1..0000000
--- a/webpack.config.js
+++ /dev/null
@@ -1,19 +0,0 @@
-const CopyPlugin = require('copy-webpack-plugin')
-const webpack = require('webpack')
-
-const config = require('cozy-konnector-build/webpack.config')
-
-module.exports = {
- ...config,
- plugins: [
- ...config.plugins,
- new webpack.IgnorePlugin({ resourceRegExp: /^canvas$/ }),
- new CopyPlugin({
- patterns: [
- {
- from: './node_modules/pronote-api-maintained/src/cas/generics/jsencrypt.min.js'
- }
- ]
- })
- ]
-}
diff --git a/webpack.config.server.js b/webpack.config.server.js
new file mode 100644
index 0000000..12075c7
--- /dev/null
+++ b/webpack.config.server.js
@@ -0,0 +1,16 @@
+const webpack = require('webpack')
+const config = require('cozy-konnector-build/webpack.config')
+var path = require('path')
+
+module.exports = {
+ ...config,
+ entry: './src/server.js',
+ output: {
+ path: path.join(process.cwd(), 'build'),
+ filename: 'index.js'
+ },
+ plugins: [
+ ...config.plugins,
+ new webpack.IgnorePlugin({ resourceRegExp: /^canvas$/ })
+ ]
+}
diff --git a/yarn.lock b/yarn.lock
index 3ef1ba3..80d131b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1184,7 +1184,7 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
-"@cozy/minilog@1.0.0":
+"@cozy/minilog@1.0.0", "@cozy/minilog@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@cozy/minilog/-/minilog-1.0.0.tgz#1acc1aad849261e931e255a5f181b638315f7b84"
integrity sha512-IkDHF9CLh0kQeSEVsou59ar/VehvenpbEUjLfwhckJyOUqZnKAWmXy8qrBgMT5Loxr8Xjs2wmMnj0D67wP00eQ==
@@ -1308,6 +1308,13 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
+"@literate.ink/utilities@1.0.0-10641118381.1":
+ version "1.0.0-10641118381.1"
+ resolved "https://registry.yarnpkg.com/@literate.ink/utilities/-/utilities-1.0.0-10641118381.1.tgz#8fb97ba575652101a545f1aba7c96ddd7dd5b418"
+ integrity sha512-omhzgwfAjNXqIjt6dmWbU8pm6QfEvNYetykEXYk+vVaJ8oICglvkChxkC6FkF8zJKMM/lFrNHgD42obQ8lwolQ==
+ dependencies:
+ set-cookie-parser "^2.7.0"
+
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -2618,6 +2625,20 @@ cozy-client@^48.8.0:
sift "^6.0.0"
url-search-params-polyfill "^8.0.0"
+cozy-clisk@^0.38.0:
+ version "0.38.1"
+ resolved "https://registry.yarnpkg.com/cozy-clisk/-/cozy-clisk-0.38.1.tgz#0b42f723ebed7ef32c13cf6eac3a8bc241dc52c1"
+ integrity sha512-gSBcIKo3XSDRsoDNn4ocEkzj0S2YZIQMfG1HLnacHrAruay6kXmAF3VB3kTRctuSDpY4vjxUG379CS7i5qMwBQ==
+ dependencies:
+ "@cozy/minilog" "^1.0.0"
+ bluebird-retry "^0.11.0"
+ ky "^0.25.1"
+ lodash "^4.17.21"
+ microee "^0.0.6"
+ p-timeout "^6.0.0"
+ p-wait-for "^5.0.2"
+ post-me "^0.4.5"
+
cozy-device-helper@^2.1.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/cozy-device-helper/-/cozy-device-helper-2.2.1.tgz#d5822afd818919fa871527e6f78b0265fc1e009b"
@@ -3926,7 +3947,7 @@ html-encoding-sniffer@^2.0.1:
dependencies:
whatwg-encoding "^1.0.5"
-html-entities@^2.4.0:
+html-entities@^2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f"
integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==
@@ -4466,6 +4487,11 @@ kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+ky@^0.25.1:
+ version "0.25.1"
+ resolved "https://registry.yarnpkg.com/ky/-/ky-0.25.1.tgz#0df0bd872a9cc57e31acd5dbc1443547c881bfbc"
+ integrity sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA==
+
levn@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
@@ -5016,11 +5042,23 @@ p-locate@^5.0.0:
dependencies:
p-limit "^3.0.2"
+p-timeout@^6.0.0:
+ version "6.1.3"
+ resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-6.1.3.tgz#9635160c4e10c7b4c3db45b7d5d26f911d9fd853"
+ integrity sha512-UJUyfKbwvr/uZSV6btANfb+0t/mOhKV/KXcCUTp8FcQI+v/0d+wXqH4htrW0E4rR6WiEO/EPvUFiV9D5OI4vlw==
+
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+p-wait-for@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/p-wait-for/-/p-wait-for-5.0.2.tgz#1546a15e64accf1897377cb1507fa4c756fffe96"
+ integrity sha512-lwx6u1CotQYPVju77R+D0vFomni/AqRfqLmqQ8hekklqZ6gAY9rONh7lBQ0uxWMkC2AuX9b2DVAl8To0NyP1JA==
+ dependencies:
+ p-timeout "^6.0.0"
+
pako@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
@@ -5120,15 +5158,15 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
-pawnote@^0.22.1:
- version "0.22.1"
- resolved "https://registry.yarnpkg.com/pawnote/-/pawnote-0.22.1.tgz#c9d18933433412a3312ccf3e6950e970f9c57573"
- integrity sha512-MvB0yO3KWpzP7j9bHMHzMEkGHrFXMXhrs3cvBO1oQFcbh6IK1pN1EGUvghOJ8hqGiQPht6213ALg/URhhPs+3Q==
+pawnote@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/pawnote/-/pawnote-1.2.2.tgz#6cdfb8ef6f178e41140b1fadf9a6c0fee02dc028"
+ integrity sha512-7Q3noEiVCcUHkbn/cziHLtaZsTCShg364Srlnl1QScqCFwRljpbCqDXtKmFTvWyOZH+9nC2drV/oTr+aZIjCwQ==
dependencies:
- html-entities "^2.4.0"
+ "@literate.ink/utilities" "1.0.0-10641118381.1"
+ html-entities "^2.5.2"
node-forge "^1.3.1"
pako "^2.1.0"
- set-cookie-parser "^2.6.0"
peek-readable@^4.1.0:
version "4.1.0"
@@ -5184,6 +5222,11 @@ polka@^0.5.2:
"@polka/url" "^0.5.0"
trouter "^2.0.1"
+post-me@^0.4.5:
+ version "0.4.5"
+ resolved "https://registry.yarnpkg.com/post-me/-/post-me-0.4.5.tgz#6171b721c7b86230c51cfbe48ddea047ef8831ce"
+ integrity sha512-XgPdktF/2M5jglgVDULr9NUb/QNv3bY3g6RG22iTb5MIMtB07/5FJB5fbVmu5Eaopowc6uZx7K3e7x1shPwnXw==
+
postcss-selector-parser@^6.0.9:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
@@ -5865,10 +5908,10 @@ set-cookie-parser@^2.3.5:
resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz#ddd3e9a566b0e8e0862aca974a6ac0e01349430b"
integrity sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==
-set-cookie-parser@^2.6.0:
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz#131921e50f62ff1a66a461d7d62d7b21d5d15a51"
- integrity sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==
+set-cookie-parser@^2.7.0:
+ version "2.7.1"
+ resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943"
+ integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==
setprototypeof@1.1.1:
version "1.1.1"