feat: first working test-app against login endpoint
This commit is contained in:
parent
2daa1b52fb
commit
e0adb9797b
|
@ -28,10 +28,7 @@ let bankIdUsed = false
|
|||
const recordFolder = `${__dirname}/record`
|
||||
|
||||
async function run() {
|
||||
const agent = new HttpProxyAgent('http://localhost:8080')
|
||||
const agentEnabledFetch = agentWrapper(nodeFetch, agent)
|
||||
|
||||
const fetch = fetchCookie(agentEnabledFetch, cookieJar)
|
||||
const fetch = fetchCookie(nodeFetch, cookieJar)
|
||||
|
||||
try {
|
||||
const api = init(fetch, cookieJar, { record })
|
||||
|
|
|
@ -646,4 +646,4 @@ SPEC CHECKSUMS:
|
|||
|
||||
PODFILE CHECKSUM: f4a92b32cc4938e15ad7ccfefe9898548670abed
|
||||
|
||||
COCOAPODS: 1.11.2
|
||||
COCOAPODS: 1.12.1
|
||||
|
|
|
@ -980,7 +980,7 @@
|
|||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 i386";
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
|
@ -1042,7 +1042,7 @@
|
|||
COPY_PHASE_STRIP = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 i386";
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
|
|
|
@ -0,0 +1,288 @@
|
|||
import { wrapToughCookie } from '@skolplattformen/api'
|
||||
import { CookieJar } from 'tough-cookie'
|
||||
import { ApiHjarntorget } from './apiAdmentum'
|
||||
|
||||
const setupSuccessfullLoginInitiation = (fetcherMock: jest.Mock) => {
|
||||
// 'begin-login'
|
||||
fetcherMock.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
url: 'some url with url encoded at the end?return=hello',
|
||||
})
|
||||
)
|
||||
|
||||
// 'init-shibboleth-login'
|
||||
fetcherMock.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
url: 'some url with url encoded at the end?Target=hello',
|
||||
})
|
||||
)
|
||||
|
||||
// 'init-bankId'
|
||||
fetcherMock.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
text: jest.fn().mockReturnValue(
|
||||
Promise.resolve(`
|
||||
<html>
|
||||
<body>
|
||||
<input name="RelayState" value="aUUID"></input>
|
||||
<input name="SAMLRequest" value="somebase64value"></input>
|
||||
</body>
|
||||
</html>`)
|
||||
),
|
||||
})
|
||||
)
|
||||
|
||||
// 'pick-mvghost'
|
||||
fetcherMock.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
url: 'some url to a mvghost',
|
||||
})
|
||||
)
|
||||
|
||||
// 'start-bankId'
|
||||
fetcherMock.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
url: 'some base url to a mvghost to use when polling status',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const setupSuccessfullBankIdLogin = (fetcherMock: jest.Mock) => {
|
||||
// 'poll-bankid-status'
|
||||
fetcherMock.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
json: jest.fn().mockReturnValue(
|
||||
Promise.resolve({
|
||||
infotext: '',
|
||||
location: 'an url to go to confirm the login',
|
||||
})
|
||||
),
|
||||
})
|
||||
)
|
||||
|
||||
// 'confirm-signature-redirect'
|
||||
fetcherMock.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
text: jest.fn().mockReturnValue(
|
||||
Promise.resolve(`
|
||||
<html>
|
||||
<body>
|
||||
<textarea name="RelayState">relay state probably same uuid as before</textarea>
|
||||
<textarea name="SAMLResponse">base64 encoded saml response</textarea>
|
||||
</body>
|
||||
</html>`)
|
||||
),
|
||||
})
|
||||
)
|
||||
|
||||
// 'authgbg-saml-login'
|
||||
fetcherMock.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
text: jest.fn().mockReturnValue(
|
||||
Promise.resolve(`
|
||||
<html>
|
||||
<body>
|
||||
<input name="RelayState" value="aUUID"></input>
|
||||
<input name="SAMLResponse" value="somebase64value"></input>
|
||||
</body>
|
||||
</html>`)
|
||||
),
|
||||
})
|
||||
)
|
||||
|
||||
// 'admentum-saml-login'
|
||||
fetcherMock.mockReturnValueOnce(Promise.resolve({ status: 200 }))
|
||||
}
|
||||
|
||||
describe('api', () => {
|
||||
let fetcherMock: jest.Mock
|
||||
let api: ApiHjarntorget
|
||||
|
||||
beforeEach(() => {
|
||||
const fetcher = jest.fn()
|
||||
fetcherMock = fetcher as jest.Mock
|
||||
|
||||
const cookieManager = wrapToughCookie(new CookieJar())
|
||||
cookieManager.clearAll()
|
||||
api = new ApiHjarntorget(jest.fn(), cookieManager)
|
||||
api.replaceFetcher(fetcher)
|
||||
})
|
||||
it('works', () => {
|
||||
expect(1 + 1).toBe(2)
|
||||
})
|
||||
// describe('#login', () => {
|
||||
// it('goes through single sing-on steps', async (done) => {
|
||||
// setupSuccessfullLoginInitiation(fetcherMock)
|
||||
// setupSuccessfullBankIdLogin(fetcherMock)
|
||||
// const personalNumber = 'my personal number'
|
||||
|
||||
// const loginComplete = new Promise((resolve, reject) => {
|
||||
// api.on('login', () => done())
|
||||
// });
|
||||
// await api.login(personalNumber)
|
||||
// })
|
||||
// it('checker emits PENDING', async (done) => {
|
||||
// // 'poll-bankid-status'
|
||||
// fetcherMock.mockReturnValueOnce(Promise.resolve({
|
||||
// json: jest.fn().mockReturnValue(Promise.resolve({
|
||||
// infotext: "some prompt to do signing in app",
|
||||
// location: ""
|
||||
// }))
|
||||
// }))
|
||||
|
||||
// const status = checkStatus(fetcherMock, "some url")
|
||||
// status.on('PENDING', () => {
|
||||
// status.cancel()
|
||||
// done()
|
||||
// })
|
||||
// })
|
||||
// it('checker emits ERROR', async (done) => {
|
||||
// // 'poll-bankid-status'
|
||||
// fetcherMock.mockReturnValueOnce(Promise.resolve({
|
||||
// json: jest.fn().mockReturnValue(Promise.resolve({
|
||||
// infotext: "some prompt to do signing in app",
|
||||
// location: "url with error in the name"
|
||||
// }))
|
||||
// }))
|
||||
|
||||
// const status = checkStatus(fetcherMock, "some url")
|
||||
// status.on('ERROR', () => {
|
||||
// status.cancel()
|
||||
// done()
|
||||
// })
|
||||
// })
|
||||
// it('checker emits ERROR when an exception occurs', async (done) => {
|
||||
// // 'poll-bankid-status'
|
||||
// fetcherMock.mockReturnValueOnce(Promise.resolve({
|
||||
// json: jest.fn().mockReturnValue(Promise.resolve({
|
||||
// infotext: undefined,
|
||||
// location: undefined
|
||||
// }))
|
||||
// }))
|
||||
|
||||
// const status = checkStatus(fetcherMock, "some url")
|
||||
// status.on('ERROR', () => {
|
||||
// status.cancel()
|
||||
// done()
|
||||
// })
|
||||
// })
|
||||
// it('remembers used personal number', async (done) => {
|
||||
// setupSuccessfullLoginInitiation(fetcherMock)
|
||||
// setupSuccessfullBankIdLogin(fetcherMock)
|
||||
// const personalNumber = 'my personal number'
|
||||
// await api.login(personalNumber)
|
||||
// api.on('login', () => {
|
||||
// expect(api.getPersonalNumber()).toEqual(personalNumber)
|
||||
// done()
|
||||
// })
|
||||
// })
|
||||
// it('forgets used personal number if sign in is unsuccessful', async (done) => {
|
||||
// setupSuccessfullLoginInitiation(fetcherMock)
|
||||
// // 'poll-bankid-status'
|
||||
// fetcherMock.mockReturnValueOnce(Promise.resolve({
|
||||
// json: jest.fn().mockReturnValue(Promise.resolve({
|
||||
// infotext: "",
|
||||
// location: "an url to go to confirm the login"
|
||||
// }))
|
||||
// }))
|
||||
// // 'confirm-signature-redirect'
|
||||
// fetcherMock.mockReturnValueOnce(Promise.resolve({
|
||||
// text: Promise.resolve("some error occured")
|
||||
// }))
|
||||
|
||||
// const personalNumber = 'my personal number'
|
||||
// const status = await api.login(personalNumber)
|
||||
|
||||
// status.on('ERROR', () => {
|
||||
// expect(api.getPersonalNumber()).toEqual(undefined)
|
||||
// done()
|
||||
// })
|
||||
// })
|
||||
|
||||
// // TODO: Possibly rewrite the mocking so we mock the responses more properly,
|
||||
// // that way it would be possible to implement a throwIfNotOk wrapper for the
|
||||
// // fetch calls.
|
||||
// // it('throws error on external api error', async () => {
|
||||
// // const personalNumber = 'my personal number'
|
||||
// // try {
|
||||
// // await api.login(personalNumber)
|
||||
// // } catch (error: any) {
|
||||
// // expect(error.message).toEqual(expect.stringContaining('Server Error'))
|
||||
// // }
|
||||
// // })
|
||||
// })
|
||||
// describe('#logout', () => {
|
||||
// // it('clears session', async () => {
|
||||
// // await api.logout()
|
||||
// // const session = await api.getSession('')
|
||||
// // expect(session).toEqual({
|
||||
// // headers: {
|
||||
// // cookie: '',
|
||||
// // },
|
||||
// // })
|
||||
// // })
|
||||
// it('emits logout event', async () => {
|
||||
// const listener = jest.fn()
|
||||
// api.on('logout', listener)
|
||||
// await api.logout()
|
||||
// expect(listener).toHaveBeenCalled()
|
||||
// })
|
||||
// it('sets .isLoggedIn', async () => {
|
||||
// api.isLoggedIn = true
|
||||
// await api.logout()
|
||||
// expect(api.isLoggedIn).toBe(false)
|
||||
// })
|
||||
// it('forgets personalNumber', async () => {
|
||||
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// (api as any).personalNumber = 'my personal number'
|
||||
// api.isLoggedIn = true
|
||||
|
||||
// await api.logout()
|
||||
|
||||
// expect(api.getPersonalNumber()).toEqual(undefined)
|
||||
// })
|
||||
// })
|
||||
/*
|
||||
describe('fake', () => {
|
||||
it('sets fake mode for the correct pnr:s', async () => {
|
||||
let status
|
||||
|
||||
status = await api.login('121212121212')
|
||||
expect(status.token).toEqual('fake')
|
||||
|
||||
status = await api.login('201212121212')
|
||||
expect(status.token).toEqual('fake')
|
||||
|
||||
status = await api.login('1212121212')
|
||||
expect(status.token).toEqual('fake')
|
||||
})
|
||||
it('delivers fake data', async (done) => {
|
||||
api.on('login', async () => {
|
||||
const user = await api.getUser()
|
||||
expect(user).toEqual({
|
||||
firstName: 'Namn',
|
||||
lastName: 'Namnsson',
|
||||
isAuthenticated: true,
|
||||
personalNumber: "195001182046",
|
||||
})
|
||||
|
||||
const children = await api.getChildren()
|
||||
expect(children).toHaveLength(2)
|
||||
|
||||
const calendar1 = await api.getCalendar(children[0])
|
||||
expect(calendar1).toHaveLength(20)
|
||||
const calendar2 = await api.getCalendar(children[1])
|
||||
expect(calendar2).toHaveLength(18)
|
||||
|
||||
const skola24Children = await api.getSkola24Children()
|
||||
expect(skola24Children).toHaveLength(1)
|
||||
|
||||
const timetable = await api.getTimetable(skola24Children[0], 2021, 15, 'sv')
|
||||
expect(timetable).toHaveLength(32)
|
||||
|
||||
done()
|
||||
})
|
||||
await api.login('121212121212')
|
||||
})
|
||||
})*/
|
||||
})
|
|
@ -0,0 +1,323 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
Api,
|
||||
CalendarItem,
|
||||
Classmate,
|
||||
CookieManager,
|
||||
EtjanstChild,
|
||||
Fetch,
|
||||
Fetcher,
|
||||
FetcherOptions,
|
||||
FrejaLoginStatusChecker,
|
||||
LoginStatusChecker,
|
||||
MenuItem,
|
||||
NewsItem,
|
||||
Notification,
|
||||
ScheduleItem,
|
||||
SchoolContact,
|
||||
Skola24Child,
|
||||
Teacher,
|
||||
TimetableEntry,
|
||||
toMarkdown,
|
||||
User,
|
||||
wrap,
|
||||
} from '@skolplattformen/api'
|
||||
import { EventEmitter } from 'events'
|
||||
import { decode } from 'he'
|
||||
import { DateTime, FixedOffsetZone } from 'luxon'
|
||||
import * as html from 'node-html-parser'
|
||||
import { fakeFetcher } from './fake/fakeFetcher'
|
||||
import { checkStatus, DummyStatusChecker } from './loginStatus'
|
||||
import { extractMvghostRequestBody, parseCalendarItem } from './parse/parsers'
|
||||
import { bankIdInitUrl, bankIdCheckUrl, apiUrls } from './routes'
|
||||
import parse from '@skolplattformen/curriculum'
|
||||
|
||||
function getDateOfISOWeek(week: number, year: number) {
|
||||
const simple = new Date(year, 0, 1 + (week - 1) * 7)
|
||||
const dow = simple.getDay()
|
||||
const isoWeekStart = simple
|
||||
if (dow <= 4) isoWeekStart.setDate(simple.getDate() - simple.getDay() + 1)
|
||||
else isoWeekStart.setDate(simple.getDate() + 8 - simple.getDay())
|
||||
return isoWeekStart
|
||||
}
|
||||
|
||||
export class ApiAdmentum extends EventEmitter implements Api {
|
||||
private fetch: Fetcher
|
||||
private realFetcher: Fetcher
|
||||
|
||||
private personalNumber?: string
|
||||
|
||||
private cookieManager: CookieManager
|
||||
|
||||
public isLoggedIn = false
|
||||
|
||||
private _isFake = false
|
||||
|
||||
public set isFake(fake: boolean) {
|
||||
this._isFake = fake
|
||||
if (this._isFake) {
|
||||
this.fetch = fakeFetcher
|
||||
} else {
|
||||
this.fetch = this.realFetcher
|
||||
}
|
||||
}
|
||||
|
||||
public get isFake() {
|
||||
return this._isFake
|
||||
}
|
||||
|
||||
constructor(
|
||||
fetch: Fetch,
|
||||
cookieManager: CookieManager,
|
||||
options?: FetcherOptions
|
||||
) {
|
||||
super()
|
||||
this.fetch = wrap(fetch, options)
|
||||
this.realFetcher = this.fetch
|
||||
this.cookieManager = cookieManager
|
||||
}
|
||||
|
||||
public replaceFetcher(fetcher: Fetcher) {
|
||||
this.fetch = fetcher
|
||||
}
|
||||
|
||||
async getSchedule(
|
||||
child: EtjanstChild,
|
||||
from: DateTime,
|
||||
to: DateTime
|
||||
): Promise<(CalendarItem & ScheduleItem)[]> {
|
||||
const lessonsResponseJson: any[] = []
|
||||
|
||||
return lessonsResponseJson.map((l) => {
|
||||
const start = DateTime.fromMillis(l.startDate.ts, {
|
||||
zone: FixedOffsetZone.instance(l.startDate.timezoneOffsetMinutes),
|
||||
})
|
||||
const end = DateTime.fromMillis(l.endDate.ts, {
|
||||
zone: FixedOffsetZone.instance(l.endDate.timezoneOffsetMinutes),
|
||||
})
|
||||
return {
|
||||
id: l.id,
|
||||
title: l.title,
|
||||
description: l.note,
|
||||
location: l.location,
|
||||
startDate: start.toISO(),
|
||||
endDate: end.toISO(),
|
||||
oneDayEvent: false,
|
||||
allDayEvent: false,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getPersonalNumber(): string | undefined {
|
||||
return this.personalNumber
|
||||
}
|
||||
|
||||
public async getSessionHeaders(
|
||||
url: string
|
||||
): Promise<{ [index: string]: string }> {
|
||||
const cookie = await this.cookieManager.getCookieString(url)
|
||||
return {
|
||||
cookie,
|
||||
}
|
||||
}
|
||||
|
||||
async setSessionCookie(sessionCookie: string): Promise<void> {
|
||||
// this.cookieManager.setCookieString(sessionCookie, admentumUrl)
|
||||
|
||||
const user = await this.getUser()
|
||||
if (!user.isAuthenticated) {
|
||||
throw new Error('Session cookie is expired')
|
||||
}
|
||||
|
||||
this.isLoggedIn = true
|
||||
this.emit('login')
|
||||
}
|
||||
|
||||
async getUser(): Promise<User> {
|
||||
console.log('fetching user')
|
||||
const currentUserResponse = await this.fetch('current-user', apiUrls.users) // + /id?
|
||||
if (currentUserResponse.status !== 200) {
|
||||
return { isAuthenticated: false }
|
||||
}
|
||||
|
||||
const retrivedUser = await currentUserResponse.json()
|
||||
return { ...retrivedUser, isAuthenticated: true }
|
||||
}
|
||||
|
||||
async getChildren(): Promise<(Skola24Child & EtjanstChild)[]> {
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
console.log('fetching children')
|
||||
|
||||
const myChildrenResponseJson: any[] = []
|
||||
|
||||
return myChildrenResponseJson.map(
|
||||
(c) =>
|
||||
({
|
||||
id: c.id,
|
||||
sdsId: c.id,
|
||||
personGuid: c.id,
|
||||
firstName: c.firstName,
|
||||
lastName: c.lastName,
|
||||
name: `${c.firstName} ${c.lastName}`,
|
||||
} as Skola24Child & EtjanstChild)
|
||||
)
|
||||
}
|
||||
|
||||
async getCalendar(child: EtjanstChild): Promise<CalendarItem[]> {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getClassmates(_child: EtjanstChild): Promise<Classmate[]> {
|
||||
// TODO: We could get this from the events a child is associated with...
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
public async getTeachers(child: EtjanstChild): Promise<Teacher[]> {
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
public async getSchoolContacts(
|
||||
child: EtjanstChild
|
||||
): Promise<SchoolContact[]> {
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async getNews(_child: EtjanstChild): Promise<NewsItem[]> {
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
async getNewsDetails(_child: EtjanstChild, item: NewsItem): Promise<any> {
|
||||
return { ...item }
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getMenu(_child: EtjanstChild): Promise<MenuItem[]> {
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
// Have not found this available on hjärntorget. Perhaps do a mapping to https://www.skolmaten.se/ ?
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
async getChildEventsWithAssociatedMembers(child: EtjanstChild) {
|
||||
return this.getEventsWithAssociatedMembersForChildren([child])
|
||||
}
|
||||
|
||||
async getEventsWithAssociatedMembersForChildren(children: EtjanstChild[]) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
async getNotifications(child: EtjanstChild): Promise<Notification[]> {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
async getSkola24Children(): Promise<Skola24Child[]> {
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async getTimetable(
|
||||
child: Skola24Child,
|
||||
week: number,
|
||||
year: number,
|
||||
_lang: string
|
||||
): Promise<TimetableEntry[]> {
|
||||
const startDate = DateTime.fromJSDate(getDateOfISOWeek(week, year))
|
||||
const endDate = startDate.plus({ days: 7 })
|
||||
|
||||
const lessonsResponseJson: any[] = []
|
||||
|
||||
return lessonsResponseJson.map((l) => {
|
||||
const start = DateTime.fromMillis(l.startDate.ts, {
|
||||
zone: FixedOffsetZone.instance(l.startDate.timezoneOffsetMinutes),
|
||||
})
|
||||
const end = DateTime.fromMillis(l.endDate.ts, {
|
||||
zone: FixedOffsetZone.instance(l.endDate.timezoneOffsetMinutes),
|
||||
})
|
||||
return {
|
||||
...parse(l.title, _lang),
|
||||
id: l.id,
|
||||
teacher: l.bookedTeacherNames && l.bookedTeacherNames[0],
|
||||
location: l.location,
|
||||
timeStart: start.toISOTime().substring(0, 5),
|
||||
timeEnd: end.toISOTime().substring(0, 5),
|
||||
dayOfWeek: start.toJSDate().getDay(),
|
||||
blockName: l.title,
|
||||
dateStart: start.toISODate(),
|
||||
dateEnd: end.toISODate(),
|
||||
} as TimetableEntry
|
||||
})
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
this.isLoggedIn = false
|
||||
this.personalNumber = undefined
|
||||
this.cookieManager.clearAll()
|
||||
this.emit('logout')
|
||||
}
|
||||
|
||||
public async login(personalNumber?: string): Promise<LoginStatusChecker> {
|
||||
// short circut the bank-id login if in fake mode
|
||||
if (personalNumber !== undefined && personalNumber.endsWith('1212121212'))
|
||||
return this.fakeMode()
|
||||
|
||||
this.isFake = false
|
||||
const sessionId = await this.fetch('init-session', bankIdInitUrl(''))
|
||||
.then((res) => res.text())
|
||||
.then((text) => /sessionsid=(.)/.exec(text)?.[0])
|
||||
|
||||
if (!sessionId) throw new Error('No session provided')
|
||||
|
||||
console.log('start polling', sessionId)
|
||||
const statusChecker = checkStatus(this.fetch, bankIdCheckUrl(sessionId))
|
||||
|
||||
statusChecker.on('OK', async () => {
|
||||
// setting these similar to how the sthlm api does it
|
||||
// not sure if it is needed or if the cookies are enough for fetching all info...
|
||||
this.isLoggedIn = true
|
||||
this.personalNumber = personalNumber
|
||||
this.emit('login')
|
||||
})
|
||||
statusChecker.on('ERROR', () => {
|
||||
this.personalNumber = undefined
|
||||
})
|
||||
|
||||
return statusChecker
|
||||
}
|
||||
|
||||
private async fakeMode(): Promise<LoginStatusChecker> {
|
||||
this.isFake = true
|
||||
|
||||
setTimeout(() => {
|
||||
this.isLoggedIn = true
|
||||
this.emit('login')
|
||||
}, 50)
|
||||
|
||||
const emitter: any = new EventEmitter()
|
||||
emitter.token = 'fake'
|
||||
return emitter
|
||||
}
|
||||
|
||||
async loginFreja(): Promise<FrejaLoginStatusChecker> {
|
||||
throw new Error('Not implemented...')
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,21 @@
|
|||
export const currentUser = () =>
|
||||
({
|
||||
url: 'https://admentum.goteborg.se/api/core/current-user',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
id: '889911_goteborgsstad',
|
||||
firstName: 'TOLV',
|
||||
lastName: 'TOLVAN',
|
||||
email: null,
|
||||
online: true,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
}),
|
||||
} as any as Response)
|
|
@ -0,0 +1,225 @@
|
|||
export const eventRoleMembers21 = () =>
|
||||
({
|
||||
url: 'https://admentum.goteborg.se/api/event-members/members-having-role?eventId=21&roleId=821',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: '__system$virtual$calendar__',
|
||||
firstName: 'Kalendern',
|
||||
lastName: 'i PING PONG',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/default/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
// Klass: 8B
|
||||
id: '133700_goteborgsstad',
|
||||
firstName: 'Azra',
|
||||
lastName: 'Göransson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
]),
|
||||
} as any as Response)
|
||||
|
||||
export const eventRoleMembers14 = () =>
|
||||
({
|
||||
url: 'https://admentum.goteborg.se/api/event-members/members-having-role?eventId=14&roleId=821',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
// Klass: 8B
|
||||
id: '133700_goteborgsstad',
|
||||
firstName: 'Azra',
|
||||
lastName: 'Göransson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
id: '362119_goteborgsstad',
|
||||
firstName: 'Elina',
|
||||
lastName: 'Cocolis',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
id: '999999_goteborgsstad',
|
||||
firstName: 'Sanne',
|
||||
lastName: 'Berggren',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
id: '168925_goteborgsstad',
|
||||
firstName: 'Teddy',
|
||||
lastName: 'Karlsson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
id: '494949_goteborgsstad',
|
||||
firstName: 'Fideli',
|
||||
lastName: 'Sundström',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
]),
|
||||
} as any as Response)
|
||||
|
||||
export const eventRoleMembers18 = () =>
|
||||
({
|
||||
url: 'https://admentum.goteborg.se/api/event-members/members-having-role?eventId=18&roleId=821',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: '776655_goteborgsstad',
|
||||
firstName: 'Walid',
|
||||
lastName: 'Söderström',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
id: '388601_goteborgsstad',
|
||||
firstName: 'Rosa',
|
||||
lastName: 'Fredriksson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
id: '654654_goteborgsstad',
|
||||
firstName: 'Moses',
|
||||
lastName: 'Johansson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
id: '1313131_goteborgsstad',
|
||||
firstName: 'Haris',
|
||||
lastName: 'Jonsson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
id: '887766_goteborgsstad',
|
||||
firstName: 'Neo',
|
||||
lastName: 'Lundström',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
// Klass: 5A
|
||||
id: '123456_goteborgsstad',
|
||||
firstName: 'Jon',
|
||||
lastName: 'Göransson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
]),
|
||||
} as any as Response)
|
||||
|
||||
export const eventRoleMembers24 = () =>
|
||||
({
|
||||
url: 'https://admentum.goteborg.se/api/event-members/members-having-role?eventId=24&roleId=821',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: '393939_goteborgsstad',
|
||||
firstName: 'Malik Maria',
|
||||
lastName: 'Henriksson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
id: '444444_goteborgsstad',
|
||||
firstName: 'Idas',
|
||||
lastName: 'Svensson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
id: '818181_goteborgsstad',
|
||||
firstName: 'Nadja',
|
||||
lastName: 'Ekström',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
id: '919191_goteborgsstad',
|
||||
firstName: 'Karim',
|
||||
lastName: 'Fakir',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
// Klass: Förskola
|
||||
id: '133737_goteborgsstad',
|
||||
firstName: 'Havin',
|
||||
lastName: 'Göransson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
]),
|
||||
} as any as Response)
|
|
@ -0,0 +1,38 @@
|
|||
export const events = () =>
|
||||
({
|
||||
url: 'https://admentum.goteborg.se/api/events/events-sorted-by-name?offset=0&limit=100',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: 18,
|
||||
name: '138JÄTS 21/22 5A',
|
||||
url: 'https://admentum.goteborg.se/o/apiAccessWithKey.do?forwardUrl=%2FlaunchCourse.do%3Fid%3D12',
|
||||
state: 'ONGOING',
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: '138JÄTS 21/22 8B',
|
||||
url: 'https://admentum.goteborg.se/o/apiAccessWithKey.do?forwardUrl=%2FlaunchCourse.do%3Fid%3D14',
|
||||
state: 'ONGOING',
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
name: '138JÄTS Provschema år 8',
|
||||
url: 'https://admentum.goteborg.se/o/apiAccessWithKey.do?forwardUrl=%2FlaunchCourse.do%3Fid%3D21',
|
||||
state: 'ONGOING',
|
||||
},
|
||||
{
|
||||
id: 24,
|
||||
name: '139SS27F Södra Bangatan förskola',
|
||||
url: 'https://admentum.goteborg.se/o/apiAccessWithKey.do?forwardUrl=%2FlaunchCourse.do%3Fid%3D24',
|
||||
state: 'ONGOING',
|
||||
},
|
||||
]),
|
||||
} as any as Response)
|
|
@ -0,0 +1,36 @@
|
|||
import { Fetcher, Response } from '@skolplattformen/api'
|
||||
import { calendars, calendar_14241345 } from './calendars';
|
||||
import { currentUser } from './current-user';
|
||||
import { events } from './events';
|
||||
import { lessons_123456_goteborgsstad, lessons_133700_goteborgsstad, lessons_133737_goteborgsstad } from './lessons';
|
||||
import { myChildren } from './my-children';
|
||||
import { wallEvents } from './wall-events';
|
||||
import { information } from './information'
|
||||
import { genericRolesInEvent } from './roles-in-event';
|
||||
import { eventRoleMembers14, eventRoleMembers18, eventRoleMembers21, eventRoleMembers24 } from './event-role-members';
|
||||
|
||||
const fetchMappings: { [name:string]: () => Response} = {
|
||||
'current-user': currentUser,
|
||||
'events': events,
|
||||
'my-children': myChildren,
|
||||
'wall-events': wallEvents,
|
||||
'lessons-133700_goteborgsstad': lessons_133700_goteborgsstad,
|
||||
'lessons-133737_goteborgsstad': lessons_133737_goteborgsstad,
|
||||
'lessons-123456_goteborgsstad': lessons_123456_goteborgsstad,
|
||||
'info': information,
|
||||
'roles-in-event-14': genericRolesInEvent,
|
||||
'roles-in-event-18': genericRolesInEvent,
|
||||
'roles-in-event-21': genericRolesInEvent,
|
||||
'roles-in-event-24': genericRolesInEvent,
|
||||
'event-role-members-14-821': eventRoleMembers14,
|
||||
'event-role-members-18-821': eventRoleMembers18,
|
||||
'event-role-members-21-821': eventRoleMembers21,
|
||||
'event-role-members-24-821': eventRoleMembers24,
|
||||
'calendars': calendars,
|
||||
'calendar-14241345': calendar_14241345,
|
||||
}
|
||||
|
||||
export const fakeFetcher: Fetcher = (name: string, url: string, init?: any): Promise<Response> => {
|
||||
const responder = fetchMappings[name] ?? (() => {throw new Error("Request not faked for name: " + name)})
|
||||
return Promise.resolve(responder());
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/* eslint-disable no-useless-escape */
|
||||
export const information = () =>
|
||||
({
|
||||
url: 'https://admentum.goteborg.se/api/information/messages-by-date-desc?messageStatus=CURRENT&offset=0&limit=10&language=en',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: 3276034,
|
||||
title: 'Nu får du och ditt barn tillgång till Polyglutt hemma',
|
||||
body: '<p><strong>Nu får alla barn som går i kommunal förskola i Göteborg tillgång till bilderboksappen Polyglutt hemifrån! Det innebär att du som vårdnadshavare och barn kan ta del av ett bibliotek av böcker på både svenska och 60 andra språk, inklusive TAKK och teckenspråk via telefon eller läsplatta.</strong></p>\r\n<p>Polyglutt är en app med bilderböcker som fungerar som ett verktyg för att arbeta med språkutveckling och litteratur i förskolan och hemma.</p>\r\n<p>Polyglutt Home Access är en tjänst som innebär att alla barn som går i kommunal förskola i Göteborg får tillgång till ett bibliotek av böcker på både svenska och 60 andra språk, inklusive TAKK och teckenspråk hemifrån. Varje förskola kan också skapa egna bokhyllor med boktips i appen som du och ditt barn kan läsa hemma.</p>\r\n<p>Tjänsten fungerar på iPad, Androidplattor och i mobilen.</p>\r\n<p>Vill du veta mer om tjänsten, kontakta pedagogerna på ditt barns förskola.</p>',
|
||||
creator: {
|
||||
id: '501747_goteborgsstad',
|
||||
firstName: 'Information Digitalisering',
|
||||
lastName: 'Innovation',
|
||||
email:
|
||||
'information.digitaliseringochinnovation@forskola.goteborg.se',
|
||||
online: false,
|
||||
imagePath:
|
||||
'/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
recipientGroups: [
|
||||
{
|
||||
id: 1121821,
|
||||
name: 'DL Göteborg Vhavare förskolor',
|
||||
},
|
||||
],
|
||||
created: {
|
||||
ts: 1629970713111,
|
||||
timezoneOffsetMinutes: 120,
|
||||
},
|
||||
attachments: [],
|
||||
readByUser: false,
|
||||
archivedByUser: false,
|
||||
},
|
||||
{
|
||||
id: 3270718,
|
||||
title: 'Information från grundskoleförvaltningen',
|
||||
body: '<p>Till vårdnadshavare med barn på Göteborgs Stads grundskolor och grundsärskolor.</p>\r\n<p>Spridningen av covid-19 har ökat. Därför är det viktigt att alla hjälper till att minska spridningen av smitta.</p>\r\n<h2>Vi fortsätter hålla avstånd</h2>\r\n<ul>\r\n<li>Om du vill ha kontakt med någon på ditt barns skola vill vi gärna att du ringer eller skickar e-post.</li>\r\n<li>Lämna och hämta ditt barn utomhus på skolgården.</li>\r\n<li>En del möten som skolan har kommer att vara digitala.</li>\r\n<li>Uppmuntra ditt barn att promenera till och från skolan för att minska trängseln i kollektivtrafiken.</li>\r\n</ul>\r\n<h2>Detta gäller när ditt barn är sjukt</h2>\r\n<ul>\r\n<li>Barn som bara är lite sjuka, som till exempel är snuviga eller har ont i halsen, ska stanna hemma.</li>\r\n<li>Berätta alltid för skolan om ditt barn har konstaterad covid-19.</li>\r\n</ul>\r\n<p><a href="https://goteborg.se/wps/wcm/connect/a515d17c-7078-4663-8493-d1900b78cfb3/Om+ditt+barn+%C3%A4r+sjukt+eller+borta+fr%C3%A5n+skolan_information+till+v%C3%A5rdnadshavare_uppdaterad+13+augusti+2021.pdf?MOD=AJPERES">Här hittar du mer information om vad som gäller när ditt barn är sjukt.</a></p>\r\n<h2>Om ditt barn har varit på resa utomlands</h2>\r\n<p>Folkhälsomyndigheten rekommenderar alla som har varit i länder utanför Norden att ta ett test för covid-19 när de kommer tillbaka Sverige. Detta gäller oavsett om man har symtom eller inte.</p>\r\n<p>Läs mer på Krisinformation.se om vad som gäller för resor från olika länder: <br /><a href="https://www.krisinformation.se/detta-kan-handa/handelser-och-storningar/20192/myndigheterna-om-det-nya-coronaviruset/reseinformation-med-anledning-av-det-nya-coronaviruset">Utrikesresor och att vistas utomlands - Krisinformation.se</a></p>\r\n<h2>Undervisning på skolan</h2>\r\n<p>Från och med höstterminen 2021 har alla skolor undervisning på plats i skolan. Detta gäller även för årskurs 7-9.</p>\r\n<p>För förskoleklass till och med årskurs 9 finns det fortfarande möjlighet att få undervisning på distans om:</p>\r\n<ul>\r\n<li>Många av de som jobbar på skolan är frånvarande på grund av covid-19 och det inte går att ha undervisning i skolan.</li>\r\n<li>Det är stor spridningen av covid-19 bland elever och medarbetare.</li>\r\n</ul>\r\n<h2>Nytt test för covid-19 på skolorna</h2>\r\n<p>Inom kort börjar Västra Götalandsregionen med ett nytt test för covid-19 riktat mot elever. Om ditt barn har haft nära kontakt med en person på skolan som har konstaterad covid-19 får ni med ett paket hem med ett test. </p>\r\n<p>Du som vårdnadshavare hjälper ditt barn att ta testet. Testet lämnar du som vårdnadshavare sedan till en utvald vårdcentral.</p>\r\n<p>Om ditt barn ska ta ett test får du mer information från ditt barns skola om hur testet går till och vilken vårdcentral du ska lämna det till.</p>\r\n<h2>Kontakt</h2>\r\n<p>Har du frågor eller funderingar kontaktar du ditt barns skola.</p>\r\n<p><a href="https://goteborg.se/wps/portal/press-och-media/aktuelltarkivet/aktuellt/18b9930e-d34c-4d6a-817a-c1b8e74e5f9f#Z7_42G01J41KGV2F0ALK2K1SN1M75">Läs mer om covid-19 och vad som gäller för grundskoleförvaltningen.</a></p>\r\n<p> </p>',
|
||||
creator: {
|
||||
id: '486497_goteborgsstad',
|
||||
firstName: 'Grundskola',
|
||||
lastName: 'Informerar',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath:
|
||||
'/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
recipientGroups: [
|
||||
{
|
||||
id: 4925595,
|
||||
name: 'DL Göteborg Grundskola Vhavare Alla',
|
||||
},
|
||||
{
|
||||
id: 4525636,
|
||||
name: 'Grundskola - informationskonto',
|
||||
},
|
||||
{
|
||||
id: 4925600,
|
||||
name: 'DL Göteborg Grundsärskola Vhavare Alla',
|
||||
},
|
||||
],
|
||||
created: {
|
||||
ts: 1629096850743,
|
||||
timezoneOffsetMinutes: 120,
|
||||
},
|
||||
attachments: [
|
||||
{
|
||||
id: 67888219,
|
||||
name: 'Om ditt barn är sjukt eller borta från skolan_information till vårdnadshavare_uppdaterad 13 augusti 2021.pdf',
|
||||
size: 70466,
|
||||
},
|
||||
],
|
||||
readByUser: false,
|
||||
archivedByUser: false,
|
||||
},
|
||||
{
|
||||
id: 2982365,
|
||||
title: 'Nya regler för skolplacering i förskoleklass och grundskola',
|
||||
body: '<p>Grundskolenämnden har beslutat om nya regler för skolplacering i förskoleklass och grundskola. Reglerna ska stärka elevernas rätt till en skola nära hemmet och börjar gälla 1 januari 2021.</p>\r\n<p>Du kan läsa mer på sidan <a href="https://goteborg.se/wps/portal/press-och-media/aktuelltarkivet/aktuellt/e45ce367-4d46-48b4-936d-900a3e45e490">Nya regler för skolplacering i förskoleklass och grundskola</a>. </p>\r\n<p>Om du har frågor kan du kontakta grundskoleförvaltningen på telefon: 031-365 09 60 eller e-post: <a href="mailto:grundskola@grundskola.goteborg.se">grundskola@grundskola.goteborg.se</a>. </p>\r\n<p><em>Observera att detta meddelande inte går att svara på. </em></p>\r\n<p> </p>',
|
||||
creator: {
|
||||
id: '486497_goteborgsstad',
|
||||
firstName: 'Grundskola',
|
||||
lastName: 'Informerar',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath:
|
||||
'/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
recipientGroups: [
|
||||
{
|
||||
id: 4925595,
|
||||
name: 'DL Göteborg Grundskola Vhavare Alla',
|
||||
},
|
||||
{
|
||||
id: 4525636,
|
||||
name: 'Grundskola - informationskonto',
|
||||
},
|
||||
],
|
||||
created: {
|
||||
ts: 1603974943027,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
attachments: [],
|
||||
readByUser: false,
|
||||
archivedByUser: false,
|
||||
},
|
||||
]),
|
||||
} as any as Response)
|
|
@ -0,0 +1,431 @@
|
|||
import { toNamespacedPath } from 'path'
|
||||
|
||||
// TODO: fix the startDate/endDate of all lessons
|
||||
export const lessons_133700_goteborgsstad = () => {
|
||||
const baseTime = 1636357800000
|
||||
const baseDate = new Date(baseTime)
|
||||
const today = new Date()
|
||||
const currentHour = today.getHours()
|
||||
today.setHours(baseDate.getHours())
|
||||
today.setMinutes(baseDate.getMinutes())
|
||||
today.setSeconds(0)
|
||||
|
||||
let offset = Math.abs(baseTime - today.getTime())
|
||||
const weekDay = today.getDay()
|
||||
|
||||
if (weekDay == 6 || (weekDay == 5 && currentHour >= 18))
|
||||
offset = offset + 2 * 86400000
|
||||
if (weekDay == 0) offset = offset + 86400000
|
||||
if (weekDay > 0 && weekDay < 6 && currentHour >= 18)
|
||||
offset = offset + 86400000
|
||||
|
||||
return {
|
||||
url: 'https://admentum.goteborg.se/api/schema/lessons?forUser=133700_goteborgsstad&startDateIso=2021-11-01&endDateIso=2021-11-08',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: '36080472:1',
|
||||
title: 'HKK',
|
||||
location: 'A402',
|
||||
calendars: ['138JÄTS 21/22 8B/HKK'],
|
||||
startDate: {
|
||||
ts: offset + 1636357800000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636360500000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: ['Noel Nyström (NNM)'],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
id: '36080497:1',
|
||||
title: 'BL',
|
||||
location: 'B260',
|
||||
calendars: ['138JÄTS 21/22 8B/BL'],
|
||||
startDate: {
|
||||
ts: offset + 1636361700000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636365000000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: ['Joseph Ekström (JHE)'],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
id: '37164864:1',
|
||||
title: 'IDH',
|
||||
location: 'IDH Ute',
|
||||
calendars: ['138JÄTS 21/22 8B/IDH'],
|
||||
startDate: {
|
||||
ts: offset + 1636365600000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636369800000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: ['Katja Fransson (KAF)'],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
id: '36080557:1',
|
||||
title: 'LUNCH',
|
||||
location: '-',
|
||||
calendars: ['138JÄTS 21/22 8B'],
|
||||
startDate: {
|
||||
ts: offset + 1636370700000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636372800000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: [],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
id: '36080576:1',
|
||||
title: 'EN',
|
||||
location: 'A402',
|
||||
calendars: ['138JÄTS 21/22 8B/EN'],
|
||||
startDate: {
|
||||
ts: offset + 1636372800000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636376400000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: ['Henrietta Fransson (HAF)'],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
id: '36080591:1',
|
||||
title: 'MA',
|
||||
location: 'A402',
|
||||
calendars: ['138JÄTS 21/22 8B/MA'],
|
||||
startDate: {
|
||||
ts: offset + 1636377000000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636380600000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: ['Amin Månsson (ANM)'],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
]),
|
||||
} as any as Response
|
||||
}
|
||||
|
||||
export const lessons_123456_goteborgsstad = () => {
|
||||
const baseTime = 1636357800000
|
||||
const baseDate = new Date(baseTime)
|
||||
const today = new Date()
|
||||
const currentHour = today.getHours()
|
||||
today.setHours(baseDate.getHours())
|
||||
today.setMinutes(baseDate.getMinutes())
|
||||
today.setSeconds(0)
|
||||
|
||||
let offset = Math.abs(baseTime - today.getTime())
|
||||
const weekDay = today.getDay()
|
||||
|
||||
if (weekDay == 6 || (weekDay == 5 && currentHour >= 18))
|
||||
offset = offset + 2 * 86400000
|
||||
if (weekDay == 0) offset = offset + 86400000
|
||||
if (weekDay > 0 && weekDay < 6 && currentHour >= 18)
|
||||
offset = offset + 86400000
|
||||
|
||||
return {
|
||||
url: 'https://admentum.goteborg.se/api/schema/lessons?forUser=123456_goteborgsstad&startDateIso=2021-11-01&endDateIso=2021-11-08',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () => [
|
||||
{
|
||||
id: '36080454:1',
|
||||
title: 'EV',
|
||||
location: 'P18',
|
||||
calendars: ['138JÄTS 21/22 5A'],
|
||||
startDate: {
|
||||
ts: offset + 1636355400000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636357500000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: ['Petra Modin (PMO)', 'Joakim Ness (JNE)'],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
id: '36080467:1',
|
||||
title: 'MENT',
|
||||
location: 'P18',
|
||||
calendars: ['138JÄTS 21/22 5A'],
|
||||
startDate: {
|
||||
ts: offset + 1636357500000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636358100000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: ['Petra Modin (PMO)', 'Joakim Ness (JNE)'],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
id: '36080474:1',
|
||||
title: 'EN',
|
||||
location: 'P18',
|
||||
calendars: ['138JÄTS 21/22 5A'],
|
||||
startDate: {
|
||||
ts: offset + 1636358400000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636362000000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: ['Petra Modin (PMO)'],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
id: '36080502:1',
|
||||
title: 'SV',
|
||||
location: 'P18',
|
||||
calendars: ['138JÄTS 21/22 5A'],
|
||||
startDate: {
|
||||
ts: offset + 1636362900000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636366500000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: ['Joakim Ness (JNE)'],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
id: '36080529:1',
|
||||
title: 'LUNCH',
|
||||
location: '-',
|
||||
calendars: ['138JÄTS 21/22 5A'],
|
||||
startDate: {
|
||||
ts: offset + 1636366500000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636368300000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: [],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
id: '36080545:1',
|
||||
title: 'MA',
|
||||
location: 'P18',
|
||||
calendars: ['138JÄTS 21/22 5A'],
|
||||
startDate: {
|
||||
ts: offset + 1636369200000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636372800000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: ['Ali Gupta (AGU)'],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
id: '36080578:1',
|
||||
title: 'NO',
|
||||
location: 'P18',
|
||||
calendars: ['138JÄTS 21/22 5A'],
|
||||
startDate: {
|
||||
ts: offset + 1636373400000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
endDate: {
|
||||
ts: offset + 1636376400000,
|
||||
timezoneOffsetMinutes: 60,
|
||||
},
|
||||
ownPlannings: null,
|
||||
teacherPlannings: null,
|
||||
teacherAndStudentPlannings: null,
|
||||
ownGeneralPlannings: null,
|
||||
teacherGeneralPlannings: null,
|
||||
teacherAndStudentGeneralPlannings: null,
|
||||
bookedResourceNames: [],
|
||||
bookedTeacherNames: ['Ali Gupta (AGU)'],
|
||||
hasTest: false,
|
||||
hasHomework: false,
|
||||
hasAssignment: false,
|
||||
url: null,
|
||||
note: '',
|
||||
},
|
||||
],
|
||||
} as any as Response
|
||||
}
|
||||
|
||||
export const lessons_133737_goteborgsstad = () =>
|
||||
({
|
||||
url: 'https://admentum.goteborg.se/api/schema/lessons?forUser=133737_goteborgsstad&startDateIso=2021-11-01&endDateIso=2021-11-08',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () => Promise.resolve([] as any[]),
|
||||
} as any as Response)
|
|
@ -0,0 +1,44 @@
|
|||
export const myChildren = () =>
|
||||
({
|
||||
url: 'https://admentum.goteborg.se/api/person/children',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
// Klass: Förskola
|
||||
id: '133737_goteborgsstad',
|
||||
firstName: 'Havin',
|
||||
lastName: 'Göransson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
// Klass: 8B
|
||||
id: '133700_goteborgsstad',
|
||||
firstName: 'Azra',
|
||||
lastName: 'Göransson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
{
|
||||
// Klass: 5A
|
||||
id: '123456_goteborgsstad',
|
||||
firstName: 'Jon',
|
||||
lastName: 'Göransson',
|
||||
email: null,
|
||||
online: false,
|
||||
imagePath: '/pp/lookAndFeel/skins/admentum/icons/monalisa_large.png',
|
||||
extraInfoInCatalog: '',
|
||||
},
|
||||
]),
|
||||
} as any as Response)
|
|
@ -0,0 +1,18 @@
|
|||
export const genericRolesInEvent = () =>
|
||||
({
|
||||
url: 'https://admentum.goteborg.se/api/event-members/roles?eventId=XXX&language=en',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
cookie: 'REMOVED',
|
||||
},
|
||||
status: 200,
|
||||
statusText: '200',
|
||||
json: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: 821,
|
||||
name: 'SINGLE ROLE',
|
||||
},
|
||||
]),
|
||||
} as any as Response)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,8 @@
|
|||
import { Features } from '@skolplattformen/api'
|
||||
|
||||
export const features: Features = {
|
||||
LOGIN_BANK_ID_SAME_DEVICE_WITHOUT_ID: false,
|
||||
LOGIN_FREJA_EID: false,
|
||||
FOOD_MENU: false,
|
||||
CLASS_LIST: false,
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import {
|
||||
Api,
|
||||
Fetch,
|
||||
FetcherOptions,
|
||||
RNCookieManager,
|
||||
ToughCookieJar,
|
||||
wrapReactNativeCookieManager,
|
||||
wrapToughCookie,
|
||||
} from '@skolplattformen/api'
|
||||
import { ApiAdmentum } from './apiAdmentum'
|
||||
export { features } from './features'
|
||||
|
||||
const init = (
|
||||
fetchImpl: Fetch,
|
||||
cookieManagerImpl: RNCookieManager | ToughCookieJar,
|
||||
options?: FetcherOptions
|
||||
): Api => {
|
||||
// prettier-ignore
|
||||
const cookieManager = ((cookieManagerImpl as RNCookieManager).get)
|
||||
? wrapReactNativeCookieManager(cookieManagerImpl as RNCookieManager)
|
||||
: wrapToughCookie(cookieManagerImpl as ToughCookieJar)
|
||||
return new ApiAdmentum(fetchImpl as any, cookieManager, options)
|
||||
}
|
||||
|
||||
export default init
|
|
@ -0,0 +1,61 @@
|
|||
import { Fetcher, LoginStatusChecker } from '@skolplattformen/api'
|
||||
import { EventEmitter } from 'events'
|
||||
import { bankIdCheckUrl } from './routes'
|
||||
|
||||
export class GrandidChecker extends EventEmitter implements LoginStatusChecker {
|
||||
private fetcher: Fetcher
|
||||
|
||||
private basePollingUrl: string
|
||||
|
||||
public token: string
|
||||
|
||||
private cancelled = false
|
||||
|
||||
constructor(fetcher: Fetcher, basePollingUrl: string) {
|
||||
super()
|
||||
this.token = '' // not used, but needed for compatability with the LoginStatusChecker
|
||||
this.fetcher = fetcher
|
||||
this.basePollingUrl = basePollingUrl
|
||||
|
||||
this.check()
|
||||
}
|
||||
|
||||
async check(): Promise<void> {
|
||||
// try {
|
||||
// console.log('polling bankid signature')
|
||||
// // https://mNN-mg-local.idp.funktionstjanster.se/mg-local/auth/ccp11/grp/pollstatus
|
||||
// if (true)
|
||||
// this.emit('OK')
|
||||
// } else if (isError) {
|
||||
// console.log('polling error')
|
||||
// this.emit('ERROR')
|
||||
// } else if (!this.cancelled && keepPolling) {
|
||||
// console.log('keep on polling...')
|
||||
// this.emit('PENDING')
|
||||
// setTimeout(() => this.check(), 3000)
|
||||
// }
|
||||
// } catch (er) {
|
||||
// console.log('Error validating login to Hjärntorget', er)
|
||||
// this.emit('ERROR')
|
||||
// }
|
||||
}
|
||||
|
||||
async cancel(): Promise<void> {
|
||||
this.cancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
export const checkStatus = (
|
||||
fetch: Fetcher,
|
||||
basePollingUrl: string
|
||||
): LoginStatusChecker => new GrandidChecker(fetch, basePollingUrl)
|
||||
|
||||
export class DummyStatusChecker
|
||||
extends EventEmitter
|
||||
implements LoginStatusChecker
|
||||
{
|
||||
token = ''
|
||||
async cancel(): Promise<void> {
|
||||
// do nothing
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
declare module 'h2m'
|
|
@ -0,0 +1,58 @@
|
|||
import * as html from 'node-html-parser'
|
||||
import { decode } from 'he'
|
||||
|
||||
// TODO: Move this into the parse folder and convert it to follow the pattern of other parsers (include tests).
|
||||
|
||||
export const extractInputField = (sought: string, attrs: string[]) => {
|
||||
// there must be a better way to do this...
|
||||
const s = attrs.find(e => e.indexOf(sought) >= 0) || ""
|
||||
const v = s.substring(s.indexOf('value="') + 'value="'.length)
|
||||
return v.substring(0, v.length - 2)
|
||||
}
|
||||
|
||||
export function extractMvghostRequestBody(initBankIdResponseText: string) {
|
||||
const doc = html.parse(decode(initBankIdResponseText))
|
||||
const inputAttrs = doc.querySelectorAll('input').map(i => (i as any).rawAttrs)
|
||||
const relayState = extractInputField('RelayState', inputAttrs)
|
||||
const samlRequest = extractInputField("SAMLRequest", inputAttrs)
|
||||
const mvghostRequestBody = `RelayState=${encodeURIComponent(relayState)}&SAMLRequest=${encodeURIComponent(samlRequest)}`
|
||||
|
||||
return mvghostRequestBody
|
||||
}
|
||||
|
||||
export function extractHjarntorgetSAMLLogin(authGbgLoginResponseText: string) {
|
||||
const authGbgLoginDoc = html.parse(decode(authGbgLoginResponseText))
|
||||
const inputAttrs = authGbgLoginDoc.querySelectorAll('input').map(i => (i as any).rawAttrs)
|
||||
const RelayStateText = extractInputField('RelayState', inputAttrs)
|
||||
const SAMLResponseText = extractInputField("SAMLResponse", inputAttrs)
|
||||
|
||||
return `SAMLResponse=${encodeURIComponent(SAMLResponseText || '')}&RelayState=${encodeURIComponent(RelayStateText || '')}`
|
||||
}
|
||||
|
||||
export function extractAuthGbgLoginRequestBody(signatureResponseText: string) {
|
||||
const signatureResponseDoc = html.parse(decode(signatureResponseText))
|
||||
const signatureResponseTextAreas = signatureResponseDoc.querySelectorAll('textarea')
|
||||
const SAMLResponseElem = signatureResponseTextAreas.find(ta => {
|
||||
const nameAttr = ta.getAttribute("name")
|
||||
return nameAttr === 'SAMLResponse'
|
||||
})
|
||||
const SAMLResponseText = SAMLResponseElem?.rawText
|
||||
const RelayStateElem = signatureResponseTextAreas.find(ta => {
|
||||
const nameAttr = ta.getAttribute("name")
|
||||
return nameAttr === 'RelayState'
|
||||
})
|
||||
const RelayStateText = RelayStateElem?.rawText
|
||||
const authGbgLoginBody = `SAMLResponse=${encodeURIComponent(SAMLResponseText || '')}&RelayState=${encodeURIComponent(RelayStateText || '')}`
|
||||
return authGbgLoginBody
|
||||
}
|
||||
|
||||
export const parseCalendarItem = (x: html.HTMLElement): { id: number; title: string; startDate: string; endDate: string } => {
|
||||
const info = Array.from(x.querySelectorAll('a'))
|
||||
// TODO: the identifier is realy on this format: '\d+:\d+' currently we only take the first part so Id will clash between items
|
||||
const id = info[0].getAttribute("onClick")?.replace(new RegExp("return viewEvent\\('(\\d+).+"), "$1") || NaN
|
||||
const day = info[1].textContent
|
||||
const timeSpan = info[2].textContent
|
||||
const [startTime, endTime] = timeSpan.replace(".", ":").split("-")
|
||||
|
||||
return { id: +id, title: info[0].textContent, startDate: `${day} ${startTime}`, endDate: `${day} ${endTime}` }
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
const baseUrl = 'https://skola.admentum.se/api/v1/'
|
||||
|
||||
export const apiUrls = {
|
||||
assignments: baseUrl + 'assignments',
|
||||
attendance_summary_users: baseUrl + 'attendance/summary/users',
|
||||
course_sections: baseUrl + 'course_sections',
|
||||
courses: baseUrl + 'courses',
|
||||
forecast_collections: baseUrl + 'forecast_collections',
|
||||
forecasts: baseUrl + 'forecasts',
|
||||
grade_permissions: baseUrl + 'grade_permissions',
|
||||
grades: baseUrl + 'grades',
|
||||
gymnasium_courses: baseUrl + 'gymnasium_courses',
|
||||
leisure_group_enrollments: baseUrl + 'leisure_group_enrollments',
|
||||
leisure_groups: baseUrl + 'leisure_groups',
|
||||
lesson_infos: baseUrl + 'lesson_infos',
|
||||
lessons: baseUrl + 'lessons',
|
||||
organisations: baseUrl + 'organisations',
|
||||
orientations: baseUrl + 'orientations',
|
||||
permission_groups: baseUrl + 'permission_groups',
|
||||
primary_group_enrollments: baseUrl + 'primary_group_enrollments',
|
||||
primary_group_municipality_statistics:
|
||||
baseUrl + 'primary_groups/municipality_statistic',
|
||||
primary_groups: baseUrl + 'primary_groups',
|
||||
program_courses: baseUrl + 'program_courses',
|
||||
programs: baseUrl + 'programs',
|
||||
reviews: baseUrl + 'reviews',
|
||||
rooms: baseUrl + 'rooms',
|
||||
schedule_breaks: baseUrl + 'schedule_breaks',
|
||||
schedule_event_instances: baseUrl + 'schedule_event_instances',
|
||||
schedule_events: baseUrl + 'schedule_events',
|
||||
schedule_group_enrollments: baseUrl + 'schedule_group_enrollments',
|
||||
schedule_group_teacher_enrollments:
|
||||
baseUrl + 'schedule_group_teacher_enrollments',
|
||||
schedule_groups: baseUrl + 'schedule_groups',
|
||||
schedules: baseUrl + 'schedules',
|
||||
school_enrollments: baseUrl + 'school_enrollments',
|
||||
school_years: baseUrl + 'school_years',
|
||||
schools: baseUrl + 'schools',
|
||||
sickness: baseUrl + 'sickness',
|
||||
subjects: baseUrl + 'subjects',
|
||||
teachers: baseUrl + 'teachers',
|
||||
upper_secondary_subjects: baseUrl + 'upper_secondary_subjects',
|
||||
users: baseUrl + 'users',
|
||||
}
|
||||
|
||||
export const bankIdCheckUrl = (sessionId: string) =>
|
||||
`https://login.grandid.com/?sessionid=${sessionId}&eleg=1&bankid=1`
|
||||
|
||||
export const bankIdInitUrl = (returnUrl: string) =>
|
||||
`https://auth.admentum.se/larande${returnUrl ? `?next=${returnUrl}` : ''}`
|
|
@ -0,0 +1,14 @@
|
|||
const Admentum = require('./lib/index.ts')
|
||||
const nodeFetch = require('node-fetch')
|
||||
const { CookieJar } = require('tough-cookie')
|
||||
const fetchCookie = require('fetch-cookie/node-fetch')
|
||||
|
||||
const cookieJar = new CookieJar()
|
||||
const fetch = fetchCookie(nodeFetch, cookieJar)
|
||||
const admentum = new Admentum(fetch, {})
|
||||
|
||||
const run = async () => {
|
||||
const sessionId = await admentum.login('7612040233')
|
||||
}
|
||||
|
||||
run()
|
|
@ -1,20 +0,0 @@
|
|||
import { ApiAdmentum } from './api'
|
||||
|
||||
describe('api', () => {
|
||||
let api: ApiAdmentum
|
||||
|
||||
beforeEach(() => {
|
||||
api = new ApiAdmentum()
|
||||
})
|
||||
|
||||
test('should request and return calendar items', async () => {
|
||||
expect((await api.getCalendar())[0]).toMatchObject({
|
||||
allDay: false,
|
||||
endDate: '2023-08-07T07:30:00.000Z',
|
||||
id: 2990834,
|
||||
location: '',
|
||||
startDate: '2023-08-07T06:00:00.000Z',
|
||||
title: 'Matematik',
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,32 +0,0 @@
|
|||
import { EventEmitter } from 'events'
|
||||
|
||||
import { CalendarItem } from '@skolplattformen/api'
|
||||
|
||||
import * as fake from './fakeData'
|
||||
import { parseDate } from './parse'
|
||||
|
||||
const fakeResponse = <T>(data: T): Promise<T> =>
|
||||
new Promise((res) => setTimeout(() => res(data), 200 + Math.random() * 800))
|
||||
|
||||
export class ApiAdmentum extends EventEmitter {
|
||||
public async getCalendar(): Promise<CalendarItem[]> {
|
||||
const events = await fakeResponse(fake.calendar)
|
||||
|
||||
return events.map(
|
||||
({
|
||||
id,
|
||||
title,
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
schedule_event: { start_time: startTime, end_time: endTime },
|
||||
}: any) => ({
|
||||
id,
|
||||
title,
|
||||
location: '',
|
||||
allDay: startTime === '00:00:00',
|
||||
startDate: parseDate(startDate + 'T' + startTime),
|
||||
endDate: parseDate(endDate + 'T' + endTime),
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
export const calendar = [
|
||||
{
|
||||
url: 'https://skola.admentum.se/api/v1/schedule_event_instances/2990834/?format=api',
|
||||
id: 2990834,
|
||||
school_id: 824,
|
||||
start_date: '2023-08-07',
|
||||
end_date: '2023-08-07',
|
||||
schedule_event: {
|
||||
url: 'https://skola.admentum.se/api/v1/schedule_events/148722/?format=api',
|
||||
id: 148722,
|
||||
eid: null,
|
||||
schedule_id: 4385,
|
||||
start_time: '08:00:00',
|
||||
end_time: '09:30:00',
|
||||
rooms: [],
|
||||
teachers: [
|
||||
{
|
||||
url: 'https://skola.admentum.se/api/v1/users/437302/?format=api',
|
||||
id: 437302,
|
||||
},
|
||||
],
|
||||
schedule_groups: [],
|
||||
primary_groups: [
|
||||
{
|
||||
url: 'https://skola.admentum.se/api/v1/primary_groups/36874/?format=api',
|
||||
id: 36874,
|
||||
},
|
||||
],
|
||||
weekly_interval: '',
|
||||
},
|
||||
},
|
||||
]
|
|
@ -1,7 +0,0 @@
|
|||
export const parseDate = (input?: string): string | undefined => {
|
||||
if (!input) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return new Date(input).toISOString()
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
function requestLogger(httpModule) {
|
||||
var original = httpModule.request
|
||||
httpModule.request = function (options, callback) {
|
||||
console.log('-----------------------------------------------')
|
||||
console.log(
|
||||
options.href || options.proto + '://' + options.host + options.path,
|
||||
options.method
|
||||
)
|
||||
console.log(options.headers)
|
||||
console.log('-----------------------------------------------')
|
||||
return original(options, callback)
|
||||
}
|
||||
}
|
||||
|
||||
requestLogger(require('http'))
|
||||
requestLogger(require('https'))
|
||||
|
||||
const { DateTime } = require('luxon')
|
||||
const nodeFetch = require('node-fetch')
|
||||
const { CookieJar } = require('tough-cookie')
|
||||
const fetchCookie = require('fetch-cookie/node-fetch')
|
||||
const { writeFile } = require('fs/promises')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const { inspect } = require('util')
|
||||
const init = require('@skolplattformen/api-admentum').default
|
||||
|
||||
const [, , personalNumber] = process.argv
|
||||
|
||||
if (!personalNumber) {
|
||||
console.error(
|
||||
'You must pass in a valid personal number, eg `node run 197001011111`'
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function ensureDirectoryExistence(filePath) {
|
||||
var dirname = path.dirname(filePath)
|
||||
if (fs.existsSync(dirname)) {
|
||||
return true
|
||||
}
|
||||
ensureDirectoryExistence(dirname)
|
||||
fs.mkdirSync(dirname)
|
||||
}
|
||||
|
||||
const record = async (info, data) => {
|
||||
const name = info.error ? `${info.name}_error` : info.name
|
||||
const filename = `./record/${name}.json`
|
||||
ensureDirectoryExistence(filename)
|
||||
const content = {
|
||||
url: info.url,
|
||||
headers: info.headers,
|
||||
status: info.status,
|
||||
statusText: info.statusText,
|
||||
}
|
||||
if (data) {
|
||||
switch (info.type) {
|
||||
case 'json':
|
||||
content.json = data
|
||||
break
|
||||
case 'text':
|
||||
content.text = data
|
||||
break
|
||||
case 'blob':
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const buffer = await data.arrayBuffer()
|
||||
content.blob = Buffer.from(buffer).toString('base64')
|
||||
break
|
||||
}
|
||||
} else if (info.error) {
|
||||
const { message, stack } = info.error
|
||||
content.error = {
|
||||
message,
|
||||
stack,
|
||||
}
|
||||
}
|
||||
await writeFile(filename, JSON.stringify(content, null, 2))
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const cookieJar = new CookieJar()
|
||||
const fetch = fetchCookie(nodeFetch, cookieJar)
|
||||
|
||||
try {
|
||||
const api = init(fetch, cookieJar, { record })
|
||||
console.log('inited...')
|
||||
|
||||
api.on('login', async () => {
|
||||
console.log('Logged in!')
|
||||
await api.getUser()
|
||||
const children = await api.getChildren()
|
||||
const now = DateTime.fromJSDate(new Date())
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const c = children[i]
|
||||
await api.getCalendar(c)
|
||||
await api.getNotifications(c)
|
||||
await api.getTimetable(c, 44, 2021, 'ignored')
|
||||
}
|
||||
const news = await api.getNews()
|
||||
// const news = await api.getNews()
|
||||
// //console.table(news.map(n => ({ id: n.id, author: n.author, published: n.published})))
|
||||
// //news.length && console.log(news[0])
|
||||
|
||||
// const notifications = await api.getNotifications(children[2])
|
||||
// //const ns = notifications.map(n => ({id: n.id, sender: n.sender, type: n.type}))
|
||||
// //console.table(ns)
|
||||
// console.log("notifications count", notifications.length)
|
||||
// notifications.slice(0, 10).forEach(console.log)
|
||||
|
||||
// await api.getCalendar(children[1])
|
||||
|
||||
// await api.getTimetable(children[1], 38, 2021, "en")
|
||||
|
||||
// await api.getClassmates()
|
||||
// console.table(schema)
|
||||
})
|
||||
const res = await api.login(personalNumber)
|
||||
console.log(res)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
|
@ -15,6 +15,7 @@
|
|||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@skolplattformen/api": ["libs/api/lib/index.ts"],
|
||||
"@skolplattformen/api-admentum": ["libs/api-admentum/lib/index.ts"],
|
||||
"@skolplattformen/api-hjarntorget": ["libs/api-hjarntorget/lib/index.ts"],
|
||||
"@skolplattformen/api-skolplattformen": [
|
||||
"libs/api-skolplattformen/lib/index.ts"
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
{
|
||||
"version": 2,
|
||||
"projects": {
|
||||
"api-hjarntorget": "libs/api-hjarntorget",
|
||||
"api-admentum": "libs/api-admentum",
|
||||
"api-skolplattformen": "libs/api-skolplattformen",
|
||||
"api-vklass": "libs/api-vklass",
|
||||
"api": "libs/api",
|
||||
"api-admentum": "libs/api-admentum",
|
||||
"api-hjarntorget": "libs/api-hjarntorget",
|
||||
"api-skolplattformen": "libs/api-skolplattformen",
|
||||
"api-test-app": "apps/api-test-app",
|
||||
"api-vklass": "libs/api-vklass",
|
||||
"curriculum": "libs/curriculum",
|
||||
"hooks": "libs/hooks",
|
||||
"api-test-app": "apps/api-test-app",
|
||||
"skolplattformen-app": "apps/skolplattformen-app"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue