{
const appURLFacts = getAppURLFactsFromRequestURL(req.originalUrl);
const { basePath, baseDomain } = appURLFacts;
const baseURL = basePath.replace(/\/$/, '');
const baseHref = baseDomain + baseURL;
- const appPromise = getWebpackCompiledRootComponentForSSR();
+ 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 statePromises = {
+ const Loading = await loadingPromise;
+ const reactStream = renderToNodeStream();
+ reactStream.pipe(res, { end: false });
+
+ await waitForStream(reactStream);
+ res.write(html`
+
+
`);
}
export { websiteResponder };
diff --git a/web/webpack.config.cjs b/web/webpack.config.cjs
index d17a12ea4..21164aab7 100644
--- a/web/webpack.config.cjs
+++ b/web/webpack.config.cjs
@@ -1,82 +1,82 @@
const AssetsPlugin = require('assets-webpack-plugin');
const path = require('path');
const {
createProdBrowserConfig,
createDevBrowserConfig,
createNodeServerRenderingConfig,
} = require('lib/webpack/shared.cjs');
const babelConfig = require('./babel.config.cjs');
const baseBrowserConfig = {
entry: {
browser: ['./script.js'],
},
output: {
filename: 'prod.[hash:12].build.js',
path: path.join(__dirname, 'dist'),
},
resolve: {
alias: {
'../images': path.resolve('../keyserver/images'),
},
},
};
const baseDevBrowserConfig = {
...baseBrowserConfig,
output: {
...baseBrowserConfig.output,
filename: 'dev.build.js',
pathinfo: true,
publicPath: 'http://localhost:8080/',
},
devServer: {
hot: true,
port: 8080,
contentBase: path.join(__dirname, 'dist'),
headers: { 'Access-Control-Allow-Origin': '*' },
allowedHosts: ['all'],
host: '0.0.0.0',
},
};
const baseProdBrowserConfig = {
...baseBrowserConfig,
plugins: [
new AssetsPlugin({
filename: 'assets.json',
path: path.join(__dirname, 'dist'),
}),
],
};
const baseNodeServerRenderingConfig = {
externals: ['react', 'react-dom', 'react-redux'],
entry: {
- keyserver: ['./app.react.js'],
+ keyserver: ['./loading.react.js'],
},
output: {
filename: 'app.build.cjs',
library: 'app',
libraryTarget: 'commonjs2',
path: path.join(__dirname, 'dist'),
},
};
module.exports = function (env) {
const browserConfig =
env === 'prod'
? createProdBrowserConfig(baseProdBrowserConfig, babelConfig)
: createDevBrowserConfig(baseDevBrowserConfig, babelConfig);
const nodeConfig = createNodeServerRenderingConfig(
baseNodeServerRenderingConfig,
babelConfig,
);
const nodeServerRenderingConfig = {
...nodeConfig,
mode: env === 'prod' ? 'production' : 'development',
};
return [browserConfig, nodeServerRenderingConfig];
};