aboutsummaryrefslogblamecommitdiff
path: root/src/background.ts
blob: 3c37865ad586bea952e0ff43f80541607a795a72 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
                               
                                     
                                                                        
                            
                                                  
                                                           
 



                                                       
              
                     


                                                                             

                                                                                                    
  
                                    
                          

                                      
                           
 






















                                                                                                       
                                             

            
                      

                                                                                                                    

                                        

                                                                                                          
                                           
         


                                                            

 

                               

                  

                                                                        


                                                              



                                  









                                                                                
                                                                            








                                                         
                                 











                                                             

 
                                 
                                    
                                                                       


                       













                                                           

                                                                 
         
                                                                       
                                                   
                          
                  

                                                  

                  











                                                          
                                                           

                                   


                                                                                         





                                


                                                                  
                     
                                


                                          
                                            






                            
                        



                                             
                                                     

                                                      
            
     
                                                                      
                                       
                                

                                              
                      




                                                                    




                                                                  
                                               
                                        

                                         
                         
                          
                                      
                                                                                                   
                                         
                                           
                

                                        
                                                
                  
         
                                   




                                           
                                                                          
                  
         
                                       
                                 
                                 
                                                
                  
         
                                    




                                                        
                                                                                                                   

                                                
                  
         
                                    
                                                                                                      
                                  
                                                                   


                                       
                  
         
                                    






                                                                                                                            
                                 


                                                
                                 

                                                

                                                                                     

                                                               
               


                                               
                                    


                                                                                                 

                                      
                                                
                 




                                                                      

                      

                  




                                                











































                                                                        
                                                  

         

 

                                                          



                                                









                                                        
 

                                              
   
import * as gapi from './gapi';
import { MsgType, Msg } from './msg';
import { Duration, TrackedPeriod, TrackedPeriodFlat } from './duration';
import moment from 'moment';
import { GraphData, getGraphData } from './graph';
import { PatternEntry, PatternEntryFlat } from './pattern';

let mainPatterns: PatternEntry[] = [];
let analyzePatterns: PatternEntry[] = [];
let calendars: {[id: string]: gapi.GCalendarMeta} = {};
let calData: {[id: string]: gapi.GCalendar} = {};
let config = {
    trackedPeriods: [
        new TrackedPeriod('Today', Duration.days(1), Duration.days(0)),
        new TrackedPeriod('Yesterday', Duration.days(2), Duration.days(1)),
        new TrackedPeriod('This Week', Duration.weeks(1), Duration.weeks(0)),
        new TrackedPeriod('This Month', Duration.months(1), Duration.months(0))] as TrackedPeriod[],
    overrideNewTab: false
};
let mainGraphData: GraphData[] = [];
let dirtyMetadata = false;
let dirtyCalData = false;
let loadPromise: Promise<void> = null;
let auth = new gapi.Auth();

enum ChromeError {
    storageGetError = "storageGetError",
    storageSetError = "storageSetError"
}

const chromeStorageGet = (keys: string[]): Promise<any> => (
    new Promise(resolver => chrome.storage.local.get(keys, items => {
        if (chrome.runtime.lastError) throw ChromeError.storageGetError;
        resolver(items);
    }))
);

const chromeStorageSet = (obj: {[key: string]: any}): Promise<void> => (
    new Promise(resolver => chrome.storage.local.set(obj, () => {
        if (chrome.runtime.lastError) throw ChromeError.storageSetError;
        resolver();
    }))
);

async function loadMetadata() {
    try {
        let items = await chromeStorageGet(['calendars', 'config', 'mainPatterns', 'analyzePatterns']);
        if (!items.hasOwnProperty('config'))
            console.log("no saved metadata");
        else
        {
            config = {
                trackedPeriods: items.config.trackedPeriods.map((p: TrackedPeriodFlat) => TrackedPeriod.inflate(p)),
                overrideNewTab: items.config.overrideNewTab
            };
            calendars = items.calendars;
            mainPatterns = items.mainPatterns.map((p: PatternEntryFlat) => PatternEntry.inflate(p));
            analyzePatterns = items.analyzePatterns.map((p: PatternEntryFlat) => PatternEntry.inflate(p));
            console.log('metadata loaded');
        }
    } catch (_) {
        console.error("error while loading saved metadata");
    }
}

async function saveMetadata() {
    await chromeStorageSet({
        calendars,
        config: {
            trackedPeriods: config.trackedPeriods.map(p => p.deflate()),
            overrideNewTab: config.overrideNewTab
        },
        mainPatterns: mainPatterns.map(p => p.deflate()),
        analyzePatterns: analyzePatterns.map(p => p.deflate())
    });
    console.log('metadata saved');
}

async function loadCachedCals() {
    try {
        let items = await chromeStorageGet(['calData']);
        if (!items.hasOwnProperty('calData'))
            console.log("no cached cals");
        else
        {
            let calDataFlat: {[id: string]: gapi.GCalendarFlat} = items.calData;
            console.log(calDataFlat);
            for (let id in calDataFlat) {
                calData[id] = gapi.GCalendar.inflate(calDataFlat[id], auth);
            }
            console.log("cached cals loaded");
        }
    } catch (e) {
        console.log(e);
        console.error("error while loading cached cals");
    }
}

async function saveCachedCals() {
    let calDataFlat: {[id: string]: gapi.GCalendarFlat} = {};
    for (let id in calData) {
        if (!(calendars.hasOwnProperty(id) &&
            calendars[id].enabled)) continue;
        calDataFlat[id] = calData[id].deflate();
    }
    try {
        await chromeStorageSet({ calData: calDataFlat });
        console.log('cached data saved');
    } catch (_) {
        console.log("failed to save cached data");
    }
}

function getCalData(id: string) {
    if (!calData.hasOwnProperty(id))
        calData[id] = new gapi.GCalendar(id, calendars[id].name, auth);
    return calData[id];
}

function handleGApiError(id: string, err: gapi.GApiError) {
    if (err === gapi.GApiError.fetchError) {
        console.log(`${id}: fetch error`);
    } else if (err === gapi.GApiError.invalidAuthToken) {
        console.log(`${id}: invalid auth token`);
        calendars[id].enabled = false;
    } else if (err === gapi.GApiError.notLoggedIn) {
        console.log(`${id}: not logged in`);
    } else {
        console.log(`${id}: ${err}`);
        calendars[id].enabled = false;
    }
}

async function getCalEvents(id: string, start: Date, end: Date) {
    let gcal = getCalData(id);
    try {
        let res = await gcal.getEvents(new Date(start), new Date(end));
        dirtyCalData = dirtyCalData || res.changed;
        return res.events;
    } catch(err) {
        handleGApiError(id, err);
        console.log(`cannot load calendar ${id}`);
        return [];
    }
}

function updateMainGraphData() {
    console.log('refreshing graph data');
    console.log(mainGraphData);
    let pms = [];
    for (let i = 0; i < config.trackedPeriods.length; i++)
    {
        let p = config.trackedPeriods[i];
        let startD = p.start.toMoment();
        let endD = p.end.toMoment();
        if (!(startD && endD)) return;
        let start = moment().add(1, 'days').startOf('day');
        if (endD.valueOf() == 0) {
            switch (p.start.unit) {
                case 'days': start = moment().add(1, 'days').startOf('day'); break;
                case 'weeks': start = moment().add(1, 'weeks').startOf('isoWeek'); break;
                case 'months': start = moment().add(1, 'months').startOf('month'); break;
                default:
            }
        }
        let end = start.clone();
        start.subtract(startD);
        end.subtract(endD);
        pms.push(getGraphData(
            start.toDate(), end.toDate(), mainPatterns, calendars,
            getCalEvents
        ).then(r => {
            mainGraphData[i] = {
                    name: p.name,
                    start: start.toDate(),
                    end: end.toDate(),
                    data: r.patternGraphData
            };
        }));
    }
    return Promise.all(pms);
}

async function pollSync() {
    console.log('poll');
    /* sync all enabled calendars */
    let pms = [];
    for (let id in calendars) {
        if (!calendars[id].enabled) continue;
        pms.push(getCalData(id).sync().catch(err => {
            handleGApiError(id, err);
            console.log(`cannot sync calendar ${id}`);
        }));
    }
    (await Promise.all(pms)).forEach(b => b && (dirtyCalData = true));
    /* update the tracked graph data */
    await updateMainGraphData();
    pms = [];
    /* save the storage if state is changed */
    if (dirtyMetadata)
        pms.push(saveMetadata().then(() => dirtyMetadata = false));
    if (dirtyCalData)
        pms.push(saveCachedCals().then(() => dirtyCalData = false));
    await Promise.all(pms);
    /* setup the next loop */
    return new Promise(resolver => (
        window.setTimeout(() => { resolver(); pollSync();}, 10000)
    ));
}

function handleMsg(port: chrome.runtime.Port) {
    console.assert(port.name == 'main');
    port.onMessage.addListener(_msg => {
        let msg = Msg.inflate<any>(_msg);
        console.log(msg);
        switch (msg.opt) {
        case MsgType.updatePatterns: {
            let patterns = msg.data.patterns.map((p: PatternEntryFlat) => PatternEntry.inflate(p));
            if (msg.data.id == 'analyze')
                analyzePatterns = patterns;
            else
                mainPatterns = patterns;
            dirtyMetadata = true;
            port.postMessage(msg.genResp(null));
            break;
        }
        case MsgType.getPatterns: {
            let patterns;
            if (msg.data.id == 'analyze')
                patterns = analyzePatterns;
            else
                patterns = mainPatterns;
            port.postMessage(msg.genResp(patterns.map(p => p.deflate())));
            break;
        }
        case MsgType.updateCalendars: {
            calendars = msg.data;
            dirtyMetadata = true;
            port.postMessage(msg.genResp(null));
            break;
        }
        case MsgType.getCalendars: {
            let cals = calendars;
            if (msg.data.enabledOnly)
            {
                cals = Object.keys(calendars)
                    .filter(id => calendars[id].enabled)
                    .reduce((res, id) => (res[id] = calendars[id], res), {} as {[id: string]: gapi.GCalendarMeta});
            }
            port.postMessage(msg.genResp(cals));
            break;
        }
        case MsgType.getCalEvents: {
            getCalEvents(msg.data.id, new Date(msg.data.start), new Date(msg.data.end)).then(data => {
                console.log(data);
                let resp = msg.genResp(data.map(e => e.deflate()));
                console.log(resp);
                port.postMessage(resp);
            });
            break;
        }
        case MsgType.updateConfig: {
            for (let prop in msg.data) {
                if (prop === 'trackedPeriods') {
                    config.trackedPeriods = msg.data.trackedPeriods.map((p: TrackedPeriodFlat) => TrackedPeriod.inflate(p));
                } else if (prop == 'overrideNewTab') {
                    config.overrideNewTab = msg.data.overrideNewTab as boolean;
                }
            }
            dirtyMetadata = true;
            port.postMessage(msg.genResp(null));
            break;
        }
        case MsgType.getConfig: {
            let res: {[prop: string]: any} = {};
            msg.data.forEach((prop: string) => {
                if (prop === 'trackedPeriods')
                    res.trackedPeriods = config.trackedPeriods.map(p => p.deflate());
                else if (prop === 'overrideNewTab')
                    res.overrideNewTab = config.overrideNewTab;
            });
            port.postMessage(msg.genResp(res));
            break;
        }
        case MsgType.getGraphData: {
            (async () => {
                await (msg.data.sync ? updateMainGraphData().then(() => {}) : Promise.resolve());
                if (mainGraphData.length === 0)
                {
                    await loadPromise;
                    await updateMainGraphData();
                }
                port.postMessage(msg.genResp(mainGraphData.map(d => ({
                    name: d.name,
                    start: d.start.toISOString(),
                    end: d.end.toISOString(),
                    data: d.data
                }))));
            })();
            break;
        }
        case MsgType.clearCache: {
            calData = {};
            port.postMessage(msg.genResp(null));
            break;
        }
        case MsgType.fetchCalendars: {
            (async () => {
                let token = await auth.getAuthToken();
                let results = await gapi.getCalendars(token);
                port.postMessage(msg.genResp(results));
            })();
            break;
        }
        case MsgType.fetchColors: {
            (async () => {
                let token = await auth.getAuthToken();
                let results = await gapi.getColors(token);
                port.postMessage(msg.genResp(results));
            })();
            break;
        }
        case MsgType.login: {
            (async () => {
                let succ = true;
                try {
                    await auth.login();
                } catch (_) {
                    succ = false;
                }
                port.postMessage(msg.genResp(succ));
            })();
            break;
        }
        case MsgType.logout: {
            (async () => {
                let succ = true;
                try {
                    await auth.logout();
                } catch (_) {
                    succ = false;
                }
                port.postMessage(msg.genResp(succ));
            })();
            break;
        }
        case MsgType.getLoggedIn: {
            auth.loggedIn().then(b => port.postMessage(msg.genResp(b)));
            break;
        }
        default: console.error("unknown msg opt");
        }
    });
}

loadPromise = (async () => {
    await Promise.all([loadMetadata(), loadCachedCals()]);
    pollSync();
})();

chrome.runtime.onConnect.addListener(handleMsg);

chrome.tabs.onCreated.addListener(function(tab) {
    if (tab.url === "chrome://newtab/") {
        if (config.overrideNewTab) {
            chrome.tabs.update(tab.id, {
                url: chrome.extension.getURL("tab.html")
            });
        }
    }
});

chrome.runtime.onInstalled.addListener(() => {
    chrome.tabs.create({ url: "index.html" });
});