aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Analyze.tsx11
-rw-r--r--src/Settings.tsx72
-rw-r--r--src/background.ts75
-rw-r--r--src/gapi.ts120
-rw-r--r--src/msg.ts12
-rw-r--r--src/tab.tsx16
6 files changed, 215 insertions, 91 deletions
diff --git a/src/Analyze.tsx b/src/Analyze.tsx
index b272f30..e7563d5 100644
--- a/src/Analyze.tsx
+++ b/src/Analyze.tsx
@@ -77,9 +77,14 @@ class Analyze extends React.Component<AnalyzeProps> {
this.setState({ calendars: msg.data });
});
- gapi.getLoggedIn().then(b => !b &&
- this.openSnackbar('Not logged in. Operating in offline mode.',
- 'warning' as SnackbarVariant));
+ this.msgClient.sendMsg({
+ opt: MsgType.getLoggedIn,
+ data: {}
+ }).then(msg => {
+ if (!msg.data)
+ this.openSnackbar('Not logged in. Operating in offline mode.',
+ 'warning' as SnackbarVariant);
+ });
this.dialogPromiseResolver = null;
}
diff --git a/src/Settings.tsx b/src/Settings.tsx
index d44f1f6..3537894 100644
--- a/src/Settings.tsx
+++ b/src/Settings.tsx
@@ -187,11 +187,16 @@ class Settings extends React.Component<SettingsProps> {
constructor(props: SettingsProps) {
super(props);
- gapi.getLoggedIn().then(b => this.setState({ isLoggedIn: b }));
-
this.msgClient = new MsgClient('main');
this.msgClient.sendMsg({
+ opt: MsgType.getLoggedIn,
+ data: {}
+ }).then(msg => {
+ this.setState({ isLoggedIn: msg.data })
+ });
+
+ this.msgClient.sendMsg({
opt: MsgType.getPatterns,
data: { id: 'main' }
}).then(msg => {
@@ -224,7 +229,8 @@ class Settings extends React.Component<SettingsProps> {
handleLogin = async () => {
try {
- await gapi.login();
+ let resp = await this.msgClient.sendMsg({ opt: MsgType.login, data: {} });
+ if (!resp.data) throw new Error("backend failes to login");
this.setState({ isLoggedIn: true });
this.loadAll(true);
} catch (_) {
@@ -236,10 +242,12 @@ class Settings extends React.Component<SettingsProps> {
let ans = await this.openDialog("Logout", "Are you sure to logout?");
if (!ans) return;
try {
- await gapi.logout();
+ let resp = await this.msgClient.sendMsg({ opt: MsgType.logout, data: {} });
+ if (!resp.data) throw new Error("backend fails to logout");
await this.msgClient.sendMsg({ opt: MsgType.clearCache, data: {} });
this.setState({ isLoggedIn: false });
- } catch (_) {
+ } catch (err) {
+ console.log(err);
this.openSnackbar("Failed to logout!", 'error' as SnackbarVariant);
}
}
@@ -250,30 +258,38 @@ class Settings extends React.Component<SettingsProps> {
this.setState({ calendars });
}
- async loadAll(reloadAll = false) {
+ async loadAll(reloadAll = false): Promise<void> {
await new Promise(resolver => (this.setState({ calendarsLoading: true }, resolver)));
- let pm_colors = gapi.getAuthToken().then(gapi.getColors).then(color => color.calendar);
- let pm_cals = gapi.getAuthToken().then(gapi.getCalendars);
- let [colors, _cals] = await Promise.all([pm_colors, pm_cals]);
- var cals: { [id: string]: gapi.GCalendarMeta } = {};
- _cals.forEach((cal: any) => {
- let _color = colors[cal.colorId];
- cals[cal.id] = {
- name: cal.summary,
- color: {
- background: ('#' + getColorFamily(_color.background)[300]).toLowerCase()
- },
- enabled: true
- };
- });
+ try {
+ let pm_colors = this.msgClient.sendMsg(
+ { opt: MsgType.fetchColors, data: {} }).then(msg => msg.data.calendar);
+ let pm_cals = this.msgClient.sendMsg(
+ { opt: MsgType.fetchCalendars, data: {} }).then(msg => msg.data);
+ let [colors, _cals] = await Promise.all([pm_colors, pm_cals]);
+ var cals: { [id: string]: gapi.GCalendarMeta } = {};
+ _cals.forEach((cal: any) => {
+ let _color = colors[cal.colorId];
+ cals[cal.id] = {
+ name: cal.summary,
+ color: {
+ background: ('#' + getColorFamily(_color.background)[300]).toLowerCase()
+ },
+ enabled: true
+ };
+ });
- let pms = [this.loadCalendars(cals, reloadAll)];
- if (reloadAll)
- pms.push(this.loadDefaultPatterns(cals));
- await Promise.all(pms);
- this.setState({ calendarsLoading: false });
- if (reloadAll) this.handleApply();
+ let pms = [this.loadCalendars(cals, reloadAll)];
+ if (reloadAll)
+ pms.push(this.loadDefaultPatterns(cals));
+ await Promise.all(pms);
+ if (reloadAll) this.handleApply();
+ } catch (err) {
+ console.log(err);
+ this.openSnackbar("Failed to update calendars!", 'error' as SnackbarVariant);
+ } finally {
+ this.setState({ calendarsLoading: false });
+ }
};
loadDefaultPatterns(calendars: {[ id: string ]: gapi.GCalendarMeta }) {
@@ -445,8 +461,8 @@ class Settings extends React.Component<SettingsProps> {
Calendars
</STableCell>
<STableCell className={classes.tableContent} style={{paddingRight: 0}}>
- <div className={classNames(classes.calendarList, classes.list)}>
{(this.state.isLoggedIn &&
+ <div className={classNames(classes.calendarList, classes.list)}>
<List disablePadding>
{Object.keys(this.state.calendars).sort().map(id =>
<CompactListItem
@@ -459,7 +475,7 @@ class Settings extends React.Component<SettingsProps> {
disableRipple />
<ListItemText primary={this.state.calendars[id].name} />
</CompactListItem>)}
- </List>) || 'Please Login.'}</div>
+ </List></div>) || 'Please Login.'}
</STableCell>
</TableRow>
<TableRow>
diff --git a/src/background.ts b/src/background.ts
index f536005..c42281d 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -21,6 +21,7 @@ let mainGraphData: GraphData[] = [];
let dirtyMetadata = false;
let dirtyCalData = false;
let loadPromise: Promise<void> = null;
+let auth = new gapi.Auth();
enum ChromeError {
storageGetError = "storageGetError",
@@ -85,7 +86,7 @@ async function loadCachedCals() {
let calDataFlat: {[id: string]: gapi.GCalendarFlat} = items.calData;
console.log(calDataFlat);
for (let id in calDataFlat) {
- calData[id] = gapi.GCalendar.inflate(calDataFlat[id]);
+ calData[id] = gapi.GCalendar.inflate(calDataFlat[id], auth);
}
console.log("cached cals loaded");
}
@@ -112,10 +113,24 @@ async function saveCachedCals() {
function getCalData(id: string) {
if (!calData.hasOwnProperty(id))
- calData[id] = new gapi.GCalendar(id, calendars[id].name);
+ 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 {
@@ -123,8 +138,8 @@ async function getCalEvents(id: string, start: Date, end: Date) {
dirtyCalData = res.changed;
return res.events;
} catch(err) {
- console.log(`cannot load calendar ${id}`, err);
- calendars[id].enabled = false;
+ handleGApiError(id, err);
+ console.log(`cannot load calendar ${id}`);
return [];
}
}
@@ -173,8 +188,8 @@ async function pollSync() {
for (let id in calendars) {
if (!calendars[id].enabled) continue;
pms.push(getCalData(id).sync().catch(err => {
- console.log(`cannot sync calendar ${id}`, err);
- calendars[id].enabled = false;
+ handleGApiError(id, err);
+ console.log(`cannot sync calendar ${id}`);
}));
}
await Promise.all(pms);
@@ -289,6 +304,50 @@ function handleMsg(port: chrome.runtime.Port) {
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");
}
});
@@ -310,3 +369,7 @@ chrome.tabs.onCreated.addListener(function(tab) {
}
}
});
+
+chrome.runtime.onInstalled.addListener(async () => {
+ try { await auth.logout(); } catch (_) {}
+});
diff --git a/src/gapi.ts b/src/gapi.ts
index 9ddf751..4178426 100644
--- a/src/gapi.ts
+++ b/src/gapi.ts
@@ -3,13 +3,13 @@
import LRU from "lru-cache";
const gapiBase = 'https://www.googleapis.com/calendar/v3';
-let loggedIn: boolean = null;
-enum GApiError {
+export enum GApiError {
invalidSyncToken = "invalidSyncToken",
invalidAuthToken = "invalidAuthToken",
notLoggedIn = "notLoggedIn",
notLoggedOut = "notLoggedOut",
+ fetchError = "fetchError",
otherError = "otherError",
}
@@ -34,43 +34,51 @@ function _removeCachedAuthToken(token: string) {
chrome.identity.removeCachedAuthToken({ token }, () => resolver()));
}
-export async function getLoggedIn(): Promise<boolean> {
- if (loggedIn === null)
- {
- try {
- await _getAuthToken(false);
- loggedIn = true;
- } catch(_) {
- loggedIn = false;
+export class Auth {
+ _loggedIn: boolean;
+
+ constructor() {
+ this._loggedIn = null;
+ }
+
+ async loggedIn(): Promise<boolean> {
+ if (this._loggedIn === null)
+ {
+ try {
+ await _getAuthToken(false);
+ this._loggedIn = true;
+ } catch(_) {
+ this._loggedIn = false;
+ }
}
+ return this._loggedIn;
}
- return loggedIn;
-}
-export async function getAuthToken(): Promise<string> {
- let b = await getLoggedIn();
- if (b) return _getAuthToken(false);
- else throw GApiError.notLoggedIn;
-}
+ async getAuthToken(): Promise<string> {
+ let b = await this.loggedIn();
+ if (b) return _getAuthToken(false);
+ else throw GApiError.notLoggedIn;
+ }
-export async function login(): Promise<void> {
- let b = await getLoggedIn();
- if (!b) {
- await _getAuthToken(true);
- loggedIn = true;
+ async login(): Promise<void> {
+ let b = await this.loggedIn();
+ if (!b) {
+ await _getAuthToken(true);
+ this._loggedIn = true;
+ }
+ else throw GApiError.notLoggedOut;
}
- else throw GApiError.notLoggedOut;
-}
-export async function logout(): Promise<void> {
- let token = await getAuthToken();
- let response = await fetch(
- `https://accounts.google.com/o/oauth2/revoke?${toParams({ token })}`,
- { method: 'GET' });
- //if (response.status === 200)
- await _removeCachedAuthToken(token);
- //else throw GApiError.otherError;
- loggedIn = false;
+ async logout(): Promise<void> {
+ let token = await this.getAuthToken();
+ this._loggedIn = false;
+ let response = await fetch(
+ `https://accounts.google.com/o/oauth2/revoke?${toParams({ token })}`,
+ { method: 'GET' });
+ //if (response.status === 200)
+ await _removeCachedAuthToken(token);
+ //else throw GApiError.otherError;
+ }
}
export type GCalendarColor = {
@@ -87,14 +95,24 @@ export async function getCalendars(token: string): Promise<any> {
let response = await fetch(
`${gapiBase}/users/me/calendarList?${toParams({access_token: token})}`,
{ method: 'GET' });
- return (await response.json()).items;
+ try {
+ return (await response.json()).items;
+ } catch (err) {
+ console.log(err);
+ throw GApiError.fetchError;
+ }
}
export async function getColors(token: string): Promise<any> {
let response = await fetch(
`${gapiBase}/colors?${toParams({access_token: token})}`,
{ method: 'GET' });
- return response.json();
+ try {
+ return response.json();
+ } catch (err) {
+ console.log(err);
+ throw GApiError.fetchError;
+ }
}
async function getEvent(calId: string, eventId: string, token: string): Promise<any> {
@@ -113,14 +131,20 @@ function getEvents(calId: string, token: string,
let results = [] as any[];
const singleFetch = async (pageToken: string, syncToken: string):
Promise<{nextSyncToken: string, results: any[]}> => {
- let response = await fetch(`${gapiBase}/calendars/${calId}/events?${toParams({
- access_token: token,
- pageToken,
- syncToken,
- timeMin,
- timeMax,
- maxResults: resultsPerRequest
- })}`, { method: 'GET' });
+ let response;
+ try {
+ response = await fetch(`${gapiBase}/calendars/${calId}/events?${toParams({
+ access_token: token,
+ pageToken,
+ syncToken,
+ timeMin,
+ timeMax,
+ maxResults: resultsPerRequest
+ })}`, { method: 'GET' });
+ } catch (err) {
+ console.log(err);
+ throw GApiError.fetchError;
+ }
switch (response.status) {
case 200: {
let data = await response.json();
@@ -235,11 +259,13 @@ export class GCalendar {
eventMeta: { [id: string]: { keys: Set<number>, summary: string } };
options: GCalendarOptions;
divider: number;
+ auth: Auth;
- constructor(calId: string, name: string,
+ constructor(calId: string, name: string, auth: Auth,
options={maxCachedItems: 100, nDaysPerSlot: 10, largeQuery: 10}) {
this.calId = calId;
this.name = name;
+ this.auth = auth;
this.syncToken = '';
this.cache = new LRU<number, GCalendarSlot>({
max: options.maxCachedItems,
@@ -275,7 +301,7 @@ export class GCalendar {
}
}
- static inflate(obj: GCalendarFlat) {
+ static inflate(obj: GCalendarFlat, auth: Auth) {
let cache = obj.cache.map(t => {
let slot: GCalendarSlot = {};
for (let id in t.v)
@@ -289,7 +315,7 @@ export class GCalendar {
eventMeta[id] = { keys: new Set(m.keys), summary: m.summary };
}
- let gcal = new GCalendar(obj.calId, obj.name, obj.options);
+ let gcal = new GCalendar(obj.calId, obj.name, auth, obj.options);
gcal.syncToken = obj.syncToken;
gcal.cache.load(cache);
gcal.eventMeta = eventMeta;
@@ -297,7 +323,7 @@ export class GCalendar {
return gcal;
}
- get token() { return getAuthToken(); }
+ get token() { return this.auth.getAuthToken(); }
dateToCacheKey(date: Date) {
return Math.floor(date.getTime() / this.divider);
diff --git a/src/msg.ts b/src/msg.ts
index daef6f9..c43e80f 100644
--- a/src/msg.ts
+++ b/src/msg.ts
@@ -9,7 +9,12 @@ export enum MsgType {
updateConfig = "updateConfig",
getConfig = "getConfig",
getGraphData = "getGraphData",
- clearCache = "clearCache"
+ clearCache = "clearCache",
+ fetchCalendars = "fetchCalendars",
+ fetchColors = "fetchColors",
+ login = "login",
+ logout = "logout",
+ getLoggedIn = "getLoggedIn"
}
function stringifyMsgType(opt: MsgType): string { return MsgType[opt]; }
@@ -25,6 +30,11 @@ function parseMsgType(s: string): MsgType {
case "getConfig": return MsgType.getConfig;
case "getGraphData": return MsgType.getGraphData;
case "clearCache": return MsgType.clearCache;
+ case "fetchCalendars": return MsgType.fetchCalendars;
+ case "fetchColors": return MsgType.fetchColors;
+ case "login": return MsgType.login;
+ case "logout": return MsgType.logout;
+ case "getLoggedIn": return MsgType.getLoggedIn;
default: console.error(`unknown MsgType: ${s}`);
}
}
diff --git a/src/tab.tsx b/src/tab.tsx
index 727a3a5..8edd67e 100644
--- a/src/tab.tsx
+++ b/src/tab.tsx
@@ -94,11 +94,11 @@ class Tab extends React.Component<TabProps> {
</IconButton>
</div>
<div className={classes.buttonSpacer} />
- <Grid container spacing={0} style={{ maxWidth: 1024, minWidth: 640, margin: '0 auto' }}>
+ <Grid container spacing={0} style={{ maxWidth: 900, minWidth: 640, margin: '0 auto' }}>
{
(data.length > 0 &&
data.map((d, idx) => (
- <Grid item key={idx} xs={12} lg={6}>
+ <Grid item key={idx} xs={12} md={6}>
<Typography variant="subtitle1" align="center" color="textPrimary">
{d.name}
</Typography>
@@ -119,26 +119,30 @@ class Tab extends React.Component<TabProps> {
padAngle={0.8} />
</div>) ||
<div style={{
- height: 250,
marginLeft: 100,
marginRight: 100,
marginTop: 60,
marginBottom: 60,
- textAlign: 'center',
- position: 'relative'
+ textAlign: 'center'
}}>
+ <div style={{
+ position: 'relative',
+ height: 250,
+ display: 'inline-block'
+ }}>
<Donut style={{
height: '100%'
}} />
<div style={{
position: 'absolute',
top: -40,
- left: 83,
+ left: 55,
}}>
<Typography variant="subtitle1" align="center" color="textSecondary">
No matching events.
</Typography>
</div>
+ </div>
</div>}
</Grid>
))) || (