-
-
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)
}
}