Changeset View
Changeset View
Standalone View
Standalone View
keyserver/src/responders/website-responders.js
// @flow | // @flow | ||||
import html from 'common-tags/lib/html/index.js'; | import html from 'common-tags/lib/html/index.js'; | ||||
import { detect as detectBrowser } from 'detect-browser'; | import { detect as detectBrowser } from 'detect-browser'; | ||||
import type { $Response, $Request } from 'express'; | import type { $Response, $Request } from 'express'; | ||||
import fs from 'fs'; | import fs from 'fs'; | ||||
import _isEqual from 'lodash/fp/isEqual.js'; | |||||
import _keyBy from 'lodash/fp/keyBy.js'; | import _keyBy from 'lodash/fp/keyBy.js'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
// eslint-disable-next-line import/extensions | // eslint-disable-next-line import/extensions | ||||
import ReactDOMServer from 'react-dom/server'; | import ReactDOMServer from 'react-dom/server'; | ||||
import t from 'tcomb'; | |||||
import { promisify } from 'util'; | import { promisify } from 'util'; | ||||
import { baseLegalPolicies } from 'lib/facts/policies.js'; | import { baseLegalPolicies } from 'lib/facts/policies.js'; | ||||
import stores from 'lib/facts/stores.js'; | import stores from 'lib/facts/stores.js'; | ||||
import { daysToEntriesFromEntryInfos } from 'lib/reducers/entry-reducer.js'; | import { daysToEntriesFromEntryInfos } from 'lib/reducers/entry-reducer.js'; | ||||
import { freshMessageStore } from 'lib/reducers/message-reducer.js'; | import { freshMessageStore } from 'lib/reducers/message-reducer.js'; | ||||
import { mostRecentlyReadThread } from 'lib/selectors/thread-selectors.js'; | import { mostRecentlyReadThread } from 'lib/selectors/thread-selectors.js'; | ||||
import { mostRecentMessageTimestamp } from 'lib/shared/message-utils.js'; | import { mostRecentMessageTimestamp } from 'lib/shared/message-utils.js'; | ||||
import { | import { | ||||
threadHasPermission, | threadHasPermission, | ||||
threadIsPending, | threadIsPending, | ||||
parsePendingThreadID, | parsePendingThreadID, | ||||
createPendingThread, | createPendingThread, | ||||
} from 'lib/shared/thread-utils.js'; | } from 'lib/shared/thread-utils.js'; | ||||
import { defaultWebEnabledApps } from 'lib/types/enabled-apps.js'; | import { defaultWebEnabledApps } from 'lib/types/enabled-apps.js'; | ||||
import { entryStoreValidator } from 'lib/types/entry-types.js'; | |||||
import { defaultCalendarFilters } from 'lib/types/filter-types.js'; | import { defaultCalendarFilters } from 'lib/types/filter-types.js'; | ||||
import { defaultNumberPerThread } from 'lib/types/message-types.js'; | import { | ||||
defaultNumberPerThread, | |||||
messageStoreValidator, | |||||
} from 'lib/types/message-types.js'; | |||||
import { defaultEnabledReports } from 'lib/types/report-types.js'; | import { defaultEnabledReports } from 'lib/types/report-types.js'; | ||||
import { defaultConnectionInfo } from 'lib/types/socket-types.js'; | import { defaultConnectionInfo } from 'lib/types/socket-types.js'; | ||||
import { threadPermissions } from 'lib/types/thread-permission-types.js'; | import { threadPermissions } from 'lib/types/thread-permission-types.js'; | ||||
import { threadTypes } from 'lib/types/thread-types-enum.js'; | import { threadTypes } from 'lib/types/thread-types-enum.js'; | ||||
import { threadStoreValidator } from 'lib/types/thread-types.js'; | |||||
import { | |||||
currentUserInfoValidator, | |||||
userInfosValidator, | |||||
} from 'lib/types/user-types.js'; | |||||
import { currentDateInTimeZone } from 'lib/utils/date-utils.js'; | import { currentDateInTimeZone } from 'lib/utils/date-utils.js'; | ||||
import { ServerError } from 'lib/utils/errors.js'; | import { ServerError } from 'lib/utils/errors.js'; | ||||
import { promiseAll } from 'lib/utils/promises.js'; | import { promiseAll } from 'lib/utils/promises.js'; | ||||
import { defaultNotifPermissionAlertInfo } from 'lib/utils/push-alerts.js'; | import { defaultNotifPermissionAlertInfo } from 'lib/utils/push-alerts.js'; | ||||
import { tBool, tNumber, tShape, tString } from 'lib/utils/validation-utils.js'; | |||||
import getTitle from 'web/title/getTitle.js'; | import getTitle from 'web/title/getTitle.js'; | ||||
import { navInfoValidator } from 'web/types/nav-types.js'; | |||||
import { navInfoFromURL } from 'web/url-utils.js'; | import { navInfoFromURL } from 'web/url-utils.js'; | ||||
import { fetchEntryInfos } from '../fetchers/entry-fetchers.js'; | import { fetchEntryInfos } from '../fetchers/entry-fetchers.js'; | ||||
import { fetchMessageInfos } from '../fetchers/message-fetchers.js'; | import { fetchMessageInfos } from '../fetchers/message-fetchers.js'; | ||||
import { hasAnyNotAcknowledgedPolicies } from '../fetchers/policy-acknowledgment-fetchers.js'; | import { hasAnyNotAcknowledgedPolicies } from '../fetchers/policy-acknowledgment-fetchers.js'; | ||||
import { fetchThreadInfos } from '../fetchers/thread-fetchers.js'; | import { fetchThreadInfos } from '../fetchers/thread-fetchers.js'; | ||||
import { | import { | ||||
fetchCurrentUserInfo, | fetchCurrentUserInfo, | ||||
fetchKnownUserInfos, | fetchKnownUserInfos, | ||||
} from '../fetchers/user-fetchers.js'; | } from '../fetchers/user-fetchers.js'; | ||||
import { getWebPushConfig } from '../push/providers.js'; | import { getWebPushConfig } from '../push/providers.js'; | ||||
import { setNewSession } from '../session/cookies.js'; | import { setNewSession } from '../session/cookies.js'; | ||||
import { Viewer } from '../session/viewer.js'; | import { Viewer } from '../session/viewer.js'; | ||||
import { streamJSON, waitForStream } from '../utils/json-stream.js'; | import { streamJSON, waitForStream } from '../utils/json-stream.js'; | ||||
import { getAppURLFactsFromRequestURL } from '../utils/urls.js'; | import { getAppURLFactsFromRequestURL } from '../utils/urls.js'; | ||||
import { validateOutput } from '../utils/validation-utils.js'; | |||||
const { renderToNodeStream } = ReactDOMServer; | const { renderToNodeStream } = ReactDOMServer; | ||||
const access = promisify(fs.access); | const access = promisify(fs.access); | ||||
const readFile = promisify(fs.readFile); | const readFile = promisify(fs.readFile); | ||||
const googleFontsURL = | const googleFontsURL = | ||||
'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=Inter:wght@400;500;600&display=swap'; | 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=Inter:wght@400;500;600&display=swap'; | ||||
▲ Show 20 Lines • Show All 76 Lines • ▼ Show 20 Lines | async function getWebpackCompiledRootComponentForSSR() { | ||||
} catch { | } catch { | ||||
throw new Error( | throw new Error( | ||||
'Could not load app.build.cjs. ' + | 'Could not load app.build.cjs. ' + | ||||
'Did you forget to run `yarn dev` in the web folder?', | 'Did you forget to run `yarn dev` in the web folder?', | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
const initialReduxStateValidator = tShape({ | |||||
navInfo: navInfoValidator, | |||||
deviceID: t.Nil, | |||||
currentUserInfo: currentUserInfoValidator, | |||||
draftStore: t.irreducible('default draftStore', _isEqual({ drafts: {} })), | |||||
sessionID: t.maybe(t.String), | |||||
entryStore: entryStoreValidator, | |||||
threadStore: threadStoreValidator, | |||||
userStore: tShape({ | |||||
userInfos: userInfosValidator, | |||||
inconsistencyReports: t.irreducible( | |||||
'default inconsistencyReports', | |||||
_isEqual([]), | |||||
), | |||||
}), | |||||
messageStore: messageStoreValidator, | |||||
updatesCurrentAsOf: t.Number, | |||||
loadingStatuses: t.irreducible('default loadingStatuses', _isEqual({})), | |||||
calendarFilters: t.irreducible( | |||||
'defaultCalendarFilters', | |||||
_isEqual(defaultCalendarFilters), | |||||
), | |||||
urlPrefix: tString(''), | |||||
windowDimensions: t.irreducible( | |||||
'default windowDimensions', | |||||
_isEqual({ width: 0, height: 0 }), | |||||
), | |||||
baseHref: t.String, | |||||
notifPermissionAlertInfo: t.irreducible( | |||||
'default notifPermissionAlertInfo', | |||||
_isEqual(defaultNotifPermissionAlertInfo), | |||||
), | |||||
connection: tShape({ | |||||
status: tString('connecting'), | |||||
queuedActivityUpdates: t.irreducible( | |||||
'default queuedActivityUpdates', | |||||
_isEqual([]), | |||||
), | |||||
actualizedCalendarQuery: tShape({ | |||||
startDate: t.String, | |||||
endDate: t.String, | |||||
filters: t.irreducible( | |||||
'default filters', | |||||
_isEqual(defaultCalendarFilters), | |||||
), | |||||
}), | |||||
lateResponses: t.irreducible('default lateResponses', _isEqual([])), | |||||
showDisconnectedBar: tBool(false), | |||||
}), | |||||
watchedThreadIDs: t.irreducible('default watchedThreadIDs', _isEqual([])), | |||||
lifecycleState: tString('active'), | |||||
enabledApps: t.irreducible( | |||||
'defaultWebEnabledApps', | |||||
_isEqual(defaultWebEnabledApps), | |||||
), | |||||
reportStore: t.irreducible( | |||||
'default reportStore', | |||||
_isEqual({ | |||||
enabledReports: defaultEnabledReports, | |||||
queuedReports: [], | |||||
}), | |||||
), | |||||
nextLocalID: tNumber(0), | |||||
cookie: t.Nil, | |||||
deviceToken: t.Nil, | |||||
dataLoaded: t.Boolean, | |||||
windowActive: tBool(true), | |||||
userPolicies: t.irreducible('default userPolicies', _isEqual({})), | |||||
cryptoStore: t.irreducible( | |||||
'default cryptoStore', | |||||
_isEqual({ | |||||
primaryIdentityKeys: null, | |||||
notificationIdentityKeys: null, | |||||
}), | |||||
), | |||||
pushApiPublicKey: t.maybe(t.String), | |||||
_persist: t.Nil, | |||||
commServicesAccessToken: t.Nil, | |||||
}); | |||||
async function websiteResponder( | async function websiteResponder( | ||||
viewer: Viewer, | viewer: Viewer, | ||||
req: $Request, | req: $Request, | ||||
res: $Response, | res: $Response, | ||||
): Promise<void> { | ): Promise<void> { | ||||
const appURLFacts = getAppURLFactsFromRequestURL(req.originalUrl); | const appURLFacts = getAppURLFactsFromRequestURL(req.originalUrl); | ||||
const { basePath, baseDomain } = appURLFacts; | const { basePath, baseDomain } = appURLFacts; | ||||
const baseURL = basePath.replace(/\/$/, ''); | const baseURL = basePath.replace(/\/$/, ''); | ||||
▲ Show 20 Lines • Show All 275 Lines • ▼ Show 20 Lines | const initialReduxState = await promiseAll({ | ||||
cryptoStore: { | cryptoStore: { | ||||
primaryIdentityKeys: null, | primaryIdentityKeys: null, | ||||
notificationIdentityKeys: null, | notificationIdentityKeys: null, | ||||
}, | }, | ||||
pushApiPublicKey: pushApiPublicKeyPromise, | pushApiPublicKey: pushApiPublicKeyPromise, | ||||
_persist: null, | _persist: null, | ||||
commServicesAccessToken: null, | commServicesAccessToken: null, | ||||
}); | }); | ||||
const jsonStream = streamJSON(res, initialReduxState); | const validatedInitialReduxState = validateOutput( | ||||
viewer, | |||||
initialReduxStateValidator, | |||||
initialReduxState, | |||||
); | |||||
const jsonStream = streamJSON(res, validatedInitialReduxState); | |||||
await waitForStream(jsonStream); | await waitForStream(jsonStream); | ||||
res.end(html` | res.end(html` | ||||
; | ; | ||||
var baseURL = "${baseURL}"; | var baseURL = "${baseURL}"; | ||||
var olmFilename = "${olmFilename}"; | var olmFilename = "${olmFilename}"; | ||||
var sqljsFilename = "${sqljsFilename}"; | var sqljsFilename = "${sqljsFilename}"; | ||||
var opaqueURL = "${opaqueURL}"; | var opaqueURL = "${opaqueURL}"; | ||||
▲ Show 20 Lines • Show All 192 Lines • Show Last 20 Lines |