diff options
-rw-r--r-- | src/Utils.ts | 92 | ||||
-rw-r--r-- | src/client/Network.ts | 25 | ||||
-rw-r--r-- | src/client/apis/google_docs/GoogleApiClientUtils.ts | 2 | ||||
-rw-r--r-- | src/client/apis/google_docs/GooglePhotosClientUtils.ts | 3 | ||||
-rw-r--r-- | src/client/util/Import & Export/DirectoryImportBox.tsx | 4 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 2 | ||||
-rw-r--r-- | src/new_fields/RichTextUtils.ts | 4 | ||||
-rw-r--r-- | src/server/apis/google/GoogleApiServerUtils.ts | 51 | ||||
-rw-r--r-- | src/server/database.ts | 34 | ||||
-rw-r--r-- | src/server/index.ts | 12 |
10 files changed, 126 insertions, 103 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index 5f06b5cec..ae8371f15 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -3,22 +3,20 @@ import v5 = require("uuid/v5"); import { Socket } from 'socket.io'; import { Message } from './server/Message'; import { RouteStore } from './server/RouteStore'; -import requestPromise = require('request-promise'); -import { CurrentUserUtils } from './server/authentication/models/current_user_utils'; -export class Utils { +export namespace Utils { - public static DRAG_THRESHOLD = 4; + export const DRAG_THRESHOLD = 4; - public static GenerateGuid(): string { + export function GenerateGuid(): string { return v4(); } - public static GenerateDeterministicGuid(seed: string): string { + export function GenerateDeterministicGuid(seed: string): string { return v5(seed, v5.URL); } - public static GetScreenTransform(ele?: HTMLElement): { scale: number, translateX: number, translateY: number } { + export function GetScreenTransform(ele?: HTMLElement): { scale: number, translateX: number, translateY: number } { if (!ele) { return { scale: 1, translateX: 1, translateY: 1 }; } @@ -35,23 +33,23 @@ export class Utils { * requested extension * @param extension the specified sub-path to append to the window origin */ - public static prepend(extension: string): string { + export function prepend(extension: string): string { return window.location.origin + extension; } - public static fileUrl(filename: string): string { - return this.prepend(`/files/${filename}`); + export function fileUrl(filename: string): string { + return prepend(`/files/${filename}`); } - public static shareUrl(documentId: string): string { - return this.prepend(`/doc/${documentId}?sharing=true`); + export function shareUrl(documentId: string): string { + return prepend(`/doc/${documentId}?sharing=true`); } - public static CorsProxy(url: string): string { - return this.prepend(RouteStore.corsProxy + "/") + encodeURIComponent(url); + export function CorsProxy(url: string): string { + return prepend(RouteStore.corsProxy + "/") + encodeURIComponent(url); } - public static CopyText(text: string) { + export function CopyText(text: string) { var textArea = document.createElement("textarea"); textArea.value = text; document.body.appendChild(textArea); @@ -63,7 +61,7 @@ export class Utils { document.body.removeChild(textArea); } - public static fromRGBAstr(rgba: string) { + export function fromRGBAstr(rgba: string) { let rm = rgba.match(/rgb[a]?\(([0-9]+)/); let r = rm ? Number(rm[1]) : 0; let gm = rgba.match(/rgb[a]?\([0-9]+,([0-9]+)/); @@ -74,11 +72,12 @@ export class Utils { let a = am ? Number(am[1]) : 0; return { r: r, g: g, b: b, a: a }; } - public static toRGBAstr(col: { r: number, g: number, b: number, a?: number }) { + + export function toRGBAstr(col: { r: number, g: number, b: number, a?: number }) { return "rgba(" + col.r + "," + col.g + "," + col.b + (col.a !== undefined ? "," + col.a : "") + ")"; } - public static HSLtoRGB(h: number, s: number, l: number) { + export function HSLtoRGB(h: number, s: number, l: number) { // Must be fractions of 1 // s /= 100; // l /= 100; @@ -108,7 +107,7 @@ export class Utils { return { r: r, g: g, b: b }; } - public static RGBToHSL(r: number, g: number, b: number) { + export function RGBToHSL(r: number, g: number, b: number) { // Make r, g, and b fractions of 1 r /= 255; g /= 255; @@ -150,7 +149,7 @@ export class Utils { } - public static GetClipboardText(): string { + export function GetClipboardText(): string { var textArea = document.createElement("textarea"); document.body.appendChild(textArea); textArea.focus(); @@ -163,51 +162,53 @@ export class Utils { return val; } - public static loggingEnabled: Boolean = false; - public static logFilter: number | undefined = undefined; - private static log(prefix: string, messageName: string, message: any, receiving: boolean) { - if (!this.loggingEnabled) { + export const loggingEnabled: Boolean = false; + export const logFilter: number | undefined = undefined; + + function log(prefix: string, messageName: string, message: any, receiving: boolean) { + if (!loggingEnabled) { return; } message = message || {}; - if (this.logFilter !== undefined && this.logFilter !== message.type) { + if (logFilter !== undefined && logFilter !== message.type) { return; } let idString = (message.id || "").padStart(36, ' '); prefix = prefix.padEnd(16, ' '); console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)}`); } - private static loggingCallback(prefix: string, func: (args: any) => any, messageName: string) { + + function loggingCallback(prefix: string, func: (args: any) => any, messageName: string) { return (args: any) => { - this.log(prefix, messageName, args, true); + log(prefix, messageName, args, true); func(args); }; } - public static Emit<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T) { - this.log("Emit", message.Name, args, false); + export function Emit<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T) { + log("Emit", message.Name, args, false); socket.emit(message.Message, args); } - public static EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T): Promise<any>; - public static EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn: (args: any) => any): void; - public static EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> { - this.log("Emit", message.Name, args, false); + export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T): Promise<any>; + export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn: (args: any) => any): void; + export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> { + log("Emit", message.Name, args, false); if (fn) { - socket.emit(message.Message, args, this.loggingCallback('Receiving', fn, message.Name)); + socket.emit(message.Message, args, loggingCallback('Receiving', fn, message.Name)); } else { - return new Promise<any>(res => socket.emit(message.Message, args, this.loggingCallback('Receiving', res, message.Name))); + return new Promise<any>(res => socket.emit(message.Message, args, loggingCallback('Receiving', res, message.Name))); } } - public static AddServerHandler<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, handler: (args: T) => any) { - socket.on(message.Message, this.loggingCallback('Incoming', handler, message.Name)); + export function AddServerHandler<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, handler: (args: T) => any) { + socket.on(message.Message, loggingCallback('Incoming', handler, message.Name)); } - public static AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) { + export function AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) { socket.on(message.Message, (arg: T, fn: (res: any) => any) => { - this.log('S receiving', message.Name, arg, true); - handler([arg, this.loggingCallback('S sending', fn, message.Name)]); + log('S receiving', message.Name, arg, true); + handler([arg, loggingCallback('S sending', fn, message.Name)]); }); } } @@ -291,15 +292,4 @@ export namespace JSONUtils { return results; } -} - -export function PostToServer(relativeRoute: string, body?: any) { - body = { userId: CurrentUserUtils.id, ...body }; - let options = { - method: "POST", - uri: Utils.prepend(relativeRoute), - json: true, - body - }; - return requestPromise.post(options); }
\ No newline at end of file diff --git a/src/client/Network.ts b/src/client/Network.ts new file mode 100644 index 000000000..cb46105f8 --- /dev/null +++ b/src/client/Network.ts @@ -0,0 +1,25 @@ +import { Utils } from "../Utils"; +import { CurrentUserUtils } from "../server/authentication/models/current_user_utils"; +import requestPromise = require('request-promise'); + +export async function PostToServer(relativeRoute: string, body?: any) { + let options = { + uri: Utils.prepend(relativeRoute), + method: "POST", + headers: { userId: CurrentUserUtils.id }, + body, + json: true + }; + return requestPromise.post(options); +} + +export async function PostFormDataToServer(relativeRoute: string, formData: FormData) { + const parameters = { + method: 'POST', + headers: { userId: CurrentUserUtils.id }, + body: formData, + }; + const response = await fetch(relativeRoute, parameters); + const text = await response.json(); + return text; +}
\ No newline at end of file diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 2c84741db..0f0f81891 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -1,9 +1,9 @@ import { docs_v1, slides_v1 } from "googleapis"; -import { PostToServer } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; import { Opt } from "../../../new_fields/Doc"; import { isArray } from "util"; import { EditorState } from "prosemirror-state"; +import { PostToServer } from "../../Network"; export const Pulls = "googleDocsPullCount"; export const Pushes = "googleDocsPushCount"; diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index c45a49f1a..b1b29210a 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -1,4 +1,4 @@ -import { PostToServer, Utils } from "../../../Utils"; +import { Utils } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; import { ImageField } from "../../../new_fields/URLField"; import { Cast, StrCast } from "../../../new_fields/Types"; @@ -14,6 +14,7 @@ import { NewMediaItemResult, MediaItem } from "../../../server/apis/google/Share import { AssertionError } from "assert"; import { DocumentView } from "../../views/nodes/DocumentView"; import { DocumentManager } from "../../util/DocumentManager"; +import { PostToServer } from "../../Network"; export namespace GooglePhotos { diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 6670f685e..d0291aec4 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -21,6 +21,7 @@ import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import "./DirectoryImportBox.scss"; import BatchedArray from "array-batcher"; +import { PostFormDataToServer } from "../../Network"; const unsupported = ["text/html", "text/plain"]; interface FileResponse { @@ -106,7 +107,6 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps> const uploads = await BatchedArray.from(validated, { batchSize: 15 }).batchedMapAsync(async batch => { const formData = new FormData(); - const parameters = { method: 'POST', body: formData }; batch.forEach(file => { sizes.push(file.size); @@ -114,7 +114,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps> formData.append(Utils.GenerateGuid(), file); }); - const responses = await (await fetch(RouteStore.upload, parameters)).json(); + const responses = await PostFormDataToServer(RouteStore.upload, formData); runInAction(() => this.completed += batch.length); return responses as FileResponse[]; }); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b1f753635..296574a04 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -15,7 +15,7 @@ import { listSpec } from '../../new_fields/Schema'; import { BoolCast, Cast, FieldValue, StrCast, NumCast } from '../../new_fields/Types'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { RouteStore } from '../../server/RouteStore'; -import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString, PostToServer } from '../../Utils'; +import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils'; import { DocServer } from '../DocServer'; import { ClientUtils } from '../util/ClientUtils'; import { DictationManager } from '../util/DictationManager'; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 02079e92c..f3208ce41 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -7,17 +7,17 @@ import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox"; import { Opt, Doc } from "./Doc"; import Color = require('color'); import { sinkListItem } from "prosemirror-schema-list"; -import { Utils, PostToServer } from "../Utils"; +import { Utils } from "../Utils"; import { RouteStore } from "../server/RouteStore"; import { Docs } from "../client/documents/Documents"; import { schema } from "../client/util/RichTextSchema"; import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; -import { SchemaHeaderField } from "./SchemaHeaderField"; import { DocServer } from "../client/DocServer"; import { Cast, StrCast } from "./Types"; import { Id } from "./FieldSymbols"; import { DocumentView } from "../client/views/nodes/DocumentView"; import { AssertionError } from "assert"; +import { PostToServer } from "../client/Network"; export namespace RichTextUtils { diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 684a8081b..665dcf862 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -66,6 +66,15 @@ export namespace GoogleApiServerUtils { }); }; + export const RetrieveAccessToken = (information: CredentialInformation) => { + return new Promise<string>((resolve, reject) => { + RetrieveCredentials(information).then( + credentials => resolve(credentials.token.access_token!), + error => reject(`Error: unable to authenticate Google Photos API request.\n${error}`) + ); + }); + }; + export const RetrieveCredentials = (information: CredentialInformation) => { return new Promise<TokenResult>((resolve, reject) => { readFile(information.credentialsPath, async (err, credentials) => { @@ -78,15 +87,6 @@ export namespace GoogleApiServerUtils { }); }; - export const RetrieveAccessToken = (information: CredentialInformation) => { - return new Promise<string>((resolve, reject) => { - RetrieveCredentials(information).then( - credentials => resolve(credentials.token.access_token!), - error => reject(`Error: unable to authenticate Google Photos API request.\n${error}`) - ); - }); - }; - export const RetrievePhotosEndpoint = (paths: CredentialInformation) => { return new Promise<any>((resolve, reject) => { RetrieveAccessToken(paths).then( @@ -107,7 +107,7 @@ export namespace GoogleApiServerUtils { client_id, client_secret, redirect_uris[0]); return new Promise<TokenResult>((resolve, reject) => { - Database.Auxiliary.FetchGoogleAuthenticationToken(userId).then(token => { + Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId).then(token => { // Check if we have previously stored a token for this userId. if (!token) { return getNewToken(oAuth2Client, userId).then(resolve, reject); @@ -123,8 +123,8 @@ export namespace GoogleApiServerUtils { } const refreshEndpoint = "https://oauth2.googleapis.com/token"; - const refreshToken = (credentials: Credentials, client_id: string, client_secret: string, oAuth2Client: OAuth2Client, token_path: string) => { - return new Promise<TokenResult>((resolve, reject) => { + const refreshToken = (credentials: Credentials, client_id: string, client_secret: string, oAuth2Client: OAuth2Client, userId: string) => { + return new Promise<TokenResult>(resolve => { let headerParameters = { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }; let queryParameters = { refreshToken: credentials.refresh_token, @@ -133,19 +133,13 @@ export namespace GoogleApiServerUtils { grant_type: "refresh_token" }; let url = `${refreshEndpoint}?${qs.stringify(queryParameters)}`; - request.post(url, headerParameters).then(response => { + request.post(url, headerParameters).then(async response => { let parsed = JSON.parse(response); credentials.access_token = parsed.access_token; credentials.expiry_date = new Date().getTime() + (parsed.expires_in * 1000); - writeFile(token_path, JSON.stringify(credentials), (err) => { - if (err) { - console.error(err); - reject(err); - } - console.log('Refreshed token stored to', token_path); - oAuth2Client.setCredentials(credentials); - resolve({ token: credentials, client: oAuth2Client }); - }); + oAuth2Client.setCredentials(credentials); + await Database.Auxiliary.GoogleAuthenticationToken.Write(userId, credentials); + resolve({ token: credentials, client: oAuth2Client }); }); }); }; @@ -156,7 +150,7 @@ export namespace GoogleApiServerUtils { * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for. * @param {getEventsCallback} callback The callback for the authorized client. */ - function getNewToken(oAuth2Client: OAuth2Client, token_path: string) { + function getNewToken(oAuth2Client: OAuth2Client, userId: string) { return new Promise<TokenResult>((resolve, reject) => { const authUrl = oAuth2Client.generateAuthUrl({ access_type: 'offline', @@ -169,20 +163,13 @@ export namespace GoogleApiServerUtils { }); rl.question('Enter the code from that page here: ', (code) => { rl.close(); - oAuth2Client.getToken(code, (err, token) => { + oAuth2Client.getToken(code, async (err, token) => { if (err || !token) { reject(err); return console.error('Error retrieving access token', err); } oAuth2Client.setCredentials(token); - // Store the token to disk for later program executions - writeFile(token_path, JSON.stringify(token), (err) => { - if (err) { - console.error(err); - reject(err); - } - console.log('Token stored to', token_path); - }); + await Database.Auxiliary.GoogleAuthenticationToken.Write(userId, token); resolve({ token, client: oAuth2Client }); }); }); diff --git a/src/server/database.ts b/src/server/database.ts index ce29478ad..890ac6b32 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -231,19 +231,37 @@ export namespace Database { const GoogleAuthentication = "GoogleAuthentication"; - const SanitizedSingletonQuery = async (query: { [key: string]: any }, collection: string) => { + const SanitizedCappedQuery = async (query: { [key: string]: any }, collection: string, cap: number) => { const cursor = await Instance.query(query, undefined, collection); - const existing = (await cursor.toArray())[0]; - if (existing) { - delete existing._id; - } - return existing; + const results = await cursor.toArray(); + const slice = results.slice(0, Math.min(cap, results.length)); + return slice.map(result => { + delete result._id; + return result; + }); + }; + + const SanitizedSingletonQuery = async (query: { [key: string]: any }, collection: string) => { + const results = await SanitizedCappedQuery(query, collection, 1); + return results.length ? results[0] : undefined; }; export const QueryUploadHistory = async (contentSize: number): Promise<Opt<DashUploadUtils.UploadInformation>> => { return SanitizedSingletonQuery({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory); }; + export namespace GoogleAuthenticationToken { + + export const Fetch = async (userId: string) => { + return SanitizedSingletonQuery({ userId }, GoogleAuthentication); + }; + + export const Write = async (userId: string, token: any) => { + return Instance.insert({ userId, ...token }, GoogleAuthentication); + }; + + } + export const LogUpload = async (information: DashUploadUtils.UploadInformation) => { const bundle = { _id: Utils.GenerateDeterministicGuid(String(information.contentSize!)), @@ -258,10 +276,6 @@ export namespace Database { return Promise.all(pendingDeletions); }; - export const FetchGoogleAuthenticationToken = async (userId: string) => { - return SanitizedSingletonQuery({ userId }, GoogleAuthentication); - }; - } }
\ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 386ecce4d..2ff63ab78 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -810,7 +810,7 @@ const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerU app.post(RouteStore.googleDocs + "/:sector/:action", (req, res) => { let sector: GoogleApiServerUtils.Service = req.params.sector as GoogleApiServerUtils.Service; let action: GoogleApiServerUtils.Action = req.params.action as GoogleApiServerUtils.Action; - GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentialsPath, userId: req.body.userId }).then(endpoint => { + GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentialsPath, userId: req.headers.userId as string }).then(endpoint => { let handler = EndpointHandlerMap.get(action); if (endpoint && handler) { let execute = handler(endpoint, req.body).then( @@ -824,10 +824,11 @@ app.post(RouteStore.googleDocs + "/:sector/:action", (req, res) => { }); }); -app.get(RouteStore.googlePhotosAccessToken, (req, res) => GoogleApiServerUtils.RetrieveAccessToken({ credentialsPath, userId: req.body.userId }).then(token => res.send(token))); +app.get(RouteStore.googlePhotosAccessToken, (req, res) => GoogleApiServerUtils.RetrieveAccessToken({ credentialsPath, userId: req.headers.userId as string }).then(token => res.send(token))); const tokenError = "Unable to successfully upload bytes for all images!"; const mediaError = "Unable to convert all uploaded bytes to media items!"; +const userIdError = "Unable to parse the identification of the user!"; export interface NewMediaItem { description: string; @@ -837,7 +838,12 @@ export interface NewMediaItem { } app.post(RouteStore.googlePhotosMediaUpload, async (req, res) => { - const { userId, media } = req.body; + const { media } = req.body; + const { userId } = req.headers; + + if (!userId || Array.isArray(userId)) { + return _error(res, userIdError); + } await GooglePhotosUploadUtils.initialize({ credentialsPath, userId }); |