From c594888953151ddfb4ca04b7752bfd51edc1d6da Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 13 Feb 2019 01:11:31 -0500 Subject: WIP: migrate to TypeScriptX --- src/gapi.js | 335 ------------------------------------------------------------ 1 file changed, 335 deletions(-) delete mode 100644 src/gapi.js (limited to 'src/gapi.js') diff --git a/src/gapi.js b/src/gapi.js deleted file mode 100644 index 3938864..0000000 --- a/src/gapi.js +++ /dev/null @@ -1,335 +0,0 @@ -/* global chrome */ -import LRU from "lru-cache"; -const gapi_base = 'https://www.googleapis.com/calendar/v3'; - -const GApiError = Object.freeze({ - invalidSyncToken: Symbol("invalidSyncToken"), - notLoggedIn: Symbol("notLoggedIn"), - notLoggedOut: Symbol("notLoggedOut"), - otherError: Symbol("otherError"), -}); - -function to_params(dict) { - return Object.entries(dict).filter(([k, v]) => v).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&'); -} - -let loggedIn = null; - -function _getAuthToken(interactive = false) { - return new Promise(resolver => - chrome.identity.getAuthToken( - { interactive }, token => resolver([token, !chrome.runtime.lastError]))) - .then(([token, ok]) => { - if (ok) return token; - else throw GApiError.notLoggedIn; - }); -} - -function _removeCachedAuthToken(token) { - return new Promise(resolver => - chrome.identity.removeCachedAuthToken({ token }, () => resolver())); -} - -export function getLoggedIn() { - if (loggedIn === null) - { - return _getAuthToken(false) - .then(() => loggedIn = true) - .catch(() => loggedIn = false) - .then(() => loggedIn); - } - else return Promise.resolve(loggedIn); -} - -export function getAuthToken() { - return getLoggedIn().then(b => { - if (b) return _getAuthToken(false); - else throw GApiError.notLoggedIn; - }); -} - -export function login() { - return getLoggedIn().then(b => { - if (!b) return _getAuthToken(true).then(() => loggedIn = true); - else throw GApiError.notLoggedOut; - }); -} - -export function logout() { - return getAuthToken().then(token => { - return fetch(`https://accounts.google.com/o/oauth2/revoke?${to_params({ token })}`, - { method: 'GET', async: true }).then(response => { - //if (response.status === 200) - return _removeCachedAuthToken(token); - //else throw GApiError.otherError; - }); - }).then(() => loggedIn = false); -} - -export function getCalendars(token) { - return fetch(`${gapi_base}/users/me/calendarList?${to_params({access_token: token})}`, - { method: 'GET', async: true }) - .then(response => response.json()) - .then(data => data.items); -} - -export function getColors(token) { - return fetch(`${gapi_base}/colors?${to_params({access_token: token})}`, - { method: 'GET', async: true }) - .then(response => response.json()); -} - -function getEvent(calId, eventId, token) { - return fetch(`${gapi_base}/calendars/${calId}/events/${eventId}?${to_params({access_token: token})}`, - { method: 'GET', async: true }) - .then(response => response.json()); -} - -function getEvents(calId, token, syncToken=null, timeMin=null, timeMax=null, resultsPerRequest=100) { - let results = []; - const singleFetch = (pageToken, syncToken) => fetch(`${gapi_base}/calendars/${calId}/events?${to_params({ - access_token: token, - pageToken, - syncToken, - timeMin, - timeMax, - maxResults: resultsPerRequest - })}`, { method: 'GET', async: true }) - .then(response => { - if (response.status === 200) - return response.json(); - else if (response.status === 410) - throw GApiError.invalidSyncToken; - else throw GApiError.otherError; - }) - .then(data => { - results.push(...data.items); - if (data.nextPageToken) { - return singleFetch(data.nextPageToken, ''); - } else { - return ({ - nextSyncToken: data.nextSyncToken, - results - }); - } - }) - - return singleFetch('', syncToken); -} - -export class GCalendar { - constructor(calId, name, options={maxCachedItems: 100, nDaysPerSlot: 10, largeQuery: 10}) { - this.calId = calId; - this.name = name; - this.syncToken = ''; - this.cache = new LRU({ - max: options.maxCachedItems, - dispose: (k, v) => this.onRemoveSlot(k, v) - }); - this.eventMeta = {}; - this.options = options; - this.divider = 8.64e7 * this.options.nDaysPerSlot; - } - - get token() { return getAuthToken(); } - - dateToCacheKey(date) { - return Math.floor(date / this.divider); - } - - dateRangeToCacheKeys(range) { - return { - start: this.dateToCacheKey(range.start), - end: this.dateToCacheKey(new Date(range.end.getTime() - 1)) - }; - } - - getSlot(k) { - if (!this.cache.has(k)) - { - let res = {}; - this.cache.set(k, res); - return res; - } - else return this.cache.get(k); - } - - onRemoveSlot(k, v) { - for (let id in v) { - console.assert(this.eventMeta[id]); - let keys = this.eventMeta[id].keys; - keys.delete(k); - if (keys.size === 0) - delete this.eventMeta[id]; - } - } - - slotStartDate(k) { return new Date(k * this.divider); } - slotEndDate(k) { return new Date((k + 1) * this.divider); } - - addEvent(e, evict = false) { - //console.log('adding event', e); - if (this.eventMeta.hasOwnProperty(e.id)) - this.removeEvent(e); - let r = this.dateRangeToCacheKeys(e); - let ks = r.start; - let ke = r.end; - let t = this.cache.length; - let keys = new Set(); - for (let i = ks; i <= ke; i++) - { - keys.add(i); - if (!this.cache.has(i)) t++; - } - this.eventMeta[e.id] = { - keys, - summary: e.summary, - }; - if (!evict && t > this.options.maxCachedItems) return; - if (ks === ke) - this.getSlot(ks)[e.id] = { - start: e.start, - end: e.end, - id: e.id }; - else - { - this.getSlot(ks)[e.id] = { - start: e.start, - end: this.slotEndDate(ks), - id: e.id }; - this.getSlot(ke)[e.id] = { - start: this.slotStartDate(ke), - end: e.end, - id: e.id }; - for (let k = ks + 1; k < ke; k++) - this.getSlot(k)[e.id] = { - start: this.slotStartDate(k), - end: this.slotEndDate(k), - id: e.id}; - } - } - - removeEvent(e) { - let keys = this.eventMeta[e.id].keys; - console.assert(keys); - keys.forEach(k => delete this.getSlot(k)[e.id]); - delete this.eventMeta[e.id]; - } - - getSlotEvents(k, start, end) { - let s = this.getSlot(k); - //console.log(s); - let results = []; - for (let id in s) { - if (!(s[id].start >= end || s[id].end <= start)) - { - results.push({ - id, - start: s[id].start < start ? start: s[id].start, - end: s[id].end > end ? end: s[id].end, - summary: this.eventMeta[id].summary - }); - } - } - return results; - } - - getCachedEvents(_r) { - let r = this.dateRangeToCacheKeys(_r); - let ks = r.start; - let ke = r.end; - let results = this.getSlotEvents(ks, _r.start, _r.end); - for (let k = ks + 1; k < ke; k++) - { - let s = this.getSlot(k); - for (let id in s) - results.push(s[id]); - } - if (ke > ks) - results.push(...this.getSlotEvents(ke, _r.start, _r.end)); - return results; - } - - sync() { - return this.token.then(token => getEvents(this.calId, token, this.syncToken).then(r => { - let pms = r.results.map(e => e.start ? Promise.resolve(e) : getEvent(this.calId, e.id, token)); - return Promise.all(pms).then(results => { - results.forEach(e => { - e.start = new Date(e.start.dateTime); - e.end = new Date(e.end.dateTime); - if (e.status === 'confirmed') - this.addEvent(e); - else if (e.status === 'cancelled') - this.removeEvent(e); - }); - this.syncToken = r.nextSyncToken; - }); - })).catch(e => { - if (e === GApiError.invalidSyncToken) { - this.syncToken = ''; - this.sync(); - } else throw e; - }); - } - - getEvents(start, end) { - let r = this.dateRangeToCacheKeys({ start, end }); - let query = {}; - for (let k = r.start; k <= r.end; k++) - if (!this.cache.has(k)) - { - if (!query.hasOwnProperty('start')) - query.start = k; - query.end = k; - } - //console.log(`start: ${start} end: ${end}`); - if (query.hasOwnProperty('start')) - { - console.assert(query.start <= query.end); - if (query.end - query.start + 1 > this.options.largeQuery) { - console.log(`encounter large query, use direct fetch`); - return this.token.then(token => getEvents(this.calId, token, null, - start.toISOString(), end.toISOString()).then(r => { - let results = []; - r.results.forEach(e => { - console.assert(e.start); - e.start = new Date(e.start.dateTime); - e.end = new Date(e.end.dateTime); - results.push(e); - }); - return results.filter(e => !(e.start >= end || e.end <= start)).map(e => { - return { - id: e.id, - start: e.start < start ? start: e.start, - end: e.end > end ? end: e.end, - summary: e.summary, - }; - }); - })); - } - - console.log(`fetching short event list`); - return this.token.then(token => getEvents(this.calId, token, null, - this.slotStartDate(query.start).toISOString(), - this.slotEndDate(query.end).toISOString()).then(r => { - r.results.forEach(e => { - if (e.status === 'confirmed') - { - console.assert(e.start); - e.start = new Date(e.start.dateTime); - e.end = new Date(e.end.dateTime); - this.addEvent(e, true); - } - }); - if (this.syncToken === '') - this.syncToken = r.nextSyncToken; - })).then(() => this.sync()) - .then(() => this.getCachedEvents({ start, end })); - } - else - { - console.log(`cache hit`); - return this.sync().then(() => this.getCachedEvents({ start, end })); - } - } -} -- cgit v1.2.3