Skip to content

Commit d8da84c

Browse files
authored
Merge pull request #243 from qgustavor/next
Release - v1.3.7
2 parents 08089f9 + a319a1c commit d8da84c

File tree

2 files changed

+60
-8
lines changed

2 files changed

+60
-8
lines changed

lib/api.mjs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { EventEmitter } from 'events'
22
import { Agent as HttpAgent } from 'http'
33
import { Agent as HttpsAgent } from 'https'
44
import { createPromise } from './util.mjs'
5+
import { generateHashcashToken } from './crypto/index.mjs'
56

67
const MAX_RETRIES = 4
78
const ERRORS = {
@@ -89,7 +90,16 @@ class API extends EventEmitter {
8990
if (this.closed && !isLogout) throw Error('API is closed')
9091
const [cb, promise] = createPromise(originalCb)
9192

92-
const qs = { id: (this.counterId++).toString() }
93+
// Don't increment counterId when re-requesting with a hashcash
94+
// Thanks @angelvega93
95+
if (typeof json._hashcash !== 'string') {
96+
this.counterId++
97+
}
98+
99+
const qs = {
100+
id: this.counterId.toString()
101+
}
102+
93103
if (this.sid) {
94104
qs.sid = this.sid
95105
}
@@ -99,11 +109,25 @@ class API extends EventEmitter {
99109
delete json._querystring
100110
}
101111

112+
const headers = { 'Content-Type': 'application/json' }
113+
if (typeof json._hashcash === 'string') {
114+
headers['X-Hashcash'] = json._hashcash
115+
delete json._hashcash
116+
}
117+
102118
this.fetch(`${this.gateway}cs?${new URLSearchParams(qs)}`, {
103119
method: 'POST',
104-
headers: { 'Content-Type': 'application/json' },
120+
headers,
105121
body: JSON.stringify([json])
106-
}).then(handleApiResponse).then(resp => {
122+
}).then(async resp => {
123+
const hashcashChallenge = resp.headers.get('X-Hashcash')
124+
if (hashcashChallenge) {
125+
json._hashcash = await generateHashcashToken(hashcashChallenge)
126+
// Simulate an EAGAIN response
127+
return -3
128+
}
129+
return handleApiResponse(resp)
130+
}).then(resp => {
107131
if (this.closed && !isLogout) return
108132
if (!resp) return cb(Error('Empty response'))
109133

@@ -203,15 +227,13 @@ class API extends EventEmitter {
203227
}
204228

205229
static getShouldAvoidUA () {
206-
// Checks defined using
207-
// - https://codepen.io/qgustavor/pen/JjqqBPp
208-
// - https://www.browserstack.com/screenshots/149d6d45a4a10de06e05f743190a4c12a9faa6ef
209-
210230
// It's not possible to detect when a browser fails CORS requests by defining an user-agent
211231
// using feature detection, so the alternatives were using user-agent detection
212232
// (which is not ideal because might not catch Firefox forks) or hacks.
213233

214-
// This library uses hacks:
234+
// This library uses hacks.
235+
// Those were found using BrowserStack and the following tests: https://codepen.io/qgustavor/pen/JjqqBPp
236+
215237
let headersErr
216238
try {
217239
globalThis.Headers()

lib/crypto/index.mjs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,33 @@ function constantTimeCompare (bufferA, bufferB) {
167167
}
168168

169169
export { constantTimeCompare }
170+
171+
export async function generateHashcashToken (challenge) {
172+
const [versionStr, easinessStr,, tokenStr] = challenge.split(':')
173+
const version = Number(versionStr)
174+
if (version !== 1) throw Error('hashcash challenge is not version 1')
175+
176+
const easiness = Number(easinessStr)
177+
const base = ((easiness & 63) << 1) + 1
178+
const shifts = (easiness >> 6) * 7 + 3
179+
const threshold = base << shifts
180+
const token = d64(tokenStr)
181+
182+
const buffer = Buffer.alloc(4 + 262144 * 48)
183+
for (let i = 0; i < 262144; i++) {
184+
buffer.set(token, 4 + i * 48)
185+
}
186+
187+
while (true) {
188+
const view = new DataView(await globalThis.crypto.subtle.digest('SHA-256', buffer))
189+
if (view.getUint32(0) <= threshold) {
190+
return `1:${tokenStr}:${e64(buffer.slice(0, 4))}`
191+
}
192+
193+
let j = 0
194+
while (true) {
195+
buffer[j]++
196+
if (buffer[j++]) break
197+
}
198+
}
199+
}

0 commit comments

Comments
 (0)