aboutsummaryrefslogblamecommitdiff
path: root/public/gapi.js
blob: f5ab73ea673a52df2977a11578ee87f886ec7370 (plain) (tree)





















































































































































































                                                                                                                  
/* global chrome */
const gapi_base = 'https://www.googleapis.com/calendar/v3';

const GApiError = {
    invalidSyncToken: 1,
    otherError: 2,
};

function to_params(dict) {
    return Object.entries(dict).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
}

function getAuthToken() {
    return new Promise(resolver =>
        chrome.identity.getAuthToken(
            {interactive: true}, token => resolver(token)));
}

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

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, resultsPerRequest=100) {
    let results = [];
    const singleFetch = (pageToken, syncToken) => fetch(`${gapi_base}/calendars/${calId}/events?${to_params({
            access_token: token,
            pageToken,
            syncToken,
            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.otherErrors;
            })
            .then(data => {
                results.push(...data.items);
                if (data.nextPageToken) {
                    return singleFetch(data.nextPageToken, '');
                } else {
                    return ({
                        nextSyncToken: data.nextSyncToken,
                        results
                    });
                }
            })

    return singleFetch('', syncToken);
}

class GCalendar {
    constructor(calId, name) {
        this.calId = calId;
        this.name = name;
        this.token = getAuthToken();
        this.syncToken = '';
        this.cache = {};
    }

    static dateToCacheKey(date) {
        return Math.floor(date / 8.64e7);
    }

    getSlot(k) {
        if (!this.cache[k])
            this.cache[k] = {};
        return this.cache[k];
    }

    static slotStartDate(k) { return new Date(k * 8.64e7); }
    static slotEndDate(k) { return new Date((k + 1) * 8.64e7); }

    addEvent(e) {
        let ks = GCalendar.dateToCacheKey(e.start);
        let ke = GCalendar.dateToCacheKey(new Date(e.end.getTime() - 1));
        if (ks === ke)
            this.getSlot(ks)[e.id] = {
                start: e.start,
                end: e.end,
                id: e.id,
                summary: e.summary};
        else
        {
            this.getSlot(ks)[e.id] = {
                start: e.start,
                end: GCalendar.slotEndDate(ks),
                id: e.id,
                summary: e.summary};
            this.getSlot(ke)[e.id] = {
                start: GCalendar.slotStartDate(ke),
                end: e.end,
                id: e.id,
                summary: e.summary};
            for (let k = ks + 1; k < ke; k++)
                this.getSlot(k)[e.id] = {
                    start: GCalendar.slotStartDate(k),
                    end: GCalendar.slotEndDate(k),
                    id: e.id,
                    summary: e.summary};
        }
    }

    removeEvent(e) {
        let ks = GCalendar.dateToCacheKey(e.start);
        let ke = GCalendar.dateToCacheKey(new Date(e.end.getTime() - 1));
        for (let k = ks; k <= ke; k++)
            delete this.getSlot(k)[e.id];
    }

    getSlotEvents(k, start, end) {
        let s = this.getSlot(k);
        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: s[id].summary
                });
            }
        }
        return results;
    }

    getCachedEvents(start, end) {
        let ks = GCalendar.dateToCacheKey(start);
        let ke = GCalendar.dateToCacheKey(new Date(end.getTime() - 1));
        let results = this.getSlotEvents(ks, start, 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, start, end));
        return results;
    }

    sync() {
        return this.token.then(token => getEvents(this.calId, token, this.syncToken).then(r => {
            this.syncToken = r.nextSyncToken;
            let pm_results = r.results.map(e => e.start ? Promise.resolve(e) : getEvent(this.calId, e.id, token));
            return Promise.all(pm_results).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);
            }));
        })).catch(e => {
            if (e == GApiError.invalidSyncToken) {
                this.syncToken = '';
                this.sync();
            } else throw e;
        });
    }

    getEvents(start, end) {
        return this.sync().then(() => this.getCachedEvents(start, end));
    }
}