Stackedit/src/services/helpers/googleHelper.js
2017-08-15 11:43:26 +01:00

225 lines
7.2 KiB
JavaScript

import utils from '../utils';
import store from '../../store';
const clientId = '241271498917-t4t7d07qis7oc0ahaskbif3ft6tk63cd.apps.googleusercontent.com';
const appsDomain = null;
const tokenExpirationMargin = 10 * 60 * 1000; // 10 min
// const scopeMap = {
// profile: [
// 'https://www.googleapis.com/auth/userinfo.profile',
// ],
// gdrive: [
// 'https://www.googleapis.com/auth/drive.install',
// store.getters['data/settings'].gdriveFullAccess === true ?
// 'https://www.googleapis.com/auth/drive' :
// 'https://www.googleapis.com/auth/drive.file',
// ],
// blogger: [
// 'https://www.googleapis.com/auth/blogger',
// ],
// picasa: [
// 'https://www.googleapis.com/auth/photos',
// ],
// };
const request = (googleToken, options) => utils.request({
...options,
headers: {
...options.headers,
Authorization: `Bearer ${googleToken.accessToken}`,
},
});
const saveFile = (googleToken, data, appData) => {
const options = {
method: 'POST',
url: 'https://www.googleapis.com/upload/drive/v2/files',
headers: {},
};
if (appData) {
options.method = 'PUT';
options.url = `https://www.googleapis.com/drive/v2/files/${appData.id}`;
options.headers['if-match'] = appData.etag;
}
const metadata = {
title: data.name,
parents: [{
id: 'appDataFolder',
}],
properties: Object.keys(data)
.filter(key => key !== 'name' && key !== 'tx')
.map(key => ({
key,
value: JSON.stringify(data[key]),
visibility: 'PUBLIC',
})),
};
const media = null;
const boundary = `-------${utils.uid()}`;
const delimiter = `\r\n--${boundary}\r\n`;
const closeDelimiter = `\r\n--${boundary}--`;
if (media) {
let multipartRequestBody = '';
multipartRequestBody += delimiter;
multipartRequestBody += 'Content-Type: application/json\r\n\r\n';
multipartRequestBody += JSON.stringify(metadata);
multipartRequestBody += delimiter;
multipartRequestBody += 'Content-Type: application/json\r\n\r\n';
multipartRequestBody += JSON.stringify(media);
multipartRequestBody += closeDelimiter;
return request(googleToken, {
...options,
params: {
uploadType: 'multipart',
},
headers: {
...options.headers,
'Content-Type': `multipart/mixed; boundary="${boundary}"`,
},
body: multipartRequestBody,
});
}
return request(googleToken, {
...options,
body: metadata,
}).then(res => ({
id: res.body.id,
etag: res.body.etag,
}));
};
export default {
startOauth2(scopes, sub = null, silent = false) {
return utils.startOauth2(
'https://accounts.google.com/o/oauth2/v2/auth', {
client_id: clientId,
response_type: 'token',
scope: scopes.join(' '),
hd: appsDomain,
login_hint: sub,
prompt: silent ? 'none' : null,
}, silent)
// Call the tokeninfo endpoint
.then(data => utils.request({
method: 'POST',
url: 'https://www.googleapis.com/oauth2/v3/tokeninfo',
params: {
access_token: data.accessToken,
},
}).then((res) => {
// Check the returned client ID consistency
if (res.body.aud !== clientId) {
throw new Error('Client ID inconsistent.');
}
// Check the returned sub consistency
if (sub && res.body.sub !== sub) {
throw new Error('Google account ID not expected.');
}
// Build token object including scopes and sub
return {
scopes,
accessToken: data.accessToken,
expiresOn: Date.now() + (data.expiresIn * 1000),
sub: res.body.sub,
isLogin: !store.getters['data/loginToken'],
};
}))
// Call the tokeninfo endpoint
.then(googleToken => request(googleToken, {
method: 'GET',
url: 'https://www.googleapis.com/plus/v1/people/me',
}).then((res) => {
// Add name to googleToken
googleToken.name = res.body.displayName;
const existingToken = store.getters['data/googleTokens'][googleToken.sub];
if (existingToken) {
if (!sub) {
throw new Error('Google account already linked.');
}
// Add isLogin and lastChangeId to googleToken
googleToken.isLogin = existingToken.isLogin;
googleToken.lastChangeId = existingToken.lastChangeId;
}
// Add googleToken to googleTokens
store.dispatch('data/setGoogleToken', googleToken);
return googleToken;
}));
},
refreshToken(scopes, googleToken) {
const sub = googleToken.sub;
const lastToken = store.getters['data/googleTokens'][sub];
const mergedScopes = [...new Set([
...scopes,
...lastToken.scopes,
])];
return Promise.resolve()
.then(() => {
if (mergedScopes.length === lastToken.scopes.length) {
return lastToken;
}
// New scopes are requested, popup an authorize window
return this.startOauth2(mergedScopes, sub);
})
.then((refreshedToken) => {
if (refreshedToken.expiresOn > Date.now() + tokenExpirationMargin) {
// Token is fresh enough
return refreshedToken;
}
// Token is almost outdated, try to take one in background
return this.startOauth2(mergedScopes, sub, true)
// If it fails try to popup a window
.catch(() => this.startOauth2(mergedScopes, sub));
});
},
getChanges(googleToken) {
let changes = [];
return this.refreshToken(['https://www.googleapis.com/auth/drive.appdata'], googleToken)
.then((refreshedToken) => {
const lastChangeId = refreshedToken.lastChangeId || 0;
const getPage = pageToken => request(refreshedToken, {
method: 'GET',
url: 'https://www.googleapis.com/drive/v2/changes',
params: {
pageToken,
startChangeId: pageToken || !lastChangeId ? null : lastChangeId + 1,
spaces: 'appDataFolder',
fields: 'nextPageToken,items(deleted,file/id,file/etag,file/title,file/properties(key,value))',
},
}).then((res) => {
changes = changes.concat(res.body.items);
if (res.body.nextPageToken) {
return getPage(res.body.nextPageToken);
}
return changes;
});
return getPage();
});
},
updateLastChangeId(googleToken, changes) {
const refreshedToken = store.getters['data/googleTokens'][googleToken.sub];
let lastChangeId = refreshedToken.lastChangeId || 0;
changes.forEach((change) => {
if (change.id > lastChangeId) {
lastChangeId = change.id;
}
});
if (lastChangeId !== refreshedToken.lastChangeId) {
store.dispatch('data/setGoogleToken', {
...refreshedToken,
lastChangeId,
});
}
},
insertData(googleToken, data) {
return this.refreshToken(['https://www.googleapis.com/auth/drive.appdata'], googleToken)
.then(refreshedToken => saveFile(refreshedToken, data));
},
updateData(googleToken, data, appData) {
return this.refreshToken(['https://www.googleapis.com/auth/drive.appdata'], googleToken)
.then(refreshedToken => saveFile(refreshedToken, data, appData));
},
};