1 import assert from 'assert'
2 import fsPromises from 'fs/promises'
3 import http from 'http'
4 import https from 'https'
5 import stream_buffers from 'stream-buffers'
6 import Problem from './Problem.mjs'
7 import XDate from 'xdate'
12 this.persistent = {url: 'http://localhost:8080', cookies: {}}
18 buffer = await fsPromises.readFile(this.path)
21 if (error.code !== 'ENOENT')
25 await fsPromises.rename(this.path + '.new', this.path)
28 throw error // ENOENT will make more sense to the user
31 buffer = await fsPromises.readFile(this.path)
33 this.persistent = JSON.parse(buffer.toString('utf-8'))
37 await fsPromises.writeFile(
39 Buffer.from(JSON.stringify(this.persistent, null, 2) + '\n', 'utf-8')
42 await fsPromises.unlink(this.path)
45 if (error.code !== 'ENOENT')
48 await fsPromises.rename(this.path + '.new', this.path)
51 async api_call(endpoint, ...args) {
52 let url = new URL(this.persistent.url + endpoint)
54 let http_or_https, default_port
55 if (url.protocol === 'https:') {
64 let buffer = Buffer.from(
65 JSON.stringify(args) + '\n',
70 'Content-Type': 'application/json',
71 'Content-Length': buffer.length
76 for (let i in this.persistent.cookies) {
77 let cookie = this.persistent.cookies[i]
79 let expires = cookie.expires
80 if (expires !== undefined && now >= new XDate(expires).getTime())
83 let path = cookie.path
84 if (path !== undefined) {
85 if (path.slice(-1) != '/')
87 if (url.pathname.slice(0, path.length) !== path)
91 cookies.push(`${i}=${cookie.value}`)
94 headers.Cookie = cookies.join(', ')
96 let response = await new Promise(
97 (resolve, reject) => {
98 let request = http_or_https.request(
100 hostname: url.hostname,
101 port: url.port.length === 0 ? default_port : parseInt(url.port),
106 response => {resolve(response)}
108 request.on('error', error => {reject(error)})
109 request.write(buffer)
114 let response_cookies = response.headers['set-cookie'] || []
115 for (let i = 0; i < response_cookies.length; ++i) {
116 let fields = response_cookies[i].split(';')
117 assert(fields.length >= 1)
119 let j = fields[0].indexOf('=')
121 let name = fields[0].slice(0, j).trim()
122 let value = fields[0].slice(j + 1).trim()
125 value.charAt(0) === 0x22 &&
126 value.charAt(value.length - 1) === 0x22
128 value = value.slice(1, -1)
131 for (/*let*/ j = 1; j < fields.length; ++j) {
132 let k = fields[j].indexOf('=')
135 attr = fields[j].slice(0, k).trim()
136 value = fields[j].slice(k + 1).trim()
139 attr = fields[j].trim()
140 if (attr.length === 0)
144 attr = attr.toLowerCase()
146 attr === 'expires' ||
147 attr === 'max-age' ||
151 attr === 'httponly' ||
153 ) // cannot be 'value'
157 let max_age = attrs['max-age']
158 if (max_age !== undefined) {
159 let expires = new XDate()
160 expires.addSeconds(parseInt(max_age))
161 attrs['expires'] = expires.toUTCString()
162 delete attrs['max-age']
165 this.persistent.cookies[name] = attrs
168 let write_stream = new stream_buffers.WritableStreamBuffer()
169 let data = new Promise(
170 (resolve, reject) => {
172 on('finish', () => {resolve(write_stream.getContents())}).
173 on('error', () => {reject()})
176 response.pipe(write_stream)
178 let result = JSON.parse((await data).toString('utf-8'))
179 if (response.statusCode < 200 || response.statusCode >= 300)
180 throw new Problem(result.title, result.detail, result.status)
185 export default Session