{
const appURLFacts = getAppURLFactsFromRequestURL(req.originalUrl);
const { basePath, baseDomain } = appURLFacts;
const baseURL = basePath.replace(/\/$/, '');
const baseHref = baseDomain + baseURL;
const loadingPromise = getWebpackCompiledRootComponentForSSR();
const hasNotAcknowledgedPoliciesPromise = hasAnyNotAcknowledgedPolicies(
viewer.id,
baseLegalPolicies,
);
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 }, hasNotAcknowledgedPolicies] = await Promise.all([
threadInfoPromise,
hasNotAcknowledgedPoliciesPromise,
]);
return { threadInfos: hasNotAcknowledgedPolicies ? {} : threadInfos };
})();
const messageStorePromise = (async () => {
const [
{ threadInfos },
{ rawMessageInfos, truncationStatuses },
hasNotAcknowledgedPolicies,
] = await Promise.all([
threadInfoPromise,
messageInfoPromise,
hasNotAcknowledgedPoliciesPromise,
]);
if (hasNotAcknowledgedPolicies) {
return {
messages: {},
threads: {},
local: {},
currentAsOf: 0,
};
}
const { messageStore: freshStore } = freshMessageStore(
rawMessageInfos,
truncationStatuses,
mostRecentMessageTimestamp(rawMessageInfos, initialTime),
threadInfos,
);
return freshStore;
})();
const entryStorePromise = (async () => {
const [{ rawEntryInfos }, hasNotAcknowledgedPolicies] = await Promise.all([
entryInfoPromise,
hasNotAcknowledgedPoliciesPromise,
]);
if (hasNotAcknowledgedPolicies) {
return {
entryInfos: {},
daysToEntries: {},
lastUserInteractionCalendar: 0,
};
}
return {
entryInfos: _keyBy('id')(rawEntryInfos),
daysToEntries: daysToEntriesFromEntryInfos(rawEntryInfos),
lastUserInteractionCalendar: initialTime,
};
})();
const userStorePromise = (async () => {
const [userInfos, hasNotAcknowledgedPolicies] = await Promise.all([
userInfoPromise,
hasNotAcknowledgedPoliciesPromise,
]);
return {
userInfos: hasNotAcknowledgedPolicies ? {} : 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, currentUserInfo.id]
.map(id => {
const userInfo = userInfos[id];
if (!userInfo || !userInfo.username) {
return undefined;
}
const { username } = userInfo;
return { id, username };
})
.filter(Boolean);
const newPendingThread = createPendingThread({
viewerID: currentUserInfo.id,
threadType: pendingThreadData.threadType,
members,
});
finalNavInfo.activeChatThreadID = newPendingThread.id;
finalNavInfo.pendingThread = newPendingThread;
}
}
return finalNavInfo;
})();
const currentAsOfPromise = (async () => {
const hasNotAcknowledgedPolicies = await hasNotAcknowledgedPoliciesPromise;
return hasNotAcknowledgedPolicies ? 0 : initialTime;
})();
const pushApiPublicKeyPromise = (async () => {
const pushConfig = await getWebPushConfig();
if (!pushConfig) {
if (process.env.NODE_ENV !== 'development') {
console.warn('keyserver/secrets/web_push_config.json should exist');
}
return null;
}
return pushConfig.publicKey;
})();
- const { jsURL, fontsURL, cssInclude } = await assetInfoPromise;
+ const { jsURL, fontsURL, cssInclude, olmFilename } = 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/web/account/log-in-form.react.js b/web/account/log-in-form.react.js
index 7fccadb6f..6591dc5d4 100644
--- a/web/account/log-in-form.react.js
+++ b/web/account/log-in-form.react.js
@@ -1,129 +1,130 @@
// @flow
import olm from '@matrix-org/olm';
import { useConnectModal } from '@rainbow-me/rainbowkit';
import * as React from 'react';
import { useDispatch } from 'react-redux';
import uuid from 'uuid';
import { useSigner } from 'wagmi';
import css from './log-in-form.css';
import SIWEButton from './siwe-button.react.js';
import SIWELoginForm from './siwe-login-form.react.js';
import TraditionalLoginForm from './traditional-login-form.react.js';
import OrBreak from '../components/or-break.react.js';
+import { initOlm } from '../olm/olm-utils.js';
import {
setPrimaryIdentityKeys,
setNotificationIdentityKeys,
setPickledPrimaryAccount,
setPickledNotificationAccount,
} from '../redux/crypto-store-reducer.js';
import { useSelector } from '../redux/redux-utils.js';
function LoginForm(): React.Node {
const { openConnectModal } = useConnectModal();
const { data: signer } = useSigner();
const dispatch = useDispatch();
const primaryIdentityPublicKeys = useSelector(
state => state.cryptoStore.primaryIdentityKeys,
);
const notificationIdentityPublicKeys = useSelector(
state => state.cryptoStore.notificationIdentityKeys,
);
React.useEffect(() => {
(async () => {
if (
primaryIdentityPublicKeys !== null &&
primaryIdentityPublicKeys !== undefined &&
notificationIdentityPublicKeys !== null &&
notificationIdentityPublicKeys !== undefined
) {
return;
}
- await olm.init();
+ await initOlm();
const identityAccount = new olm.Account();
identityAccount.create();
const { ed25519: identityED25519, curve25519: identityCurve25519 } =
JSON.parse(identityAccount.identity_keys());
dispatch({
type: setPrimaryIdentityKeys,
payload: { ed25519: identityED25519, curve25519: identityCurve25519 },
});
const identityAccountPicklingKey = uuid.v4();
const pickledIdentityAccount = identityAccount.pickle(
identityAccountPicklingKey,
);
dispatch({
type: setPickledPrimaryAccount,
payload: {
picklingKey: identityAccountPicklingKey,
pickledAccount: pickledIdentityAccount,
},
});
const notificationAccount = new olm.Account();
notificationAccount.create();
const {
ed25519: notificationED25519,
curve25519: notificationCurve25519,
} = JSON.parse(notificationAccount.identity_keys());
dispatch({
type: setNotificationIdentityKeys,
payload: {
ed25519: notificationED25519,
curve25519: notificationCurve25519,
},
});
const notificationAccountPicklingKey = uuid.v4();
const pickledNotificationAccount = notificationAccount.pickle(
notificationAccountPicklingKey,
);
dispatch({
type: setPickledNotificationAccount,
payload: {
picklingKey: notificationAccountPicklingKey,
pickledAccount: pickledNotificationAccount,
},
});
})();
}, [dispatch, notificationIdentityPublicKeys, primaryIdentityPublicKeys]);
const [siweAuthFlowSelected, setSIWEAuthFlowSelected] =
React.useState(false);
const onSIWEButtonClick = React.useCallback(() => {
setSIWEAuthFlowSelected(true);
openConnectModal && openConnectModal();
}, [openConnectModal]);
const cancelSIWEAuthFlow = React.useCallback(() => {
setSIWEAuthFlowSelected(false);
}, []);
if (siweAuthFlowSelected && signer) {
return (
);
}
return (
);
}
export default LoginForm;
diff --git a/web/olm/olm-utils.js b/web/olm/olm-utils.js
new file mode 100644
index 000000000..5e5d6a652
--- /dev/null
+++ b/web/olm/olm-utils.js
@@ -0,0 +1,16 @@
+// @flow
+
+import olm from '@matrix-org/olm';
+
+declare var olmFilename: string;
+
+async function initOlm(): Promise {
+ if (!olmFilename) {
+ return await olm.init();
+ }
+ const locateFile = (wasmFilename: string, httpAssetsHost: string) =>
+ httpAssetsHost + olmFilename;
+ return await olm.init({ locateFile });
+}
+
+export { initOlm };
diff --git a/web/webpack.config.cjs b/web/webpack.config.cjs
index 629301879..c2411b254 100644
--- a/web/webpack.config.cjs
+++ b/web/webpack.config.cjs
@@ -1,126 +1,133 @@
const AssetsPlugin = require('assets-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const path = require('path');
const {
createProdBrowserConfig,
createDevBrowserConfig,
createNodeServerRenderingConfig,
createWebWorkersConfig,
} = require('lib/webpack/shared.cjs');
const babelConfig = require('./babel.config.cjs');
const baseBrowserConfig = {
- plugins: [
- new CopyPlugin({
- patterns: [
- {
- from: 'node_modules/@matrix-org/olm/olm.wasm',
- to: path.join(__dirname, 'dist'),
- },
- ],
- }),
- ],
entry: {
browser: ['./script.js'],
},
output: {
filename: 'prod.[contenthash:12].build.js',
path: path.join(__dirname, 'dist'),
},
resolve: {
alias: {
'../images': path.resolve('../keyserver/images'),
},
fallback: {
crypto: false,
fs: false,
path: false,
},
},
};
const baseDevBrowserConfig = {
...baseBrowserConfig,
output: {
...baseBrowserConfig.output,
filename: 'dev.build.js',
pathinfo: true,
publicPath: 'http://localhost:8080/',
},
devServer: {
port: 8080,
headers: { 'Access-Control-Allow-Origin': '*' },
allowedHosts: ['all'],
host: '0.0.0.0',
static: {
directory: path.join(__dirname, 'dist'),
},
},
+ plugins: [
+ new CopyPlugin({
+ patterns: [
+ {
+ from: 'node_modules/@matrix-org/olm/olm.wasm',
+ to: path.join(__dirname, 'dist'),
+ },
+ ],
+ }),
+ ],
};
const baseProdBrowserConfig = {
...baseBrowserConfig,
plugins: [
- ...baseBrowserConfig.plugins,
+ new CopyPlugin({
+ patterns: [
+ {
+ from: 'node_modules/@matrix-org/olm/olm.wasm',
+ to: path.join(__dirname, 'dist', 'olm.[contenthash:12].wasm'),
+ },
+ ],
+ }),
new AssetsPlugin({
filename: 'assets.json',
path: path.join(__dirname, 'dist'),
removeFullPathAutoPrefix: true,
}),
],
};
const baseNodeServerRenderingConfig = {
externals: ['react', 'react-dom', 'react-redux'],
entry: {
keyserver: ['./loading.react.js'],
},
output: {
filename: 'app.build.cjs',
library: 'app',
libraryTarget: 'commonjs2',
path: path.join(__dirname, 'dist'),
},
};
const baseWebWorkersConfig = {
plugins: [
new CopyPlugin({
patterns: [
{
from: 'node_modules/sql.js/dist/sql-wasm.wasm',
to: path.join(__dirname, 'dist'),
},
],
}),
],
entry: {
pushNotif: './push-notif/service-worker.js',
},
output: {
filename: '[name].build.js',
path: path.join(__dirname, 'dist', 'webworkers'),
},
};
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',
};
const webWorkersConfig = createWebWorkersConfig(
env,
baseWebWorkersConfig,
babelConfig,
);
return [browserConfig, nodeServerRenderingConfig, webWorkersConfig];
};