Skip to content

Instantly share code, notes, and snippets.

@Rebolon
Created March 13, 2017 17:32
Show Gist options
  • Select an option

  • Save Rebolon/2d0c848e202faca4a1c69432d6dc5f8a to your computer and use it in GitHub Desktop.

Select an option

Save Rebolon/2d0c848e202faca4a1c69432d6dc5f8a to your computer and use it in GitHub Desktop.

Revisions

  1. Rebolon created this gist Mar 13, 2017.
    177 changes: 177 additions & 0 deletions interceptors.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,177 @@
    /**
    * allow to cache response and/or to listen on newRequest event
    *
    * for cache system, it's freely adapted from https://github.com/pagekit/vue-resource/issues/252 @airtonix sample
    */

    export class HttpInterceptors {
    _cache

    constructor (Vue, ttlInHours) {
    if (Vue.http === undefined) {
    throw new Error('you have to add the vue-resource plugin')
    }

    if (!Vue.DI || !Vue.DI.Bus) {
    throw new Error('you have to add the Vue.DI.Bus system')
    }

    this.vueJS = Vue
    this.ttlInHours = ttlInHours
    }

    /**
    *
    * @param IBus {$emit: function}
    * @returns {HttpInterceptors}
    */
    addNewRequestListener (bus) {
    if (typeof bus.$emit !== 'function') {
    throw new Error('bus must have an $emit method')
    }

    this.vueJS.http.interceptors.push((request, next) => {
    bus.$emit('newRequest', request)

    // continue to next interceptor
    next()
    })

    return this
    }

    /**
    * @params ICache {get: function, put: function, remove: function}
    * @returns {HttpInterceptors}
    */
    addHttpCache (cache) {
    ['get', 'put', 'remove'].forEach(method => {
    if (typeof cache[method] !== 'function') {
    throw new Error(`cache must have an ${method} method`)
    }
    })

    // this is to be able to test the doCache method lonely
    this._cache = cache

    this.vueJS.http.interceptors.push(this._doCache.bind(this))

    return this
    }

    /**
    *
    * @param request
    * @param next
    * @private
    */
    _doCache (request, next) {
    if (!this._cache) {
    throw new Error(`you forgot the cache system`)
    }

    let id = this._getId(request)
    if (request.method.toLowerCase() === 'get') {
    this._cache.get(this._getId(request))
    .then(doc => {
    if (this._checkTTL(doc)) {
    console.log('cache hit', id)
    next(request.respondWith(doc.body, {status: 200, statusText: 'Ok'}))
    } else {
    console.log(doc.ttl, this._getTTL(), this._checkTTL(doc))
    this._cache.remove(doc)
    throw Object.create({code: 1000})
    }
    })
    .catch(err => {
    if (err.status === 404) {
    console.info('cache miss, not in db (catch)', id)
    next((response) => {
    let {status, statusText, body} = response
    if (status === 200 && request.method.toLowerCase() === 'get') {
    response._id = id
    response.ttl = this._getTTL()
    // if Conflict we have to get the current object, merge the response with the object Object.assign(docFound, response) and then put it in db
    this._cache
    .put(response)
    .catch(err => {
    if (err.status === 409) {
    console.warn('Conflict error in insert')
    return
    }

    console.error('error during put db', err, response)
    })
    }

    request.respondWith(body, {status, statusText})
    })
    return
    } else if (err.code === 1000) {
    console.info('cache miss, ttl delayed (catch)', id)
    next()
    return
    }

    console.error('error during get db', err)
    })
    } else {
    next()
    }
    }

    /**
    *
    * @param request
    * @returns {string}
    */
    _getId (request) {
    let qs = []

    Object.keys(request.params).forEach(key => {
    if (key === 'apikey') return
    qs.push(`${key}=${request.params[key]}`)
    })

    if (qs.length) {
    qs = `?${qs.join('&')}`
    }

    const id = `CACHE_${request.url}${qs}`

    return id
    }

    /**
    *
    */
    _getTTL () {
    const now = new Date()

    return now.setHours(now.getHours() + this.ttlInHours)
    }

    /**
    *
    * @param data
    * @returns {boolean}
    */
    _checkTTL (data) {
    return data.ttl > new Date()
    }
    }

    /**
    * to use it with a 60 minutes of TTL on each http call:
    *
    * import Vue from 'vue'
    * import PouchDB from 'pouchdb'
    * import { HttpInterceptors } from './interceptors'
    * const VueResource = require('vue-resource')
    *
    * Vue.use(VueResource)
    * const interceptors = new HttpInterceptors(Vue, 1)
    * interceptors
    * .addNewRequestListener(new Vue)
    * .addHttpCache(new PouchDB('your-channel'))
    */