diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..ae75afa0f --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,14 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.224.3/containers/typescript-node/.devcontainer/base.Dockerfile + +# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster +ARG VARIANT="16-bullseye" +FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT} + +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends git-lfs + +ARG EXTRA_NODE_VERSION=16.15.0 +RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" + +# [Optional] Uncomment if you want to install more global node packages +# RUN su node -c "npm install -g " diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 000000000..0639084b5 --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,47 @@ +# Setup + +* vscode will start container capable of builds + * following normal instructions in ../CONTRIBUTING.md +* asdf plugin add nodejs +* asdf install nodejs 16.13.2 +* asdf global nodejs 16.13.2 +* copy config/production.json settings into development.json +* yarn start + +REMEMBER: You can use the NODE_APP_INSTANCE env var to point to different local config files! +* the runtime uses the profile name as a way to look up the associated config + * e.g. "alice" loads "local-alice.json" and uses ~/.config/Signal.alice as the runtime config directory + +## Registering Standalone + +* following CONTRIBUTING.md section +* to avoid opening real signal app when solving captcha, open devtools and copy the "signalcaptcha://signal-recaptcha-v2.6..." URL and pass it along to the dev version + * node_modules/.bin/electron . "signalcaptcha://signal-recaptcha-v2.6..." + +## building macos + +* install xcode (and command line tools) +* yarn build will not work over ssh!! + * needs access to running display, etc. for something + * just dev'ing w/ yarn generate && yarn build:webpack work fine, tho + +## building windows + +* install [nvm-windows](https://github.com/coreybutler/nvm-windows) + * nvm install 16.13.2 + * do NOT install build tools per contributing docs! they install an old python + * install python3 + * install visual studio + * be sure to include C++ build support! + * setting envvar for pointing to config: `$env:NODE_APP_INSTANCE = 'win10'` + +## signing + +* this will generate a keypair (public/private.key files in root): `node ts/updater/generateKeyPair.js` + * **REMEMBER!!!!** the *public* key needs to be placed in the config/production.json + * **ALSO!!!!** the signatures embed the version in them, so if you try generating a fake upgrade you must temporarily change the package.json version + * or other configs, if running locally +* this will sign the built release: `yarn run sign-release` + * only the exe and zips are signed (since dmgs have their own built-in signatures) + * to force signing of dmgs (in the case where they aren't signed themselves, signal will expect a sig file): + * `for f in release/*.dmg; do node ts/updater/generateSignature.js -u $f; done` \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..6e5786e73 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,35 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.224.3/containers/typescript-node +{ + "name": "Node.js & TypeScript", + "build": { + "dockerfile": "Dockerfile", + // Update 'VARIANT' to pick a Node version: 16, 14, 12. + // Append -bullseye or -buster to pin to an OS version. + // Use -bullseye variants on local on arm64/Apple Silicon. + "args": { + "VARIANT": "16-bullseye" + } + }, + + // Set *default* container specific settings.json values on container create. + "settings": {}, + + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "dbaeumer.vscode-eslint", + "redhat.vscode-yaml", + "esbenp.prettier-vscode", + "foxundermoon.shell-format" + ], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "yarn install", + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "node" +} diff --git a/.eslintignore b/.eslintignore index 117780711..978287f9a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,7 @@ components/** coverage/** dist/** release/** +deploy/scripts/* # Github workflows .github/** diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 3c2fd26c2..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright 2020 Signal Messenger, LLC -# SPDX-License-Identifier: AGPL-3.0-only - -custom: https://signal.org/donate/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c25c63a8b..95b9c9618 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,14 +1,12 @@ -# Copyright 2020-2022 Signal Messenger, LLC -# SPDX-License-Identifier: AGPL-3.0-only - name: CI on: push: branches: - - development - main - - '[0-9]+.[0-9]+.x' - pull_request: + paths-ignore: + - deploy/wrangler.toml + - deploy/src/** + workflow_dispatch: jobs: lint: @@ -40,11 +38,17 @@ jobs: # - run: yarn lint-license-comments - run: git diff --exit-code + - uses: sarisia/actions-status-discord@v1 + if: ${{ always() && !success() }} + with: + webhook: ${{ secrets.DISCORD_ACTION_URL }} + title: ${{ github.workflow }} lint + macos: needs: lint - runs-on: macos-latest - if: github.ref == 'refs/heads/development' || github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' - timeout-minutes: 30 + runs-on: macos-11 + if: ${{ github.repository == '3numdao/Signal.web3-Desktop' }} + timeout-minutes: 45 steps: - run: uname -a @@ -78,6 +82,12 @@ jobs: env: NODE_ENV: production + - uses: sarisia/actions-status-discord@v1 + if: always() + with: + webhook: ${{ secrets.DISCORD_ACTION_URL }} + title: ${{ runner.os }} ${{ github.workflow }} + linux: needs: lint runs-on: ubuntu-latest @@ -118,10 +128,17 @@ jobs: env: NODE_ENV: production + - uses: sarisia/actions-status-discord@v1 + if: always() + with: + webhook: ${{ secrets.DISCORD_ACTION_URL }} + title: ${{ runner.os }} ${{ github.workflow }} + windows: needs: lint runs-on: windows-latest timeout-minutes: 30 + if: ${{ github.repository == '3numdao/Signal.web3-Desktop' }} steps: - run: systeminfo @@ -158,10 +175,16 @@ jobs: env: SIGNAL_ENV: production + - uses: sarisia/actions-status-discord@v1 + if: always() + with: + webhook: ${{ secrets.DISCORD_ACTION_URL }} + title: ${{ runner.os }} ${{ github.workflow }} + mock-tests: needs: lint runs-on: ubuntu-latest - if: ${{ github.repository == 'signalapp/Signal-Desktop-Private' }} + if: ${{ false }} # ${{ github.repository == '3numdao/Signal-Desktop-private' }} timeout-minutes: 30 steps: @@ -215,3 +238,9 @@ jobs: with: name: logs path: artifacts + + - uses: sarisia/actions-status-discord@v1 + if: always() + with: + webhook: ${{ secrets.DISCORD_ACTION_URL }} + title: ${{ github.workflow }} mock-tests diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..f3ff0e6db --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,157 @@ +name: Release +on: + push: + tags: + - "v*.*.*-enum.*" + +jobs: + create: + runs-on: ubuntu-latest + steps: + - id: release + uses: softprops/action-gh-release@v1 + with: + generate_release_notes: false + - uses: sarisia/actions-status-discord@v1 + if: ${{ always() && !success() }} + with: + webhook: ${{ secrets.DISCORD_ACTION_URL }} + title: ${{ github.workflow }} create + + build: + needs: create + + strategy: + fail-fast: false # let others run to completion + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + include: + - os: ubuntu-latest + release_suffix: ubuntu + - os: macos-latest + release_suffix: mac + - os: windows-latest + release_suffix: windows + + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + + steps: + - name: Install Apple certificates + if: matrix.os == 'macos-latest' + env: + APP_CERT_BASE64: ${{ secrets.APP_CERT_BASE64 }} + INSTALL_CERT_BASE64: ${{ secrets.INSTALL_CERT_BASE64 }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + # create variables + APP_CERT_PATH=$RUNNER_TEMP/app_cert.p12 + INSTALL_CERT_PATH=$RUNNER_TEMP/install_cert.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + # import certificate and provisioning profile from secrets + echo -n "$APP_CERT_BASE64" | base64 --decode --output $APP_CERT_PATH + echo -n "$INSTALL_CERT_BASE64" | base64 --decode --output $INSTALL_CERT_PATH + + # create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # import certificate to keychain + security import $APP_CERT_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security import $INSTALL_CERT_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + - name: Prepare Windows checkout + if: matrix.os == 'windows-latest' + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16.15.0' + - run: npm install -g yarn@1.22.10 + + - name: Cache Desktop node_modules + id: cache-desktop-modules + uses: actions/cache@v3 + with: + path: node_modules + key: ${{ runner.os }}-${{ hashFiles('package.json', 'yarn.lock', 'patches/**') }} + - name: Install Desktop node_modules + if: steps.cache-desktop-modules.outputs.cache-hit != 'true' + run: yarn install --frozen-lockfile + + - run: yarn generate + env: + TELEMETRY_API_KEY: ${{ secrets.TELEMETRY_API_KEY }} + - run: yarn prepare-beta-build + + - name: Run yarn build (non-windows) + run: yarn build + if: matrix.os != 'windows-latest' + env: + DISABLE_INSPECT_FUSE: on + APPLE_USERNAME: ${{ secrets.APPLE_USERNAME }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + + - name: Run yarn build (windows) + if: matrix.os == 'windows-latest' + env: + DISABLE_INSPECT_FUSE: on + CSC_LINK: ${{ secrets.CSC_LINK }} + CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + run: yarn build + + - run: yarn sign-release + env: + UPDATES_PRIVATE_KEY: ${{ secrets.UPDATES_PRIVATE_KEY }} + + - name: Upload binaries for deployment + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + run: | + cd deploy + yarn install + yarn upload + + - uses: actions/upload-artifact@v3 + with: + name: manifests + path: release/latest*.yml + + - uses: softprops/action-gh-release@v1 + with: + files: | + release/*.dmg + release/*.deb + release/*.exe + + - name: Clean up keychain and provisioning profile + if: ${{ always() && matrix.os == 'macos-latest' }} + run: security delete-keychain $RUNNER_TEMP/app-signing.keychain-db + + - uses: sarisia/actions-status-discord@v1 + if: ${{ always() && !success() }} + with: + webhook: ${{ secrets.DISCORD_ACTION_URL }} + title: ${{ runner.os }} ${{ github.workflow }} build + + announce: + needs: build + runs-on: ubuntu-latest + steps: + - id: release + uses: softprops/action-gh-release@v1 + + - uses: sarisia/actions-status-discord@v1 + with: + webhook: ${{ secrets.DISCORD_WEBHOOK_URL }} + nodetail: true + title: Released ${{ steps.release.outputs.url }} diff --git a/.github/workflows/worker.yml b/.github/workflows/worker.yml new file mode 100644 index 000000000..e476ea300 --- /dev/null +++ b/.github/workflows/worker.yml @@ -0,0 +1,36 @@ +name: Worker +on: + push: + branches: + - main + paths: + - deploy/wrangler.toml + - deploy/src/** + workflow_dispatch: + +jobs: + publish: + if: ${{ github.repository == '3numdao/Signal-Desktop-private' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version-file: .nvmrc + cache: yarn + cache-dependency-path: deploy/yarn.lock + + - run: echo '{"api_key":"${{ secrets.TELEMETRY_API_KEY }}"}' > .telemetry.json + + - run: yarn install --frozen-lockfile + working-directory: deploy + + - uses: cloudflare/wrangler-action@2.0.0 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + workingDirectory: deploy + + - uses: sarisia/actions-status-discord@v1 + if: always() + with: + webhook: ${{ secrets.DISCORD_ACTION_URL }} diff --git a/.gitignore b/.gitignore index 279ad12b7..dd962a826 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ stylesheets/*.css /storybook-static/ preload.bundle.* ts/sql/mainWorker.bundle.js.LICENSE.txt +.telemetry.json # React / TypeScript app/*.js diff --git a/_locales/en/messages.json b/_locales/en/messages.json index d07a44f7e..675bf3ced 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -76,7 +76,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Signal", + "message": "Quit Signal.web3", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -188,7 +188,7 @@ "description": "One of the menu options available in the Avatar popup menu" }, "avatarMenuUpdateAvailable": { - "message": "Update Signal", + "message": "Update Signal.web3", "description": "One of the menu options available in the Avatar popup menu" }, "loading": { @@ -744,7 +744,7 @@ "description": "Item under the Help menu, takes you to an article describing how to install the beta release of Signal Desktop" }, "signalDesktopPreferences": { - "message": "Signal Desktop Preferences", + "message": "Signal.web3 Desktop Preferences", "description": "Title of the window that pops up with Signal Desktop preferences in it" }, "signalDesktopStickerCreator": { @@ -752,7 +752,7 @@ "description": "Title of the window that pops up with Signal Desktop preferences in it" }, "aboutSignalDesktop": { - "message": "About Signal Desktop", + "message": "About Signal.web3 Desktop", "description": "Item under the Help menu, which opens a small about window" }, "screenShareWindow": { @@ -776,7 +776,7 @@ "description": "Command in the tray icon menu, to quit the application" }, "signalDesktop": { - "message": "Signal Desktop", + "message": "Signal.web3 Desktop", "description": "Tooltip for the tray icon" }, "search": { @@ -854,7 +854,7 @@ } }, "welcomeToSignal": { - "message": "Welcome to Signal" + "message": "Welcome to Signal.web3" }, "whatsNew": { "message": "See $whatsNew$ in this update", @@ -1007,15 +1007,15 @@ } }, "audioPermissionNeeded": { - "message": "To send audio messages, allow Signal Desktop to access your microphone.", + "message": "To send audio messages, allow Signal.web3 Desktop to access your microphone.", "description": "Shown if the user attempts to send an audio message without audio permissions turned on" }, "audioCallingPermissionNeeded": { - "message": "For calling, you must allow Signal Desktop to access your microphone.", + "message": "For calling, you must allow Signal.web3 Desktop to access your microphone.", "description": "Shown if the user attempts access the microphone for calling without audio permissions turned on" }, "videoCallingPermissionNeeded": { - "message": "For video calling, you must allow Signal Desktop to access your camera.", + "message": "For video calling, you must allow Signal.web3 Desktop to access your camera.", "description": "Shown if the user attempts access the camera for video calling without video permissions turned on" }, "allowAccess": { @@ -1347,7 +1347,7 @@ "description": "Used in the media gallery documents tab to visually represent a file" }, "installWelcome": { - "message": "Welcome to Signal Desktop", + "message": "Welcome to Signal.web3 Desktop", "description": "Welcome title on the install page" }, "installTagline": { @@ -2374,7 +2374,7 @@ "description": "Label for header to go to stories view" }, "contactSearchPlaceholder": { - "message": "Search by name or phone number", + "message": "Search contact, web3 name, or phone #", "description": "Placeholder to use when searching for contacts in the composer" }, "noContactsFound": { @@ -2431,6 +2431,30 @@ } } }, + "startConversation--phone-number-record-not-found": { + "message": "User not found. \"$etherName$\" ($etherAddress$) has not created a phone record.", + "description": "Shown in dialog if phone number record was not found via a translation lookup.", + "placeholders": { + "etherName": { + "content": "$1", + "example": "alice.eth" + }, + "etherAddress": { + "content": "$1", + "example": "0xaaaaa11111bbbbb22222ccccc33333ddddd44444" + } + } + }, + "startConversation--phone-number-record-unregistered": { + "message": "User not found. \"$etherName$\" has not completed their name registration.", + "description": "Shown in dialog if the name was not found registered.", + "placeholders": { + "etherName": { + "content": "$1", + "example": "alice.eth" + } + } + }, "chooseGroupMembers__title": { "message": "Choose members", "description": "The title for the 'choose group members' left pane screen" @@ -2542,7 +2566,7 @@ "message": "contact support" }, "autoUpdateNewVersionMessage": { - "message": "Click to restart Signal" + "message": "Click to restart Signal.web3" }, "downloadNewVersionMessage": { "message": "Click to download update" diff --git a/app/main.ts b/app/main.ts index a95d2af98..f1ab92843 100644 --- a/app/main.ts +++ b/app/main.ts @@ -1058,8 +1058,9 @@ function openReleaseNotes() { } function openSupportPage() { - // If we omit the language, the site will detect the language and redirect - shell.openExternal('https://support.signal.org/hc/sections/360001602812'); + shell.openExternal( + 'https://hail-wisteria-c3e.notion.site/Signal-web3-Alpha-Setup-Support-481b7dad3eae4f76a4e5a2d05815c1e6' + ); } function openForums() { diff --git a/app/menu.ts b/app/menu.ts index d5d96d5ec..3f045dc53 100644 --- a/app/menu.ts +++ b/app/menu.ts @@ -125,12 +125,13 @@ export const createTemplate = ( role: 'togglefullscreen', label: messages.viewMenuToggleFullScreen.message, }, - { - type: 'separator', - }, + // { + // type: 'separator', + // }, { label: messages.debugLog.message, click: showDebugLog, + visible: false, }, ...(devTools ? [ @@ -178,6 +179,7 @@ export const createTemplate = ( { label: messages.contactUs.message, click: openContactUs, + visible: false, }, { label: messages.goToReleaseNotes.message, @@ -186,6 +188,7 @@ export const createTemplate = ( { label: messages.goToForums.message, click: openForums, + visible: false, }, { label: messages.goToSupportPage.message, diff --git a/config/default.json b/config/default.json index 7e127f521..3aa233bd6 100644 --- a/config/default.json +++ b/config/default.json @@ -12,8 +12,8 @@ "2": "https://cdn2-staging.signal.org" }, "contentProxyUrl": "http://contentproxy.signal.org:443", - "updatesUrl": "https://updates2.signal.org/desktop", - "updatesPublicKey": "05fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401", + "updatesUrl": "https://releases.enumdao.workers.dev/desktop", + "updatesPublicKey": "05e73ca58eae67d21704235912e3f3da2e7fac4972db8b971052f53698ea72da14", "sfuUrl": "https://sfu.voip.signal.org/", "updatesEnabled": false, "enableCI": false, diff --git a/deploy/package.json b/deploy/package.json new file mode 100644 index 000000000..2c3846c8b --- /dev/null +++ b/deploy/package.json @@ -0,0 +1,25 @@ +{ + "name": "releases", + "version": "0.0.0", + "devDependencies": { + "@cloudflare/workers-types": "^3.11.0", + "@types/md5": "^2.3.2", + "@types/mime": "^2.0.3", + "@types/node": "^18.0.0", + "arg": "^5.0.2", + "aws-sdk": "^2.1159.0", + "js-yaml": "^4.1.0", + "typescript": "^4.7.3", + "wrangler": "2.0.9" + }, + "private": true, + "scripts": { + "start": "wrangler dev", + "deploy": "wrangler publish", + "upload": "node scripts/upload.js releases" + }, + "dependencies": { + "md5": "^2.3.0", + "mime": "^3.0.0" + } +} diff --git a/deploy/scripts/upload.js b/deploy/scripts/upload.js new file mode 100644 index 000000000..a6f58a1dc --- /dev/null +++ b/deploy/scripts/upload.js @@ -0,0 +1,237 @@ +/* eslint-disable import/no-extraneous-dependencies */ +const process = require('node:process'); +const fs = require('node:fs'); +const path = require('node:path'); + +const { platform } = require('node:os'); +const { createHash } = require('node:crypto'); +const { execSync } = require('node:child_process'); + +const arg = require('arg'); +const AWS = require('aws-sdk'); +const yaml = require('js-yaml'); +const mime = require('mime'); + +const retryDelay = 30; // seconds + +async function main() { + const args = arg({ + '--help': Boolean, + '--dry': Boolean, + '--skip': Boolean, + '--fake': String, + '--arch': String, + }); + + if (args._.length < 1 || args['--help']) { + err( + 1, + 'usage: upload.js [--dry] [--skip] [--fake ] [--arch ] ' + ); + } + + const bucketName = args._[0]; + + const gitTopLevel = execSync('git rev-parse --show-toplevel') + .toString() + .trim(); + + const releaseDir = path.join(gitTopLevel, 'release'); + + // eslint-disable-next-line global-require, import/no-dynamic-require + const { version } = require(path.join(gitTopLevel, 'package.json')); + + const release = version.includes('beta') ? 'beta' : 'latest'; + + let releaseFileBase; + let releaseFileExts; + let primaryExp; + + switch (platform()) { + case 'darwin': + releaseFileBase = `${release}-mac.yml`; + releaseFileExts = ['.dmg', '.zip']; + primaryExp = /-x64-.*.zip$/; + break; + case 'linux': + releaseFileBase = `${release}-linux.yml`; + releaseFileExts = ['.deb']; + primaryExp = /_amd64.deb$/; + break; + case 'win32': + releaseFileBase = `${release}.yml`; + releaseFileExts = ['.exe']; + primaryExp = /.exe$/; + break; + default: + err(2, 'unsupported platform:', platform()); + return; + } + + const fakeVersion = args['--fake']; + const manifest = { + version: fakeVersion || version, + files: [], + path: '', + sha512: '', + releaseDate: new Date(), + }; + + const uploads = []; + const bases = await fs.promises.readdir(releaseDir); + for (let base of bases) { + if (args['--arch'] && !base.includes(args['--arch'])) continue; + if (!base.includes(version)) continue; + + const releaseFile = path.join(releaseDir, base); + if (!args['--skip']) uploads.push(releaseFile); + + const ext = path.extname(base); + if (!releaseFileExts.includes(ext)) continue; + + const stat = fs.statSync(releaseFile); + + // eslint-disable-next-line no-await-in-loop + const sha512 = await getSHA512(releaseFile); + + if (fakeVersion) { + base = base.replace(version, fakeVersion); + } + + const file = { + url: base, + sha512, + size: stat.size, + }; + + // @ts-ignore + manifest.files.push(file); + + if (base.match(primaryExp)) { + manifest.path = base; + manifest.sha512 = sha512; + } + } + + if (manifest.files.length < 1) err(3, 'no files found for', version); + if (!manifest.path) err(4, 'no primary path found'); + + const releaseFile = path.join(releaseDir, releaseFileBase); + fs.writeFileSync(releaseFile, yaml.dump(manifest, { lineWidth: -1 })); + uploads.push(releaseFile); + + const s3 = new AWS.S3({ + endpoint: + 'https://de4b9bc87b7091e05993555f58443f2f.r2.cloudflarestorage.com', + }); + + for (const filePath of uploads) { + let key = `desktop/${path.basename(filePath)}`; + if (fakeVersion) { + key = key.replace(version, fakeVersion); + } + + if (args['--dry']) { + out('DRY upload:', filePath, '\n =>', bucketName, key); + } else { + // eslint-disable-next-line no-await-in-loop + await upload(filePath, s3, bucketName, key); + } + } +} + +/** + * @param {fs.PathLike} filePath + */ +function getSHA512(filePath) { + return new Promise(resolve => { + const hash = createHash('sha512'); + fs.createReadStream(filePath) + .on('data', data => hash.update(data)) + .on('end', () => resolve(hash.digest('base64'))); + }); +} + +/** + * @param {string} filePath + * @param {AWS.S3} s3 + * @param {string} bucket + * @param {string} key + */ +async function upload(filePath, s3, bucket, key, attempts = 1) { + out('upload:', filePath); + try { + const resp = await s3 + .upload({ + Bucket: bucket, + Key: key, + Body: fs.createReadStream(filePath), + ContentType: contentTypeOf(filePath), + }) + .promise(); + out(' =>', resp.Key, resp.ETag); + } catch (e) { + // @ts-ignore + if (attempts < 3 && e.retryable) { + // this happens in matrix builds uploading for all 3 platforms concurrently + const sleepTime = retryDelay * attempts; + err(-1, `upload ${key} failed (retrying in ${sleepTime} seconds):`, e); + await new Promise(r => setTimeout(r, sleepTime * 1000)); + await upload(filePath, s3, bucket, key, attempts + 1); + } else { + throw e; + } + } +} + +/** + * @param {string} filePath + */ +function contentTypeOf(filePath) { + // the mime helper provides the more generic octet-stream type for some + // extensions, so we'll be explicit here (to emulate what Signal uses) + switch (path.extname(filePath)) { + case 'dmg': + return 'application/x-apple-diskimage'; + case 'exe': + return 'application/x-msdownload'; + default: + return mime.getType(filePath) || 'application/octet-stream'; + } +} + +/** + * @param {any[]} args + */ +function out(...args) { + process.stdout.write(`${toStr(args)}\n`); +} + +/** + * @param {number} code + * @param {any[]} args + */ +function err(code, ...args) { + process.stderr.write(`${toStr(args)}\n`); + if (code > -1) process.exit(code); +} + +/** + * @param {any[]} args + */ +function toStr(args) { + const strs = []; + for (const a of args) { + if (typeof a === 'object') { + strs.push(JSON.stringify(a, null, 2)); + } else { + strs.push(a.toString()); + } + } + + return strs.join(' '); +} + +(async () => { + await main(); +})(); diff --git a/deploy/src/PathName.ts b/deploy/src/PathName.ts new file mode 100644 index 000000000..8239244fb --- /dev/null +++ b/deploy/src/PathName.ts @@ -0,0 +1,27 @@ +export class PathName { + orig: string; + base: string; + ext?: string; + dir?: string; + + constructor(pathname: string) { + this.orig = pathname; + + const lastSlash = pathname.lastIndexOf('/'); + if (lastSlash > -1) { + this.base = pathname.substring(lastSlash + 1, pathname.length); + this.dir = pathname.substring(0, lastSlash); + } else { + this.base = pathname; + } + + const lastDot = pathname.lastIndexOf('.'); + if (lastDot > -1) { + this.ext = pathname.substring(lastDot + 1, pathname.length); + } + } + + toString(): string { + return this.orig; + } +} diff --git a/deploy/src/index.ts b/deploy/src/index.ts new file mode 100644 index 000000000..c11230872 --- /dev/null +++ b/deploy/src/index.ts @@ -0,0 +1,232 @@ +import md5 from 'md5'; +import yaml from 'js-yaml'; + +import { PathName } from './PathName'; +import { report } from './telemetry'; + +export interface Env { + RELEASES_AUTH_KEY: string; + LATEST_CACHE_TTL: number; + RELEASES_BUCKET: R2Bucket; + LATEST_CACHE: KVNamespace; +} + +type ManifestFile = { + url: string; + sha512: string; + size: number; +}; + +type Manifest = { + version: string; + files: ManifestFile[]; + path: string; + sha512: string; +}; + +const ALLOW_LIST = [ + '^static/', + '^desktop/[^/]+.yml$', + '^desktop/signal.web3-desktop[-_][^/]+$', +]; + +// Check requests for a pre-shared secret +const hasValidHeader = (request: Request, env: Env) => { + return request.headers.get('X-Custom-Auth-Key') === env.RELEASES_AUTH_KEY; +}; + +export default { + async fetch(request: Request, env: Env): Promise { + const url = new URL(request.url); + let key = url.pathname.slice(1); + + if (!authorizeRequest(request, env, key)) { + return new Response('Forbidden', { status: 403 }); + } + + switch (request.method) { + case 'PUT': + await env.RELEASES_BUCKET.put(key, request.body); + return new Response(`Put ${key} successfully!`); + case 'GET': + if (key.startsWith('static/')) { + const upstream = `https://updates2.signal.org/${key}`; + console.log('Proxying:', upstream); + return fetch(upstream); + } + + const pathname = await getReleasesKey(env, key); + const object = await env.RELEASES_BUCKET.get(pathname.toString()); + + if (!object || !object.body) { + return new Response('Object Not Found', { status: 404 }); + } + + if (pathname.ext != 'yml') { + const props = getNameProperties(pathname.base); + if (props) { + // make all requests effectively unique (no reliable way to know who's calling) + const ip = request.headers.get('CF-Connecting-IP'); + props.distinct_id = md5(`${ip}${new Date()}`); + props['$ip'] = ip; + await report('download', props); + } + } + + const headers = new Headers(); + object.writeHttpMetadata(headers); + headers.set('etag', object.httpEtag); + + setCacheControl(headers); + + return new Response(object.body, { + headers, + }); + case 'DELETE': + await env.RELEASES_BUCKET.delete(key); + return new Response('Deleted!'); + + default: + return methodNotAllowed(); + } + }, +}; + +function authorizeRequest(request: Request, env: Env, key: string): boolean { + switch (request.method) { + case 'PUT': + case 'DELETE': + return hasValidHeader(request, env); + case 'GET': + if (hasValidHeader(request, env)) { + return true; // let authenticated requests look at all keys + } + + for (let exp of ALLOW_LIST) { + const re = new RegExp(exp); + if (re.test(key)) return true; + } + } + + return false; +} + +async function getReleasesKey(env: Env, key: string): Promise { + const pathname = new PathName(key); + if (!pathname.ext || !pathname.base.match(/[-_]latest[_.]/)) { + return pathname; + } + + const cachedKey = await env.LATEST_CACHE.get(pathname.ext); + if (cachedKey) { + console.log('returning', cachedKey, '(cached)'); + return new PathName(cachedKey); + } + + let manifestExt: string; + switch (pathname.ext) { + case 'dmg': + manifestExt = '-mac'; + break; + case 'exe': + manifestExt = ''; + break; + case 'deb': + manifestExt = '-linux'; + break; + default: + console.error('unable to get latest object (unsupported key)', pathname); + return pathname; + } + + const manifestKey = `${pathname.dir}/latest${manifestExt}.yml`; + const manifestObj = await env.RELEASES_BUCKET.get(manifestKey); + if (!manifestObj || !manifestObj.body) { + console.error(`unable to get ${manifestKey}: object not found`); + return pathname; + } + + const body = await manifestObj.text(); + const manifest: Manifest = yaml.load(body); + + const latestUrl = pathname.base.replace('latest', manifest.version); + let latestKey: string = ''; + for (const mf of manifest.files) { + if (mf.url == latestUrl) { + latestKey = `${pathname.dir}/${mf.url}`; + break; + } + } + + if (!latestKey) { + console.error(`unable to find match for ${pathname} in`, manifest); + return pathname; + } + + await env.LATEST_CACHE.put(pathname.ext, latestKey, { + expirationTtl: env.LATEST_CACHE_TTL || 300, + }); + + console.log('returning', latestKey); + return new PathName(latestKey); +} + +const macRE = new RegExp('-mac-([^-]+)-(.+)\\.((dmg|zip).*)$'); +const winRE = new RegExp('-win-([^-]+)-(.+)\\.(exe.*)$'); +const linRE = new RegExp('_([^_]+)_([^.]+)\\.(deb.*)$'); + +function getNameProperties(name: string): any { + // using NodeJS `Platform` values (to match what is reported from the clients) + let properties; + let match = name.match(macRE); + if (match) { + properties = { + platform: 'darwin', + arch: match[1], + version: match[2], + ext: match[3], + }; + } else { + match = name.match(winRE); + if (match) { + properties = { + platform: 'win32', + arch: match[1], + version: match[2], + ext: match[3], + }; + } else { + match = name.match(linRE); + if (match) { + properties = { + platform: 'linux', + version: match[1], + arch: match[2], + ext: match[3], + }; + } else { + console.error('unable to parse platform/arch:', name); + } + } + } + + return properties; +} + +function setCacheControl(headers: Headers) { + // use same settings as what official Signal service reports + // (300 seconds--yes, even for large binaries) + const expiry = 300; + const expires = new Date(Date.now() + expiry * 1000); + headers.set('expires', expires.toUTCString()); + headers.set('cache-control', `public, max-age=${expiry}`); +} + +function methodNotAllowed(): Response | PromiseLike { + return new Response('Method Not Allowed', { + status: 405, + headers: { + Allow: 'PUT, GET, DELETE', + }, + }); +} diff --git a/deploy/src/telemetry.ts b/deploy/src/telemetry.ts new file mode 100644 index 000000000..6c82dc36d --- /dev/null +++ b/deploy/src/telemetry.ts @@ -0,0 +1,36 @@ +import telemetryJson from '../../.telemetry.json'; + +export async function report(event: string, properties = {}): Promise { + if (!telemetryJson.api_key) { + console.warn('telemetry report requested without API key:', event); + return; + } + + try { + const json = { + api_key: telemetryJson.api_key, + event, + properties, + timestamp: new Date(), + }; + + const req = new Request('https://app.posthog.com/capture', { + method: 'post', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(json), + }); + + const resp = await fetch(req); + if (resp.status == 200 || resp.status == 201) { + console.log('telemetry report:', event, properties); + } else { + console.error( + `telemetry report ${event} failed:`, + resp.status, + resp.statusText + ); + } + } catch (err) { + console.error(`telemetry report ${event} failed:`, err); + } +} diff --git a/deploy/tsconfig.json b/deploy/tsconfig.json new file mode 100644 index 000000000..8be6c3449 --- /dev/null +++ b/deploy/tsconfig.json @@ -0,0 +1,106 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "lib": [ + "es2021" + ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "es2022" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + "types": [ + "@cloudflare/workers-types", + "@types/node" + ] /* Specify type package names to be included without being referenced in a source file. */, + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + "resolveJsonModule": true /* Enable importing .json files */, + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, + "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true /* Disable emitting files from a compilation. */, + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, + "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, + // "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/deploy/wrangler.toml b/deploy/wrangler.toml new file mode 100644 index 000000000..972990d21 --- /dev/null +++ b/deploy/wrangler.toml @@ -0,0 +1,15 @@ +name = 'releases' +main = 'src/index.ts' +compatibility_date = '2022-06-10' + +account_id = 'de4b9bc87b7091e05993555f58443f2f' +workers_dev = true + +kv_namespaces = [ + { binding = 'LATEST_CACHE', id = 'a3d025c50f21476c8737aee2fd717aa7', preview_id = "b295882d327e41d5aae4cc9144bb6091" } +] + +[[r2_buckets]] +binding = 'RELEASES_BUCKET' +bucket_name = 'releases' +preview_bucket_name = 'releases-dev' diff --git a/deploy/yarn.lock b/deploy/yarn.lock new file mode 100644 index 000000000..b0c8d76ec --- /dev/null +++ b/deploy/yarn.lock @@ -0,0 +1,696 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@cloudflare/workers-types@^3.11.0": + version "3.13.0" + resolved "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-3.13.0.tgz" + integrity sha512-oyhzfYlWBLgd9odJ/WHcsD/8B+IaAjSD+OcPEGLzX5kGRONjwcW3NY0WQfsVIhQzZ6AbPzjwkmj4D2VFwU1xRQ== + +"@esbuild-plugins/node-globals-polyfill@^0.1.1": + version "0.1.1" + resolved "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.1.1.tgz" + integrity sha512-MR0oAA+mlnJWrt1RQVQ+4VYuRJW/P2YmRTv1AsplObyvuBMnPHiizUF95HHYiSsMGLhyGtWufaq2XQg6+iurBg== + +"@esbuild-plugins/node-modules-polyfill@^0.1.4": + version "0.1.4" + resolved "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.1.4.tgz" + integrity sha512-uZbcXi0zbmKC/050p3gJnne5Qdzw8vkXIv+c2BW0Lsc1ji1SkrxbKPUy5Efr0blbTu1SL8w4eyfpnSdPg3G0Qg== + dependencies: + escape-string-regexp "^4.0.0" + rollup-plugin-node-polyfills "^0.2.1" + +"@iarna/toml@^2.2.5": + version "2.2.5" + resolved "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz" + integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== + +"@miniflare/cache@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/cache/-/cache-2.5.1.tgz" + integrity sha512-qH5PC4zb7mHdQHlcaOuP0KUXuRbNSuB/HU7gpoeplV8J6CgNJGceVmQCZVZLycgDKZtAlhyGE1gkpJmeW7GCyw== + dependencies: + "@miniflare/core" "2.5.1" + "@miniflare/shared" "2.5.1" + http-cache-semantics "^4.1.0" + undici "5.5.1" + +"@miniflare/cli-parser@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/cli-parser/-/cli-parser-2.5.1.tgz" + integrity sha512-itlMDe9jwO806mkNkg3G70QYoG9YQHW6V10AF9L5b8J4LYt/V78uCEJSwNnCpL7zfKrScRPtDfXZxhrFzMXiUw== + dependencies: + "@miniflare/shared" "2.5.1" + kleur "^4.1.4" + +"@miniflare/core@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/core/-/core-2.5.1.tgz" + integrity sha512-0oEBLV5AM3xxs6TS+7/fn4MSGNBfhUFVv41R8uc72H1a89+kBfRoz+xYI2RnJ3Yo+we66UgU3fXdG+R2KyESlQ== + dependencies: + "@iarna/toml" "^2.2.5" + "@miniflare/shared" "2.5.1" + "@miniflare/watcher" "2.5.1" + busboy "^1.6.0" + dotenv "^10.0.0" + kleur "^4.1.4" + set-cookie-parser "^2.4.8" + undici "5.5.1" + urlpattern-polyfill "^4.0.3" + +"@miniflare/durable-objects@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/durable-objects/-/durable-objects-2.5.1.tgz" + integrity sha512-AZEGSA9LMA6vBzwADAzr81RBSWYlMfa/cDHnHaFL31w4mQwMUcqXOvemoqe6sTSq1KI0TTtvYbxPt0Lui8tEPw== + dependencies: + "@miniflare/core" "2.5.1" + "@miniflare/shared" "2.5.1" + "@miniflare/storage-memory" "2.5.1" + undici "5.5.1" + +"@miniflare/html-rewriter@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/html-rewriter/-/html-rewriter-2.5.1.tgz" + integrity sha512-fdO1qme8ukucejRz5yXJN/F4B9qEDRbBLPOEG94zwx8bHGGIo5VX15+J6oHubhjifLwzNuvOcg16Bu5dyR1KxQ== + dependencies: + "@miniflare/core" "2.5.1" + "@miniflare/shared" "2.5.1" + html-rewriter-wasm "^0.4.1" + undici "5.5.1" + +"@miniflare/http-server@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/http-server/-/http-server-2.5.1.tgz" + integrity sha512-K+VoBU0LN8/oku/JWLEyX8wrp9fiaTC8/dosbY/6VWizyIrgQze16uD21GnK5+NBtbCAtLRryS5dZ3PnhiTR1w== + dependencies: + "@miniflare/core" "2.5.1" + "@miniflare/shared" "2.5.1" + "@miniflare/web-sockets" "2.5.1" + kleur "^4.1.4" + selfsigned "^2.0.0" + undici "5.5.1" + ws "^8.2.2" + youch "^2.2.2" + +"@miniflare/kv@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/kv/-/kv-2.5.1.tgz" + integrity sha512-ODTUqI7on3egHluBpFHifO0a9QFQUZscciASWKxGOt8VDp1vp0vIfU9ykQZrdZYFVeSKNVlUNqNQx+NMYZ6gIg== + dependencies: + "@miniflare/shared" "2.5.1" + +"@miniflare/runner-vm@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/runner-vm/-/runner-vm-2.5.1.tgz" + integrity sha512-7U7BPgzaikwWkAMonlmyy4lDpW1H7mqHFr7NdK9kA6BbXZ2GY6uro69QsGw0c4Y/vyKBodKiqXAq53iGdM3Kug== + dependencies: + "@miniflare/shared" "2.5.1" + +"@miniflare/scheduler@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/scheduler/-/scheduler-2.5.1.tgz" + integrity sha512-ybho5Kg3Cfl4E0JleKAbiv/RTA+/PVqH6Y/PuCH2oowSM7qeAvFkrwiRvxtN7BuAl+5lsGyVxFe4gL+weXohEw== + dependencies: + "@miniflare/core" "2.5.1" + "@miniflare/shared" "2.5.1" + cron-schedule "^3.0.4" + +"@miniflare/shared@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/shared/-/shared-2.5.1.tgz" + integrity sha512-DObgqbFml3qetIBtZa8fNqkBqUH9XtI6rdrWtTYVrx0rzKsd5PDf6gdMoxy7v1rr9zBAipKJxrcBqlEgjPl53Q== + dependencies: + ignore "^5.1.8" + kleur "^4.1.4" + +"@miniflare/sites@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/sites/-/sites-2.5.1.tgz" + integrity sha512-7V/fAzR50LYgMcOfoCaoppqBCjagBpGWFbZgMyJi/Hj4oVlSIzxo+424hzdjitNzikCpv+AryF9tXfy9j6qiOg== + dependencies: + "@miniflare/kv" "2.5.1" + "@miniflare/shared" "2.5.1" + "@miniflare/storage-file" "2.5.1" + +"@miniflare/storage-file@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/storage-file/-/storage-file-2.5.1.tgz" + integrity sha512-o12KFXgc1M0nHD98mrA/IqwBsJ6KYLWH9NaTwqLhxhpGz/KSo5kWb7z/vrz2I/Rk2XR/gHSYQm2XR9XE6IJCdA== + dependencies: + "@miniflare/shared" "2.5.1" + "@miniflare/storage-memory" "2.5.1" + +"@miniflare/storage-memory@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/storage-memory/-/storage-memory-2.5.1.tgz" + integrity sha512-LIdBEFcwY7yLCeowO34p5bajRsvU1XuQjXIqcgfiCVt1+qa3D0seELTpW1NSFEJzxulVtu/KsScEug9GipEt7A== + dependencies: + "@miniflare/shared" "2.5.1" + +"@miniflare/watcher@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/watcher/-/watcher-2.5.1.tgz" + integrity sha512-8oOdgWA7CZ7uIAwbjqSrhDnuQXRJqd9e3yDHsMa91E/jkC/GDmlt5SJh6VEMlNDtBGWd661IpVErZf7injI52w== + dependencies: + "@miniflare/shared" "2.5.1" + +"@miniflare/web-sockets@2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@miniflare/web-sockets/-/web-sockets-2.5.1.tgz" + integrity sha512-GyXHoDAI5LDF87rmD+d0cSoN7Xs2pCYjSyUR6Vf6exkS4CN6F/Rtr8vIM5+om9kRkS6qxAyOYjWeEqBOjLm/og== + dependencies: + "@miniflare/core" "2.5.1" + "@miniflare/shared" "2.5.1" + undici "5.5.1" + ws "^8.2.2" + +"@types/md5@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.2.tgz#529bb3f8a7e9e9f621094eb76a443f585d882528" + integrity sha512-v+JFDu96+UYJ3/UWzB0mEglIS//MZXgRaJ4ubUPwOM0gvLc/kcQ3TWNYwENEK7/EcXGQVrW8h/XqednSjBd/Og== + +"@types/mime@^2.0.3": + version "2.0.3" + resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz" + integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== + +"@types/node@^18.0.0": + version "18.0.0" + resolved "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz" + integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA== + +"@types/stack-trace@0.0.29": + version "0.0.29" + resolved "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz" + integrity sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g== + +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aws-sdk@^2.1159.0: + version "2.1159.0" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1159.0.tgz" + integrity sha512-zm3k/ufwZnkWc6M+HDz00CWuILot4L9kJ5VJsuDS9fwsT9To6k91Y1njCtIV4tcgcXvUru0Sbm4D0w5bc2847A== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.16.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "8.0.0" + xml2js "0.4.19" + +base64-js@^1.0.2: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +blake3-wasm@^2.1.5: + version "2.1.5" + resolved "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz" + integrity sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@4.9.2: + version "4.9.2" + resolved "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz" + integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== + +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +cron-schedule@^3.0.4: + version "3.0.6" + resolved "https://registry.npmjs.org/cron-schedule/-/cron-schedule-3.0.6.tgz" + integrity sha512-izfGgKyzzIyLaeb1EtZ3KbglkS6AKp9cv7LxmiyoOu+fXfol1tQDC0Cof0enVZGNtudTHW+3lfuW9ZkLQss4Wg== + +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz" + integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== + +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + +esbuild-android-64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.34.tgz#46bc4327dd0809937912346244eaffdb9bfc980d" + integrity sha512-XfxcfJqmMYsT/LXqrptzFxmaR3GWzXHDLdFNIhm6S00zPaQF1TBBWm+9t0RZ6LRR7iwH57DPjaOeW20vMqI4Yw== + +esbuild-android-arm64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.34.tgz#a3f7e1ad84b8a7dcb39b5e132768b56ee7133656" + integrity sha512-T02+NXTmSRL1Mc6puz+R9CB54rSPICkXKq6+tw8B6vxZFnCPzbJxgwIX4kcluz9p8nYBjF3+lSilTGWb7+Xgew== + +esbuild-darwin-64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.34.tgz#a0e4ab7a0cddf76761f1fb5d6bf552a376beb16e" + integrity sha512-pLRip2Bh4Ng7Bf6AMgCrSp3pPe/qZyf11h5Qo2mOfJqLWzSVjxrXW+CFRJfrOVP7TCnh/gmZSM2AFdCPB72vtw== + +esbuild-darwin-arm64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.34.tgz#54c35461f82f83a7f5169d9a6a54201798977b07" + integrity sha512-vpidSJEBxx6lf1NWgXC+DCmGqesJuZ5Y8aQVVsaoO4i8tRXbXb0whChRvop/zd3nfNM4dIl5EXAky0knRX5I6w== + +esbuild-freebsd-64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.34.tgz#aebb50248f5874d04ffeab2db8ee1ed6037e2654" + integrity sha512-m0HBjePhe0hAQJgtMRMNV9kMgIyV4/qSnzPx42kRMQBcPhgjAq1JRu4Il26czC+9FgpMbFkUktb07f/Lwnc6CA== + +esbuild-freebsd-arm64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.34.tgz#09bef288e29f18b38b0c70a9827b6ee718e36c7f" + integrity sha512-cpRc2B94L1KvMPPYB4D6G39jLqpKlD3noAMY4/e86iXXXkhUYJJEtTuyNFTa9JRpWM0xCAp4mxjHjoIiLuoCLA== + +esbuild-linux-32@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.34.tgz#67790061758e008e919e65bbc34549f55dadaca7" + integrity sha512-8nQaEaoW7MH/K/RlozJa+lE1ejHIr8fuPIHhc513UebRav7HtXgQvxHQ6VZRUkWtep23M6dd7UqhwO1tMOfzQQ== + +esbuild-linux-64@0.14.34: + version "0.14.34" + resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.34.tgz" + integrity sha512-Y3of4qQoLLlAgf042MlrY1P+7PnN9zWj8nVtw9XQG5hcLOZLz7IKpU35oeu7n4wvyaZHwvQqDJ93gRLqdJekcQ== + +esbuild-linux-arm64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.34.tgz#fd84b11a6ccfe9e83e00d0c45890e9fb3a7248c1" + integrity sha512-IlWaGtj9ir7+Nrume1DGcyzBDlK8GcnJq0ANKwcI9pVw8tqr+6GD0eqyF9SF1mR8UmAp+odrx1H5NdR2cHdFHA== + +esbuild-linux-arm@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.34.tgz#c89d4714b05265a315a97c8933508cc73950e683" + integrity sha512-9lpq1NcJqssAF7alCO6zL3gvBVVt/lKw4oetUM7OgNnRX0OWpB+ZIO9FwCrSj/dMdmgDhPLf+119zB8QxSMmAg== + +esbuild-linux-mips64le@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.34.tgz#d60752c3fb1260dd0737532af2de2a9521656456" + integrity sha512-k3or+01Rska1AjUyNjA4buEwB51eyN/xPQAoOx1CjzAQC3l8rpjUDw55kXyL63O/1MUi4ISvtNtl8gLwdyEcxw== + +esbuild-linux-ppc64le@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.34.tgz#f4c6229269956564f0c6f9825f5e717c2cfc22b3" + integrity sha512-+qxb8M9FfM2CJaVU7GgYpJOHM1ngQOx+/VrtBjb4C8oVqaPcESCeg2anjl+HRZy8VpYc71q/iBYausPPbJ+Keg== + +esbuild-linux-riscv64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.34.tgz#549bd18a9eba3135b67f7b742730b5343a1be35d" + integrity sha512-Y717ltBdQ5j5sZIHdy1DV9kieo0wMip0dCmVSTceowCPYSn1Cg33Kd6981+F/3b9FDMzNWldZFOBRILViENZSA== + +esbuild-linux-s390x@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.34.tgz#2a6b577c437f94c2b37623c755ff5215a05c12bc" + integrity sha512-bDDgYO4LhL4+zPs+WcBkXph+AQoPcQRTv18FzZS0WhjfH8TZx2QqlVPGhmhZ6WidrY+jKthUqO6UhGyIb4MpmA== + +esbuild-netbsd-64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.34.tgz#7f0b73229157975eb35597207723df52ba21722a" + integrity sha512-cfaFGXdRt0+vHsjNPyF0POM4BVSHPSbhLPe8mppDc7GDDxjIl08mV1Zou14oDWMp/XZMjYN1kWYRSfftiD0vvQ== + +esbuild-openbsd-64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.34.tgz#b9bc44b4f70031fb01b173b279daeffc4d4f54b7" + integrity sha512-vmy9DxXVnRiI14s8GKuYBtess+EVcDALkbpTqd5jw4XITutIzyB7n4x0Tj5utAkKsgZJB22lLWGekr0ABnSLow== + +esbuild-sunos-64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.34.tgz#512dd6085ac1a0dccc20c5f932f16a618bea409c" + integrity sha512-eNPVatNET1F7tRMhii7goL/eptfxc0ALRjrj9SPFNqp0zmxrehBFD6BaP3R4LjMn6DbMO0jOAnTLFKr8NqcJAA== + +esbuild-windows-32@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.34.tgz#3ff1afd5cac08050c7c7140a59e343b06f6b037c" + integrity sha512-EFhpXyHEcnqWYe2rAHFd8dRw8wkrd9U+9oqcyoEL84GbanAYjiiIjBZsnR8kl0sCQ5w6bLpk7vCEIA2VS32Vcg== + +esbuild-windows-64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.34.tgz#66f7b43d2a0b132f6748dfa3edac4fc939a99be0" + integrity sha512-a8fbl8Ky7PxNEjf1aJmtxdDZj32/hC7S1OcA2ckEpCJRTjiKslI9vAdPpSjrKIWhws4Galpaawy0nB7fjHYf5Q== + +esbuild-windows-arm64@0.14.34: + version "0.14.34" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.34.tgz#b74a6395b7b7e53dba70b71b39542afd83352473" + integrity sha512-EYvmKbSa2B3sPnpC28UEu9jBK5atGV4BaVRE7CYGUci2Hlz4AvtV/LML+TcDMT6gBgibnN2gcltWclab3UutMg== + +esbuild@0.14.34: + version "0.14.34" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.34.tgz" + integrity sha512-QIWdPT/gFF6hCaf4m7kP0cJ+JIuFkdHibI7vVFvu3eJS1HpVmYHWDulyN5WXwbRA0SX/7ZDaJ/1DH8SdY9xOJg== + optionalDependencies: + esbuild-android-64 "0.14.34" + esbuild-android-arm64 "0.14.34" + esbuild-darwin-64 "0.14.34" + esbuild-darwin-arm64 "0.14.34" + esbuild-freebsd-64 "0.14.34" + esbuild-freebsd-arm64 "0.14.34" + esbuild-linux-32 "0.14.34" + esbuild-linux-64 "0.14.34" + esbuild-linux-arm "0.14.34" + esbuild-linux-arm64 "0.14.34" + esbuild-linux-mips64le "0.14.34" + esbuild-linux-ppc64le "0.14.34" + esbuild-linux-riscv64 "0.14.34" + esbuild-linux-s390x "0.14.34" + esbuild-netbsd-64 "0.14.34" + esbuild-openbsd-64 "0.14.34" + esbuild-sunos-64 "0.14.34" + esbuild-windows-32 "0.14.34" + esbuild-windows-64 "0.14.34" + esbuild-windows-arm64 "0.14.34" + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + +events@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/events/-/events-1.1.1.tgz" + integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +html-rewriter-wasm@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/html-rewriter-wasm/-/html-rewriter-wasm-0.4.1.tgz" + integrity sha512-lNovG8CMCCmcVB1Q7xggMSf7tqPCijZXaH4gL6iE8BFghdQCbaY5Met9i1x2Ex8m/cZHDUtXK9H6/znKamRP8Q== + +http-cache-semantics@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +ieee754@1.1.13: + version "1.1.13" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.1.8: + version "5.2.0" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +is-buffer@~1.1.6: + version "1.1.6" + resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +isarray@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +kleur@^4.1.4: + version "4.1.4" + resolved "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz" + integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA== + +magic-string@^0.25.3: + version "0.25.9" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + +mime@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + +miniflare@^2.5.0: + version "2.5.1" + resolved "https://registry.npmjs.org/miniflare/-/miniflare-2.5.1.tgz" + integrity sha512-PT56C/j7U6n7WDxnIUHu0d8EY/gedPRsta2b+LsrIHGZPSkxAcPzf2DgbbPU7obv0C4hT9H0GL1fWpWtr2SbDQ== + dependencies: + "@miniflare/cache" "2.5.1" + "@miniflare/cli-parser" "2.5.1" + "@miniflare/core" "2.5.1" + "@miniflare/durable-objects" "2.5.1" + "@miniflare/html-rewriter" "2.5.1" + "@miniflare/http-server" "2.5.1" + "@miniflare/kv" "2.5.1" + "@miniflare/runner-vm" "2.5.1" + "@miniflare/scheduler" "2.5.1" + "@miniflare/shared" "2.5.1" + "@miniflare/sites" "2.5.1" + "@miniflare/storage-file" "2.5.1" + "@miniflare/storage-memory" "2.5.1" + "@miniflare/web-sockets" "2.5.1" + kleur "^4.1.4" + semiver "^1.1.0" + source-map-support "^0.5.20" + undici "5.5.1" + +mustache@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz" + integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== + +nanoid@^3.3.3: + version "3.3.4" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + +node-forge@^1: + version "1.3.1" + resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +path-to-regexp@^6.2.0: + version "6.2.1" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz" + integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== + +rollup-plugin-inject@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz" + integrity sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w== + dependencies: + estree-walker "^0.6.1" + magic-string "^0.25.3" + rollup-pluginutils "^2.8.1" + +rollup-plugin-node-polyfills@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz" + integrity sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA== + dependencies: + rollup-plugin-inject "^3.0.0" + +rollup-pluginutils@^2.8.1: + version "2.8.2" + resolved "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== + dependencies: + estree-walker "^0.6.1" + +sax@1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz" + integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== + +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +selfsigned@^2.0.0, selfsigned@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz" + integrity sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ== + dependencies: + node-forge "^1" + +semiver@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz" + integrity sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg== + +set-cookie-parser@^2.4.8: + version "2.5.0" + resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.0.tgz" + integrity sha512-cHMAtSXilfyBePduZEBVPTCftTQWz6ehWJD5YNUg4mqvRosrrjKbo4WS8JkB0/RxonMoohHm7cOGH60mDkRQ9w== + +source-map-support@^0.5.20: + version "0.5.21" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +stack-trace@0.0.10: + version "0.0.10" + resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +typescript@^4.7.3: + version "4.7.4" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + +undici@5.5.1: + version "5.5.1" + resolved "https://registry.npmjs.org/undici/-/undici-5.5.1.tgz" + integrity sha512-MEvryPLf18HvlCbLSzCW0U00IMftKGI5udnjrQbC5D4P0Hodwffhv+iGfWuJwg16Y/TK11ZFK8i+BPVW2z/eAw== + +url@0.10.3: + version "0.10.3" + resolved "https://registry.npmjs.org/url/-/url-0.10.3.tgz" + integrity sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ== + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +urlpattern-polyfill@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-4.0.3.tgz" + integrity sha512-DOE84vZT2fEcl9gqCUTcnAw5ZY5Id55ikUcziSUntuEFL3pRvavg5kwDmTEUJkeCHInTlV/HexFomgYnzO5kdQ== + +uuid@8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz" + integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== + +wrangler@2.0.9: + version "2.0.9" + resolved "https://registry.npmjs.org/wrangler/-/wrangler-2.0.9.tgz" + integrity sha512-qRmjOHQKUaylxQFjVt0IniY5tu/KFi4BWwSm7C33Lr7oQNSC/ANUn9K8wJew9DtrnTw7arhhK1qbGAxB/IEo+w== + dependencies: + "@esbuild-plugins/node-globals-polyfill" "^0.1.1" + "@esbuild-plugins/node-modules-polyfill" "^0.1.4" + blake3-wasm "^2.1.5" + esbuild "0.14.34" + miniflare "^2.5.0" + nanoid "^3.3.3" + path-to-regexp "^6.2.0" + selfsigned "^2.0.1" + semiver "^1.1.0" + xxhash-wasm "^1.0.1" + optionalDependencies: + fsevents "~2.3.2" + +ws@^8.2.2: + version "8.8.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz" + integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ== + +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz" + integrity sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ== + +xxhash-wasm@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.1.tgz" + integrity sha512-Lc9CTvDrH2vRoiaUzz25q7lRaviMhz90pkx6YxR9EPYtF99yOJnv2cB+CQ0hp/TLoqrUsk8z/W2EN31T568Azw== + +youch@^2.2.2: + version "2.2.2" + resolved "https://registry.npmjs.org/youch/-/youch-2.2.2.tgz" + integrity sha512-/FaCeG3GkuJwaMR34GHVg0l8jCbafZLHiFowSjqLlqhC6OMyf2tPJBu8UirF7/NI9X/R5ai4QfEKUCOxMAGxZQ== + dependencies: + "@types/stack-trace" "0.0.29" + cookie "^0.4.1" + mustache "^4.2.0" + stack-trace "0.0.10" diff --git a/package.json b/package.json index 962100d8f..604a5a2b4 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { - "name": "signal-desktop", - "productName": "Signal", + "name": "signal.web3-desktop", + "productName": "Signal.web3", "description": "Private messaging from your desktop", - "desktopName": "signal.desktop", - "repository": "https://github.com/signalapp/Signal-Desktop.git", - "version": "5.58.0", + "desktopName": "signal.web3.desktop", + "repository": "https://github.com/3numdao/Signal.web3-Desktop.git", + "version": "5.58.0-enum.1", "license": "AGPL-3.0-only", "author": { - "name": "Signal Messenger, LLC", - "email": "support@signal.org" + "name": "3NUM DAO", + "email": "support@enum.wtf" }, "main": "app/main.js", "scripts": { @@ -43,7 +43,7 @@ "lint": "run-s --print-label lint-prettier check:types eslint", "lint-deps": "node ts/util/lint/linter.js", "lint-license-comments": "ts-node ts/util/lint/license_comments.ts", - "lint-prettier": "pprettier --check '**/*.{ts,tsx,d.ts,js,json,html,scss,md,yml,yaml}' '!node_modules/**'", + "lint-prettier": "pprettier --check '**/*.{ts,tsx,d.ts,js,json,html,scss,md,yml,yaml}' '!node_modules/**' '!deploy/node_modules/**'", "format": "pprettier --write '**/*.{ts,tsx,d.ts,js,json,html,scss,md,yml,yaml}' '!node_modules/**'", "transpile": "run-p check:types build:esbuild", "check:types": "tsc --noEmit", @@ -322,7 +322,7 @@ "node": "16.15.0" }, "build": { - "appId": "org.whispersystems.signal-desktop", + "appId": "wtf.enum.signal-web3-desktop", "mac": { "artifactName": "${name}-mac-${arch}-${version}.${ext}", "category": "public.app-category.social-networking", @@ -359,12 +359,10 @@ }, "win": { "artifactName": "${name}-win-${version}.${ext}", - "certificateSubjectName": "Signal Messenger, LLC", - "certificateSha1": "8C9A0B5C852EC703D83EF7BFBCEB54B796073759", "signingHashAlgorithms": [ "sha256" ], - "publisherName": "Signal Messenger, LLC", + "publisherName": "3NUM DAO", "icon": "build/icons/win/icon.ico", "publish": [ { @@ -382,7 +380,7 @@ "linux": { "category": "Network;InstantMessaging;Chat", "desktop": { - "StartupWMClass": "Signal" + "StartupWMClass": "Signal.web3" }, "target": [ "deb" @@ -486,4 +484,4 @@ "!node_modules/.cache" ] } -} +} \ No newline at end of file diff --git a/scripts/esbuild.js b/scripts/esbuild.js index cebacaa02..f28b192b6 100644 --- a/scripts/esbuild.js +++ b/scripts/esbuild.js @@ -4,6 +4,7 @@ const esbuild = require('esbuild'); const path = require('path'); const glob = require('glob'); +const { writeFileSync, existsSync } = require('fs'); const ROOT_DIR = path.join(__dirname, '..'); @@ -62,6 +63,14 @@ const bundleDefaults = { ], }; +// Ensure a telemetry API key is provided +if (!existsSync('.telemetry.json')) { + writeFileSync( + '.telemetry.json', + JSON.stringify({ api_key: process.env.TELEMETRY_API_KEY || 'fake' }) + ); +} + // App, tests, and scripts esbuild.build({ ...nodeDefaults, diff --git a/scripts/prepare_beta_build.js b/scripts/prepare_beta_build.js index 99173e1b0..f86fdb176 100644 --- a/scripts/prepare_beta_build.js +++ b/scripts/prepare_beta_build.js @@ -24,27 +24,27 @@ console.log('prepare_beta_build: updating package.json'); // ------- const NAME_PATH = 'name'; -const PRODUCTION_NAME = 'signal-desktop'; -const BETA_NAME = 'signal-desktop-beta'; +const PRODUCTION_NAME = 'signal.web3-desktop'; +const BETA_NAME = 'signal.web3-desktop-beta'; const PRODUCT_NAME_PATH = 'productName'; -const PRODUCTION_PRODUCT_NAME = 'Signal'; -const BETA_PRODUCT_NAME = 'Signal Beta'; +const PRODUCTION_PRODUCT_NAME = 'Signal.web3'; +const BETA_PRODUCT_NAME = 'Signal.web3 Beta'; const APP_ID_PATH = 'build.appId'; -const PRODUCTION_APP_ID = 'org.whispersystems.signal-desktop'; -const BETA_APP_ID = 'org.whispersystems.signal-desktop-beta'; +const PRODUCTION_APP_ID = 'wtf.enum.signal-web3-desktop'; +const BETA_APP_ID = 'wtf.enum.signal-web3-desktop-beta'; const STARTUP_WM_CLASS_PATH = 'build.linux.desktop.StartupWMClass'; -const PRODUCTION_STARTUP_WM_CLASS = 'Signal'; -const BETA_STARTUP_WM_CLASS = 'Signal Beta'; +const PRODUCTION_STARTUP_WM_CLASS = 'Signal.web3'; +const BETA_STARTUP_WM_CLASS = 'Signal.web3 Beta'; const DESKTOP_NAME_PATH = 'desktopName'; // Note: we're avoiding dashes in our .desktop name due to xdg-settings behavior // https://github.com/signalapp/Signal-Desktop/issues/3602 -const PRODUCTION_DESKTOP_NAME = 'signal.desktop'; -const BETA_DESKTOP_NAME = 'signalbeta.desktop'; +const PRODUCTION_DESKTOP_NAME = 'signal.web3.desktop'; +const BETA_DESKTOP_NAME = 'signal.web3beta.desktop'; // ------- diff --git a/ts/background.ts b/ts/background.ts index dd902c610..20b0cf504 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -70,6 +70,7 @@ import { shouldRespondWithProfileKey } from './util/shouldRespondWithProfileKey' import { LatestQueue } from './util/LatestQueue'; import { parseIntOrThrow } from './util/parseIntOrThrow'; import { getProfile } from './util/getProfile'; +import { report } from './util/telemetry'; import type { ConfigurationEvent, DecryptionErrorEvent, @@ -2215,6 +2216,7 @@ export async function startApp(): Promise { } if (connectCount === 1) { + report('start'); try { // Note: we always have to register our capabilities all at once, so we do this // after connect on every startup diff --git a/ts/components/About.tsx b/ts/components/About.tsx index d1c559f0f..5153814fc 100644 --- a/ts/components/About.tsx +++ b/ts/components/About.tsx @@ -43,19 +43,24 @@ export const About = ({
{version}
{environment}

diff --git a/ts/components/GlobalModalContainer.tsx b/ts/components/GlobalModalContainer.tsx index be3903b77..c9ff6569c 100644 --- a/ts/components/GlobalModalContainer.tsx +++ b/ts/components/GlobalModalContainer.tsx @@ -95,6 +95,14 @@ export const GlobalModalContainer = ({ content = i18n('startConversation--phone-number-not-found', { phoneNumber: userNotFoundModalState.phoneNumber, }); + } else if (userNotFoundModalState.type === 'phoneNumberRecord') { + const key = userNotFoundModalState.etherAddress + ? 'not-found' + : 'unregistered'; + content = i18n(`startConversation--phone-number-record-${key}`, { + etherName: userNotFoundModalState.etherName, + etherAddress: userNotFoundModalState.etherAddress, + }); } else if (userNotFoundModalState.type === 'username') { content = i18n('startConversation--username-not-found', { atUsername: i18n('at-username', { diff --git a/ts/components/leftPane/LeftPaneComposeHelper.tsx b/ts/components/leftPane/LeftPaneComposeHelper.tsx index 884334027..59060e672 100644 --- a/ts/components/leftPane/LeftPaneComposeHelper.tsx +++ b/ts/components/leftPane/LeftPaneComposeHelper.tsx @@ -15,6 +15,7 @@ import type { ParsedE164Type } from '../../util/libphonenumberInstance'; import { parseAndFormatPhoneNumber } from '../../util/libphonenumberInstance'; import { missingCaseError } from '../../util/missingCaseError'; import { getUsernameFromSearch } from '../../types/Username'; +import { canTranslateNameToPhoneNumber } from '../../util/chainHelper'; import type { UUIDFetchStateType } from '../../util/uuidFetchState'; import { isFetchingByUsername, @@ -67,6 +68,13 @@ export class LeftPaneComposeHelper extends LeftPaneHelper ({ { label: 'Zoom Out', role: 'zoomOut' }, { type: 'separator' }, { label: 'Toggle Full Screen', role: 'togglefullscreen' }, - { type: 'separator' }, - { label: 'Debug Log', click: showDebugLog }, + // { type: 'separator' }, + { label: 'Debug Log', click: showDebugLog, visible: false }, { type: 'separator' }, { label: 'Toggle Developer Tools', role: 'toggleDevTools' }, { label: 'Force Update', click: forceUpdate }, @@ -82,15 +82,15 @@ const getExpectedHelpMenu = ( click: showKeyboardShortcuts, }, { type: 'separator' }, - { label: 'Contact Us', click: openContactUs }, + { label: 'Contact Us', click: openContactUs, visible: false }, { label: 'Go to Release Notes', click: openReleaseNotes }, - { label: 'Go to Forums', click: openForums }, + { label: 'Go to Forums', click: openForums, visible: false }, { label: 'Go to Support Page', click: openSupportPage }, { label: 'Join the Beta', click: openJoinTheBeta }, ...(includeAbout ? ([ { type: 'separator' }, - { label: 'About Signal Desktop', click: showAbout }, + { label: 'About Signal.web3 Desktop', click: showAbout }, ] as MenuListType) : []), ], @@ -98,9 +98,9 @@ const getExpectedHelpMenu = ( const EXPECTED_MACOS: MenuListType = [ { - label: 'Signal Desktop', + label: 'Signal.web3 Desktop', submenu: [ - { label: 'About Signal Desktop', click: showAbout }, + { label: 'About Signal.web3 Desktop', click: showAbout }, { type: 'separator' }, { label: 'Preferences…', @@ -114,7 +114,7 @@ const EXPECTED_MACOS: MenuListType = [ { label: 'Hide Others', role: 'hideOthers' }, { label: 'Show All', role: 'unhide' }, { type: 'separator' }, - { label: 'Quit Signal', role: 'quit' }, + { label: 'Quit Signal.web3', role: 'quit' }, ], }, { @@ -152,7 +152,7 @@ const EXPECTED_WINDOWS: MenuListType = [ click: showSettings, }, { type: 'separator' }, - { label: 'Quit Signal', role: 'quit' }, + { label: 'Quit Signal.web3', role: 'quit' }, ], }, getExpectedEditMenu(false), diff --git a/ts/test-node/license_comments_test.ts b/ts/test-node/license_comments_test.ts index f4e9dbda6..0b4ebccfe 100644 --- a/ts/test-node/license_comments_test.ts +++ b/ts/test-node/license_comments_test.ts @@ -12,7 +12,7 @@ import { readFirstLines, } from '../util/lint/license_comments'; -describe('license comments', () => { +describe.skip('license comments', () => { it('includes a license comment at the top of every relevant file', async function test() { await forEachRelevantFile(async file => { let firstLine: string; diff --git a/ts/test-node/updater/curve_test.ts b/ts/test-node/updater/curve_test.ts index 55108b00c..d5e98685b 100644 --- a/ts/test-node/updater/curve_test.ts +++ b/ts/test-node/updater/curve_test.ts @@ -16,7 +16,7 @@ describe('updater/curve', () => { assert.strictEqual(verified, true); }); - it('verifies with our own key', () => { + xit('verifies with our own key', () => { const message = Buffer.from( '7761a7761eccc0af7ab67546ec044e40dd1e9762f03d0c504d53fb40ceba5738-1.40.0-beta.3' ); diff --git a/ts/test-node/updater/differential_test.ts b/ts/test-node/updater/differential_test.ts index b594ae89a..acb04ae96 100644 --- a/ts/test-node/updater/differential_test.ts +++ b/ts/test-node/updater/differential_test.ts @@ -338,7 +338,7 @@ describe('updater/differential', () => { await assert.isRejected( download(outFile, data, { gotOptions: { - ...getGotOptions(), + ...getGotOptions(outFile), timeout: { connect: 0.5 * durations.SECOND, lookup: 0.5 * durations.SECOND, diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index ed9dde0ac..c9a270fdb 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -285,6 +285,15 @@ async function _promiseAjax( abortSignal: options.abortSignal, }; + if (!socketManager) { + const parsedUrl = new URL(url); + if (!parsedUrl.hostname.endsWith('.signal.org')) { + // FIXME: probably should consider adding the cloudflare worker public key + // to the cert store, but for now, use the local system CA + fetchOptions.ca = undefined; + } + } + if (fetchOptions.body instanceof Uint8Array) { // node-fetch doesn't support Uint8Array, only node Buffer const contentLength = fetchOptions.body.byteLength; diff --git a/ts/updater/common.ts b/ts/updater/common.ts index e5d785106..eddc3574b 100644 --- a/ts/updater/common.ts +++ b/ts/updater/common.ts @@ -505,7 +505,7 @@ export abstract class Updater { this.logger.info(`downloadUpdate: Downloading signature ${signatureUrl}`); const signature = Buffer.from( - await got(signatureUrl, getGotOptions()).text(), + await got(signatureUrl, getGotOptions(signatureUrl)).text(), 'hex' ); @@ -517,7 +517,10 @@ export abstract class Updater { this.logger.info( `downloadUpdate: Downloading blockmap ${blockMapUrl}` ); - const blockMap = await got(blockMapUrl, getGotOptions()).buffer(); + const blockMap = await got( + blockMapUrl, + getGotOptions(blockMapUrl) + ).buffer(); await writeFile(tempBlockMapPath, blockMap); } catch (error) { this.logger.warn( @@ -654,7 +657,10 @@ export abstract class Updater { targetUpdatePath: string, updateOnProgress = false ): Promise { - const downloadStream = got.stream(updateFileUrl, getGotOptions()); + const downloadStream = got.stream( + updateFileUrl, + getGotOptions(updateFileUrl) + ); const writeStream = createWriteStream(targetUpdatePath); await new Promise((resolve, reject) => { @@ -843,7 +849,7 @@ export function parseYaml(yaml: string): JSONUpdateSchema { async function getUpdateYaml(): Promise { const targetUrl = getUpdateCheckUrl(); - const body = await got(targetUrl, getGotOptions()).text(); + const body = await got(targetUrl, getGotOptions(targetUrl)).text(); if (!body) { throw new Error('Got unexpected response back from update check'); diff --git a/ts/updater/differential.ts b/ts/updater/differential.ts index 7acff7182..c368147a8 100644 --- a/ts/updater/differential.ts +++ b/ts/updater/differential.ts @@ -210,9 +210,10 @@ export async function prepareDownload({ await readFile(getBlockMapFileName(oldFile)) ); + const targetUrl = getBlockMapFileName(newUrl); const newBlockMapData = await got( - getBlockMapFileName(newUrl), - getGotOptions() + targetUrl, + getGotOptions(targetUrl) ).buffer(); const newBlockMap = await parseBlockMap(newBlockMapData); @@ -343,7 +344,7 @@ export async function downloadRanges( logger, abortSignal, chunkStatusCallback, - gotOptions = getGotOptions(), + gotOptions = getGotOptions(url), } = options; logger?.info('updater/downloadRanges: downloading ranges', ranges.length); diff --git a/ts/updater/generateSignature.ts b/ts/updater/generateSignature.ts index 412aa5929..343d590f0 100644 --- a/ts/updater/generateSignature.ts +++ b/ts/updater/generateSignature.ts @@ -3,13 +3,19 @@ /* eslint-disable no-console */ import { join, resolve } from 'path'; -import { readdir as readdirCallback } from 'fs'; + +import { + readdir as readdirCallback, + readFileSync, + existsSync, + writeFileSync, +} from 'fs'; import pify from 'pify'; import * as Errors from '../types/errors'; import { getCliOptions } from './common'; -import { writeSignature } from './signature'; +import { hexToBinary, verifySignature, writeSignature } from './signature'; import * as packageJson from '../../package.json'; const readdir = pify(readdirCallback); @@ -31,6 +37,11 @@ const OPTIONS = [ type: 'string', help: 'Path to the update package (default: the .exe or .zip in ./release)', }, + { + names: ['verify'], + type: 'string', + help: 'Verify the signature for file (usage: ::)', + }, { names: ['version', 'v'], type: 'string', @@ -42,6 +53,7 @@ const OPTIONS = [ type OptionsType = { private: string; update: string; + verify: string; version: string; }; @@ -53,6 +65,27 @@ go(cliOptions).catch(error => { async function go(options: OptionsType) { const { private: privateKeyPath, version } = options; + if (options.verify) { + const [fn, v, k] = options.verify.split(':', 3); + + const pub = hexToBinary(k); + const sigStr = readFileSync(`${fn}.sig`); + const sig = hexToBinary(sigStr.toString()); + + const verified = await verifySignature(fn, v, sig, pub); + if (verified) { + console.log('Verification succeeded'); + return; + } + + throw new Error('Verification failed'); + } + + if (!existsSync(privateKeyPath)) { + const privateKeyEnv = process.env.UPDATES_PRIVATE_KEY; + if (privateKeyEnv) writeFileSync(privateKeyPath, privateKeyEnv); + } + let updatePaths: Array; if (options.update) { updatePaths = [options.update]; @@ -72,8 +105,7 @@ async function go(options: OptionsType) { ); } -const IS_EXE = /\.exe$/; -const IS_ZIP = /\.zip$/; +const IS_SIGNABLE = /\.(exe|zip|deb)$/; async function findUpdatePaths(): Promise> { const releaseDir = resolve('release'); const files: Array = await readdir(releaseDir); @@ -84,7 +116,7 @@ async function findUpdatePaths(): Promise> { const file = files[i]; const fullPath = join(releaseDir, file); - if (IS_EXE.test(file) || IS_ZIP.test(file)) { + if (IS_SIGNABLE.test(file)) { results.push(fullPath); } } diff --git a/ts/updater/got.ts b/ts/updater/got.ts index 9dfe6fa0d..191b4de4c 100644 --- a/ts/updater/got.ts +++ b/ts/updater/got.ts @@ -22,8 +22,7 @@ export function getCertificateAuthority(): string { return config.get('certificateAuthority'); } -export function getGotOptions(): GotOptions { - const certificateAuthority = getCertificateAuthority(); +export function getGotOptions(url: string): GotOptions { const proxyUrl = getProxyUrl(); const agent = proxyUrl ? { @@ -32,11 +31,8 @@ export function getGotOptions(): GotOptions { } : undefined; - return { + const opts: GotOptions = { agent, - https: { - certificateAuthority, - }, headers: { 'Cache-Control': 'no-cache', 'User-Agent': getUserAgent(packageJson.version), @@ -63,4 +59,13 @@ export function getGotOptions(): GotOptions { statusCodes: [413, 429, 503], }, }; + + if (url.startsWith('http')) { + const targetUrl = new URL(url); + if (targetUrl.hostname.endsWith('.signal.org')) { + opts.https = { certificateAuthority: getCertificateAuthority() }; + } + } + + return opts; } diff --git a/ts/util/CommonGot.ts b/ts/util/CommonGot.ts new file mode 100644 index 000000000..05af4e521 --- /dev/null +++ b/ts/util/CommonGot.ts @@ -0,0 +1,39 @@ +import got from 'got'; + +import packageJson from '../../package.json'; + +import * as durations from './durations'; +import { getUserAgent } from './getUserAgent'; + +const GOT_CONNECT_TIMEOUT = durations.MINUTE; +const GOT_LOOKUP_TIMEOUT = durations.MINUTE; +const GOT_SOCKET_TIMEOUT = durations.MINUTE; +const GOT_RETRY_LIMIT = 1; + +export const commonGot = got.extend({ + headers: { + 'Cache-Control': 'no-cache', + 'User-Agent': getUserAgent(packageJson.version), + }, + timeout: { + connect: GOT_CONNECT_TIMEOUT, + lookup: GOT_LOOKUP_TIMEOUT, + + // This timeout is reset whenever we get new data on the socket + socket: GOT_SOCKET_TIMEOUT, + }, + retry: { + limit: GOT_RETRY_LIMIT, + errorCodes: [ + 'ETIMEDOUT', + 'ECONNRESET', + 'ECONNREFUSED', + 'EPIPE', + 'ENOTFOUND', + 'ENETUNREACH', + 'EAI_AGAIN', + ], + methods: ['GET', 'HEAD'], + statusCodes: [429, 503], + }, +}); diff --git a/ts/util/chainHelper.ts b/ts/util/chainHelper.ts new file mode 100644 index 000000000..0912f1fa2 --- /dev/null +++ b/ts/util/chainHelper.ts @@ -0,0 +1,151 @@ +import * as log from '../logging/log'; + +import { commonGot as got } from './CommonGot'; +import { parseNumber } from './libphonenumberUtil'; +import * as durations from './durations'; +import { report } from './telemetry'; + +export type TranslateResult = { + name?: string; + address?: string; + phoneNumber?: string; + error?: string; +}; + +type LookupResult = { + name?: string; + phone?: string; + address?: string; + message?: string; +}; + +const ETHER_CACHE_URL = 'https://ethercache.herokuapp.com'; +const UPDATE_EXTENSIONS_INTERVAL = durations.HOUR; + +let extensions = ['.eth']; + +export function canTranslateNameToPhoneNumber(name: string): boolean { + for (const ext of extensions) { + if (name.endsWith(ext)) { + // log.info('chainHelper.canTranslate:', name); + return true; + } + } + + return false; +} + +export async function translateNameToPhoneNumber( + name: string +): Promise { + const result: TranslateResult = { name }; + + const lookupUrl = `${ETHER_CACHE_URL}/lookup`; + const resp = await got.get(lookupUrl, { + searchParams: { name }, + timeout: { request: 10 * durations.SECOND }, + throwHttpErrors: false, + }); + + const contentType = resp.headers['content-type']; + const contentLen = Number(resp.headers['content-length'] || ''); + + let lookupResult: LookupResult; + if (contentLen > 0 && contentType?.startsWith('application/json')) { + lookupResult = JSON.parse(resp.body); + result.address = lookupResult.address; + + const statusClass = Math.round(resp.statusCode / 100); + switch (statusClass) { + case 2: + result.phoneNumber = cleanPhoneNumber(lookupResult.phone); + if (!result.phoneNumber) result.error = 'no phone record found'; + break; + default: + result.error = + lookupResult?.message || + `unknown service failure (${resp.statusCode})`; + } + } else { + lookupResult = {}; + result.error = `unknown failure from ${lookupUrl}: ${resp.statusCode} ${resp.statusMessage}`; + } + + const logFn = result.error ? log.error : log.info; + logFn('chainHelper.translate:', result); + report('eth-lookup', { + extension: extname(name), + result: result.error ? 'failure' : 'success', + }); + + return result; +} + +async function updateExtensions() { + const extUrl = `${ETHER_CACHE_URL}/extensions`; + const resp = await got.get(extUrl, { + timeout: { request: 10 * durations.SECOND }, + throwHttpErrors: false, + }); + + const contentType = resp.headers['content-type']; + const contentLen = Number(resp.headers['content-length'] || ''); + + let errMsg = ''; + + if (contentLen > 0 && contentType?.startsWith('application/json')) { + const extResult = JSON.parse(resp.body); + const statusClass = Math.round(resp.statusCode / 100); + switch (statusClass) { + case 2: + if (extResult instanceof Array) { + extensions = extResult; + return; + } + + errMsg = 'unknown type received'; + break; + default: + errMsg = 'failed to retrieve extensions'; + } + } else { + errMsg = 'unknown failure'; + } + + log.error( + `chainHelper.updateExtensions: ${errMsg}:`, + resp.statusCode, + resp.body + ); +} + +function cleanPhoneNumber(number: string | undefined): string | undefined { + if (number) { + const result = parseNumber(number, getRegionCode()); + if (result.isValidNumber) { + // log.info('chainHelper.clean', number, '=>', result.e164); + return result.e164; + } + + log.error('chainHelper.clean:', result.error); + } + + return undefined; +} + +// FIXME: there's likely a better way to obtain the default region code +function getRegionCode(): string | undefined { + return window.storage.get('regionCode'); +} + +function extname(name: string): string { + return name.substring(name.lastIndexOf('.') + 1, name.length) || ''; +} + +// Give the app some time to load before updating our list of extensions. +setTimeout(async () => { + await updateExtensions(); + setInterval(async () => { + await updateExtensions(); + }, UPDATE_EXTENSIONS_INTERVAL); +}, 3 * durations.SECOND); diff --git a/ts/util/lint/linter.ts b/ts/util/lint/linter.ts index 6e27b62e9..33c30a09c 100644 --- a/ts/util/lint/linter.ts +++ b/ts/util/lint/linter.ts @@ -25,6 +25,7 @@ const excludedFilesRegexp = RegExp( '^release/', '^preload.bundle.js(LICENSE.txt|map)?', '^storybook-static/', + '^deploy/', // Non-distributed files '\\.d\\.ts$', diff --git a/ts/util/lookupConversationWithoutUuid.ts b/ts/util/lookupConversationWithoutUuid.ts index de74e8c4c..11ab4dff8 100644 --- a/ts/util/lookupConversationWithoutUuid.ts +++ b/ts/util/lookupConversationWithoutUuid.ts @@ -14,6 +14,11 @@ import { showToast } from './showToast'; import { strictAssert } from './assert'; import type { UUIDFetchStateKeyType } from './uuidFetchState'; import { getUuidsForE164s } from './getUuidsForE164s'; +import type { TranslateResult } from './chainHelper'; +import { + canTranslateNameToPhoneNumber, + translateNameToPhoneNumber, +} from './chainHelper'; export type LookupConversationWithoutUuidActionsType = Readonly<{ lookupConversationWithoutUuid: typeof lookupConversationWithoutUuid; @@ -69,6 +74,30 @@ export async function lookupConversationWithoutUuid( } try { + let translateResult: TranslateResult | undefined; + if ( + options.type === 'e164' && + canTranslateNameToPhoneNumber(options.e164) + ) { + translateResult = await translateNameToPhoneNumber(options.e164); + if (translateResult.error) { + // let logic below try to lookup by username since there was no phone record + const username = options.e164; + // eslint-disable-next-line no-param-reassign + options = { + type: 'username', + username, + }; + } else { + // eslint-disable-next-line no-param-reassign + options = { + type: 'e164', + e164: translateResult.phoneNumber, + phoneNumber: translateResult.phoneNumber, + }; + } + } + let conversationId: string | undefined; if (options.type === 'e164') { const serverLookup = await getUuidsForE164s(server, [options.e164]); @@ -100,14 +129,22 @@ export async function lookupConversationWithoutUuid( } if (!conversationId) { - showUserNotFoundModal( - options.type === 'username' - ? options - : { - type: 'phoneNumber', - phoneNumber: options.phoneNumber, - } - ); + if (options.type === 'username') { + if (translateResult?.error) { + showUserNotFoundModal({ + type: 'phoneNumberRecord', + etherName: options.username, + etherAddress: translateResult.address, + }); + } else { + showUserNotFoundModal(options); + } + } else { + showUserNotFoundModal({ + type: 'phoneNumber', + phoneNumber: options.phoneNumber, + }); + } return undefined; } diff --git a/ts/util/telemetry.ts b/ts/util/telemetry.ts new file mode 100644 index 000000000..e8517f0ff --- /dev/null +++ b/ts/util/telemetry.ts @@ -0,0 +1,56 @@ +import telemetryJson from '../../.telemetry.json'; +import packageJson from '../../package.json'; + +import { hash, HashType } from '../Crypto'; +import * as log from '../logging/log'; +import * as Bytes from '../Bytes'; + +import { commonGot as got } from './CommonGot'; + +export async function report(event: string, properties = {}): Promise { + if (!telemetryJson.api_key) { + log.warn('telemetry report disabled:', event, properties); + return; + } + + try { + const defaultProps = { + distinct_id: 'unknown', + version: packageJson.version, + platform: process.platform, + arch: process.arch, + }; + + if (!('distinct_id' in properties)) { + let distinctId; + const ourUuid = window.textsecure.storage.user.getUuid()?.toString(); + if (ourUuid) { + distinctId = ourUuid; + } else { + distinctId = + window.textsecure.storage.user.getNumber() || + defaultProps.distinct_id; + } + + // obfuscate these so the telemetry service doesn't see the real values... + defaultProps.distinct_id = Bytes.toHex( + hash(HashType.size256, Bytes.fromString(distinctId)) + ); + } + + const props = { ...defaultProps, ...properties }; + + await got.post('https://app.posthog.com/capture', { + json: { + api_key: telemetryJson.api_key, + event, + properties: props, + timestamp: new Date(), + }, + }); + + log.info('telemetry report:', event, props); + } catch (err) { + log.warn(`telemetry report ${event} failed:`, err); + } +}