{
const appURLFacts = getAppURLFactsFromRequestURL(req.originalUrl);
const { basePath, baseDomain } = appURLFacts;
const baseURL = basePath.replace(/\/$/, '');
const baseHref = baseDomain + baseURL;
const loadingPromise = getWebpackCompiledRootComponentForSSR();
let initialNavInfo;
try {
initialNavInfo = navInfoFromURL(req.url, {
now: currentDateInTimeZone(viewer.timeZone),
});
} catch (e) {
throw new ServerError(e.message);
}
const calendarQuery = {
startDate: initialNavInfo.startDate,
endDate: initialNavInfo.endDate,
filters: defaultCalendarFilters,
};
const messageSelectionCriteria = { joinedThreads: true };
const initialTime = Date.now();
const assetInfoPromise = getAssetInfo();
const threadInfoPromise = fetchThreadInfos(viewer);
const messageInfoPromise = fetchMessageInfos(
viewer,
messageSelectionCriteria,
defaultNumberPerThread,
);
const entryInfoPromise = fetchEntryInfos(viewer, [calendarQuery]);
const currentUserInfoPromise = fetchCurrentUserInfo(viewer);
const userInfoPromise = fetchKnownUserInfos(viewer);
const sessionIDPromise = (async () => {
if (viewer.loggedIn) {
await setNewSession(viewer, calendarQuery, initialTime);
}
return viewer.sessionID;
})();
const threadStorePromise = (async () => {
const { threadInfos } = await threadInfoPromise;
return { threadInfos };
})();
const messageStorePromise = (async () => {
const [
{ threadInfos },
{ rawMessageInfos, truncationStatuses },
] = await Promise.all([threadInfoPromise, messageInfoPromise]);
const { messageStore: freshStore } = freshMessageStore(
rawMessageInfos,
truncationStatuses,
mostRecentMessageTimestamp(rawMessageInfos, initialTime),
threadInfos,
);
return freshStore;
})();
const entryStorePromise = (async () => {
const { rawEntryInfos } = await entryInfoPromise;
return {
entryInfos: _keyBy('id')(rawEntryInfos),
daysToEntries: daysToEntriesFromEntryInfos(rawEntryInfos),
lastUserInteractionCalendar: initialTime,
};
})();
const userStorePromise = (async () => {
const userInfos = await userInfoPromise;
return { userInfos, inconsistencyReports: [] };
})();
const navInfoPromise = (async () => {
const [
{ threadInfos },
messageStore,
currentUserInfo,
userStore,
] = await Promise.all([
threadInfoPromise,
messageStorePromise,
currentUserInfoPromise,
userStorePromise,
]);
const finalNavInfo = initialNavInfo;
const requestedActiveChatThreadID = finalNavInfo.activeChatThreadID;
if (
requestedActiveChatThreadID &&
!threadIsPending(requestedActiveChatThreadID) &&
!threadHasPermission(
threadInfos[requestedActiveChatThreadID],
threadPermissions.VISIBLE,
)
) {
finalNavInfo.activeChatThreadID = null;
}
if (!finalNavInfo.activeChatThreadID) {
const mostRecentThread = mostRecentlyReadThread(
messageStore,
threadInfos,
);
if (mostRecentThread) {
finalNavInfo.activeChatThreadID = mostRecentThread;
}
}
if (
finalNavInfo.activeChatThreadID &&
threadIsPending(finalNavInfo.activeChatThreadID) &&
finalNavInfo.pendingThread?.id !== finalNavInfo.activeChatThreadID
) {
const pendingThreadData = parsePendingThreadID(
finalNavInfo.activeChatThreadID,
);
if (
pendingThreadData &&
pendingThreadData.threadType !== threadTypes.SIDEBAR &&
currentUserInfo.id
) {
const { userInfos } = userStore;
const members = pendingThreadData.memberIDs
.map(id => userInfos[id])
.filter(Boolean);
const newPendingThread = createPendingThread({
viewerID: currentUserInfo.id,
threadType: pendingThreadData.threadType,
members,
});
finalNavInfo.activeChatThreadID = newPendingThread.id;
finalNavInfo.pendingThread = newPendingThread;
}
}
return finalNavInfo;
})();
const { jsURL, fontsURL, cssInclude } = await assetInfoPromise;
// prettier-ignore
res.write(html`
${getTitle(0)}
${cssInclude}
`);
const Loading = await loadingPromise;
const reactStream = renderToNodeStream();
reactStream.pipe(res, { end: false });
await waitForStream(reactStream);
res.write(html`
`);
}
export { websiteResponder };
diff --git a/keyserver/src/utils/json-stream.js b/keyserver/src/utils/json-stream.js
index 2586fc483..db4931ea5 100644
--- a/keyserver/src/utils/json-stream.js
+++ b/keyserver/src/utils/json-stream.js
@@ -1,51 +1,50 @@
// @flow
import type { $Response } from 'express';
import JSONStream from 'JSONStream';
import replaceStream from 'replacestream';
import Combine from 'stream-combiner';
-type Promisable = Promise | T;
-function streamJSON }>(
+function streamJSON(
res: $Response,
- input: T,
+ input: { +[key: string]: mixed },
): stream$Readable {
const jsonStream = Combine(
JSONStream.stringifyObject('{', ',', '}'),
replaceStream(/ }>(
+function resolvePromisesToStream(
stream: { +write: ([string, mixed]) => mixed, +end: () => mixed, ... },
- input: T,
+ input: { +[key: string]: mixed },
) {
const blocking = [];
for (const key in input) {
const value = input[key];
if (value instanceof Promise) {
blocking.push(
(async () => {
const result = await value;
stream.write([key, result]);
})(),
);
} else {
stream.write([key, value]);
}
}
Promise.all(blocking).then(() => {
stream.end();
});
}
function waitForStream(readable: stream$Readable): Promise {
return new Promise(r => {
readable.on('end', r);
});
}
export { streamJSON, waitForStream };