feat: 🎸 Loads schedule (#16)

* feat: 🎸 Loads schedule

 Closes: #13

* feat: 🎸 Made running code from node easier

* feat: 🎸 News

 Closes: #8

* Accurate method calls and some parsing

* fix: 🎸 Calendar and classmates are now called and parsed correctly
This commit is contained in:
Johan Öbrink 2020-12-21 15:59:49 +01:00 committed by GitHub
parent 5996f1de8f
commit 53d42de62c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 646 additions and 58 deletions

2
.gitignore vendored
View File

@ -102,5 +102,3 @@ dist
# TernJS port file
.tern-port
run.js

View File

@ -81,3 +81,11 @@ const sessionCookie = "some value";
api.setSessionCookie(sessionCookie); // will trigger `on('login')` event and set `.isLoggedIn = true`
```
## Try it out
1. Clone and enter repo: `git clone git@github.com:kolplattformen/embedded-api.git && cd embedded-api`
2. Install dependencies: `yarn`
3. Build package: `yarn build`
4. Run example: `node run [your personal number]`
5. Sign in with mobile BankID

View File

@ -1,8 +1,11 @@
import { Moment } from 'moment'
import routes from './routes'
import {
CalendarItem, Child, Classmate, Fetch, RequestInit,
} from './types'
import { etjanst, child, calendarItem } from './parse'
import {
etjanst, child, calendarItem,
} from './parse'
export const list = (fetch: Fetch, init?: RequestInit) => async (): Promise<Child[]> => {
const url = routes.children
@ -11,16 +14,25 @@ export const list = (fetch: Fetch, init?: RequestInit) => async (): Promise<Chil
return etjanst(data).map(child)
}
export const calendar = (fetch: Fetch, init?:RequestInit) => async (childId: string): Promise<CalendarItem[]> => {
export const calendar = (fetch: Fetch, init?: RequestInit) => async (childId: string): Promise<CalendarItem[]> => {
const url = routes.calendar(childId)
const response = await fetch(url, init)
const data = await response.json()
return etjanst(data).map(calendarItem)
}
export const classmates = (fetch: Fetch, init?:RequestInit) => async (childId: string): Promise<Classmate[]> => {
export const classmates = (fetch: Fetch, init?: RequestInit) => async (childId: string): Promise<Classmate[]> => {
const url = routes.classmates(childId)
const response = await fetch(url, init)
const data = await response.json()
return etjanst(data)
}
export const schedule = (fetch: Fetch, init?: RequestInit) => (
async (childId: string, from: Moment, to: moment.Moment): Promise<any> => {
const url = routes.schedule(childId, from.format('YYYY-MM-DD'), to.format('YYYY-MM-DD'))
const response = await fetch(url, init)
const data = await response.json()
return etjanst(data)
}
)

1
lib/h2m.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module 'h2m'

View File

@ -1,3 +1,4 @@
import { Moment } from 'moment'
import { EventEmitter } from 'events'
import {
checkStatus, getSessionCookie, login, LoginStatus,
@ -5,7 +6,10 @@ import {
import {
CalendarItem, Child, Classmate, Fetch, RequestInit,
} from './types'
import { calendar, classmates, list } from './children'
import {
calendar, classmates, list, schedule,
} from './children'
import { news, News } from './news'
interface AsyncishFunction { (): void | Promise<void> }
@ -50,13 +54,23 @@ export class Api extends EventEmitter {
return data
}
async getCalendar(childId: string): Promise<CalendarItem[]> {
const data = await calendar(this.fetch, this.session)(childId)
async getCalendar(child: Child): Promise<CalendarItem[]> {
const data = await calendar(this.fetch, this.session)(child.id)
return data
}
async getClassmates(childId: string): Promise<Classmate[]> {
const data = await classmates(this.fetch, this.session)(childId)
async getClassmates(child: Child): Promise<Classmate[]> {
const data = await classmates(this.fetch, this.session)(child.sdsId)
return data
}
async getSchedule(child: Child, from: Moment, to: Moment): Promise<any> {
const data = await schedule(this.fetch, this.session)(child.sdsId, from, to)
return data
}
async getNews(child: Child): Promise<News> {
const data = await news(this.fetch, this.session)(child.id)
return data
}

18
lib/news.ts Normal file
View File

@ -0,0 +1,18 @@
import { etjanst, newsItem } from './parse'
import routes from './routes'
import { Fetch, NewsItem, RequestInit } from './types'
export class News {
public items: NewsItem[]
constructor(items: NewsItem[]) {
this.items = items
}
}
export const news = (fetch: Fetch, init?: RequestInit) => async (childId: string): Promise<News> => {
const url = routes.news(childId)
const response = await fetch(url, init)
const data = await response.json()
return new News(etjanst(data).newsItems.map(newsItem))
}

View File

@ -1,8 +1,12 @@
import { etjanst, EtjanstResponse } from "./parse"
import * as moment from 'moment'
import {
etjanst, newsItem, EtjanstResponse, child, calendarItem, classmate, scheduleItem,
} from "./parse"
import { NewsItem } from "./types"
describe('parse', () => {
let response: EtjanstResponse
describe('etjanst', () => {
let response: EtjanstResponse
beforeEach(() => {
response = {
Success: true,
@ -10,20 +14,6 @@ describe('parse', () => {
Data: [
{
Name: 'Some name',
Id: '42C3997E-D772-423F-9290-6FEEB3CB2DA7',
SDSId: '786E3393-F044-4660-9105-B444DEB289AA',
Status: 'GR',
UserType: 'Student',
SchoolId: 'DE2E1293-0F40-4B91-9D91-1E99355DC257',
SchoolName: null,
GroupId: null,
GroupName: null,
Classes: 'VHsidan_0495CABC-77DB-41D7-824B-8B4D63E50D15;Section_AD1BB3B2-C1EE-4DFE-8209-CB6D42CE23D7;Section_0E67D0BF-594C-4C1B-9291-E753926DCD40;VHsidan_1C94EC54-9798-401C-B973-2454246D95DA',
isSameSDSId: false,
ResultUnitId: null,
ResultUnitName: null,
UnitId: null,
UnitName: null
}
]
}
@ -40,5 +30,261 @@ describe('parse', () => {
const parsed = etjanst(response)
expect(parsed[0].name).toEqual(response.Data[0].Name)
})
describe('children', () => {
beforeEach(() => {
response = {
Success: true,
Error: null,
Data: [
{
Name: 'Some name',
Id: '42C3997E-D772-423F-9290-6FEEB3CB2DA7',
SDSId: '786E3393-F044-4660-9105-B444DEB289AA',
Status: 'GR',
UserType: 'Student',
SchoolId: 'DE2E1293-0F40-4B91-9D91-1E99355DC257',
SchoolName: null,
GroupId: null,
GroupName: null,
Classes: 'VHsidan_0495CABC-77DB-41D7-824B-8B4D63E50D15;Section_AD1BB3B2-C1EE-4DFE-8209-CB6D42CE23D7;Section_0E67D0BF-594C-4C1B-9291-E753926DCD40;VHsidan_1C94EC54-9798-401C-B973-2454246D95DA',
isSameSDSId: false,
ResultUnitId: null,
ResultUnitName: null,
UnitId: null,
UnitName: null
}
]
}
})
it('parses children correctly', () => {
expect(etjanst(response).map(child)).toEqual([{
name: 'Some name',
id: '42C3997E-D772-423F-9290-6FEEB3CB2DA7',
sdsId: '786E3393-F044-4660-9105-B444DEB289AA',
schoolId: 'DE2E1293-0F40-4B91-9D91-1E99355DC257',
status: 'GR',
}])
})
})
describe('calendar', () => {
beforeEach(() => {
response = {
Success: true,
Error: null,
Data: [
{
Title: 'Jullov',
Id: 29,
Description: 'hello',
Location: null,
EventDate: '2020-12-21',
EventDateTime: '09:00',
LongEventDateTime: '2020-12-21 09:00',
EndDate: '2021-01-08',
EndDateTime: '10:00',
LongEndDateTime: '2021-01-08 10:00',
EventDateDayNumber: '21',
EventDateMonthName: 'dec',
EventDateMonthFullName: 'december',
FullDateDescription: '2020-12-21 09:00 - 2021-01-08 10:00',
IsSameDay: false,
AllDayEvent: false,
ListId: null,
Mentor: null,
},
]
}
})
it('parses calendar correctly', () => {
expect(etjanst(response).map(calendarItem)).toEqual([{
id: 29,
location: null,
title: 'Jullov',
description: 'hello',
startDate: moment(new Date('2020-12-21 09:00')),
endDate: moment(new Date('2021-01-08 10:00')),
allDay: false,
}])
})
})
describe('classmates', () => {
beforeEach(() => {
response = {
Success: true,
Error: null,
Data: [
{
ID: 0,
BATCH: null,
SIS_ID: '22F0CFC7-09C7-45DC-9388-AE9A9EA1356B',
USERNAME: null,
SCHOOL_SIS_ID: null,
EMAILADDRESS: null,
STATUS: null,
ERRORCODE: 0,
PRIMARY_SCHOOL_SIS_ID: null,
MENTOR_SIS_ID: null,
FIRSTNAME: 'Bo',
LASTNAME: 'Burström',
ACTIVE: false,
Guardians: [
{
SOCIALNUMBER: null,
DISPLAYNAME: null,
FIRSTNAME: 'Allan',
LASTNAME: 'Fridell',
ADDRESS: 'Hult södregård',
CITY: null,
POCODE: null,
TELHOME: null,
TELMOBILE: '0690-6346216',
EMAILHOME: 'allan.fridell@mailinater.com',
SECTION_NAME: null,
SECTION_ID: null,
TERM_STARTDATE: null,
TERM_ENDDATE: null,
GROUPTYPE: null,
STUDENT_FIRSTNAME: null,
STUDENT_LASTNAME: null,
STUDENT_ID: null
}
],
ClassName: '7C',
ClassId: 'B2BF465B-581B-43AC-9CA7-F11BB0ED4646'
},
]
}
})
it('parses class mates correctly', () => {
expect(etjanst(response).map(classmate)).toEqual([{
sisId: '22F0CFC7-09C7-45DC-9388-AE9A9EA1356B',
firstname: 'Bo',
lastname: 'Burström',
className: '7C',
guardians: [
{
firstname: 'Allan',
lastname: 'Fridell',
address: 'Hult södregård',
mobile: '0690-6346216',
email: 'allan.fridell@mailinater.com',
}
]
}])
})
})
describe('schedule', () => {
beforeEach(() => {
response = {
"Success": true,
"Error": null,
"Data": [
{
"Title": "Canceled: Julavslutning 8C",
"Id": 0,
"Description": "Nåt kul",
"Location": "Lakritskolan",
"EventDate": "2020-12-14",
"EventDateTime": "14:10",
"LongEventDateTime": "2020-12-14 14:10",
"EndDate": "2020-12-14",
"EndDateTime": "14:40",
"LongEndDateTime": "2020-12-14 14:40",
"EventDateDayNumber": "14",
"EventDateMonthName": "dec",
"EventDateMonthFullName": "december",
"FullDateDescription": "2020-12-14 14:10 - 2020-12-14 14:40",
"IsSameDay": true,
"AllDayEvent": false,
"ListId": null,
"Mentor": null
}
]
}
})
it('parses schedule correctly', () => {
expect(etjanst(response).map(scheduleItem)).toEqual([{
title: 'Canceled: Julavslutning 8C',
description: 'Nåt kul',
location: 'Lakritskolan',
startDate: moment(new Date('2020-12-14 14:10')),
endDate: moment(new Date('2020-12-14 14:40')),
oneDayEvent: true,
allDayEvent: false,
}])
})
})
describe('news', () => {
beforeEach(() => {
response = {
Success: true,
Error: null,
Data: {
CurrentChild: null,
NewsItems: [
{
NewsId: 'news id',
SiteId: 'elevstockholm.sharepoint.com,27892ACC-BA2E-4DEC-97B8-25F7098C3BF6,A239466A-9A52-42FF-8A3F-D94C342F2700',
NewsListId: '3EC323A1-EA16-4D24-84C8-DAA49E76F9F4',
NewsItemId: 'elevstockholm.sharepoint.com,27892ACC-BA2E-4DEC-97B8-25F7098C3BF6,A239466A-9A52-42FF-8A3F-D94C342F2700_99',
Header: 'Problemet med att se betyg i bild, slöjd och teknik löst!',
PublicationDate: '/Date(1608304542000)/',
PubDateSE: '18 december 2020 16:15',
ModifiedDate: '/Date(1608304680000)/',
ModDateSE: '18 december 2020 16:18',
Source: 'Livets hårda skolklasser',
Preamble: 'Hej,Nu är problemet löst! Alla betyg syns som de ska.God jul!...',
BannerImageUrl: 'A703552D-DBF3-45B0-8E67-6E062105A0C5.jpeg',
BannerImageGuid: 'A703552D-DBF3-45B0-8E67-6E062105A0C5',
BannerImageListId: 'FFBE49E9-BDE1-4C75-BA0E-D98D4E2FCF21',
Body: '<div><div data-sp-canvascontrol="" data-sp-canvasdataversion="1.0" data-sp-controldata="&#123;&quot;controlType&quot;&#58;4,&quot;id&quot;&#58;&quot;1212fc8d-dd6b-408a-8d5d-9f1cc787efbb&quot;,&quot;position&quot;&#58;&#123;&quot;controlIndex&quot;&#58;2,&quot;sectionIndex&quot;&#58;1,&quot;sectionFactor&quot;&#58;12,&quot;zoneIndex&quot;&#58;1,&quot;layoutIndex&quot;&#58;1&#125;,&quot;addedFromPersistedData&quot;&#58;true,&quot;emphasis&quot;&#58;&#123;&#125;&#125;"><div data-sp-rte=""><p>Hej,</p><p>Nu är problemet löst! Alla betyg syns som de ska.&#160;</p><p>God jul!</p></div></div><div data-sp-canvascontrol="" data-sp-canvasdataversion="1.0" data-sp-controldata="&#123;&quot;controlType&quot;&#58;0,&quot;pageSettingsSlice&quot;&#58;&#123;&quot;isDefaultDescription&quot;&#58;true,&quot;isDefaultThumbnail&quot;&#58;true&#125;&#125;"></div></div>',
BodyNoHtml: null,
AuthorDisplayName: 'Eva-Lotta Rönnberg',
altText: 'Nyhetsbild. Bildtext ej tillgänglig.'
},
],
ViewGlobalTranslations: {},
ViewLocalTranslations: {},
Children: null,
Status: null,
GlobalTranslationIds: [
'InformationalHeader',
'ContactUsMessageLabel',
'Send',
'RequiredFieldMessageInfo',
'Sex',
'Male',
'Female',
'SSN',
'FirstName',
'LastName',
'Email',
'Zip',
'Address',
'ValidationRequiredFieldMessage',
'ValidationErrorMessage'
],
LocalTranslationIds: ['IndexPageHeading1']
}
}
})
it('parses news items (except body) correctly', () => {
const [item]: [NewsItem] = etjanst(response).newsItems.map(newsItem)
expect(item.id).toEqual('news id')
expect(item.header).toEqual('Problemet med att se betyg i bild, slöjd och teknik löst!')
expect(item.imageUrl).toEqual('A703552D-DBF3-45B0-8E67-6E062105A0C5.jpeg')
expect(item.intro).toEqual('Hej,Nu är problemet löst! Alla betyg syns som de ska.God jul!...')
expect(item.modified).toEqual(moment(new Date('18 december 2020 16:18')))
expect(item.published).toEqual(moment(new Date('18 december 2020 16:15')))
})
it('parses body correctly', () => {
const [item]: [NewsItem] = etjanst(response).newsItems.map(newsItem)
const expected = 'Hej, Nu är problemet löst! Alla betyg syns som de ska. God jul!'
const trimmed = (item.body || '').split('\n').map(t => t.trim()).join(' ')
expect(trimmed).toEqual(expected)
})
})
})
})

View File

@ -1,14 +1,19 @@
import { CalendarItem, Child } from './types'
import * as moment from 'moment'
import * as h2m from 'h2m'
import { htmlDecode } from 'js-htmlencode'
import {
CalendarItem, Child, Classmate, Guardian, NewsItem, ScheduleItem,
} from './types'
const camel = require('camelcase-keys')
export interface EtjanstResponse {
Success: boolean
Error: string|null
Data: any|any[]
Error: string | null
Data: any | any[]
}
export const etjanst = (response: EtjanstResponse): any|any[] => {
export const etjanst = (response: EtjanstResponse): any | any[] => {
if (!response.Success) {
throw new Error(response.Error || '')
}
@ -21,8 +26,58 @@ export const child = ({
id, sdsId, name, status, schoolId,
})
export const calendarItem = ({
id, title, description, location, startDate, endDate, allDay,
}: any): CalendarItem => ({
id, title, description, location, startDate, endDate, allDay,
export const guardian = ({
emailhome, firstname, lastname, address, telmobile,
}: any): Guardian => ({
firstname,
lastname,
address,
mobile: telmobile,
email: emailhome,
})
export const classmate = ({
sisId, firstname, lastname, className, guardians = [],
}: any): Classmate => ({
sisId,
firstname,
lastname,
className,
guardians: guardians.map(guardian),
})
export const calendarItem = ({
id, title, description, location, longEventDateTime, longEndDateTime, allDayEvent,
}: any): CalendarItem => ({
id,
title,
description,
location,
allDay: allDayEvent,
startDate: longEventDateTime ? moment(new Date(longEventDateTime)) : undefined,
endDate: longEndDateTime ? moment(new Date(longEndDateTime)) : undefined,
})
export const newsItem = ({
newsId, header, preamble, body, bannerImageUrl, pubDateSe, modDateSe,
}: any): NewsItem => ({
header,
id: newsId,
intro: preamble,
imageUrl: bannerImageUrl,
body: htmlDecode(h2m(body)),
published: moment(new Date(pubDateSe)),
modified: moment(new Date(modDateSe)),
})
export const scheduleItem = ({
title, description, location, longEventDateTime, longEndDateTime, isSameDay, allDayEvent,
}: any): ScheduleItem => ({
title,
description,
location,
allDayEvent,
startDate: moment(new Date(longEventDateTime)),
endDate: moment(new Date(longEndDateTime)),
oneDayEvent: isSameDay,
})

View File

@ -1,3 +1,5 @@
import { Moment } from 'moment'
export interface RequestInit {
headers?: any
method?: string
@ -28,12 +30,12 @@ export interface AuthTicket {
* @interface CalendarItem
*/
export interface CalendarItem {
id?: number;
title?: string;
id: number;
title: string;
description?: string;
location?: string;
startDate?: string;
endDate?: string;
startDate?: Moment;
endDate?: Moment;
allDay?: boolean;
}
@ -42,14 +44,14 @@ export interface CalendarItem {
* @interface Child
*/
export interface Child {
id?: string;
id: string;
/**
* <p>Special ID used to access certain subsystems</p>
* @type {string}
* @memberof Child
*/
sdsId?: string;
name?: string;
sdsId: string;
name: string;
/**
* <p>F - förskola, GR - grundskola?</p>
* @type {string}
@ -64,16 +66,11 @@ export interface Child {
* @interface Classmate
*/
export interface Classmate {
sisId?: string;
/**
* <p>The name of the class of this classmate</p>
* @type {string}
* @memberof Classmate
*/
sisId: string;
className?: string;
firstname?: string;
lastname?: string;
guardians?: Guardian[];
firstname: string;
lastname: string;
guardians: Guardian[];
}
/**
@ -82,8 +79,8 @@ export interface Classmate {
*/
export interface Guardian {
email?: string;
firstname?: string;
lastname?: string;
firstname: string;
lastname: string;
mobile?: string;
address?: string;
}
@ -98,8 +95,8 @@ export interface NewsItem {
header?: string;
intro?: string;
body?: string;
published?: string;
modified?: string;
published: Moment;
modified?: Moment;
imageUrl?: string;
}
@ -126,3 +123,17 @@ export interface Notification {
category?: string;
messageType?: string;
}
/**
* @export
* @interface ScheduleItem
*/
export interface ScheduleItem {
title: string
description?: string
location?: string
startDate: Moment
endDate: Moment
oneDayEvent: boolean
allDayEvent: boolean
}

View File

@ -35,6 +35,9 @@
},
"dependencies": {
"camelcase-keys": "^6.2.2",
"events": "^3.2.0"
"events": "^3.2.0",
"h2m": "^0.7.0",
"js-htmlencode": "^0.3.0",
"moment": "^2.29.1"
}
}

63
run.js Normal file
View File

@ -0,0 +1,63 @@
const moment = require('moment')
const nodeFetch = require('node-fetch')
const { CookieJar } = require('tough-cookie')
const fetchCookie = require('fetch-cookie/node-fetch')
const init = require('./dist').default
const [, , personalNumber] = process.argv
if (!personalNumber) {
console.error('You must pass in a valid personal number, eg `node run 197001011111`')
process.exit(1)
}
async function run() {
const cookieJar = new CookieJar()
const fetch = fetchCookie(nodeFetch, cookieJar)
try {
const api = init(fetch, () => cookieJar.removeAllCookies())
const status = await api.login(personalNumber)
status.on('PENDING', () => console.log('PENDING'))
status.on('USER_SIGN', () => console.log('USER_SIGN'))
status.on('ERROR', () => console.error('ERROR'))
status.on('OK', () => console.log('OK'))
api.on('login', async () => {
console.log('Logged in')
console.log('children')
const children = await api.getChildren()
// console.log(children)
// console.log('calendar')
// const calendar = await api.getCalendar(children[0])
// console.log(calendar)
// console.log('classmates')
// const classmates = await api.getClassmates(children[0])
// console.log(classmates)
// console.log('schedule')
// const schedule = await api.getSchedule(children[0], moment().subtract(1, 'week'), moment())
// console.log(schedule)
// console.log('news')
// const news = await api.getNews(children[0])
// console.log(news)
await api.logout()
})
api.on('logout', () => {
console.log('Logged out')
process.exit(0)
})
} catch (err) {
console.error(err)
}
}
run()

165
yarn.lock
View File

@ -833,6 +833,11 @@ anymatch@^3.0.3:
normalize-path "^3.0.0"
picomatch "^2.0.4"
arch@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@ -1169,6 +1174,14 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
clipboardy@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-1.2.3.tgz#0526361bf78724c1f20be248d428e365433c07ef"
integrity sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==
dependencies:
arch "^2.1.0"
execa "^0.8.0"
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
@ -1227,6 +1240,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@^2.9.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
component-emitter@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
@ -1264,6 +1282,15 @@ core-util-is@1.0.2:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
dependencies:
lru-cache "^4.0.1"
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^6.0.0:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@ -1422,6 +1449,24 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
dependencies:
domelementtype "^2.0.1"
entities "^2.0.0"
domelementtype@1, domelementtype@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
domelementtype@^2.0.1:
version "2.1.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e"
integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==
domexception@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304"
@ -1429,6 +1474,21 @@ domexception@^2.0.1:
dependencies:
webidl-conversions "^5.0.0"
domhandler@^2.3.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
dependencies:
domelementtype "1"
domutils@^1.5.1:
version "1.7.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
dependencies:
dom-serializer "0"
domelementtype "1"
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@ -1461,6 +1521,16 @@ enquirer@^2.3.5:
dependencies:
ansi-colors "^4.1.1"
entities@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
entities@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
error-ex@^1.2.0, error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@ -1709,6 +1779,19 @@ exec-sh@^0.3.2:
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5"
integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==
execa@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da"
integrity sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=
dependencies:
cross-spawn "^5.0.1"
get-stream "^3.0.0"
is-stream "^1.1.0"
npm-run-path "^2.0.0"
p-finally "^1.0.0"
signal-exit "^3.0.0"
strip-eof "^1.0.0"
execa@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
@ -1990,6 +2073,11 @@ get-package-type@^0.1.0:
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
get-stream@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
@ -2069,6 +2157,16 @@ growly@^1.3.0:
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
h2m@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/h2m/-/h2m-0.7.0.tgz#b7fae87f6af585aea06000f691dc1f72f809cebc"
integrity sha512-HxuYZGcbG5lqt9t4YNT1+HuZ6E7CLh0urd5zT4LqoRKCf7VCx0bGzIDOuEdIz6po7XY/Fby6kbOql6ATtBofqg==
dependencies:
clipboardy "~1.2.3"
commander "^2.9.0"
htmlparser2 "^3.9.0"
request "^2.67.0"
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@ -2152,6 +2250,18 @@ html-escaper@^2.0.0:
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
htmlparser2@^3.9.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
dependencies:
domelementtype "^1.3.1"
domhandler "^2.3.0"
domutils "^1.5.1"
entities "^1.1.1"
inherits "^2.0.1"
readable-stream "^3.1.1"
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@ -2212,7 +2322,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2:
inherits@2, inherits@^2.0.1, inherits@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -2857,6 +2967,11 @@ jest@^26.6.3:
import-local "^3.0.2"
jest-cli "^26.6.3"
js-htmlencode@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/js-htmlencode/-/js-htmlencode-0.3.0.tgz#b1ce293df94e96f8a8a08b1f3368f977bd255731"
integrity sha1-sc4pPflOlviooIsfM2j5d70lVzE=
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -3056,6 +3171,14 @@ lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
lru-cache@^4.0.1:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
dependencies:
pseudomap "^1.0.2"
yallist "^2.1.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
@ -3178,6 +3301,11 @@ mkdirp@1.x:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moment@^2.29.1:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -3587,6 +3715,11 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
psl@^1.1.28, psl@^1.1.33:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
@ -3656,6 +3789,15 @@ read-pkg@^5.2.0:
parse-json "^5.0.0"
type-fest "^0.6.0"
readable-stream@^3.1.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@ -3700,7 +3842,7 @@ request-promise-native@^1.0.8:
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
request@^2.88.2:
request@^2.67.0, request@^2.88.2:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@ -3793,7 +3935,7 @@ run-parallel@^1.1.9:
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef"
integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@ -4097,6 +4239,13 @@ string.prototype.trimstart@^1.0.1:
call-bind "^1.0.0"
define-properties "^1.1.3"
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
@ -4405,6 +4554,11 @@ use@^3.1.0:
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
util-deprecate@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
uuid@^3.3.2:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
@ -4566,6 +4720,11 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"