-
-
Save thanhpk/a1f620b40af10e414f17d70b8ff17bc0 to your computer and use it in GitHub Desktop.
var store = require('store') | |
var ajax = require('@subiz/ajax') | |
const DEAD = 'dead' | |
const REFRESHING = 'refreshing' | |
const NORMAL = 'normal' | |
const JUST_REFRESHED = 'just_refreshed' | |
function loop (cb) { | |
new Promise(cb).then(cont => { | |
if (cont) setTimeout(() => loop(cb), 1) | |
}) | |
} | |
function since (t) { | |
return new Date() - t | |
} | |
function sleep (t) { | |
return new Promise(resolve => setTimeout(() => resolve, t)) | |
} | |
export class Token { | |
constructor (tokenhost) { | |
this.actoken = '' | |
this.rftoken = '' | |
this.api = ajax | |
.post(tokenhost, 'refresh-token') | |
.setParser('json') | |
.setContentType('form') | |
this.refreshQ = [] | |
this.restartQ = [] | |
this.store = store | |
this.run() | |
} | |
run () { | |
let state = NORMAL // init state | |
let param | |
Token.init() | |
loop(resolve => { | |
Token[state](param).then((nextstate, nextparam) => { | |
[state, param] = [nextstate, nextparam] | |
resolve(true) | |
}) | |
}) | |
} | |
loadStore () { | |
return this.store.get('subiz_token') || {} | |
} | |
load () { | |
const lcs = this.loadStore() | |
if (!this.actoken) this.actoken = lcs.access_token | |
if (!this.rftoken) this.rftoken = lcs.refresh_token | |
return [this.actoken, this.rftoken] | |
} | |
set (actoken, rftoken) { | |
this.actoken = actoken | |
this.rftoken = rftoken | |
this.store.set('subiz_token', { refresh_token: rftoken, access_token: actoken }) | |
} | |
refresh () { | |
return new Promise(resolve => this.refreshQ.push({ resolve })) | |
} | |
restart () { | |
return new Promise(resolve => this.restartQ.push(new Date())) | |
} | |
NORMAL () { | |
return new Promise(transition => | |
loop(resolve => { | |
let req = this.refreshQ.pop() | |
if (!req) { | |
sleep(100).then(() => resolve(true)) | |
return | |
} | |
transition(REFRESHING, req) | |
resolve(false) | |
}) | |
) | |
} | |
JUST_REFRESHED () { | |
return new Promise(transition => { | |
let now = new Date() | |
loop(resolve => { | |
this.refreshQ.forEach(req => req.resolve([this.actoken, this.rftoken])) | |
this.refreshQ = [] | |
if (since(now) > 10000) { | |
transition(NORMAL) | |
resolve(false) | |
return | |
} | |
setTimeout(() => resolve(true), 1000) | |
}) | |
}) | |
} | |
REFRESHING (req) { | |
return new Promise(transition => { | |
this.api | |
.query({ 'refresh-token': this.rftoken }) | |
.send() | |
.then(([code, body, err]) => { | |
if (err || code !== 200) { | |
let st = this.loadStore() | |
if (st.refresh_token !== this.rftoken) { | |
/* someone change the token */ | |
this.set(st.access_token, st.refresh_token) | |
transition(JUST_REFRESHED) | |
return | |
} | |
transition(DEAD) | |
return | |
} | |
/* parsebody */ | |
this.set(body.access_token, body.refresh_token) | |
transition(JUST_REFRESHED) | |
}) | |
}) | |
} | |
DEAD () { | |
return new Promise(transition => { | |
loop(resolveloop => { | |
this.refreshQ.forEach(req => | |
req.resolve([undefined, undefined, 'dead']) | |
) | |
this.refreshQ = [] | |
if (this.restartQ.length > 0) { | |
transition(NORMAL) | |
resolveloop(false) | |
return | |
} | |
resolveloop(true) | |
}) | |
}) | |
} | |
} |
const gStore = {} // require('store')
const gAjax = {} // require('@subiz/ajax')
const DEAD = 'dead'
const REFRESHING = 'refreshing'
const NORMAL = 'normal'
const JUST_REFRESHED = 'just_refreshed'
const loop4everInternal = (f, r) =>
f((delay, err) => (err ? r(err) : setTimeout(loop4everInternal, delay, f, r)))
const loop4ever = f => new Promise(resolve => loop4everInternal(f, resolve))
const run = (self, state, param) =>
loop4ever(resolve => {
self[state]((nextstate, nextparam, delay) => {
resolve(undefined, 'invalid state' + nextstate)
;[state, param] = [nextstate, nextparam]
resolve(delay)
}, param)
})
class Token {
constructor ({ tokenep, ajax, store, dry }) {
this.api = (ajax || gAjax)
.post(tokenep)
.setParser('json')
.setContentType('form')
this.refreshQ = []
this.restartQ = []
this.store = store || gStore
dry || run(this, NORMAL)
}
loadStore () {
return this.store.get('subiz_token') || {}
}
load () {
const lcs = this.loadStore()
if (!this.actoken) this.actoken = lcs.access_token
if (!this.rftoken) this.rftoken = lcs.refresh_token
return [this.actoken, this.rftoken]
}
set (actoken, rftoken) {
this.actoken = actoken
this.rftoken = rftoken
this.store.set('subiz_token', {
refresh_token: rftoken,
access_token: actoken
})
}
refresh () {
return new Promise(resolve => this.refreshQ.push({ resolve }))
}
restart () {
return new Promise(resolve => this.restartQ.push({ resolve }))
}
NORMAL (transition) {
let req = this.refreshQ.pop()
if (req) transition(REFRESHING, req)
else transition(NORMAL, undefined, 100)
}
JUST_REFRESHED (transition, { now, then }) {
this.refreshQ.forEach(req => req.resolve([this.actoken, this.rftoken]))
this.refreshQ = []
if (then - now > 5000) transition(NORMAL)
else transition(JUST_REFRESHED, { now, then: then + 100 || 100 }, 100)
}
REFRESHING (transition, req) {
this.api
.query({ 'refresh-token': this.rftoken })
.send()
.then(([code, body, err]) => {
if (err || code !== 200) {
let st = this.loadStore()
if (st.refresh_token && st.refresh_token !== this.rftoken) {
/* someone have exchanged the token */
this.set(st.access_token, st.refresh_token)
return transition(JUST_REFRESHED, { now: new Date() })
}
return transition(DEAD)
}
/* parsebody */
this.set(body.access_token, body.refresh_token)
return transition(JUST_REFRESHED, { now: new Date() })
})
}
DEAD (transition) {
this.refreshQ.map(req => req.resolve([undefined, undefined, 'dead']))
this.refreshQ = []
this.restartQ.map(req => req.resolve)
if (this.restartQ.length > 0) {
this.restartQ = []
transition(NORMAL)
} else transition(DEAD, undefined, 100)
}
}
module.exports = { Token, loop4ever }
const gStore = require('store')
const gAjax = require('@subiz/ajax')
const DEAD = 'dead'
const REFRESHING = 'refreshing'
const NORMAL = 'normal'
const JUST_REFRESHED = 'just_refreshed'
const since = (a, b) => b - a
const gSleep = t => new Promise(resolve => setTimeout(_ => resolve, t))
const gLoopForever = cb =>
cb().then(t => setTimeout(_ => gLoopForever(cb), parseInt(t) || 1))
export class Token {
constructor ({ tokenhost, ajax, store, sleep, loopForever }) {
this.api = (ajax || gAjax)
.post(tokenhost, 'refresh-token')
.setParser('json')
.setContentType('form')
this.refreshQ = []
this.restartQ = []
this.store = store || gStore
this.sleep = sleep || gSleep
this.loopForever = loopForever || gLoopForever
this.run(NORMAL)
}
}