Skip to content

Instantly share code, notes, and snippets.

@thanhpk
Created September 8, 2018 18:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thanhpk/a1f620b40af10e414f17d70b8ff17bc0 to your computer and use it in GitHub Desktop.
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)
})
})
}
}
@thanhpk
Copy link
Author

thanhpk commented Sep 9, 2018

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

run (state, param) {
	this.loopForever(resolve =>
		this[state](param).then((nextstate, nextparam, delay) => {
			if (!this[nextstate]) throw 'invalid state' + nextstate
			;[state, param] = [nextstate, nextparam]
			resolve(delay)
		})
	)
}

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 () {
	return new Promise(transition => {
		let req = this.refreshQ.pop()
		if (req) transition(REFRESHING, req)
		else transition(NORMAL, undefined, 100)
	})
}

JUST_REFRESHED () {
	return new Promise(transition => {
		let now = new Date()
		this.refreshQ.forEach(req => req.resolve([this.actoken, this.rftoken]))
		this.refreshQ = []
		if (since(now, new Date()) > 10000) transition(NORMAL)
		else transition(JUST_REFRESHED, undefined, 100)
	})
}

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 && st.refresh_token !== this.rftoken) {
						/* someone have exchanged the token */
						this.set(st.access_token, st.refresh_token)
						return transition(JUST_REFRESHED)
					}
					return transition(DEAD)
				}

				/* parsebody */
				this.set(body.access_token, body.refresh_token)
				return transition(JUST_REFRESHED)
			})
	})
}

DEAD () {
	return new Promise(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({ state: NORMAL })
		} else transition(DEAD, undefined, 100)
	})
}

}

@thanhpk
Copy link
Author

thanhpk commented Sep 9, 2018

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 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment