diff options
Diffstat (limited to 'src/server')
29 files changed, 413 insertions, 927 deletions
diff --git a/src/server/ApiManagers/ApiManager.ts b/src/server/ApiManagers/ApiManager.ts index e2b01d585..27e9de065 100644 --- a/src/server/ApiManagers/ApiManager.ts +++ b/src/server/ApiManagers/ApiManager.ts @@ -1,4 +1,4 @@ -import RouteManager, { RouteInitializer } from "../RouteManager"; +import { RouteInitializer } from "../RouteManager"; export type Registration = (initializer: RouteInitializer) => void; diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts index bd80d6500..46c0d8a8a 100644 --- a/src/server/ApiManagers/DeleteManager.ts +++ b/src/server/ApiManagers/DeleteManager.ts @@ -1,6 +1,6 @@ import ApiManager, { Registration } from "./ApiManager"; import { Method, _permission_denied } from "../RouteManager"; -import { WebSocket } from "../Websocket/Websocket"; +import { WebSocket } from "../websocket"; import { Database } from "../database"; import rimraf = require("rimraf"); import { filesDirectory } from ".."; @@ -14,24 +14,20 @@ export default class DeleteManager extends ApiManager { register({ method: Method.GET, + requireAdminInRelease: true, subscription: new RouteSubscriber("delete").add("target?"), - secureHandler: async ({ req, res, isRelease }) => { - if (isRelease) { - return _permission_denied(res, "Cannot perform a delete operation outside of the development environment!"); - } - + secureHandler: async ({ req, res }) => { const { target } = req.params; - const { doDelete } = WebSocket; if (!target) { - await doDelete(); + await WebSocket.doDelete(); } else { let all = false; switch (target) { case "all": all = true; case "database": - await doDelete(false); + await WebSocket.doDelete(false); if (!all) break; case "files": rimraf.sync(filesDirectory); diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts index 01d2dfcad..c5f3ca717 100644 --- a/src/server/ApiManagers/DownloadManager.ts +++ b/src/server/ApiManagers/DownloadManager.ts @@ -246,7 +246,7 @@ async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hiera if (typeof result === "string") { let path: string; let matches: RegExpExecArray | null; - if ((matches = /\:1050\/files\/images\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) { + if ((matches = /\:\d+\/files\/images\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) { // image already exists on our server path = serverPathToFile(Directory.images, matches[1]); } else { diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts index 17968cc7d..f94b77cac 100644 --- a/src/server/ApiManagers/GeneralGoogleManager.ts +++ b/src/server/ApiManagers/GeneralGoogleManager.ts @@ -38,7 +38,7 @@ export default class GeneralGoogleManager extends ApiManager { method: Method.GET, subscription: "/revokeGoogleAccessToken", secureHandler: async ({ user, res }) => { - await Database.Auxiliary.GoogleAuthenticationToken.Revoke(user.id); + await Database.Auxiliary.GoogleAccessToken.Revoke(user.id); res.send(); } }); diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts index 11841a603..be17b698e 100644 --- a/src/server/ApiManagers/GooglePhotosManager.ts +++ b/src/server/ApiManagers/GooglePhotosManager.ts @@ -3,7 +3,7 @@ import { Method, _error, _success, _invalid } from "../RouteManager"; import * as path from "path"; import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils"; import { BatchedArray, TimeUnit } from "array-batcher"; -import { Opt } from "../../new_fields/Doc"; +import { Opt } from "../../fields/Doc"; import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils"; import { Database } from "../database"; import { red } from "colors"; diff --git a/src/server/ApiManagers/PDFManager.ts b/src/server/ApiManagers/PDFManager.ts index 0136b758e..d2a9e9cce 100644 --- a/src/server/ApiManagers/PDFManager.ts +++ b/src/server/ApiManagers/PDFManager.ts @@ -7,54 +7,54 @@ import { createCanvas } from "canvas"; const imageSize = require("probe-image-size"); import * as express from "express"; import * as path from "path"; -import { Directory, serverPathToFile, clientPathToFile } from "./UploadManager"; +import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from "./UploadManager"; import { red } from "colors"; +import { resolve } from "path"; export default class PDFManager extends ApiManager { protected initialize(register: Registration): void { register({ - method: Method.GET, - subscription: new RouteSubscriber("thumbnail").add("filename"), - secureHandler: ({ req, res }) => getOrCreateThumbnail(req.params.filename, res) + method: Method.POST, + subscription: new RouteSubscriber("thumbnail"), + secureHandler: async ({ req, res }) => { + const { coreFilename, pageNum, subtree } = req.body; + return getOrCreateThumbnail(coreFilename, pageNum, res, subtree); + } }); } } -async function getOrCreateThumbnail(thumbnailName: string, res: express.Response): Promise<void> { - const noExtension = thumbnailName.substring(0, thumbnailName.length - ".png".length); - const pageString = noExtension.split('-')[1]; - const pageNumber = parseInt(pageString); +async function getOrCreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string): Promise<void> { + const resolved = `${coreFilename}-${pageNum}.png`; return new Promise<void>(async resolve => { - const path = serverPathToFile(Directory.pdf_thumbnails, thumbnailName); + const path = serverPathToFile(Directory.pdf_thumbnails, resolved); if (existsSync(path)) { const existingThumbnail = createReadStream(path); const { err, viewport } = await new Promise<any>(resolve => { imageSize(existingThumbnail, (err: any, viewport: any) => resolve({ err, viewport })); }); if (err) { - console.log(red(`In PDF thumbnail response, unable to determine dimensions of ${thumbnailName}:`)); + console.log(red(`In PDF thumbnail response, unable to determine dimensions of ${resolved}:`)); console.log(err); return; } - dispatchThumbnail(res, viewport, thumbnailName); + dispatchThumbnail(res, viewport, resolved); } else { - const offset = thumbnailName.length - pageString.length - 5; - const name = thumbnailName.substring(0, offset) + ".pdf"; - const path = serverPathToFile(Directory.pdfs, name); - await CreateThumbnail(path, pageNumber, res); + await CreateThumbnail(coreFilename, pageNum, res, subtree); } resolve(); }); } -async function CreateThumbnail(file: string, pageNumber: number, res: express.Response) { - const documentProxy = await Pdfjs.getDocument(file).promise; +async function CreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string) { + const sourcePath = resolve(pathToDirectory(Directory.pdfs), `${subtree ?? ""}${coreFilename}.pdf`); + const documentProxy = await Pdfjs.getDocument(sourcePath).promise; const factory = new NodeCanvasFactory(); - const page = await documentProxy.getPage(pageNumber); + const page = await documentProxy.getPage(pageNum); const viewport = page.getViewport(1 as any); const { canvas, context } = factory.create(viewport.width, viewport.height); const renderContext = { @@ -64,14 +64,13 @@ async function CreateThumbnail(file: string, pageNumber: number, res: express.Re }; await page.render(renderContext).promise; const pngStream = canvas.createPNGStream(); - const filenames = path.basename(file).split("."); - const thumbnailName = `${filenames[0]}-${pageNumber}.png`; - const pngFile = serverPathToFile(Directory.pdf_thumbnails, thumbnailName); + const resolved = `${coreFilename}-${pageNum}.png`; + const pngFile = serverPathToFile(Directory.pdf_thumbnails, resolved); const out = createWriteStream(pngFile); pngStream.pipe(out); return new Promise<void>((resolve, reject) => { out.on("finish", () => { - dispatchThumbnail(res, viewport, thumbnailName); + dispatchThumbnail(res, viewport, resolved); resolve(); }); out.on("error", error => { diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts index 6638c50e4..7251e07a1 100644 --- a/src/server/ApiManagers/SearchManager.ts +++ b/src/server/ApiManagers/SearchManager.ts @@ -193,11 +193,8 @@ export namespace SolrManager { if (val === null || val === undefined) { return; } - console.log(val); const type = val.__type || typeof val; - console.log(type); let suffix = suffixMap[type]; - console.log(suffix); if (!suffix) { return; } diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index b185d3b55..3dae963be 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -4,14 +4,17 @@ import * as formidable from 'formidable'; import v4 = require('uuid/v4'); const AdmZip = require('adm-zip'); import { extname, basename, dirname } from 'path'; -import { createReadStream, createWriteStream, unlink } from "fs"; +import { createReadStream, createWriteStream, unlink, writeFile } from "fs"; import { publicDirectory, filesDirectory } from ".."; import { Database } from "../database"; import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils"; import * as sharp from 'sharp'; import { AcceptibleMedia, Upload } from "../SharedMediaTypes"; import { normalize } from "path"; +import RouteSubscriber from "../RouteSubscriber"; const imageDataUri = require('image-data-uri'); +import { isWebUri } from "valid-url"; +import { Opt } from "../../fields/Doc"; export enum Directory { parsed_files = "parsed_files", @@ -61,10 +64,38 @@ export default class UploadManager extends ApiManager { }); register({ - method: Method.GET, - subscription: "/hello", - secureHandler: ({ req, res }) => { - res.send("<h1>world!</h1>"); + method: Method.POST, + subscription: new RouteSubscriber("youtubeScreenshot"), + secureHandler: async ({ req, res }) => { + const { id, timecode } = req.body; + const convert = (raw: string) => { + const number = Math.floor(Number(raw)); + const seconds = number % 60; + const minutes = (number - seconds) / 60; + return `${minutes}m${seconds}s`; + }; + const suffix = timecode ? `&t=${convert(timecode)}` : ``; + const targetUrl = `https://www.youtube.com/watch?v=${id}${suffix}`; + const buffer = await captureYoutubeScreenshot(targetUrl); + if (!buffer) { + return res.send(); + } + const resolvedName = `youtube_capture_${id}_${suffix}.png`; + const resolvedPath = serverPathToFile(Directory.images, resolvedName); + return new Promise<void>(resolve => { + writeFile(resolvedPath, buffer, async error => { + if (error) { + return res.send(); + } + await DashUploadUtils.outputResizedImages(() => createReadStream(resolvedPath), resolvedName, pathToDirectory(Directory.images)); + res.send({ + accessPaths: { + agnostic: DashUploadUtils.getAccessPaths(Directory.images, resolvedName) + } + } as Upload.FileInformation); + resolve(); + }); + }); } }); @@ -244,4 +275,37 @@ export default class UploadManager extends ApiManager { } +} +function delay(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} +/** + * On success, returns a buffer containing the bytes of a screenshot + * of the video (optionally, at a timecode) specified by @param targetUrl. + * + * On failure, returns undefined. + */ +async function captureYoutubeScreenshot(targetUrl: string){ + // const browser = await launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }); + // const page = await browser.newPage(); + // await page.setViewport({ width: 1920, height: 1080 }); + + // await page.goto(targetUrl, { waitUntil: 'domcontentloaded' as any }); + + // const videoPlayer = await page.$('.html5-video-player'); + // videoPlayer && await page.focus("video"); + // await delay(7000); + // const ad = await page.$('.ytp-ad-skip-button-text'); + // await ad?.click(); + // await videoPlayer?.click(); + // await delay(1000); + // // hide youtube player controls. + // await page.evaluate(() => + // (document.querySelector('.ytp-chrome-bottom') as any).style.display = 'none'); + + // const buffer = await videoPlayer?.screenshot({ encoding: "binary" }); + // await browser.close(); + + // return buffer; + return null; }
\ No newline at end of file diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts index 68b3107ae..0d1d8f218 100644 --- a/src/server/ApiManagers/UserManager.ts +++ b/src/server/ApiManagers/UserManager.ts @@ -3,7 +3,7 @@ import { Method } from "../RouteManager"; import { Database } from "../database"; import { msToTime } from "../ActionUtilities"; import * as bcrypt from "bcrypt-nodejs"; -import { Opt } from "../../new_fields/Doc"; +import { Opt } from "../../fields/Doc"; export const timeMap: { [id: string]: number } = {}; interface ActivityUnit { diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts index aec523cd0..e2cd88726 100644 --- a/src/server/ApiManagers/UtilManager.ts +++ b/src/server/ApiManagers/UtilManager.ts @@ -1,8 +1,6 @@ import ApiManager, { Registration } from "./ApiManager"; import { Method } from "../RouteManager"; import { exec } from 'child_process'; -import RouteSubscriber from "../RouteSubscriber"; -import { red } from "colors"; // import { IBM_Recommender } from "../../client/apis/IBM_Recommender"; // import { Recommender } from "../Recommender"; @@ -34,7 +32,6 @@ export default class UtilManager extends ApiManager { // } // }); - register({ method: Method.GET, subscription: "/pull", diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts index ef9b88541..ab3dfffcc 100644 --- a/src/server/DashSession/DashSessionAgent.ts +++ b/src/server/DashSession/DashSessionAgent.ts @@ -2,7 +2,7 @@ import { Email, pathFromRoot } from "../ActionUtilities"; import { red, yellow, green, cyan } from "colors"; import { get } from "request-promise"; import { Utils } from "../../Utils"; -import { WebSocket } from "../Websocket/Websocket"; +import { WebSocket } from "../websocket"; import { MessageStore } from "../Message"; import { launchServer, onWindows } from ".."; import { readdirSync, statSync, createWriteStream, readFileSync, unlinkSync } from "fs"; diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 8567631cd..2bf4c1956 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import * as sharp from 'sharp'; import request = require('request-promise'); import { ExifImage } from 'exif'; -import { Opt } from '../new_fields/Doc'; +import { Opt } from '../fields/Doc'; import { AcceptibleMedia, Upload } from './SharedMediaTypes'; import { filesDirectory, publicDirectory } from '.'; import { File } from 'formidable'; @@ -15,7 +15,9 @@ const parse = require('pdf-parse'); import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from './ApiManagers/UploadManager'; import { red } from 'colors'; import { Stream } from 'stream'; +import { resolvedPorts } from './server_Initialization'; const requestImageSize = require("../client/util/request-image-size"); +import { resolvedServerUrl } from "./server_Initialization"; export enum SizeSuffix { Small = "_s", @@ -184,7 +186,7 @@ export namespace DashUploadUtils { if (error !== null) { return error; } - source = `http://localhost:1050${clientPathToFile(Directory.images, resolved)}`; + source = `${resolvedServerUrl}${clientPathToFile(Directory.images, resolved)}`; } let resolvedUrl: string; /** @@ -194,14 +196,14 @@ export namespace DashUploadUtils { * basename subtree (i.e. /images/<some_guid>.<ext>) and put it on the end of the server's url. * * This can always be localhost, regardless of whether this is on the server or not, since we (the server, not the client) - * will be the ones making the request, and from the perspective of dash-release or dash-web, localhost:1050 refers to the same thing - * as the full dash-release.eastus.cloudapp.azure.com:1050. + * will be the ones making the request, and from the perspective of dash-release or dash-web, localhost:<port> refers to the same thing + * as the full dash-release.eastus.cloudapp.azure.com:<port>. */ const matches = isLocal().exec(source); if (matches === null) { resolvedUrl = source; } else { - resolvedUrl = `http://localhost:1050/${matches[1].split("\\").join("/")}`; + resolvedUrl = `${resolvedServerUrl}/${matches[1].split("\\").join("/")}`; } // See header comments: not all image files have exif data (I believe only JPG is the only format that can have it) const exifData = await parseExifData(resolvedUrl); @@ -257,7 +259,7 @@ export namespace DashUploadUtils { }); } - function getAccessPaths(directory: Directory, fileName: string) { + export function getAccessPaths(directory: Directory, fileName: string) { return { client: clientPathToFile(directory, fileName), server: serverPathToFile(directory, fileName) diff --git a/src/server/Message.ts b/src/server/Message.ts index 01aae5de7..80f372733 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -1,6 +1,6 @@ import { Utils } from "../Utils"; import { Point } from "../pen-gestures/ndollar"; -import { Doc } from "../new_fields/Doc"; +import { Doc } from "../fields/Doc"; import { Image } from "canvas"; import { AnalysisResult, ImportResults } from "../scraping/buxton/final/BuxtonImporter"; diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts index aacdb4053..423ce9b46 100644 --- a/src/server/Recommender.ts +++ b/src/server/Recommender.ts @@ -1,6 +1,6 @@ -// //import { Doc } from "../new_fields/Doc"; -// //import { StrCast } from "../new_fields/Types"; -// //import { List } from "../new_fields/List"; +// //import { Doc } from "../fields/Doc"; +// //import { StrCast } from "../fields/Types"; +// //import { List } from "../fields/List"; // //import { CognitiveServices } from "../client/cognitive_services/CognitiveServices"; // // var w2v = require('word2vec'); diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts index 80e4a6741..1a2340afc 100644 --- a/src/server/RouteManager.ts +++ b/src/server/RouteManager.ts @@ -1,7 +1,8 @@ import RouteSubscriber from "./RouteSubscriber"; -import { DashUserModel } from "./authentication/models/user_model"; +import { DashUserModel } from "./authentication/DashUserModel"; import { Request, Response, Express } from 'express'; import { cyan, red, green } from 'colors'; +import { AdminPriviliges } from "."; export enum Method { GET, @@ -25,6 +26,7 @@ export interface RouteInitializer { secureHandler: SecureHandler; publicHandler?: PublicHandler; errorHandler?: ErrorHandler; + requireAdminInRelease?: true; } const registered = new Map<string, Set<Method>>(); @@ -84,7 +86,7 @@ export default class RouteManager { * @param initializer */ addSupervisedRoute = (initializer: RouteInitializer): void => { - const { method, subscription, secureHandler, publicHandler, errorHandler } = initializer; + const { method, subscription, secureHandler, publicHandler, errorHandler, requireAdminInRelease: requireAdmin } = initializer; typeof (initializer.subscription) === "string" && RouteManager.routes.push(initializer.subscription); initializer.subscription instanceof RouteSubscriber && RouteManager.routes.push(initializer.subscription.root); @@ -94,7 +96,7 @@ export default class RouteManager { }); const isRelease = this._isRelease; const supervised = async (req: Request, res: Response) => { - let { user } = req; + let user = req.user as Partial<DashUserModel> | undefined; const { originalUrl: target } = req; if (process.env.DB === "MEM" && !user) { user = { id: "guest", email: "", userDocumentId: "guestDocId" }; @@ -113,6 +115,13 @@ export default class RouteManager { } }; if (user) { + if (requireAdmin && isRelease && process.env.PASSWORD) { + if (AdminPriviliges.get(user.id)) { + AdminPriviliges.delete(user.id); + } else { + return res.redirect(`/admin/${req.originalUrl.substring(1).replace("/", ":")}`); + } + } await tryExecute(secureHandler, { ...core, user }); } else { req.session!.target = target; @@ -205,5 +214,5 @@ export function _permission_denied(res: Response, message?: string) { if (message) { res.statusMessage = message; } - res.status(STATUS.PERMISSION_DENIED).send("Permission Denied!"); + res.status(STATUS.PERMISSION_DENIED).send(`Permission Denied! ${message}`); } diff --git a/src/server/apis/google/CredentialsLoader.ts b/src/server/apis/google/CredentialsLoader.ts new file mode 100644 index 000000000..ef1f9a91e --- /dev/null +++ b/src/server/apis/google/CredentialsLoader.ts @@ -0,0 +1,67 @@ +import { readFile, readFileSync } from "fs"; +import { pathFromRoot } from "../../ActionUtilities"; +import { SecureContextOptions } from "tls"; +import { blue, red } from "colors"; + +export namespace GoogleCredentialsLoader { + + export interface InstalledCredentials { + client_id: string; + project_id: string; + auth_uri: string; + token_uri: string; + auth_provider_x509_cert_url: string; + client_secret: string; + redirect_uris: string[]; + } + + export let ProjectCredentials: InstalledCredentials; + + export async function loadCredentials() { + ProjectCredentials = await new Promise<InstalledCredentials>(resolve => { + readFile(__dirname + '/google_project_credentials.json', function processClientSecrets(err, content) { + if (err) { + console.log('Error loading client secret file: ' + err); + return; + } + resolve(JSON.parse(content.toString()).installed); + }); + }); + } + +} + +export namespace SSL { + + export let Credentials: SecureContextOptions = {}; + export let Loaded = false; + + const suffixes = { + privateKey: ".key", + certificate: ".crt", + caBundle: "-ca.crt" + }; + + export async function loadCredentials() { + const { serverName } = process.env; + const cert = (suffix: string) => readFileSync(pathFromRoot(`./${serverName}${suffix}`)).toString(); + try { + Credentials.key = cert(suffixes.privateKey); + Credentials.cert = cert(suffixes.certificate); + Credentials.ca = cert(suffixes.caBundle); + Loaded = true; + } catch (e) { + Credentials = {}; + Loaded = false; + } + } + + export function exit() { + console.log(red("Running this server in release mode requires the following SSL credentials in the project root:")); + const serverName = process.env.serverName ? process.env.serverName : "{process.env.serverName}"; + Object.values(suffixes).forEach(suffix => console.log(blue(`${serverName}${suffix}`))); + console.log(red("Please ensure these files exist and restart, or run this in development mode.")); + process.exit(0); + } + +} diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 48a8da89f..20f96f432 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -1,11 +1,11 @@ import { google } from "googleapis"; import { OAuth2Client, Credentials, OAuth2ClientOptions } from "google-auth-library"; -import { Opt } from "../../../new_fields/Doc"; +import { Opt } from "../../../fields/Doc"; import { GaxiosResponse } from "gaxios"; import request = require('request-promise'); -import * as qs from 'query-string'; +import * as qs from "query-string"; import { Database } from "../../database"; -import { GoogleCredentialsLoader } from "../../credentials/CredentialsLoader"; +import { GoogleCredentialsLoader } from "./CredentialsLoader"; /** * Scopes give Google users fine granularity of control @@ -224,7 +224,7 @@ export namespace GoogleApiServerUtils { }); }); const enriched = injectUserInfo(credentials); - await Database.Auxiliary.GoogleAuthenticationToken.Write(userId, enriched); + await Database.Auxiliary.GoogleAccessToken.Write(userId, enriched); return enriched; } @@ -280,7 +280,7 @@ export namespace GoogleApiServerUtils { * and a flag indicating whether or not they were refreshed during retrieval */ export async function retrieveCredentials(userId: string): Promise<{ credentials: Opt<EnrichedCredentials>, refreshed: boolean }> { - let credentials = await Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId); + let credentials = await Database.Auxiliary.GoogleAccessToken.Fetch(userId); let refreshed = false; if (!credentials) { return { credentials: undefined, refreshed }; @@ -318,7 +318,7 @@ export namespace GoogleApiServerUtils { }); // expires_in is in seconds, but we're building the new expiry date in milliseconds const expiry_date = new Date().getTime() + (expires_in * 1000); - await Database.Auxiliary.GoogleAuthenticationToken.Update(userId, access_token, expiry_date); + await Database.Auxiliary.GoogleAccessToken.Update(userId, access_token, expiry_date); // update the relevant properties credentials.access_token = access_token; credentials.expiry_date = expiry_date; diff --git a/src/server/credentials/google_project_credentials.json b/src/server/apis/google/google_project_credentials.json index 955c5a3c1..955c5a3c1 100644 --- a/src/server/credentials/google_project_credentials.json +++ b/src/server/apis/google/google_project_credentials.json diff --git a/src/server/apis/youtube/youtubeApiSample.js b/src/server/apis/youtube/youtubeApiSample.js index 50b3c7b38..d535bd9ff 100644 --- a/src/server/apis/youtube/youtubeApiSample.js +++ b/src/server/apis/youtube/youtubeApiSample.js @@ -1,6 +1,8 @@ const fs = require('fs'); const readline = require('readline'); -const { google } = require('googleapis'); +const { + google +} = require('googleapis'); const OAuth2 = google.auth.OAuth2; @@ -19,21 +21,27 @@ module.exports.readApiKey = (callback) => { } callback(content); }); -} +}; module.exports.authorizedGetChannel = (apiKey) => { //this didnt get called // Authorize a client with the loaded credentials, then call the YouTube API. authorize(JSON.parse(apiKey), getChannel); -} +}; module.exports.authorizedGetVideos = (apiKey, userInput, callBack) => { - authorize(JSON.parse(apiKey), getVideos, { userInput: userInput, callBack: callBack }); -} + authorize(JSON.parse(apiKey), getVideos, { + userInput: userInput, + callBack: callBack + }); +}; module.exports.authorizedGetVideoDetails = (apiKey, videoIds, callBack) => { - authorize(JSON.parse(apiKey), getVideoDetails, { videoIds: videoIds, callBack: callBack }); -} + authorize(JSON.parse(apiKey), getVideoDetails, { + videoIds: videoIds, + callBack: callBack + }); +}; /** diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/AuthenticationManager.ts index f0086d4ea..00f1fe44e 100644 --- a/src/server/authentication/controllers/user_controller.ts +++ b/src/server/authentication/AuthenticationManager.ts @@ -1,13 +1,13 @@ -import { default as User, DashUserModel, AuthToken } from "../models/user_model"; +import { default as User, DashUserModel } from "./DashUserModel"; import { Request, Response, NextFunction } from "express"; import * as passport from "passport"; import { IVerifyOptions } from "passport-local"; -import "../config/passport"; +import "./Passport"; import flash = require("express-flash"); import * as async from 'async'; import * as nodemailer from 'nodemailer'; import c = require("crypto"); -import { Utils } from "../../../Utils"; +import { Utils } from "../../Utils"; import { MailOptions } from "nodemailer/lib/stream-transport"; /** @@ -111,7 +111,7 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => { return res.redirect("/signup"); } - passport.authenticate("local", (err: Error, user: DashUserModel, info: IVerifyOptions) => { + passport.authenticate("local", (err: Error, user: DashUserModel, _info: IVerifyOptions) => { if (err) { next(err); return; } if (!user) { return res.redirect("/signup"); diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/DashUserModel.ts index a0b688328..51d920a8f 100644 --- a/src/server/authentication/models/user_model.ts +++ b/src/server/authentication/DashUserModel.ts @@ -58,11 +58,11 @@ userSchema.pre("save", function save(next) { if (!user.isModified("password")) { return next(); } - bcrypt.genSalt(10, (err, salt) => { + bcrypt.genSalt(10, (err: any, salt: string) => { if (err) { return next(err); } - bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash) => { + bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash: string) => { if (err) { return next(err); } diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/Passport.ts index 286209b20..9b0069414 100644 --- a/src/server/authentication/config/passport.ts +++ b/src/server/authentication/Passport.ts @@ -1,6 +1,6 @@ import * as passport from 'passport'; import * as passportLocal from 'passport-local'; -import { default as User } from '../models/user_model'; +import { default as User } from './DashUserModel'; const LocalStrategy = passportLocal.Strategy; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts deleted file mode 100644 index bc024356d..000000000 --- a/src/server/authentication/models/current_user_utils.ts +++ /dev/null @@ -1,744 +0,0 @@ -import { action, computed, observable, reaction } from "mobx"; -import * as rp from 'request-promise'; -import { Utils } from "../../../Utils"; -import { DocServer } from "../../../client/DocServer"; -import { Docs, DocumentOptions } from "../../../client/documents/Documents"; -import { UndoManager } from "../../../client/util/UndoManager"; -import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; -import { List } from "../../../new_fields/List"; -import { listSpec } from "../../../new_fields/Schema"; -import { ScriptField, ComputedField } from "../../../new_fields/ScriptField"; -import { Cast, PromiseValue, StrCast, NumCast } from "../../../new_fields/Types"; -import { nullAudio, ImageField } from "../../../new_fields/URLField"; -import { DragManager } from "../../../client/util/DragManager"; -import { InkingControl } from "../../../client/views/InkingControl"; -import { Scripting, CompileScript } from "../../../client/util/Scripting"; -import { CollectionViewType } from "../../../client/views/collections/CollectionView"; -import { makeTemplate } from "../../../client/util/DropConverter"; -import { RichTextField } from "../../../new_fields/RichTextField"; -import { PrefetchProxy } from "../../../new_fields/Proxy"; -import { FormattedTextBox } from "../../../client/views/nodes/formattedText/FormattedTextBox"; -import { MainView } from "../../../client/views/MainView"; -import { DocumentType } from "../../../client/documents/DocumentTypes"; -import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; -import { DimUnit } from "../../../client/views/collections/collectionMulticolumn/CollectionMulticolumnView"; - -export class CurrentUserUtils { - private static curr_id: string; - //TODO tfs: these should be temporary... - private static mainDocId: string | undefined; - - public static get id() { return this.curr_id; } - public static get MainDocId() { return this.mainDocId; } - public static set MainDocId(id: string | undefined) { this.mainDocId = id; } - @computed public static get UserDocument() { return Doc.UserDoc(); } - @computed public static get ActivePen() { return Doc.UserDoc().activePen instanceof Doc && (Doc.UserDoc().activePen as Doc).inkPen as Doc; } - - @observable public static GuestTarget: Doc | undefined; - @observable public static GuestWorkspace: Doc | undefined; - @observable public static GuestMobile: Doc | undefined; - - // sets up the default User Templates - slideView, queryView, descriptionView - static setupUserTemplateButtons(doc: Doc) { - if (doc["template-button-query"] === undefined) { - const queryTemplate = Docs.Create.MulticolumnDocument( - [ - Docs.Create.SearchDocument({ title: "query", _height: 200 }), - Docs.Create.FreeformDocument([], { title: "data", _height: 100, _LODdisable: true }) - ], - { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true } - ); - queryTemplate.isTemplateDoc = makeTemplate(queryTemplate); - doc["template-button-query"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), - dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, - removeDropProperties: new List<string>(["dropAction"]), title: "query view", icon: "question-circle" - }); - } - - if (doc["template-button-slides"] === undefined) { - const slideTemplate = Docs.Create.MultirowDocument( - [ - Docs.Create.MulticolumnDocument([], { title: "data", _height: 200 }), - Docs.Create.TextDocument("", { title: "text", _height: 100 }) - ], - { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true } - ); - slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); - doc["template-button-slides"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), - dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, - removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "address-card" - }); - } - - if (doc["template-button-description"] === undefined) { - const descriptionTemplate = Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header"); // text needs to be a space to allow templateText to be created - Doc.GetProto(descriptionTemplate).layout = - "<div><FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" + - "<FormattedTextBox {...props} height='calc(100% - {this._headerHeight||75}px)' fieldKey={'text'}/></div>"; - descriptionTemplate.isTemplateDoc = makeTemplate(descriptionTemplate, true, "descriptionView"); - - doc["template-button-description"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory)'), - dragFactory: new PrefetchProxy(descriptionTemplate) as any as Doc, - removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "window-maximize" - }); - } - - if (doc["template-button-switch"] === undefined) { - const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create; - - const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _LODdisable: true, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40 }); - const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1 }); - const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, _LODdisable: true }); - const labelTemplate = { - doc: { - type: "doc", content: [{ - type: "paragraph", - content: [{ type: "dashField", attrs: { fieldKey: "PARAMS", hideKey: true } }] - }] - }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - Doc.GetProto(name).text = new RichTextField(JSON.stringify(labelTemplate), "PARAMS"); - Doc.GetProto(yes).backgroundColor = ComputedField.MakeFunction("self[this.PARAMS] ? 'green':'red'"); - // Doc.GetProto(no).backgroundColor = ComputedField.MakeFunction("!self[this.PARAMS] ? 'red':'white'"); - // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = true"); - Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = !self[this.PARAMS]"); - // Doc.GetProto(no).onClick = ScriptField.MakeScript("self[this.PARAMS] = false"); - const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, }); - box.isTemplateDoc = makeTemplate(box, true, "switch"); - - doc["template-button-switch"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), - dragFactory: new PrefetchProxy(box) as any as Doc, - removeDropProperties: new List<string>(["dropAction"]), title: "data switch", icon: "toggle-on" - }); - } - - if (doc["template-button-detail"] === undefined) { - const { TextDocument, MasonryDocument, CarouselDocument } = Docs.Create; - - const openInTarget = ScriptField.MakeScript("openOnRight(self.doubleClickView)"); - const carousel = CarouselDocument([], { - title: "data", _height: 350, _itemIndex: 0, "_carousel-caption-xMargin": 10, "_carousel-caption-yMargin": 10, - onChildDoubleClick: openInTarget, backgroundColor: "#9b9b9b3F" - }); - - const details = TextDocument("", { title: "details", _height: 350, _autoHeight: true }); - const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 100, _autoHeight: true }); - const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 350, _autoHeight: true }); - - const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"]; - const detailedTemplate = { - doc: { - type: "doc", content: buxtonFieldKeys.map(fieldKey => ({ - type: "paragraph", - content: [{ type: "dashField", attrs: { fieldKey } }] - })) - }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" ")); - - const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 }; - const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: 12 }; - const descriptionWrapperOpts = { title: "descriptions", _height: 300, columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" }; - - const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); - descriptionWrapper.sectionHeaders = new List<SchemaHeaderField>([ - new SchemaHeaderField("[A Short Description]", "dimGray", undefined, undefined, undefined, false), - new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true), - new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true), - ]); - const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts }); - detailView.isTemplateDoc = makeTemplate(detailView); - - details.title = "Details"; - short.title = "A Short Description"; - long.title = "Long Description"; - - doc["template-button-detail"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), - dragFactory: new PrefetchProxy(detailView) as any as Doc, - removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "window-maximize" - }); - } - - if (doc["template-buttons"] === undefined) { - doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument([doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc, - doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc], { - title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", - _autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - })); - } else { - const curButnTypes = Cast(doc["template-buttons"], Doc, null); - const requiredTypes = [doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc, - doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc]; - DocListCastAsync(curButnTypes.data).then(async curBtns => { - await Promise.all(curBtns!); - requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype)); - }); - } - return doc["template-buttons"] as Doc; - } - - // setup the different note type skins - static setupNoteTemplates(doc: Doc) { - if (doc["template-note-Note"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", style: "Note", isTemplateDoc: true, backgroundColor: "yellow" }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Note"); - doc["template-note-Note"] = new PrefetchProxy(noteView); - } - if (doc["template-note-Idea"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", style: "Idea", backgroundColor: "pink" }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea"); - doc["template-note-Idea"] = new PrefetchProxy(noteView); - } - if (doc["template-note-Topic"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", style: "Topic", backgroundColor: "lightBlue" }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic"); - doc["template-note-Topic"] = new PrefetchProxy(noteView); - } - if (doc["template-note-Todo"] === undefined) { - const noteView = Docs.Create.TextDocument("", { - title: "text", style: "Todo", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption", - layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus") - }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo"); - doc["template-note-Todo"] = new PrefetchProxy(noteView); - } - const taskStatusValues = [ - { title: "todo", _backgroundColor: "blue", color: "white" }, - { title: "in progress", _backgroundColor: "yellow", color: "black" }, - { title: "completed", _backgroundColor: "green", color: "white" } - ]; - if (doc.fieldTypes === undefined) { - doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations" }); - Doc.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues); - } - - if (doc["template-notes"] === undefined) { - doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc, - doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc, doc["template-note-Todo"] as any as Doc], - { title: "Note Layouts", _height: 75 })); - } else { - const curNoteTypes = Cast(doc["template-notes"], Doc, null); - const requiredTypes = [doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, - doc["template-note-Topic"] as any as Doc, doc["template-note-Todo"] as any as Doc]; - DocListCastAsync(curNoteTypes.data).then(async curNotes => { - await Promise.all(curNotes!); - requiredTypes.map(ntype => Doc.AddDocToList(curNoteTypes, "data", ntype)); - }); - } - - return doc["template-notes"] as Doc; - } - - // creates Note templates, and initial "user" templates - static setupDocTemplates(doc: Doc) { - const noteTemplates = CurrentUserUtils.setupNoteTemplates(doc); - const userTemplateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); - const clickTemplates = CurrentUserUtils.setupClickEditorTemplates(doc); - if (doc.templateDocs === undefined) { - doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([noteTemplates, userTemplateBtns, clickTemplates], { - title: "template layouts", _xPadding: 0, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) - })); - } - } - - // setup templates for different document types when they are iconified from Document Decorations - static setupDefaultIconTemplates(doc: Doc) { - if (doc["template-icon-view"] === undefined) { - const iconView = Docs.Create.TextDocument("", { - title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") - }); - Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', ""); - iconView.isTemplateDoc = makeTemplate(iconView); - doc["template-icon-view"] = new PrefetchProxy(iconView); - } - if (doc["template-icon-view-rtf"] === undefined) { - const iconRtfView = Docs.Create.LabelDocument({ - title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") - }); - iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); - doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); - } - if (doc["template-icon-view-img"] === undefined) { - const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { - title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") - }); - iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG); - doc["template-icon-view-img"] = new PrefetchProxy(iconImageView); - } - if (doc["template-icon-view-col"] === undefined) { - const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") }); - iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); - doc["template-icon-view-col"] = new PrefetchProxy(iconColView); - } - if (doc["template-icons"] === undefined) { - doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, - doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc], { title: "icon templates", _height: 75 })); - } else { - const templateIconsDoc = Cast(doc["template-icons"], Doc, null); - const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, - doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc]; - DocListCastAsync(templateIconsDoc.data).then(async curIcons => { - await Promise.all(curIcons!); - requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype)); - }); - } - return doc["template-icons"] as Doc; - } - - static creatorBtnDescriptors(doc: Doc): { - title: string, label: string, icon: string, drag?: string, ignoreClick?: boolean, - click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc - }[] { - if (doc.emptyPresentation === undefined) { - doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(), - { title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" }); - } - if (doc.emptyCollection === undefined) { - doc.emptyCollection = Docs.Create.FreeformDocument([], - { _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" }); - } - if (doc.emptyDocHolder === undefined) { - doc.emptyDocHolder = Docs.Create.DocumentDocument( - ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]") as any, - { _width: 250, _height: 250, title: "container" }); - } - if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600, UseCors: true }); - } - return [ - { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc }, - { title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc }, - { title: "Drag a cat image", label: "Img", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' }, - { title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' }, - { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' }, - { title: "Drag a audio recorder", label: "Audio", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` }, - { title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' }, - { title: "Drag a presentation view", label: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc }, - { title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.SearchDocument({ _width: 200, title: "an image of a cat" })' }, - { title: "Drag a scripting box", label: "Script", icon: "terminal", ignoreClick: true, drag: 'Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250 title: "untitled script" })' }, - { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' }, - { title: "Drag a mobile view", label: "Phone", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' }, - { title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' }, - // { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - // { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc }, - // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc }, - { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, - { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, - { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' }, - { title: "query", icon: "bolt", label: "Col", ignoreClick: true, drag: 'Docs.Create.SearchDocument({ _width: 200, title: "an image of a cat" })' }, - ]; - - } - - // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools - static async setupCreatorButtons(doc: Doc) { - let alreadyCreatedButtons: string[] = []; - const dragCreatorSet = await Cast(doc.myItemCreators, Doc, null); - if (dragCreatorSet) { - const dragCreators = await Cast(dragCreatorSet.data, listSpec(Doc)); - if (dragCreators) { - const dragDocs = await Promise.all(dragCreators); - alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title)); - } - } - const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)); - const creatorBtns = buttons.map(({ title, label, icon, ignoreClick, drag, click, ischecked, activePen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({ - _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, - icon, - title, - label, - ignoreClick, - dropAction: "copy", - onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, - onClick: click ? ScriptField.MakeScript(click) : undefined, - ischecked: ischecked ? ComputedField.MakeFunction(ischecked) : undefined, - activePen, - backgroundColor, - removeDropProperties: new List<string>(["dropAction"]), - dragFactory, - })); - - if (dragCreatorSet === undefined) { - doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, { - title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, - _autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - })); - } else { - creatorBtns.forEach(nb => Doc.AddDocToList(doc.myItemCreators as Doc, "data", nb)); - } - return doc.myItemCreators as Doc; - } - - static setupMobileButtons(doc: Doc, buttons?: string[]) { - const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ - { title: "record", icon: "microphone", ignoreClick: true, click: "FILL" }, - { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc }, - { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc }, - // { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "red", activePen: doc }, - { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload, onSwitchMobileUpload);', backgroundColor: "orange" }, - // { title: "upload", icon: "upload", click: 'uploadImageMobile();', backgroundColor: "cyan" }, - ]; - return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({ - _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick, - onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, - ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, - backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory, - })); - } - - static setupThumbButtons(doc: Doc) { - const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, clipboard?: Doc, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ - { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300 }), backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - ]; - return docProtoData.map(data => Docs.Create.FontIconDocument({ - _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon, - dropAction: data.pointerDown ? "copy" : undefined, ignoreClick: data.ignoreClick, - onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, - clipboard: data.clipboard, - onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, - ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, pointerHack: true, - backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory, - })); - } - - static setupThumbDoc(userDoc: Doc) { - if (!userDoc.thumbDoc) { - const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { - _width: 100, _height: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", - _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white" - }); - thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { - _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", linearViewIsExpanded: true, flexDirection: "column" - }); - userDoc.thumbDoc = thumbDoc; - } - return Cast(userDoc.thumbDoc, Doc); - } - - static setupMobileDoc(userDoc: Doc) { - return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), { - columnWidth: 100, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5 - }); - } - - static setupMobileInkingDoc(userDoc: Doc) { - return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white" }); - } - - static setupMobileUploadDoc(userDoc: Doc) { - // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" }) - const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", { - title: "Upload Images From the Web", _chromeStatus: "enabled", lockedPosition: true - }); - const uploadDoc = Docs.Create.StackingDocument([], { - title: "Mobile Upload Collection", backgroundColor: "white", lockedPosition: true - }); - return Docs.Create.StackingDocument([webDoc, uploadDoc], { - _width: screen.width, lockedPosition: true, _chromeStatus: "disabled", title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray" - }); - } - - // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. - // when clicked, this panel will be displayed in the target container (ie, sidebarContainer) - static async setupToolsBtnPanel(doc: Doc, sidebarContainer: Doc) { - // setup a masonry view of all he creators - const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc); - const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); - - if (doc.myCreators === undefined) { - doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], { - title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, - _width: 500, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", - })); - } - // setup a color picker - if (doc.myColorPicker === undefined) { - const color = Docs.Create.ColorDocument({ - title: "color picker", _width: 300, dropAction: "alias", forceActive: true, removeDropProperties: new List<string>(["dropAction", "forceActive"]) - }); - doc.myColorPicker = new PrefetchProxy(color); - } - - if (doc["tabs-button-tools"] === undefined) { - doc["tabs-button-tools"] = new PrefetchProxy(Docs.Create.ButtonDocument({ - _width: 35, _height: 25, title: "Tools", _fontSize: 10, - letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", - sourcePanel: new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], { - _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true - })) as any as Doc, - targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc, - onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel"), - })); - } - (doc["tabs-button-tools"] as Doc).sourcePanel; // prefetch sourcePanel - return doc["tabs-button-tools"] as Doc; - } - - static setupWorkspaces(doc: Doc) { - // setup workspaces library item - if (doc.myWorkspaces === undefined) { - doc.myWorkspaces = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "WORKSPACES", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, - })); - } - const newWorkspace = ScriptField.MakeScript(`createNewWorkspace()`); - (doc.myWorkspaces as Doc).contextMenuScripts = new List<ScriptField>([newWorkspace!]); - (doc.myWorkspaces as Doc).contextMenuLabels = new List<string>(["Create New Workspace"]); - - return doc.myWorkspaces as Doc; - } - static setupCatalog(doc: Doc) { - if (doc.myCatalog === undefined) { - doc.myCatalog = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "CATALOG", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true, - })); - } - return doc.myCatalog as Doc; - } - static setupRecentlyClosed(doc: Doc) { - // setup Recently Closed library item - if (doc.myRecentlyClosed === undefined) { - doc.myRecentlyClosed = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, - })); - } - // this is equivalent to using PrefetchProxies to make sure the recentlyClosed doc is ready - PromiseValue(Cast(doc.myRecentlyClosed, Doc)).then(recent => recent && PromiseValue(recent.data).then(DocListCast)); - const clearAll = ScriptField.MakeScript(`self.data = new List([])`); - (doc.myRecentlyClosed as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]); - (doc.myRecentlyClosed as Doc).contextMenuLabels = new List<string>(["Clear All"]); - - return doc.myRecentlyClosed as Doc; - } - // setup the Library button which will display the library panel. This panel includes a collection of workspaces, documents, and recently closed views - static setupLibraryPanel(doc: Doc, sidebarContainer: Doc) { - const workspaces = CurrentUserUtils.setupWorkspaces(doc); - const documents = CurrentUserUtils.setupCatalog(doc); - const recentlyClosed = CurrentUserUtils.setupRecentlyClosed(doc); - - if (doc["tabs-button-library"] === undefined) { - doc["tabs-button-library"] = new PrefetchProxy(Docs.Create.ButtonDocument({ - _width: 50, _height: 25, title: "Library", _fontSize: 10, - letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", - sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], { - title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true - })) as any as Doc, - targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc, - onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;") - })); - } - return doc["tabs-button-library"] as Doc; - } - - // setup the Search button which will display the search panel. - static setupSearchBtnPanel(doc: Doc, sidebarContainer: Doc) { - if (doc["tabs-button-search"] === undefined) { - doc["tabs-button-search"] = new PrefetchProxy(Docs.Create.ButtonDocument({ - _width: 50, _height: 25, title: "Search", _fontSize: 10, - letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", - sourcePanel: new PrefetchProxy(Docs.Create.SearchDocument({ title: "search stack", })) as any as Doc, - searchFileTypes: new List<string>([DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.VID, DocumentType.WEB, DocumentType.SCRIPTING]), - targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc, - lockedPosition: true, - onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel") - })); - } - return doc["tabs-button-search"] as Doc; - } - - static setupSidebarContainer(doc: Doc) { - if (doc["tabs-panelContainer"] === undefined) { - const sidebarContainer = new Doc(); - sidebarContainer._chromeStatus = "disabled"; - sidebarContainer.onClick = ScriptField.MakeScript("freezeSidebar()"); - doc["tabs-panelContainer"] = new PrefetchProxy(sidebarContainer); - } - return doc["tabs-panelContainer"] as Doc; - } - - // setup the list of sidebar mode buttons which determine what is displayed in the sidebar - static async setupSidebarButtons(doc: Doc) { - const sidebarContainer = CurrentUserUtils.setupSidebarContainer(doc); - const toolsBtn = await CurrentUserUtils.setupToolsBtnPanel(doc, sidebarContainer); - const libraryBtn = CurrentUserUtils.setupLibraryPanel(doc, sidebarContainer); - const searchBtn = CurrentUserUtils.setupSearchBtnPanel(doc, sidebarContainer); - - // Finally, setup the list of buttons to display in the sidebar - if (doc["tabs-buttons"] === undefined) { - doc["tabs-buttons"] = new PrefetchProxy(Docs.Create.StackingDocument([searchBtn, libraryBtn, toolsBtn], { - _width: 500, _height: 80, boxShadow: "0 0", _pivotField: "title", hideHeadings: true, ignoreClick: true, _chromeStatus: "view-mode", - title: "sidebar btn row stack", backgroundColor: "dimGray", - })); - (toolsBtn.onClick as ScriptField).script.run({ this: toolsBtn }); - } - } - - static blist = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, { - ...opts, - _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", forceActive: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - backgroundColor: "black", treeViewPreventOpen: true, lockedPosition: true, _chromeStatus: "disabled", linearViewIsExpanded: true - })) as any as Doc - - static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ - ...opts, - dropAction: "alias", removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100 - })) as any as Doc - - /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window - static setupDockedButtons(doc: Doc) { - if (doc["dockedBtn-pen"] === undefined) { - doc["dockedBtn-pen"] = CurrentUserUtils.ficon({ - onClick: ScriptField.MakeScript("activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)"), - author: "systemTemplates", title: "ink mode", icon: "pen-nib", ischecked: ComputedField.MakeFunction(`sameDocs(this.activePen.inkPen, this)`), activePen: doc - }); - } - if (doc["dockedBtn-undo"] === undefined) { - doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" }); - } - if (doc["dockedBtn-redo"] === undefined) { - doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" }); - } - if (doc.dockedBtns === undefined) { - doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc, doc["dockedBtn-pen"] as Doc]); - } - } - // sets up the default set of documents to be shown in the Overlay layer - static setupOverlays(doc: Doc) { - if (doc.myOverlayDocuments === undefined) { - doc.myOverlayDocuments = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6" })); - } - } - - // the initial presentation Doc to use - static setupDefaultPresentation(doc: Doc) { - if (doc["template-presentation"] === undefined) { - doc["template-presentation"] = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ - title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data" - })); - } - if (doc.activePresentation === undefined) { - doc.activePresentation = Docs.Create.PresDocument(new List<Doc>(), { - title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", - _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" - }); - } - } - - static setupRightSidebar(doc: Doc) { - if (doc.rightSidebarCollection === undefined) { - doc.rightSidebarCollection = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Right Sidebar" })); - } - } - - static setupClickEditorTemplates(doc: Doc) { - if (doc["clickFuncs-child"] === undefined) { - const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( - "docCast(thisContainer.target).then((target) => {" + - " target && docCast(this.source).then((source) => { " + - " target.proto.data = new List([source || this]); " + - " }); " + - "})", - { target: Doc.name }), { title: "Click to open in target", _width: 300, _height: 200, targetScriptKey: "onChildClick" }); - - const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( - "openOnRight(self.doubleClickView)", - { target: Doc.name }), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" }); - - doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" }); - } - // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved. - PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); - - if (doc.clickFuncs === undefined) { - const onClick = Docs.Create.ScriptingDocument(undefined, { - title: "onClick", "onClick-rawScript": "console.log('click')", - isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200 - }, "onClick"); - const onDoubleClick = Docs.Create.ScriptingDocument(undefined, { - title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')", - isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200 - }, "onDoubleClick"); - const onCheckedClick = Docs.Create.ScriptingDocument(undefined, { - title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)", "onCheckedClick-params": new List<string>(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, isTemplateForField: "onCheckedClick", _width: 300, _height: 200 - }, "onCheckedClick"); - doc.clickFuncs = Docs.Create.TreeDocument([onClick, onDoubleClick, onCheckedClick], { title: "onClick funcs" }); - } - PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); - - return doc.clickFuncs as Doc; - } - - static async updateUserDocument(doc: Doc) { - new InkingControl(); - doc.title = Doc.CurrentUserEmail; - doc.activePen = doc; - doc.inkColor = StrCast(doc.backgroundColor, ""); - doc.fontSize = NumCast(doc.fontSize, 12); - doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // - doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // - Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]); - this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon - this.setupDocTemplates(doc); // sets up the template menu of templates - this.setupRightSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing - this.setupOverlays(doc); // documents in overlay layer - this.setupDockedButtons(doc); // the bottom bar of font icons - this.setupDefaultPresentation(doc); // presentation that's initially triggered - await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels - doc.globalLinkDatabase = Docs.Prototypes.MainLinkDocument(); - - // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet - doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); - doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); - - return doc; - } - public static async loadCurrentUser() { - return rp.get(Utils.prepend("/getCurrentUser")).then(response => { - if (response) { - const result: { id: string, email: string } = JSON.parse(response); - return result; - } else { - throw new Error("There should be a user! Why does Dash think there isn't one?"); - } - }); - } - - public static async loadUserDocument({ id, email }: { id: string, email: string }) { - this.curr_id = id; - Doc.CurrentUserEmail = email; - await rp.get(Utils.prepend("/getUserDocumentId")).then(id => { - if (id && id !== "guest") { - return DocServer.GetRefField(id).then(async field => - Doc.SetUserDoc(await this.updateUserDocument(field instanceof Doc ? field : new Doc(id, true)))); - } else { - throw new Error("There should be a user id! Why does Dash think there isn't one?"); - } - }); - } -} - -Scripting.addGlobal(function setupMobileInkingDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileInkingDoc(userDoc); }); -Scripting.addGlobal(function setupMobileUploadDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileUploadDoc(userDoc); }); -Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); });
\ No newline at end of file diff --git a/src/server/credentials/CredentialsLoader.ts b/src/server/credentials/CredentialsLoader.ts deleted file mode 100644 index e3f4d167b..000000000 --- a/src/server/credentials/CredentialsLoader.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { readFile } from "fs"; - -export namespace GoogleCredentialsLoader { - - export interface InstalledCredentials { - client_id: string; - project_id: string; - auth_uri: string; - token_uri: string; - auth_provider_x509_cert_url: string; - client_secret: string; - redirect_uris: string[]; - } - - export let ProjectCredentials: InstalledCredentials; - - export async function loadCredentials() { - ProjectCredentials = await new Promise<InstalledCredentials>(resolve => { - readFile(__dirname + '/google_project_credentials.json', function processClientSecrets(err, content) { - if (err) { - console.log('Error loading client secret file: ' + err); - return; - } - resolve(JSON.parse(content.toString()).installed); - }); - }); - } - -} diff --git a/src/server/database.ts b/src/server/database.ts index 580f7f919..a5f23c4b1 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -1,6 +1,6 @@ import * as mongodb from 'mongodb'; import { Transferable } from './Message'; -import { Opt } from '../new_fields/Doc'; +import { Opt } from '../fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { Credentials } from 'google-auth-library'; import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; @@ -293,13 +293,26 @@ export namespace Database { export const Instance = getDatabase(); + /** + * Provides definitions and apis for working with + * portions of the database not dedicated to storing documents + * or Dash-internal user data. + */ export namespace Auxiliary { + /** + * All the auxiliary MongoDB collections (schemas) + */ export enum AuxiliaryCollections { GooglePhotosUploadHistory = "uploadedFromGooglePhotos", - GoogleAuthentication = "googleAuthentication" + GoogleAccess = "googleAuthentication" } + /** + * Searches for the @param query in the specified @param collection, + * and returns at most the first @param cap results. If @param removeId is true, + * as it is by default, each object will be stripped of its database id. + */ const SanitizedCappedQuery = async (query: { [key: string]: any }, collection: string, cap: number, removeId = true) => { const cursor = await Instance.query(query, undefined, collection); const results = await cursor.toArray(); @@ -310,52 +323,89 @@ export namespace Database { }) : slice; }; + /** + * Searches for the @param query in the specified @param collection, + * and returns at most the first result. If @param removeId is true, + * as it is by default, each object will be stripped of its database id. + * Worth the special case since it converts the Array return type to a single + * object of the specified type. + */ const SanitizedSingletonQuery = async <T>(query: { [key: string]: any }, collection: string, removeId = true): Promise<Opt<T>> => { const results = await SanitizedCappedQuery(query, collection, 1, removeId); return results.length ? results[0] : undefined; }; + /** + * Checks to see if an image with the given @param contentSize + * already exists in the aux database, i.e. has already been downloaded from Google Photos. + */ export const QueryUploadHistory = async (contentSize: number) => { return SanitizedSingletonQuery<Upload.ImageInformation>({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory); }; - export namespace GoogleAuthenticationToken { + /** + * Records the uploading of the image with the given @param information, + * using the given content size as a seed for the database id. + */ + export const LogUpload = async (information: Upload.ImageInformation) => { + const bundle = { + _id: Utils.GenerateDeterministicGuid(String(information.contentSize)), + ...information + }; + return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory); + }; + + /** + * Manages the storage, retrieval and updating of the access token that + * facilitates interactions with all their APIs for a given account. + */ + export namespace GoogleAccessToken { + /** + * Format stored in database. + */ type StoredCredentials = GoogleApiServerUtils.EnrichedCredentials & { _id: string }; + /** + * Retrieves the credentials associaed with @param userId + * and optionally removes their database id according to @param removeId. + */ export const Fetch = async (userId: string, removeId = true): Promise<Opt<StoredCredentials>> => { - return SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAuthentication, removeId); + return SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAccess, removeId); }; + /** + * Writes the @param enrichedCredentials to the database, associated + * with @param userId for later retrieval and updating. + */ export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => { - return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAuthentication); + return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAccess); }; + /** + * Updates the @param access_token and @param expiry_date fields + * in the stored credentials associated with @param userId. + */ export const Update = async (userId: string, access_token: string, expiry_date: number) => { const entry = await Fetch(userId, false); if (entry) { const parameters = { $set: { access_token, expiry_date } }; - return Instance.update(entry._id, parameters, emptyFunction, true, AuxiliaryCollections.GoogleAuthentication); + return Instance.update(entry._id, parameters, emptyFunction, true, AuxiliaryCollections.GoogleAccess); } }; + /** + * Revokes the credentials associated with @param userId. + */ export const Revoke = async (userId: string) => { const entry = await Fetch(userId, false); if (entry) { - Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAuthentication); + Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAccess); } }; } - export const LogUpload = async (information: Upload.ImageInformation) => { - const bundle = { - _id: Utils.GenerateDeterministicGuid(String(information.contentSize)), - ...information - }; - return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory); - }; - } } diff --git a/src/server/index.ts b/src/server/index.ts index f26c8a6ab..590affd06 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -5,15 +5,14 @@ import * as path from 'path'; import { Database } from './database'; import { DashUploadUtils } from './DashUploadUtils'; import RouteSubscriber from './RouteSubscriber'; -import initializeServer from './server_Initialization'; +import initializeServer, { resolvedPorts } from './server_Initialization'; import RouteManager, { Method, _success, _permission_denied, _error, _invalid, PublicHandler } from './RouteManager'; import * as qs from 'query-string'; import UtilManager from './ApiManagers/UtilManager'; import { SearchManager } from './ApiManagers/SearchManager'; import UserManager from './ApiManagers/UserManager'; -import { WebSocket } from './Websocket/Websocket'; import DownloadManager from './ApiManagers/DownloadManager'; -import { GoogleCredentialsLoader } from './credentials/CredentialsLoader'; +import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader'; import DeleteManager from "./ApiManagers/DeleteManager"; import PDFManager from "./ApiManagers/PDFManager"; import UploadManager from "./ApiManagers/UploadManager"; @@ -25,8 +24,8 @@ import { yellow } from "colors"; import { DashSessionAgent } from "./DashSession/DashSessionAgent"; import SessionManager from "./ApiManagers/SessionManager"; import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent"; -import { Utils } from "../Utils"; +export const AdminPriviliges: Map<string, boolean> = new Map(); export const onWindows = process.platform === "win32"; export let sessionAgent: AppliedSessionAgent; export const publicDirectory = path.resolve(__dirname, "public"); @@ -42,6 +41,7 @@ async function preliminaryFunctions() { await DashUploadUtils.buildFileDirectories(); await Logger.initialize(); await GoogleCredentialsLoader.loadCredentials(); + SSL.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); if (process.env.DB !== "MEM") { await log_execution({ @@ -95,6 +95,11 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: secureHandler: ({ res }) => res.send(true) }); + addSupervisedRoute({ + method: Method.GET, + subscription: "/resolvedPorts", + secureHandler: ({ res }) => res.send(resolvedPorts) + }); const serve: PublicHandler = ({ req, res }) => { const detector = new mobileDetect(req.headers['user-agent'] || ""); @@ -102,6 +107,42 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: res.sendFile(path.join(__dirname, '../../deploy/' + filename)); }; + /** + * Serves a simple password input box for any + */ + addSupervisedRoute({ + method: Method.GET, + subscription: new RouteSubscriber("admin").add("previous_target"), + secureHandler: ({ res, isRelease }) => { + const { PASSWORD } = process.env; + if (!(isRelease && PASSWORD)) { + return res.redirect("/home"); + } + res.render("admin.pug", { title: "Enter Administrator Password" }); + } + }); + + addSupervisedRoute({ + method: Method.POST, + subscription: new RouteSubscriber("admin").add("previous_target"), + secureHandler: async ({ req, res, isRelease, user: { id } }) => { + const { PASSWORD } = process.env; + if (!(isRelease && PASSWORD)) { + return res.redirect("/home"); + } + const { password } = req.body; + const { previous_target } = req.params; + let redirect: string; + if (password === PASSWORD) { + AdminPriviliges.set(id, true); + redirect = `/${previous_target.replace(":", "/")}`; + } else { + redirect = `/admin/${previous_target}`; + } + res.redirect(redirect); + } + }); + addSupervisedRoute({ method: Method.GET, subscription: ["/home", new RouteSubscriber("doc").add("docId")], @@ -122,10 +163,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: }); logRegistrationOutcome(); - - // initialize the web socket (bidirectional communication: if a user changes - // a field on one client, that change must be broadcast to all other clients) - WebSocket.start(isRelease); } @@ -150,9 +187,9 @@ export async function launchServer() { * log the output of the server process, so it's not ideal for development. * So, the 'else' clause is exactly what we've always run when executing npm start. */ -if (process.env.RELEASE) { - (sessionAgent = new DashSessionAgent()).launch(); -} else { - (Database.Instance as Database.Database).doConnect(); - launchServer(); -} +// if (process.env.RELEASE) { +// (sessionAgent = new DashSessionAgent()).launch(); +// } else { +(Database.Instance as Database.Database).doConnect(); +launchServer(); +// } diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts index 91a3cb6bf..7178add93 100644 --- a/src/server/remapUrl.ts +++ b/src/server/remapUrl.ts @@ -1,4 +1,5 @@ import { Database } from "./database"; +import { resolvedPorts } from "./server_Initialization"; //npx ts-node src/server/remapUrl.ts @@ -34,7 +35,7 @@ async function update() { if (url.href.includes("localhost") && url.href.includes("Bill")) { dynfield = true; - update.$set = { ["fields." + key + ".url"]: `${url.protocol}//dash-web.eastus2.cloudapp.azure.com:1050${url.pathname}` }; + update.$set = { ["fields." + key + ".url"]: `${url.protocol}//dash-web.eastus2.cloudapp.azure.com:${resolvedPorts.server}${url.pathname}` }; } } } diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index add607761..744d4547b 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -7,9 +7,10 @@ import * as cookieParser from 'cookie-parser'; import expressFlash = require('express-flash'); import flash = require('connect-flash'); import { Database } from './database'; -import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/controllers/user_controller'; +import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager'; const MongoStore = require('connect-mongo')(session); import RouteManager from './RouteManager'; +import { WebSocket } from './websocket'; import * as webpack from 'webpack'; const config = require('../../webpack.config'); const compiler = webpack(config); @@ -19,15 +20,21 @@ import * as fs from 'fs'; import * as request from 'request'; import RouteSubscriber from './RouteSubscriber'; import { publicDirectory } from '.'; -import { logPort, } from './ActionUtilities'; +import { logPort } from './ActionUtilities'; import { blue, yellow } from 'colors'; import * as cors from "cors"; +import { createServer, Server as HttpsServer } from "https"; +import { Server as HttpServer } from "http"; +import { SSL } from './apis/google/CredentialsLoader'; /* RouteSetter is a wrapper around the server that prevents the server from being exposed. */ export type RouteSetter = (server: RouteManager) => void; export let disconnect: Function; +export let resolvedPorts: { server: number, socket: number } = { server: 1050, socket: 4321 }; +export let resolvedServerUrl: string; + export default async function InitializeServer(routeSetter: RouteSetter) { const app = buildWithMiddleware(express()); @@ -45,16 +52,27 @@ export default async function InitializeServer(routeSetter: RouteSetter) { const isRelease = determineEnvironment(); + isRelease && !SSL.Loaded && SSL.exit(); + routeSetter(new RouteManager(app, isRelease)); registerRelativePath(app); - const serverPort = isRelease ? Number(process.env.serverPort) : 1050; - const server = app.listen(serverPort, () => { - logPort("server", serverPort); - console.log(); - }); - disconnect = async () => new Promise<Error>(resolve => server.close(resolve)); + let server: HttpServer | HttpsServer; + const { serverPort, serverName } = process.env; + isRelease && serverPort && (resolvedPorts.server = Number(serverPort)); + await new Promise<void>(resolve => server = isRelease ? + createServer(SSL.Credentials, app).listen(resolvedPorts.server, resolve) : + app.listen(resolvedPorts.server, resolve) + ); + logPort("server", resolvedPorts.server); + + resolvedServerUrl = `${isRelease && serverName ? `https://${serverName}.com` : "http://localhost"}:${resolvedPorts.server}`; + // initialize the web socket (bidirectional communication: if a user changes + // a field on one client, that change must be broadcast to all other clients) + await WebSocket.initialize(isRelease, app); + + disconnect = async () => new Promise<Error>(resolve => server.close(resolve)); return isRelease; } @@ -94,6 +112,8 @@ function determineEnvironment() { const label = isRelease ? "release" : "development"; console.log(`\nrunning server in ${color(label)} mode`); + // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE + // on the client side, thanks to dotenv in webpack.config.js let clientUtils = fs.readFileSync("./src/client/util/ClientUtils.ts.temp", "utf8"); clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`; fs.writeFileSync("./src/client/util/ClientUtils.ts", clientUtils, "utf8"); @@ -120,7 +140,7 @@ function registerAuthenticationRoutes(server: express.Express) { function registerCorsProxy(server: express.Express) { const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; - server.use("/corsProxy", (req, res) => { + server.use("/corsProxy", async (req, res) => { const requrl = decodeURIComponent(req.url.substring(1)); const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : ""; @@ -129,8 +149,15 @@ function registerCorsProxy(server: express.Express) { // then we redirect again to the cors referer and just add the relative path. if (!requrl.startsWith("http") && req.originalUrl.startsWith("/corsProxy") && referer?.includes("corsProxy")) { res.redirect(referer + (referer.endsWith("/") ? "" : "/") + requrl); - } - else { + } else { + try { + await new Promise<void>((resolve, reject) => { + request(requrl).on("response", resolve).on("error", reject); + }); + } catch { + console.log(`Malformed CORS url: ${requrl}`); + return res.send(); + } req.pipe(request(requrl)).on("response", res => { const headers = Object.keys(res.headers); headers.forEach(headerName => { @@ -143,7 +170,7 @@ function registerCorsProxy(server: express.Express) { } } }); - }).pipe(res); + }).on("error", () => console.log(`Malformed CORS url: ${requrl}`)).pipe(res); } }); } @@ -152,11 +179,11 @@ function registerRelativePath(server: express.Express) { server.use("*", (req, res) => { const relativeUrl = req.originalUrl; if (!res.headersSent && req.headers.referer?.includes("corsProxy")) { // a request for something by a proxied referrer means it must be a relative reference. So construct a proxied absolute reference here. - const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:1050/corsProxy/https://en.wikipedia.org/wiki/Engelbart) - const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:1050/corsProxy/ ) + const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart) + const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ ) const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ""); // the url of the referer without the proxy (e.g., : http:s//en.wikipedia.org/wiki/Engelbart) const absoluteTargetBaseUrl = actualReferUrl.match(/http[s]?:\/\/[^\/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org) - const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e..g, http://localhost:1050/corsProxy/https://en.wikipedia.org/<somethingelse>) + const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e..g, http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>) res.redirect(redirectedProxiedUrl); } else if (relativeUrl.startsWith("/search")) { // detect search query and use default search engine res.redirect(req.headers.referer + "corsProxy/" + encodeURIComponent("http://www.google.com" + relativeUrl)); diff --git a/src/server/Websocket/Websocket.ts b/src/server/websocket.ts index 37a94cdd3..87af5fa06 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/websocket.ts @@ -1,18 +1,22 @@ -import { Utils } from "../../Utils"; -import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent, RoomMessage } from "../Message"; -import { Client } from "../Client"; +import * as fs from 'fs'; +import { logPort } from './ActionUtilities'; +import { Utils } from "../Utils"; +import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent, RoomMessage } from "./Message"; +import { Client } from "./Client"; import { Socket } from "socket.io"; -import { Database } from "../database"; -import { Search } from "../Search"; -import * as io from 'socket.io'; -import YoutubeApi from "../apis/youtube/youtubeApiSample"; -import { GoogleCredentialsLoader } from "../credentials/CredentialsLoader"; -import { logPort } from "../ActionUtilities"; -import { timeMap } from "../ApiManagers/UserManager"; +import { Database } from "./database"; +import { Search } from "./Search"; +import * as sio from 'socket.io'; +import YoutubeApi from "./apis/youtube/youtubeApiSample"; +import { GoogleCredentialsLoader, SSL } from "./apis/google/CredentialsLoader"; +import { timeMap } from "./ApiManagers/UserManager"; import { green } from "colors"; import { networkInterfaces } from "os"; -import executeImport from "../../scraping/buxton/final/BuxtonImporter"; -import { DocumentsCollection } from "../IDatabase"; +import executeImport from "../scraping/buxton/final/BuxtonImporter"; +import { DocumentsCollection } from "./IDatabase"; +import { createServer, Server } from "https"; +import * as express from "express"; +import { resolvedPorts } from './server_Initialization'; export namespace WebSocket { @@ -21,19 +25,24 @@ export namespace WebSocket { export const socketMap = new Map<SocketIO.Socket, string>(); export let disconnect: Function; + export async function initialize(isRelease: boolean, app: express.Express) { + let io: sio.Server; + if (isRelease) { + const { socketPort } = process.env; + if (socketPort) { + resolvedPorts.socket = Number(socketPort); + } + let socketEndpoint: Server; + await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve)); + io = sio(socketEndpoint!, SSL.Credentials as any); + } else { + io = sio().listen(resolvedPorts.socket); + } + logPort("websocket", resolvedPorts.socket); + console.log(); - export async function start(isRelease: boolean) { - await preliminaryFunctions(); - initialize(isRelease); - } - - async function preliminaryFunctions() { - } - function initialize(isRelease: boolean) { - const endpoint = io(); - endpoint.on("connection", function (socket: Socket) { + io.on("connection", function (socket: Socket) { _socket = socket; - socket.use((_packet, next) => { const userEmail = socketMap.get(socket); if (userEmail) { @@ -129,10 +138,6 @@ export namespace WebSocket { socket.disconnect(true); }; }); - - const socketPort = isRelease ? Number(process.env.socketPort) : 4321; - endpoint.listen(socketPort); - logPort("websocket", socketPort); } function processGesturePoints(socket: Socket, content: GestureContent) { |
