diff --git a/services/web/.eslintrc.js b/services/web/.eslintrc.js index 1ad4157ef1..0a45fde7a8 100644 --- a/services/web/.eslintrc.js +++ b/services/web/.eslintrc.js @@ -308,6 +308,7 @@ module.exports = { 'scripts/delete_orphaned_chat_threads.mjs', 'scripts/delete_orphaned_data_helper.mjs', 'scripts/delete_subscriptions.mjs', + 'scripts/devcontainer_setup.mjs', 'scripts/e2e_test_setup.mjs', 'scripts/ensure_affiliations.mjs', 'scripts/esm-check-migration.mjs', diff --git a/services/web/app/views/_metadata.pug b/services/web/app/views/_metadata.pug index c63437e9bf..ced95f89fa 100644 --- a/services/web/app/views/_metadata.pug +++ b/services/web/app/views/_metadata.pug @@ -147,4 +147,8 @@ if metadata && metadata.canonicalURL //- Manifest //- Does not currently contain a start_url to prevent browser installation prompts -link(rel='manifest' href=buildBaseAssetPath() + 'web.sitemanifest') +link( + rel='manifest' + href=buildBaseAssetPath() + 'web.sitemanifest.json' + crossorigin=settings.isCodeSpace ? 'use-credentials' : false +) diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index 2a73f2dff2..0d0e89cdd7 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -327,6 +327,8 @@ module.exports = { // that are sent out, generated links, etc. siteUrl: (siteUrl = process.env.PUBLIC_URL || 'http://127.0.0.1:3000'), + isCodeSpace: process.env.IS_CODE_SPACE === 'true', + lockManager: { lockTestInterval: intFromEnv('LOCK_MANAGER_LOCK_TEST_INTERVAL', 50), maxTestInterval: intFromEnv('LOCK_MANAGER_MAX_TEST_INTERVAL', 1000), diff --git a/services/web/package.json b/services/web/package.json index 7853804cc1..19da285d0f 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -48,8 +48,8 @@ "build-storybook": "storybook build", "build-unfilled-fonts": "node frontend/fonts/material-symbols/build-unfilled.mjs", "precompile-pug": "node app/src/infrastructure/Views.mjs", - "local:nodemon": "set -a;. ../../config/dev-environment.env;. ./docker-compose.common.env;. ../../config/local-dev.env;. ./local-dev.env;. ../../config/local.env; set +a; echo $OVERLEAF_CONFIG; WEB_PORT=13000 LISTEN_ADDRESS=0.0.0.0 npm run nodemon", - "local:webpack": "set -a;. ../../config/dev-environment.env;. ./docker-compose.common.env;. ../../config/local-dev.env;. ./local-dev.env;. ../../config/local.env; set +a; PORT=13808 OVERLEAF_CONFIG=$(pwd)/config/settings.webpack.js npm run webpack", + "local:nodemon": "set -a;. ../../config/dev-environment.env;. ./docker-compose.common.env;. ../../config/local-dev.env;. ./local-dev.env;. ../../config/local.env;. ../../config/domain.env; set +a; echo $OVERLEAF_CONFIG; WEB_PORT=13000 LISTEN_ADDRESS=0.0.0.0 npm run nodemon", + "local:webpack": "set -a;. ../../config/dev-environment.env;. ./docker-compose.common.env;. ../../config/local-dev.env;. ./local-dev.env;. ../../config/local.env;. ../../config/domain.env; set +a; PORT=13808 OVERLEAF_CONFIG=$(pwd)/config/settings.webpack.js npm run webpack", "local:test:acceptance:run_dir": "set -a;. $(pwd)/docker-compose.common.env;. $(pwd)/local-test.env; set +a; npm run test:acceptance:run_dir", "local:test:acceptance:run_app": "OVERLEAF_CONFIG=$(pwd)/test/acceptance/config/settings.test.${OVERLEAF_APP}.js npm run local:test:acceptance:run_dir -- $(pwd)/test/acceptance/src", "local:test:acceptance:run_module": "if [ ! -d $(pwd)/modules/${MODULE}/test/acceptance ]; then echo \"Module '${MODULE}' does not have acceptance tests\"; exit 0; fi; OVERLEAF_CONFIG=$(pwd)/modules/${MODULE}/test/acceptance/config/settings.test.js BASE_CONFIG=$(pwd)/test/acceptance/config/settings.test.${OVERLEAF_APP}.js npm run local:test:acceptance:run_dir -- $(pwd)/modules/${MODULE}/test/acceptance", diff --git a/services/web/public/web.sitemanifest b/services/web/public/web.sitemanifest deleted file mode 100644 index 2e78ffcccc..0000000000 --- a/services/web/public/web.sitemanifest +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "Overleaf", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#1b222c", - "background_color": "#ffffff", - "display": "standalone" -} diff --git a/services/web/public/web.sitemanifest.json b/services/web/public/web.sitemanifest.json new file mode 100644 index 0000000000..a698a6bad6 --- /dev/null +++ b/services/web/public/web.sitemanifest.json @@ -0,0 +1,18 @@ +{ + "name": "Overleaf", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#1b222c", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/services/web/scripts/devcontainer_setup.mjs b/services/web/scripts/devcontainer_setup.mjs new file mode 100644 index 0000000000..3e6d0bed1a --- /dev/null +++ b/services/web/scripts/devcontainer_setup.mjs @@ -0,0 +1,92 @@ +import { promiseMapWithLimit } from '@overleaf/promise-utils' +import Settings from '@overleaf/settings' +import { waitForDb, db } from '../app/src/infrastructure/mongodb.mjs' +import GracefulShutdown from '../app/src/infrastructure/GracefulShutdown.mjs' +import UserRegistrationHandler from '../app/src/Features/User/UserRegistrationHandler.mjs' +import minimist from 'minimist' +import { + createProjectWithOldHistoryId, + provisionSplitTests, +} from './e2e_test_setup.mjs' + +const { email: USER_EMAIL, password: PASSWORD } = minimist( + process.argv.slice(2), + { string: ['email', 'password'] } +) + +/** + * @param {string} email + * @return {Promise} + */ +async function createUser(email) { + let userId + try { + const user = await UserRegistrationHandler.promises.registerNewUser({ + email, + password: PASSWORD, + }) + userId = user._id + } catch (err) { + if (err.message.includes('EmailAlreadyRegistered')) { + userId = err.info.userId + } else { + throw err + } + } + const features = email.startsWith('free') + ? Settings.defaultFeatures + : Settings.features.professional + const isAdmin = email === USER_EMAIL || email === 'admin@overleaf.com' + let adminRoles = [] + if (isAdmin) { + adminRoles = ['engineering'] + } + await db.users.updateOne( + { _id: userId }, + { + $set: { + // Set admin flag. + isAdmin, + adminRoles, + // Override features. + features, + featuresOverrides: [{ features }], + // disable AI features, does not work with custom GH Code Spaces domain. + 'aiFeatures.enabled': false, + }, + } + ) + return userId.toString() +} + +/** + * @param {string} email + * @return {Promise} + */ +async function provisionUser(email) { + const userId = await createUser(email) + await createProjectWithOldHistoryId(userId) +} + +async function provisionUsers() { + const emails = [ + USER_EMAIL, + 'admin@overleaf.com', + 'free@overleaf.com', + 'premium@overleaf.com', + ] + await promiseMapWithLimit(5, emails, provisionUser) +} + +async function main() { + if (process.env.NODE_ENV !== 'development') { + throw new Error('only available in dev-env') + } + await waitForDb() + await Promise.all([provisionUsers(), provisionSplitTests()]) +} + +if (import.meta.main) { + await main() + await GracefulShutdown.gracefulShutdown() +} diff --git a/services/web/scripts/e2e_test_setup.mjs b/services/web/scripts/e2e_test_setup.mjs index 52bd39a67f..7c1a13d5fe 100644 --- a/services/web/scripts/e2e_test_setup.mjs +++ b/services/web/scripts/e2e_test_setup.mjs @@ -85,7 +85,7 @@ async function deleteUser(email) { await UserDeleter.promises.expireDeletedUser(user._id) } -async function createProjectWithOldHistoryId(userId) { +export async function createProjectWithOldHistoryId(userId) { const projectName = 'old history id' const historyId = parseInt( await HistoryManager.promises.initializeProject(), @@ -137,7 +137,7 @@ async function purgeNewUsers() { ) } -async function provisionSplitTests() { +export async function provisionSplitTests() { const backup = Path.join( MONOREPO, 'backup', @@ -213,12 +213,7 @@ async function main() { await Promise.all([purgeNewUsers(), provisionUsers(), provisionSplitTests()]) } -await main() -await GracefulShutdown.gracefulShutdown( - { - close(cb) { - cb() - }, - }, - 'SIGTERM' -) +if (import.meta.main) { + await main() + await GracefulShutdown.gracefulShutdown() +}