├── .aliases.config.js ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── stale.yml └── workflows │ ├── auto-approve.yml │ ├── build.yml │ ├── bump.yml │ ├── commitlint.yml │ ├── e2e.yml │ ├── reviewdog.yml │ ├── s3release.yml │ └── tests.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierrc ├── .stylelintrc ├── .tern-project ├── .testcafe-electron-rc.js ├── .travis.yml ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE-BSD ├── LICENSE-MIT ├── README.md ├── __testcafe__ ├── .eslintrc ├── bg.spec.ts ├── bookmark.spec.js ├── helpers.js ├── history.spec.js ├── navigation.spec.js ├── peruse.spec.js ├── selectors.js └── settingsMenu.spec.js ├── __tests__ ├── .eslintrc ├── actions │ ├── .gitkeep │ ├── bookmarks.spec.ts │ ├── history.spec.ts │ ├── notifications.spec.ts │ ├── remoteCall.spec.ts │ ├── tabs.spec.ts │ └── windows.spec.ts ├── components │ ├── .gitkeep │ ├── AddressBar.spec.tsx │ ├── AddressBarButtonsLHS.spec.tsx │ ├── AddressBarButtonsRHS.spec.tsx │ ├── AddressBarInput.spec.tsx │ ├── Bookmarks.spec.tsx │ ├── Browser.spec.tsx │ ├── Error.spec.tsx │ ├── History.spec.tsx │ ├── Tab.spec.tsx │ ├── TabBar.spec.tsx │ └── UrlList.spec.tsx ├── constants │ └── constants.spec.ts ├── containers │ └── .gitkeep ├── reducers │ ├── .gitkeep │ ├── bookmarks.spec.ts │ ├── history.spec.ts │ ├── notifications.spec.ts │ ├── remoteCall.spec.ts │ ├── tabs.spec.ts │ └── windows.spec.ts └── utils │ ├── handleNotifications.spec.tsx │ ├── reactNodeToElement.spec.tsx │ └── urlHelpers.spec.ts ├── afterPack.js ├── afterSign.js ├── app ├── __mocks__ │ └── logger.ts ├── actions │ ├── bookmarks_actions.ts │ ├── history_actions.ts │ ├── notification_actions.ts │ ├── remoteCall_actions.ts │ ├── resetStore_action.ts │ ├── tabs_actions.ts │ └── windows_actions.ts ├── app.global.css ├── app.html ├── app.icns ├── autoUpdate.ts ├── background.manageRemoteCalls.ts ├── background.ts ├── bg.html ├── components │ ├── AddressBar │ │ ├── AddressBar.tsx │ │ ├── ButtonsLHS │ │ │ ├── ButtonsLHS.tsx │ │ │ ├── buttonsLHS.css │ │ │ └── index.ts │ │ ├── ButtonsRHS │ │ │ ├── ButtonsRHS.tsx │ │ │ ├── buttonsRHS.css │ │ │ └── index.ts │ │ ├── Input │ │ │ ├── Input.tsx │ │ │ ├── index.ts │ │ │ └── input.css │ │ ├── addressBar.css │ │ └── index.ts │ ├── Browser │ │ ├── Browser.tsx │ │ ├── browser.css │ │ └── index.ts │ ├── CustomMenu │ │ ├── CustomMenu.tsx │ │ ├── customMenu.css │ │ └── index.ts │ ├── PerusePages │ │ ├── Bookmarks │ │ │ ├── Bookmarks.tsx │ │ │ ├── bookmarks.css │ │ │ └── index.ts │ │ ├── Error │ │ │ ├── Error.tsx │ │ │ ├── error.css │ │ │ └── index.ts │ │ └── History │ │ │ ├── History.tsx │ │ │ ├── history.css │ │ │ └── index.ts │ ├── Tab │ │ ├── Tab.tsx │ │ ├── index.ts │ │ └── tab.css │ ├── TabBar │ │ ├── TabBar.tsx │ │ ├── index.ts │ │ └── tabBar.css │ ├── TabContents │ │ ├── TabContents.tsx │ │ ├── index.ts │ │ └── tabContents.css │ └── UrlList │ │ ├── UrlList.tsx │ │ ├── index.ts │ │ └── urlList.css ├── constants.ts ├── constants │ ├── classes.js │ └── routes.json ├── containers │ ├── App.tsx │ ├── BrowserWindow.tsx │ └── app.css ├── definitions │ └── globals.d.ts ├── extensions │ ├── __mocks__ │ │ └── components.ts │ ├── backgroundProcess.ts │ ├── components.ts │ ├── index.ts │ ├── mainProcess.ts │ ├── renderProcess.ts │ └── safe │ │ ├── actions │ │ ├── __mocks__ │ │ │ └── safeBrowserApplication_actions.ts │ │ ├── aliased │ │ │ └── index.ts │ │ ├── pWeb_actions.ts │ │ └── safeBrowserApplication_actions.ts │ │ ├── backgroundProcess │ │ ├── fetch.tsx │ │ ├── handleRemoteCalls.ts │ │ ├── index.ts │ │ ├── safeBrowserApplication │ │ │ ├── index.ts │ │ │ ├── init │ │ │ │ ├── initAnon.ts │ │ │ │ ├── initAuthed.ts │ │ │ │ └── networkStateChange.ts │ │ │ ├── theApplication.ts │ │ │ ├── uploadFiles │ │ │ │ └── index.ts │ │ │ └── webIds │ │ │ │ └── index.ts │ │ └── server-routes │ │ │ ├── index.ts │ │ │ └── safe.tsx │ │ ├── components │ │ ├── FilesContainer │ │ │ ├── FilesContainer.tsx │ │ │ ├── filesContainer.css │ │ │ └── index.tsx │ │ ├── NrsRegistryBar │ │ │ ├── NrsRegistryBar.tsx │ │ │ ├── index.tsx │ │ │ └── nrsRegistryBar.css │ │ ├── SafePages │ │ │ ├── Editor │ │ │ │ ├── Editor.tsx │ │ │ │ ├── editor.css │ │ │ │ └── index.ts │ │ │ └── MySites │ │ │ │ ├── MySites.tsx │ │ │ │ ├── index.ts │ │ │ │ └── mysites.css │ │ ├── webIdDropdown │ │ │ ├── WebIdDropdown.tsx │ │ │ ├── index.tsx │ │ │ └── webIdButtons.css │ │ ├── wrapAddressBarButtons.css │ │ ├── wrapAddressBarButtonsLHS.tsx │ │ ├── wrapAddressBarButtonsRHS.tsx │ │ ├── wrapAddressBarInput.less │ │ ├── wrapAddressBarInput.tsx │ │ └── wrapBrowser.tsx │ │ ├── constants │ │ ├── browser_application.ts │ │ ├── index.ts │ │ └── safe-constants.ts │ │ ├── defaultNewSite │ │ └── index.html │ │ ├── err-constants.ts │ │ ├── index.ts │ │ ├── main-process │ │ ├── index.ts │ │ ├── onAppReady.ts │ │ ├── onReceiveUrl.ts │ │ └── preAppLoad.ts │ │ ├── menus.ts │ │ ├── network │ │ ├── authenticator-comms.ts │ │ └── index.ts │ │ ├── protocols │ │ └── safe.ts │ │ ├── reducers │ │ ├── index.ts │ │ ├── initialAppState.ts │ │ ├── pWeb_reducer.ts │ │ └── safeBrowserApp.ts │ │ ├── renderProcess │ │ └── index.ts │ │ ├── rendererProcess │ │ ├── internalPages.tsx │ │ └── styleConstants.ts │ │ ├── requestManagement.ts │ │ ├── safe-pages.css │ │ ├── safe.d.ts │ │ ├── test │ │ ├── .eslintrc │ │ ├── actions │ │ │ └── pWeb.spec.ts │ │ ├── app │ │ │ ├── onNetworkChange.placeholderspec.ts │ │ │ ├── saferSafe.spec.ts │ │ │ └── webviewPreload.spec.ts │ │ ├── components │ │ │ └── FilesContainer.spec.tsx │ │ ├── reducers │ │ │ ├── pWeb.spec.ts │ │ │ └── safeBrowserApp.spec.ts │ │ └── utils │ │ │ ├── requestManagement.spec.ts │ │ │ └── safeHelpers.spec.ts │ │ ├── utils │ │ ├── isInEditor.ts │ │ ├── safeHelpers.ts │ │ └── urlIsValid.ts │ │ └── webviewProcess │ │ ├── saferSafe.ts │ │ └── webviewPreload.ts ├── index.tsx ├── locales │ └── en.json ├── logger.ts ├── main.dev.ts ├── menu.ts ├── openWindow.ts ├── reducers │ ├── bookmarks.ts │ ├── history.ts │ ├── index.ts │ ├── initialAppState.ts │ ├── notifications.ts │ ├── remoteCalls.ts │ ├── tabs.ts │ └── windows.ts ├── server │ └── index.ts ├── setupBackground.ts ├── store │ ├── addMiddlewares.ts │ ├── configureStore.dev.ts │ ├── configureStore.prod.ts │ └── configureStore.ts ├── utils │ ├── .gitkeep │ ├── __mocks__ │ │ └── extendComponent.tsx │ ├── extendComponent.tsx │ ├── getMostRecentlyActiveWindow.ts │ ├── handleNotificiations.tsx │ ├── reactNodeToElement.tsx │ └── urlHelpers.ts └── webPreload.ts ├── babel.config.js ├── builderConfig.js ├── codeowners ├── commitlint.config.js ├── configs ├── webpack.config.background.prod.babel.js ├── webpack.config.base.js ├── webpack.config.eslint.js ├── webpack.config.main.prod.babel.js ├── webpack.config.renderer.dev.babel.js ├── webpack.config.renderer.dev.dll.babel.js ├── webpack.config.renderer.prod.babel.js └── webpack.config.web-preload.prod.babel.js ├── docs ├── browser-development │ └── Application-Design-Overview.md └── web-app-development │ └── SAFE-Web-App-Overview.md ├── install-libs.js ├── internals ├── img │ ├── eslint-padded-90.png │ ├── eslint-padded.png │ ├── eslint.png │ ├── flow-padded-90.png │ ├── flow-padded.png │ ├── flow.png │ ├── jest-padded-90.png │ ├── jest-padded.png │ ├── jest.png │ ├── js-padded.png │ ├── js.png │ ├── npm.png │ ├── react-padded-90.png │ ├── react-padded.png │ ├── react-router-padded-90.png │ ├── react-router-padded.png │ ├── react-router.png │ ├── react.png │ ├── redux-padded-90.png │ ├── redux-padded.png │ ├── redux.png │ ├── webpack-padded-90.png │ ├── webpack-padded.png │ ├── webpack.png │ ├── yarn-padded-90.png │ ├── yarn-padded.png │ └── yarn.png ├── mocks │ └── fileMock.ts ├── scripts │ ├── CheckBuiltsExist.ts │ ├── CheckNodeEnv.js │ ├── CheckPortInUse.ts │ └── updateSharedVaultConfig.js └── testsite │ ├── css │ └── style.css │ ├── img │ └── logo.png │ ├── index.html │ └── sub │ └── index.html ├── jest.config.js ├── mac.zip ├── mocks └── fileMock.ts ├── package.json ├── postcss.config.js ├── renovate.json ├── resources ├── Electron Helper.safe_core.config ├── crust.config ├── entitlements.mac.plist ├── favicon.ico ├── icon.icns ├── icon.ico ├── icon.png ├── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ ├── 96x96.png │ ├── heart.png │ └── make-sizes.sh ├── locales │ └── en.json ├── readme │ ├── experiments-toggle.png │ ├── experiments-visual-indicator.png │ ├── md-viewer.png │ ├── mock-visual-indicator.png │ ├── webid-selector.png │ └── xorurl-screenshot.png └── safeicon.png ├── tests_setup.js ├── tsconfig.json └── yarn.lock /.aliases.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | $App: './app', 3 | $Test: './__tests__', 4 | $Actions: './app/actions', 5 | $BuilderConfig: './builderConfig.js', 6 | $Store: './app/store', 7 | $Extensions: './app/extensions', 8 | $Reducers: './app/reducers', 9 | $Logger: './app/logger.ts', 10 | $Components: './app/components', 11 | $Constants: './app/constants.ts', 12 | $Package: './package.json', 13 | $Utils: './app/utils', 14 | $TestCafeHelpers: './__testcafe__/helpers.js' 15 | }; 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.{json,js,jsx,html,css}] 11 | indent_style = space 12 | indent_size = 4 13 | 14 | [.eslintrc] 15 | indent_style = space 16 | indent_size = 4 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | app/node_modules 30 | 31 | # OSX 32 | .DS_Store 33 | 34 | # App packaged 35 | release 36 | app/*.prod.js 37 | app/*.js.map 38 | app/style.css 39 | app/style.css.map 40 | dist 41 | dll 42 | 43 | .idea 44 | npm-debug.log.* 45 | __snapshots__ 46 | 47 | # Package.json 48 | package.json 49 | .travis.yml 50 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | *.ico binary 4 | *.icns binary 5 | resources/PreloadDevVault binary 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Prerequisites 4 | 5 | - [ ] Using yarn 6 | - [ ] Using an up-to-date master branch 7 | - [ ] Link to stacktrace in a Gist (for bugs) 8 | 9 | ## Expected Behavior 10 | 11 | 12 | 13 | 14 | ## Current Behavior 15 | 16 | 17 | 18 | 19 | ## Possible Solution 20 | 21 | 22 | 23 | 24 | ## Steps to Reproduce (for bugs) 25 | 26 | 27 | 28 | 29 | 1. 30 | 31 | 2. 32 | 33 | 3. 34 | 35 | 4. 36 | 37 | ## Context 38 | 39 | 40 | 41 | 42 | 43 | ## Your Environment 44 | 45 | 46 | 47 | - Node version : 48 | - Version or Branch used : 49 | - Operating System and version : 50 | - Link to your project : 51 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 71 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | day: sunday 8 | time: "02:30" 9 | timezone: Europe/London 10 | open-pull-requests-limit: 10 11 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pr 8 | - discussion 9 | - e2e 10 | - enhancement 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | name: Auto approve 2 | 3 | on: 4 | pull_request 5 | 6 | jobs: 7 | auto-approve: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: hmarr/auto-approve-action@v2.0.0 11 | if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]' 12 | with: 13 | github-token: "${{ secrets.GITHUB_TOKEN }}" 14 | -------------------------------------------------------------------------------- /.github/workflows/bump.yml: -------------------------------------------------------------------------------- 1 | name: Version Bump and Tag 2 | 3 | on: 4 | # Trigger the workflow on push only for the master branch 5 | push: 6 | branches: 7 | - master 8 | 9 | env: 10 | NODE_ENV: 'development' 11 | GITHUB_TOKEN: ${{ secrets.PERSONAL_GITHUB_TOKEN }} 12 | 13 | jobs: 14 | bump: 15 | runs-on: ubuntu-latest 16 | if: "!startsWith(github.event.head_commit.message, 'chore(release):')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | fetch-depth: '0' 21 | - uses: actions/setup-node@v1 22 | with: 23 | node-version: '12.x' 24 | - name: Setup git 25 | run: | 26 | git remote add github "$REPO" 27 | git config --local user.email "action@github.com" 28 | git config --local user.name "GitHub Action" 29 | - name: Bump Version 30 | run: npx standard-version -a 31 | - name: Push changes 32 | run: git push "https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY" HEAD:master 33 | - name: Push tags to master 34 | run: git push "https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY" HEAD:master --tags 35 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | name: Commitlint 2 | on: [pull_request] 3 | 4 | jobs: 5 | lint: 6 | runs-on: ubuntu-latest 7 | env: 8 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 13 | - uses: wagoid/commitlint-github-action@f114310111fdbd07e99f47f9ca13d62b3ec98372 14 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | name: E2E Tests 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 7 | 8 | # No CSC Keys etc as PRs dont have access to this. 9 | NODE_ENV: prod 10 | jobs: 11 | build: 12 | name: E2E Tests 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [windows-latest, ubuntu-20.04] 17 | # os: [ubuntu-latest, windows-latest, macos-latest] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | with: 22 | fetch-depth: '0' 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: '12' 26 | 27 | - name: Install 28 | run: yarn install --ignore-engines --network-timeout 800000; 29 | 30 | - run: yarn package 31 | 32 | - name: Run headless test 33 | uses: GabrielBB/xvfb-action@v1.2 34 | with: 35 | run: yarn test-e2e-packed 36 | # - uses: DevExpress/testcafe-action@latest 37 | # with: 38 | # args: 'electron:. ./__testcafe__/*.spec.*' 39 | -------------------------------------------------------------------------------- /.github/workflows/reviewdog.yml: -------------------------------------------------------------------------------- 1 | name: Spell Check 2 | on: [pull_request] 3 | jobs: 4 | misspell: 5 | name: runner / misspell 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Check out code. 9 | uses: actions/checkout@v1 10 | - name: Remove lockfiles to avoid spellcheck 11 | run: rm ./*.lock 12 | - name: misspell 13 | uses: reviewdog/action-misspell@v1 14 | with: 15 | github_token: ${{ secrets.github_token }} 16 | locale: 'US' 17 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 7 | NODE_ENV: prod 8 | jobs: 9 | tests: 10 | name: Test 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, windows-latest, macos-latest] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | fetch-depth: '0' 20 | - uses: actions/setup-node@v1 21 | with: 22 | node-version: '12' 23 | 24 | - name: Install 25 | run: yarn install --ignore-engines --network-timeout 800000; 26 | 27 | - name: Lint 28 | run: yarn lint --quiet 29 | - name: Peruse Tests 30 | run: yarn run test-peruse --forceExit 31 | - name: Extension Tests 32 | run: yarn run test-exts --forceExit 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | app/*.prod.js 5 | *.prod.js.map 6 | TODO 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # TODO 14 | TODO 15 | 16 | # packaging license files 17 | *.js.LICENSE 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # node-waf configuration 29 | .lock-wscript 30 | 31 | # Compiled binary addons (http://nodejs.org/api/addons.html) 32 | build/Release 33 | .eslintcache 34 | 35 | # Dependency directory 36 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 37 | node_modules 38 | 39 | # OSX 40 | .DS_Store 41 | 42 | # flow-typed 43 | flow-typed/npm/* 44 | !flow-typed/npm/module_vx.x.x.js 45 | 46 | # App packaged 47 | release 48 | app/main.prod.js 49 | app/main.prod.js.map 50 | app/renderer.prod.js 51 | app/renderer.prod.js.map 52 | app/style.css 53 | app/style.css.map 54 | dist 55 | dll 56 | 57 | .idea 58 | npm-debug.log.* 59 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | runtime = electron 2 | target = 8.4.0 3 | arch = x64 4 | target_arch = x64 5 | disturl = https://electronjs.org/headers 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": [".prettierrc", ".babelrc", ".eslintrc", ".stylelintrc"], 5 | "options": { 6 | "parser": "json" 7 | } 8 | } 9 | ], 10 | "singleQuote": true 11 | } 12 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaVersion": 7, 3 | "libs": [ 4 | "browser" 5 | ], 6 | "dontLoad": [ 7 | "node_modules/**" 8 | ], 9 | "plugins": { 10 | "doc_comment": { 11 | "fullDocs": true, 12 | "strong": true 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.testcafe-electron-rc.js: -------------------------------------------------------------------------------- 1 | // const RELEASE_FOLDER_NAME = require('./releaseName'); 2 | let TEST_UNPACKED = process.env.TEST_UNPACKED; 3 | const pkg = require('./package'); 4 | 5 | let appString = 'safe-browser'; 6 | let appResources = 'resources/app.asar'; 7 | 8 | const { platform } = process; 9 | const MAC_OS = 'darwin'; 10 | const LINUX = 'linux'; 11 | const WINDOWS = 'win32'; 12 | 13 | let appChannel = ''; 14 | if (pkg.version.includes('-alpha')) { 15 | appChannel = ' Alpha'; 16 | } 17 | 18 | if (pkg.version.includes('-beta')) { 19 | appChannel = ' Beta'; 20 | } 21 | 22 | if (platform === MAC_OS) { 23 | PLATFORM_NAME = 'mac'; 24 | appString = `SAFE Browser${appChannel}.app`; 25 | appResources = 'Contents/Resources/app.asar'; 26 | } 27 | 28 | if (platform === LINUX) { 29 | PLATFORM_NAME = 'linux-unpacked'; 30 | } 31 | 32 | if (platform === WINDOWS) { 33 | PLATFORM_NAME = 'win-unpacked'; 34 | appString = `SAFE Browser${appChannel}.exe`; 35 | } 36 | 37 | const allArgs = ['--ignoreAppLocation']; 38 | 39 | // Changing mainWindowURl to that of a tab gets us the browser UI going too. 40 | const config = { 41 | mainWindowUrl: './app/app.html', 42 | appPath: '.' 43 | // electronPath: TEST_UNPACKED ? 'undefined' : `./release/${RELEASE_FOLDER_NAME}/${appString}`, 44 | // , appArgs: allArgs 45 | // openDevTools: true 46 | }; 47 | 48 | if (!TEST_UNPACKED) { 49 | if (platform === MAC_OS) { 50 | config.mainWindowUrl = `./release/${PLATFORM_NAME}/${appString}/${appResources}/app/app.html`; 51 | config.appPath = `./release/${PLATFORM_NAME}/${appString}/${appResources}`; 52 | } 53 | 54 | config.electronPath = `./release/${PLATFORM_NAME}/${appString}`; 55 | console.log('Testing packaged app.', config, ' \n'); 56 | } else { 57 | console.log('Testing unpackaged app. ', config, ' \n'); 58 | } 59 | 60 | module.exports = config; 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - secure: "sDQ/Ai7CWofxj203YwmMQhGlfDhCnDQn8ti3rTPk9tVxbqR8uYYbSBJw46xxdzcVfwCXN6z+6ZG1Xp9DXYSn5tGMW+JiSMY0gaSI0/fuL3GqBlOGRMCljJEQNb8dHeXjVqbWfviymx5QT8ATF4xRR9jslLLAQkiqFqqNanB6A2pAZqYo9OVWg8+K5wN+/kpKevYkMFw0Bg2bZxVte8r/TTMN6aHmy8uCFCZ1jdjdZquaG/qu89jwFDriJXGUFqzyqa+j1Lp2HDEf9FsaapXLzrj3q6H7ykfYYoFcHo/6MO46YGBFvbyd0yFy4Ipca+gRUR6p4U74+rL0BQfj5JfM0Ekjnz9KwvbGH178oY02CN2KG+OeLkRpaom94ncibZR7WvbBi050EZpkbpqW9H9e5UqQQI3wL+YqWjD6rqjA2L5MxBSSi5Na3L7m2UdXlXQd7yBQK9uCywcCpHWoihNLC8GJ1DVD2EUBReW0YQatBdmlsP8XoElOngILA72+p7bjJ5aJugxSc1gZejy4P0suYVSdYm1k6+Og0PojH38gaqWunOCA+fb5bxGIsB/dFfTkEzp/G/NGNl6b8KwTXCCbS5E6qWI0/NHinCLGNQoovEgFwdVjF6+zOmroCwSSx23xgG1Fr1TY4GIcw1Td73ibGtJU1B6UyoBaaOQ9WpDxEes=" 4 | matrix: 5 | fast_finish: true 6 | include: 7 | - os: osx 8 | osx_image: xcode11.2 9 | language: node_js 10 | env: 11 | - ELECTRON_CACHE=$HOME/.cache/electron 12 | - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder 13 | - NODE_ENV=prod 14 | 15 | cache: 16 | yarn: true 17 | directories: 18 | - node_modules 19 | - $(npm config get prefix)/lib/node_modules 20 | - $HOME/.cache/electron 21 | 22 | before_install: 23 | - echo $TRAVIS_NODE_VERSION 24 | 25 | install: 26 | - yarn 27 | - ls node_modules/safe-nodejs/native 28 | 29 | 30 | before_script: 31 | # osx set window size 32 | - "/Library/Application Support/VMware Tools/vmware-resolutionSet" 1920 1080; 33 | 34 | 35 | script: 36 | - xattr -cr . ; 37 | - yarn build-e2e 38 | - travis_retry yarn cross-env NODE_ENV=test TEST_CAFE=true TEST_UNPACKED=true IS_UNPACKED=true t testcafe electron:. ./__testcafe__/peruse.spec.* 39 | - travis_retry yarn cross-env NODE_ENV=test TEST_UNPACKED=true TEST_CAFE=true IS_UNPACKED=true t testcafe electron:. ./__testcafe__/navigation.spec.* 40 | - travis_retry yarn cross-env NODE_ENV=test TEST_UNPACKED=true TEST_CAFE=true IS_UNPACKED=true t testcafe electron:. ./__testcafe__/settingsMenu.spec.* 41 | 42 | 43 | after_failure: 44 | - system_profiler SPDisplaysDataType | grep Resolution 45 | 46 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.validate.enable": false, 3 | "search.exclude": { 4 | ".git": true, 5 | "node_modules": true, 6 | "bower_components": true 7 | }, 8 | "eslint.validate": [ 9 | "javascript", 10 | "javascriptreact", 11 | { 12 | "language": "typescript", 13 | "autoFix": true 14 | }, 15 | { 16 | "language": "typescriptreact", 17 | "autoFix": true 18 | } 19 | ], 20 | "files.exclude": { 21 | "**/node_modules": true, 22 | "**/release": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE-BSD: -------------------------------------------------------------------------------- 1 | Copyright 2018 MaidSafe.net limited. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2018 Maidsafe.net limited 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /__testcafe__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "jest/no-disabled-tests": "warn", 4 | "jest/no-focused-tests": "error", 5 | "jest/no-test-callback": "off", 6 | "jest/no-identical-title": "error" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /__testcafe__/bg.spec.ts: -------------------------------------------------------------------------------- 1 | // import { ClientFunction, Selector } from 'testcafe'; 2 | // // import { getPageUrl } from './helpers'; 3 | // 4 | // const getPageTitle = ClientFunction(() => document.title); 5 | // 6 | // const assertNoConsoleErrors = async t => { 7 | // const { error } = await t.getBrowserConsoleMessages(); 8 | // await t.expect(error).eql([]); 9 | // }; 10 | // // 11 | // // fixture`Browser BG Page` 12 | // // .page('../app/bg.html'); 13 | // // // .afterEach(assertNoConsoleErrors); 14 | // // 15 | // // test('Bg page should exist', async t => { 16 | // // await t.expect(getPageTitle()).eql('Background Process of SAFE Browser'); 17 | // // }); 18 | -------------------------------------------------------------------------------- /__testcafe__/bookmark.spec.js: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe'; 2 | import { ReactSelector, waitForReact } from 'testcafe-react-selectors'; 3 | import { 4 | getMainMenuItem, 5 | clickOnMainMenuItem, 6 | } from 'testcafe-browser-provider-electron'; 7 | 8 | import { 9 | getPageUrl, 10 | getPageTitle, 11 | openLocation, 12 | navigateTo, 13 | resetStore, 14 | } from './helpers'; 15 | import { CLASSES } from '../app/constants/classes'; 16 | import { bookmarkPage, closeTab, addTab, tab } from './selectors'; 17 | 18 | fixture`bookmarks successfully reset w/ reset store` 19 | .page( '../app/app.html' ) 20 | .afterEach( async ( t ) => { 21 | await resetStore( t ); 22 | await t.wait( 500 ); 23 | } ) 24 | .beforeEach( async () => { 25 | await waitForReact(); 26 | } ); 27 | 28 | test( 'check bookmark items', async ( t ) => { 29 | await t.click( addTab ).expect( tab.count ).eql( 2 ); 30 | await navigateTo( t, 'cat.ashi' ); 31 | await t.click( `.${CLASSES.BOOKMARK_PAGE}` ); 32 | await navigateTo( t, 'eye.eye' ); 33 | await t.click( `.${CLASSES.BOOKMARK_PAGE}` ); 34 | 35 | await t 36 | .click( `.${CLASSES.SETTINGS_MENU__BUTTON}` ) 37 | .click( `.${CLASSES.SETTINGS_MENU__BOOKMARKS}` ); 38 | await t 39 | .expect( Selector( 'h1' ).withText( 'Bookmarks' ).exists ) 40 | .ok() 41 | .expect( Selector( '.urlList__table' ).exists ) 42 | .ok() 43 | .expect( Selector( '.tableCell__default' ).count ) 44 | .eql( 3 ) 45 | .expect( 46 | Selector( '.tableCell__default' ).withText( 'safe://cat.ashi' ).exists 47 | ) 48 | .ok() 49 | .expect( 50 | Selector( '.tableCell__default' ).withText( 'safe://eye.eye' ).exists 51 | ) 52 | .ok(); 53 | } ); 54 | 55 | test( 'Check if on reset store bookmarks reset to InitialState', async ( t ) => { 56 | await resetStore( t ); 57 | 58 | await t 59 | .click( `.${CLASSES.SETTINGS_MENU__BUTTON}` ) 60 | .click( `.${CLASSES.SETTINGS_MENU__BOOKMARKS}` ); 61 | await t.expect( Selector( '.tableCell__default' ).count ).eql( 1 ); 62 | } ); 63 | -------------------------------------------------------------------------------- /__testcafe__/helpers.js: -------------------------------------------------------------------------------- 1 | import { ClientFunction } from 'testcafe'; 2 | import { clickOnMainMenuItem } from 'testcafe-browser-provider-electron'; 3 | 4 | import { addressBarInput } from './selectors'; 5 | 6 | export const getPageUrl = ClientFunction( () => window.location.href ); 7 | export const resetStore = async ( t ) => { 8 | await clickOnMainMenuItem( ['&Tests', 'Reset the store'] ); 9 | }; 10 | export const addTabNext = async ( t ) => { 11 | await clickOnMainMenuItem( ['&Tests', 'Add Tab Next'] ); 12 | }; 13 | export const openLocation = async ( t ) => { 14 | await clickOnMainMenuItem( ['&File', 'Open Location'] ); 15 | }; 16 | export const selectPreviousTab = async ( t ) => { 17 | await clickOnMainMenuItem( ['&File', 'Select Previous Tab'] ); 18 | }; 19 | 20 | export const getPageTitle = ClientFunction( () => document.title ); 21 | 22 | export const navigateTo = async ( t, address ) => { 23 | return ( 24 | t 25 | .selectText( addressBarInput ) 26 | .pressKey( 'backspace' ) 27 | // .debug() 28 | .typeText( addressBarInput, address ) 29 | .pressKey( 'enter' ) 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /__testcafe__/history.spec.js: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe'; 2 | import { ReactSelector, waitForReact } from 'testcafe-react-selectors'; 3 | import { 4 | getMainMenuItem, 5 | clickOnMainMenuItem, 6 | } from 'testcafe-browser-provider-electron'; 7 | 8 | import { 9 | getPageUrl, 10 | getPageTitle, 11 | openLocation, 12 | navigateTo, 13 | resetStore, 14 | } from './helpers'; 15 | import { CLASSES } from '../app/constants/classes'; 16 | import { bookmarkPage, closeTab, addTab, tab } from './selectors'; 17 | 18 | fixture`history successfully reset w/ reset store` 19 | .page( '../app/app.html' ) 20 | .afterEach( async ( t ) => { 21 | await resetStore( t ); 22 | await t.wait( 500 ); 23 | } ) 24 | .beforeEach( async () => { 25 | await waitForReact(); 26 | } ); 27 | 28 | // test( 'navigate to various pages', async ( t ) => { 29 | // await t 30 | // .click( addTab ) 31 | // .expect( tab.count ) 32 | // .eql( 2 ); 33 | // await navigateTo( t, 'cat.ashi' ); 34 | // await navigateTo( t, 'eye.eye' ); 35 | // } ); 36 | 37 | test( 'check history items', async ( t ) => { 38 | await t.click( addTab ).expect( tab.count ).eql( 2 ); 39 | await navigateTo( t, 'cat.ashi' ); 40 | await navigateTo( t, 'eye.eye' ); 41 | 42 | await t 43 | .click( `.${CLASSES.SETTINGS_MENU__BUTTON}` ) 44 | .click( `.${CLASSES.SETTINGS_MENU__HISTORY}` ); 45 | 46 | await t 47 | .expect( Selector( 'h1' ).withText( 'History' ).exists ) 48 | .ok() 49 | .expect( Selector( '.history__table' ).exists ) 50 | .ok() 51 | .expect( Selector( '.tableCell__default' ).count ) 52 | .eql( 3 ) 53 | .expect( 54 | Selector( '.tableCell__default' ).withText( 'safe://cat.ashi' ).exists 55 | ) 56 | .ok() 57 | .expect( 58 | Selector( '.tableCell__default' ).withText( 'safe://eye.eye' ).exists 59 | ) 60 | .ok(); 61 | } ); 62 | 63 | test( 'Check if on reset store history reset to InitialState', async ( t ) => { 64 | resetStore( t ); 65 | 66 | await t 67 | .click( `.${CLASSES.SETTINGS_MENU__BUTTON}` ) 68 | .click( `.${CLASSES.SETTINGS_MENU__HISTORY}` ); 69 | await t.expect( Selector( '.tableCell__default' ).count ).eql( 1 ); 70 | } ); 71 | -------------------------------------------------------------------------------- /__testcafe__/selectors.js: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe'; 2 | 3 | import { CLASSES } from '../app/constants/classes'; 4 | 5 | export const addressBar = Selector( `.${CLASSES.ADDRESS_BAR}` ); 6 | export const addressBarInput = Selector( `.${CLASSES.ADDRESS_INPUT}` ); 7 | export const addTab = Selector( `.${CLASSES.ADD_TAB}` ); 8 | export const closeTab = Selector( `.${CLASSES.CLOSE_TAB}` ); 9 | export const tab = Selector( `.${CLASSES.TAB}` ); 10 | export const bookmarkPage = Selector( `.${CLASSES.BOOKMARK_PAGE}` ); 11 | -------------------------------------------------------------------------------- /__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest/globals": true 4 | }, 5 | "plugins": ["jest"], 6 | "rules": { 7 | "jest/no-disabled-tests": "warn", 8 | "jest/no-focused-tests": "error", 9 | "jest/no-identical-title": "error" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /__tests__/actions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/__tests__/actions/.gitkeep -------------------------------------------------------------------------------- /__tests__/actions/bookmarks.spec.ts: -------------------------------------------------------------------------------- 1 | import * as bookmarks from '$Actions/bookmarks_actions'; 2 | 3 | describe( 'bookmark actions', () => { 4 | it( 'should have types', () => { 5 | expect( bookmarks.TYPES ).toBeDefined(); 6 | } ); 7 | 8 | it( 'should add a bookmark', () => { 9 | const payload = { text: 'hi' }; 10 | const expectedAction = { 11 | type: bookmarks.TYPES.ADD_BOOKMARK, 12 | payload 13 | }; 14 | expect( bookmarks.addBookmark( payload ) ).toEqual( expectedAction ); 15 | } ); 16 | 17 | it( 'should remove a bookmark', () => { 18 | const payload = { text: 'ciao' }; 19 | const expectedAction = { 20 | type: bookmarks.TYPES.REMOVE_BOOKMARK, 21 | payload 22 | }; 23 | expect( bookmarks.removeBookmark( payload ) ).toEqual( expectedAction ); 24 | } ); 25 | 26 | it( 'should update a bookmark', () => { 27 | const payload = { text: 'ciao' }; 28 | const expectedAction = { 29 | type: bookmarks.TYPES.UPDATE_BOOKMARK, 30 | payload 31 | }; 32 | expect( bookmarks.updateBookmark( payload ) ).toEqual( expectedAction ); 33 | } ); 34 | } ); 35 | -------------------------------------------------------------------------------- /__tests__/actions/history.spec.ts: -------------------------------------------------------------------------------- 1 | import * as history from '$Actions/history_actions'; 2 | 3 | const date = new Date().toLocaleDateString(); 4 | 5 | describe( 'history actions', () => { 6 | it( 'should have types', () => { 7 | expect( history.TYPES ).toBeDefined(); 8 | } ); 9 | 10 | it( 'should update history', () => { 11 | const payload = { 12 | history: { 13 | [date]: [ 14 | { url: 'safe-auth://home/#/login', timeStamp: 1559635322450 }, 15 | { url: 'safe://cat.ashi', timeStamp: 1559635322111 }, 16 | { url: 'safe://home.dgeddes', timeStamp: 1559635322123 }, 17 | { url: 'safe://eye.eye', timeStamp: 1559635322345 }, 18 | { url: 'safe://safenetworkprimer', timeStamp: 1559635322456 }, 19 | { url: 'safe://typer.game', timeStamp: 1559635322678 } 20 | ], 21 | '10/11/2019': [ 22 | { 23 | url: 'safe://another-another-url', 24 | timeStamp: 1469635322567 25 | }, 26 | { 27 | url: 'safe://another-url', 28 | timeStamp: 1239635322567 29 | } 30 | ] 31 | } 32 | }; 33 | const expectedAction = { 34 | type: history.TYPES.UPDATE_HISTORY_STATE, 35 | payload 36 | }; 37 | expect( history.updateHistoryState( payload ) ).toEqual( expectedAction ); 38 | } ); 39 | } ); 40 | -------------------------------------------------------------------------------- /__tests__/actions/notifications.spec.ts: -------------------------------------------------------------------------------- 1 | import * as notifications from '$Actions/notification_actions'; 2 | 3 | describe( 'notification actions', () => { 4 | it( 'should have types', () => { 5 | expect( notifications.TYPES ).toBeDefined(); 6 | } ); 7 | 8 | it( 'should add a notification', () => { 9 | const payload = { title: 'hi' }; 10 | const expectedAction = { 11 | type: notifications.TYPES.ADD_NOTIFICATION, 12 | payload 13 | }; 14 | expect( notifications.addNotification( payload ) ).toEqual( expectedAction ); 15 | } ); 16 | 17 | it( 'should update a notification', () => { 18 | const payload = { title: 'hi', id: 'A' }; 19 | const expectedAction = { 20 | type: notifications.TYPES.UPDATE_NOTIFICATION, 21 | payload 22 | }; 23 | expect( notifications.updateNotification( payload ) ).toEqual( expectedAction ); 24 | } ); 25 | 26 | it( 'should clear a notification', () => { 27 | const expectedAction = { 28 | type: notifications.TYPES.CLEAR_NOTIFICATION 29 | }; 30 | expect( notifications.clearNotification() ).toEqual( expectedAction ); 31 | } ); 32 | } ); 33 | -------------------------------------------------------------------------------- /__tests__/actions/remoteCall.spec.ts: -------------------------------------------------------------------------------- 1 | import * as remoteCall from '$Actions/remoteCall_actions'; 2 | 3 | describe( 'remote call actions', () => { 4 | const payload = { id: 1, data: [] }; 5 | it( 'should have types', () => { 6 | expect( remoteCall.TYPES ).toBeDefined(); 7 | } ); 8 | 9 | it( 'should add a remote call', () => { 10 | const expectedAction = { 11 | type: remoteCall.TYPES.ADD_REMOTE_CALL, 12 | payload 13 | }; 14 | expect( remoteCall.addRemoteCall( payload ) ).toEqual( expectedAction ); 15 | } ); 16 | 17 | it( 'should remove a remote call', () => { 18 | const expectedAction = { 19 | type: remoteCall.TYPES.REMOVE_REMOTE_CALL, 20 | payload 21 | }; 22 | expect( remoteCall.removeRemoteCall( payload ) ).toEqual( expectedAction ); 23 | } ); 24 | 25 | it( 'should update a remote call', () => { 26 | const expectedAction = { 27 | type: remoteCall.TYPES.UPDATE_REMOTE_CALL, 28 | payload 29 | }; 30 | expect( remoteCall.updateRemoteCall( payload ) ).toEqual( expectedAction ); 31 | } ); 32 | } ); 33 | -------------------------------------------------------------------------------- /__tests__/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/__tests__/components/.gitkeep -------------------------------------------------------------------------------- /__tests__/components/AddressBar.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import configureStore from 'redux-mock-store'; 4 | 5 | import { AddressBar } from '$Components/AddressBar'; 6 | 7 | const mockStore = configureStore(); 8 | 9 | jest.mock( '$Logger' ); 10 | 11 | jest.mock( 'extensions/safe/actions/safeBrowserApplication_actions' ); 12 | 13 | describe( 'AddressBar', () => { 14 | let wrapper; 15 | let instance; 16 | let props; 17 | let store; 18 | 19 | beforeEach( () => { 20 | props = { 21 | windowId: 1, 22 | address: 'about:blank', 23 | isSelected: false, 24 | isBookmarked: false, 25 | experimentsEnabled: false, 26 | addBookmark: jest.fn(), 27 | removeBookmark: jest.fn(), 28 | tabBackwards: jest.fn(), 29 | tabForwards: jest.fn(), 30 | onBlur: jest.fn(), 31 | onSelect: jest.fn(), 32 | onFocus: jest.fn(), 33 | activeTab: { 34 | isLoading: false, 35 | historyIndex: 1, 36 | history: ['a', 'b'] 37 | } 38 | }; 39 | } ); 40 | 41 | describe( 'constructor( props )', () => { 42 | beforeEach( () => { 43 | store = mockStore( props ); 44 | 45 | wrapper = shallow( ); 46 | 47 | instance = wrapper.instance(); 48 | } ); 49 | it( 'should have name AddressBar', () => { 50 | expect( instance.constructor.name ).toBe( 'AddressBar' ); 51 | } ); 52 | } ); 53 | } ); 54 | -------------------------------------------------------------------------------- /__tests__/components/AddressBarButtonsLHS.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { Button } from 'antd'; 4 | import configureStore from 'redux-mock-store'; 5 | 6 | import { ButtonsLHS as AddressBarButtonsLHS } from '$Components/AddressBar/ButtonsLHS'; 7 | 8 | const mockStore = configureStore(); 9 | 10 | // Some mocks to negate FFI and native libs we dont care about 11 | 12 | // 13 | 14 | jest.mock( 'extensions/safe/actions/safeBrowserApplication_actions' ); 15 | 16 | jest.mock( '$Utils/extendComponent' ); 17 | jest.mock( '$Logger' ); 18 | 19 | describe( 'AddressBarButtonsLHS', () => { 20 | let wrapper; 21 | let props; 22 | let instance; 23 | let store; 24 | 25 | beforeEach( () => { 26 | props = { 27 | windowId: 1, 28 | address: 'about:blank', 29 | isSelected: false, 30 | isBookmarked: false, 31 | experimentsEnabled: false, 32 | addBookmark: jest.fn(), 33 | removeBookmark: jest.fn(), 34 | tabBackwards: jest.fn(), 35 | TabForwards: jest.fn(), 36 | onBlur: jest.fn(), 37 | onSelect: jest.fn(), 38 | onFocus: jest.fn(), 39 | activeTab: { isLoading: false } 40 | }; 41 | } ); 42 | 43 | describe( 'constructor( props )', () => { 44 | beforeEach( () => { 45 | store = mockStore( props ); 46 | 47 | wrapper = shallow( ); 48 | instance = wrapper.instance(); 49 | } ); 50 | 51 | it( 'should render 3 buttons', () => { 52 | expect( wrapper.find( Button ).length ).toBe( 3 ); 53 | } ); 54 | } ); 55 | } ); 56 | -------------------------------------------------------------------------------- /__tests__/components/AddressBarButtonsRHS.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import configureStore from 'redux-mock-store'; 4 | 5 | import { ButtonsRHS as AddressBarButtonsRHS } from '$Components/AddressBar/ButtonsRHS'; 6 | 7 | const mockStore = configureStore(); 8 | jest.mock( '$Logger' ); 9 | 10 | // Some mocks to negate FFI and native libs we dont care about 11 | 12 | // 13 | 14 | jest.mock( 'extensions/safe/actions/safeBrowserApplication_actions' ); 15 | 16 | jest.mock( '$Utils/extendComponent' ); 17 | 18 | describe( 'AddressBarButtonsRHS', () => { 19 | let wrapper; 20 | let instance; 21 | let props; 22 | let store; 23 | 24 | beforeEach( () => { 25 | props = { 26 | windowId: 1, 27 | address: 'about:blank', 28 | isSelected: false, 29 | isBookmarked: false, 30 | addBookmark: jest.fn(), 31 | removeBookmark: jest.fn(), 32 | tabBackwards: jest.fn(), 33 | tabForwards: jest.fn(), 34 | onBlur: jest.fn(), 35 | onSelect: jest.fn(), 36 | onFocus: jest.fn(), 37 | activeTab: { isLoading: false } 38 | }; 39 | } ); 40 | 41 | describe( 'constructor( props )', () => { 42 | beforeEach( () => { 43 | store = mockStore( props ); 44 | 45 | wrapper = shallow( ); 46 | instance = wrapper.instance(); 47 | } ); 48 | 49 | it( 'should have name AddressBarButtonsRHS', () => { 50 | expect( instance.constructor.name ).toMatch( 'ButtonsRHS' ); 51 | } ); 52 | } ); 53 | } ); 54 | -------------------------------------------------------------------------------- /__tests__/components/Bookmarks.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | 4 | import { Bookmarks } from '$Components/PerusePages/Bookmarks'; 5 | import { UrlList } from '$Components/UrlList'; 6 | import { CLASSES } from '$Constants'; 7 | 8 | describe( 'Bookmarks', () => { 9 | let wrapper; 10 | let instance; 11 | let props; 12 | 13 | beforeEach( () => { 14 | props = { 15 | bookmarks: [], 16 | addTab: jest.fn() 17 | }; 18 | 19 | wrapper = mount( ); 20 | } ); 21 | 22 | describe( 'constructor( props )', () => { 23 | it( 'should have name Bookmarks', () => { 24 | expect( Bookmarks.name ).toBe( 'Bookmarks' ); 25 | } ); 26 | } ); 27 | 28 | describe( 'render() with one tab', () => { 29 | beforeEach( () => { 30 | props = { 31 | ...props, 32 | bookmarks: [{ url: 'hello', isActiveTab: true }] 33 | }; 34 | wrapper = shallow( ); 35 | } ); 36 | 37 | it( 'should have one url list', () => { 38 | expect( wrapper.find( UrlList ).length ).toBe( 1 ); 39 | } ); 40 | 41 | it( 'should have one link', () => { 42 | wrapper = mount( ); 43 | expect( wrapper.find( 'a' ).length ).toBe( 1 ); 44 | } ); 45 | } ); 46 | 47 | describe( 'props', () => { 48 | describe( 'tabs', () => { 49 | it( 'tabs length should be "0" by default', () => { 50 | expect( wrapper.props().bookmarks.length ).toBe( 0 ); 51 | } ); 52 | } ); 53 | } ); 54 | } ); 55 | -------------------------------------------------------------------------------- /__tests__/components/Error.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount } from 'enzyme'; 3 | 4 | import { Error } from '$Components/PerusePages/Error'; 5 | 6 | describe( 'Error Component', () => { 7 | let wrapper; 8 | let props = { 9 | address: 'safe://ups', 10 | type: 'CONNECTION_FAILED' 11 | }; 12 | 13 | describe( 'render()', () => { 14 | beforeEach( () => { 15 | props = { ...props, error: { header: 'Error Header' } }; 16 | } ); 17 | 18 | it( 'renders a required h1 header', () => { 19 | wrapper = mount( ); 20 | expect( wrapper.find( 'h1' ).length ).toBe( 1 ); 21 | expect( wrapper.find( 'h1' ).text() ).toBe( 22 | 'Could not connect to the network' 23 | ); 24 | } ); 25 | 26 | it( 'renders a bad request error', () => { 27 | props.type = 'BAD_REQUEST'; 28 | wrapper = mount( ); 29 | expect( wrapper.find( 'h1' ).length ).toBe( 1 ); 30 | expect( wrapper.find( 'h1' ).text() ).toBe( 'Invalid address' ); 31 | } ); 32 | 33 | it( 'renders a INVALID_VERSION error', () => { 34 | props.type = 'INVALID_VERSION'; 35 | wrapper = mount( ); 36 | expect( wrapper.find( 'h1' ).length ).toBe( 1 ); 37 | expect( wrapper.find( 'h1' ).text() ).toBe( 38 | 'This page version does not exist' 39 | ); 40 | } ); 41 | 42 | it( 'renders a NO_CONTENT_FOUND error', () => { 43 | props.type = 'NO_CONTENT_FOUND'; 44 | wrapper = mount( ); 45 | expect( wrapper.find( 'h1' ).length ).toBe( 1 ); 46 | expect( wrapper.find( 'h1' ).text() ).toBe( 'Not Found' ); 47 | } ); 48 | 49 | it( 'renders a UNKNOWN_NAME error', () => { 50 | props.type = 'UNKNOWN_NAME'; 51 | wrapper = mount( ); 52 | expect( wrapper.find( 'h1' ).length ).toBe( 1 ); 53 | expect( wrapper.find( 'h1' ).text() ).toBe( 'Nobody owns this address yet' ); 54 | expect( wrapper.find( 'p' ).text() ).toBe( 55 | 'safe://ups has not been registered yet.' 56 | ); 57 | expect( wrapper.find( 'a' ).text() ).toBe( 'Register safe://ups' ); 58 | } ); 59 | 60 | it( 'optionally renders a subheader', () => { 61 | props = { ...props, error: { subHeader: 'Error subheader' } }; 62 | wrapper = mount( ); 63 | expect( wrapper.find( 'p' ).length ).toBe( 1 ); 64 | } ); 65 | } ); 66 | } ); 67 | -------------------------------------------------------------------------------- /__tests__/components/UrlList.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount } from 'enzyme'; 3 | import { Table, TableRow } from 'nessie-ui'; 4 | 5 | import { UrlList } from '$Components/UrlList'; 6 | 7 | describe( 'UrlList', () => { 8 | let wrapper; 9 | let instance; 10 | let props; 11 | 12 | beforeEach( () => { 13 | props = { 14 | list: [] 15 | }; 16 | 17 | wrapper = mount( ); 18 | } ); 19 | 20 | describe( 'constructor( props )', () => { 21 | it( 'should have name UrlList', () => { 22 | expect( UrlList.name ).toBe( 'UrlList' ); 23 | } ); 24 | } ); 25 | 26 | describe( 'render() with one tab', () => { 27 | it( 'should have one link', () => { 28 | props = { ...props, list: ['hello'] }; 29 | wrapper = mount( ); 30 | expect( wrapper.find( 'a' ).length ).toBe( 1 ); 31 | } ); 32 | 33 | it( 'should have one Table', () => { 34 | expect( wrapper.find( Table ).length ).toBe( 1 ); 35 | } ); 36 | it( 'should have one TableRow', () => { 37 | expect( wrapper.find( TableRow ).length ).toBe( 1 ); 38 | } ); 39 | } ); 40 | 41 | describe( 'props', () => { 42 | describe( 'list', () => { 43 | it( 'list length should be "0" by default', () => { 44 | expect( wrapper.props().list.length ).toBe( 0 ); 45 | } ); 46 | } ); 47 | } ); 48 | } ); 49 | -------------------------------------------------------------------------------- /__tests__/constants/constants.spec.ts: -------------------------------------------------------------------------------- 1 | import * as CONSTANTS from '$Constants'; 2 | 3 | describe( 'CONSTANTS', () => { 4 | it( 'should exist', async () => { 5 | expect( CONSTANTS ).not.toBeNull(); 6 | } ); 7 | 8 | it( 'should contain PROTOCOLS', async () => { 9 | expect( CONSTANTS.PROTOCOLS ).not.toBeNull(); 10 | } ); 11 | } ); 12 | -------------------------------------------------------------------------------- /__tests__/containers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/__tests__/containers/.gitkeep -------------------------------------------------------------------------------- /__tests__/reducers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/__tests__/reducers/.gitkeep -------------------------------------------------------------------------------- /__tests__/reducers/remoteCall.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-names */ 2 | import { remoteCalls } from '$Reducers/remoteCalls'; 3 | import { TYPES } from '$Actions/remoteCall_actions'; 4 | import { initialAppState } from '$Reducers/initialAppState'; 5 | 6 | describe( 'notification reducer', () => { 7 | let aCall; 8 | beforeEach( () => { 9 | aCall = { id: 'A', args: [] }; 10 | } ); 11 | 12 | it( 'should return the initial state', () => { 13 | expect( remoteCalls( undefined, {} ) ).toEqual( initialAppState.remoteCalls ); 14 | } ); 15 | 16 | describe( 'ADD_REMOTE_CALL', () => { 17 | it( 'should handle adding a remote call', () => { 18 | expect( 19 | remoteCalls( [], { 20 | type: TYPES.ADD_REMOTE_CALL, 21 | payload: aCall 22 | } ) 23 | ).toEqual( [aCall] ); 24 | } ); 25 | } ); 26 | 27 | describe( 'REMOVE_REMOTE_CALL', () => { 28 | it( 'should handle removing a remote call', () => { 29 | expect( 30 | remoteCalls( [{ id: 'unimportant' }, aCall], { 31 | type: TYPES.REMOVE_REMOTE_CALL, 32 | payload: aCall 33 | } ) 34 | ).toEqual( [{ id: 'unimportant' }] ); 35 | } ); 36 | } ); 37 | 38 | describe( 'UPDATE_REMOTE_CALL', () => { 39 | it( 'should handle updating a call', () => { 40 | expect( 41 | remoteCalls( [aCall], { 42 | type: TYPES.UPDATE_REMOTE_CALL, 43 | payload: { 44 | id: 'A', 45 | data: ['hi'] 46 | } 47 | } ) 48 | ).toEqual( [ 49 | { 50 | ...aCall, 51 | data: ['hi'] 52 | } 53 | ] ); 54 | } ); 55 | } ); 56 | } ); 57 | -------------------------------------------------------------------------------- /__tests__/utils/handleNotifications.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { notification } from 'antd'; 3 | 4 | import { handleNotifications } from '$Utils/handleNotificiations'; 5 | import { reactNodeToElement } from '$Utils/reactNodeToElement'; 6 | 7 | jest.mock( '$Utils/reactNodeToElement', () => ( { 8 | reactNodeToElement: jest.fn() 9 | } ) ); 10 | 11 | jest.mock( 'antd/lib/notification', () => ( { 12 | error: jest.fn(), 13 | warning: jest.fn() 14 | } ) ); 15 | 16 | const clearNotification = jest.fn(); 17 | 18 | describe( 'handleNotifications', () => { 19 | it( 'should exist', () => { 20 | expect( handleNotifications ).not.toBeNull(); 21 | } ); 22 | 23 | it( 'opens antd error notification by default', () => { 24 | const previousBrowserProperties = { notifications: [] }; 25 | const currentBrowserProperties = { 26 | notifications: [{ id: 'ie93dk203', body: 'Error notification body' }] 27 | }; 28 | handleNotifications( previousBrowserProperties, currentBrowserProperties ); 29 | expect( notification.error ).toHaveBeenCalled(); 30 | expect( reactNodeToElement ).not.toHaveBeenCalled(); 31 | } ); 32 | 33 | it( 'opens antd notification with ReactNode', () => { 34 | const previousBrowserProperties = { notifications: [] }; 35 | const reactElement = ( 36 |
37 | Warning notification body 38 |
39 | ); 40 | const currentBrowserProperties = { 41 | notifications: [ 42 | { id: 'ie93dk203', type: 'warning', reactNode: reactElement } 43 | ] 44 | }; 45 | handleNotifications( previousBrowserProperties, currentBrowserProperties ); 46 | expect( notification.warning ).toHaveBeenCalled(); 47 | expect( reactNodeToElement ).toHaveBeenCalled(); 48 | } ); 49 | } ); 50 | -------------------------------------------------------------------------------- /__tests__/utils/reactNodeToElement.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import { reactNodeToElement } from '$Utils/reactNodeToElement'; 5 | 6 | describe( 'reactNodeToElement', () => { 7 | it( 'should exist', () => { 8 | expect( reactNodeToElement ).not.toBeNull(); 9 | } ); 10 | 11 | it( 'returns React DOMElement', () => { 12 | const paraOne = 'Paragraph 1 text'; 13 | const paraTwo = 'Paragraph 2 text'; 14 | const paraThree = 'Paragraph 3 text'; 15 | const nodeObject = { 16 | _owner: null, 17 | key: null, 18 | props: { 19 | className: 'parentDiv', 20 | children: [ 21 | { 22 | _owner: null, 23 | props: { 24 | children: paraOne, 25 | key: '1' 26 | }, 27 | ref: null, 28 | type: 'p' 29 | }, 30 | { 31 | _owner: null, 32 | props: { 33 | children: paraTwo, 34 | key: '2' 35 | }, 36 | ref: null, 37 | type: 'p' 38 | }, 39 | { 40 | _owner: null, 41 | props: { 42 | children: paraThree, 43 | key: '3' 44 | }, 45 | ref: null, 46 | type: 'p' 47 | } 48 | ] 49 | }, 50 | ref: null, 51 | type: 'div' 52 | }; 53 | const wrapper = shallow( reactNodeToElement( nodeObject ) ); 54 | const expectedElement = ( 55 |
56 |

Paragraph 1 text

57 |

Paragraph 2 text

58 |

Paragraph 3 text

59 |
60 | ); 61 | expect( wrapper.equals( expectedElement ) ).toBeTruthy(); 62 | } ); 63 | } ); 64 | -------------------------------------------------------------------------------- /afterPack.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require( 'path' ); 3 | const fs = require( 'fs-extra' ); 4 | 5 | const thePackage = require( './package' ); 6 | 7 | const { platform } = process; 8 | const MAC = 'darwin'; 9 | const LINUX = 'linux'; 10 | const WINDOWS = 'win32'; 11 | 12 | // let CONTAINING_FOLDER; 13 | 14 | // AfterPackContext { 15 | // outDir: string 16 | // appOutDir: string 17 | // packager: PlatformPackager 18 | // electronPlatformName: string 19 | // arch: Arch 20 | // targets: Array 21 | // } 22 | 23 | module.exports = async ( AfterPackContext ) => { 24 | // const targetDir = path.resolve( __dirname, 'release' ); 25 | 26 | const CONTAINING_FOLDER = AfterPackContext.appOutDir; 27 | 28 | let APP_ITSELF_DIR = AfterPackContext.appOutDir; 29 | 30 | if ( platform === MAC ) 31 | APP_ITSELF_DIR = path.resolve( 32 | AfterPackContext.appOutDir, 33 | 'SAFE Browser.app/Contents/Resources' 34 | ); 35 | 36 | // add version file 37 | fs.outputFileSync( 38 | path.resolve( APP_ITSELF_DIR, 'version' ), 39 | thePackage.version 40 | ); 41 | 42 | // remove licenses 43 | const removalArray = [ 44 | 'LICENSE.electron.txt', 45 | 'LICENSES.chromium.html', 46 | 'LICENSE' 47 | ]; 48 | 49 | removalArray.forEach( ( file ) => { 50 | fs.removeSync( `${CONTAINING_FOLDER}/${file}` ); 51 | } ); 52 | }; 53 | -------------------------------------------------------------------------------- /afterSign.js: -------------------------------------------------------------------------------- 1 | // See: https://medium.com/@TwitterArchiveEraser/notarize-electron-apps-7a5f988406db 2 | 3 | const fs = require( 'fs' ); 4 | const path = require( 'path' ); 5 | const electronNotarize = require( 'electron-notarize' ); 6 | 7 | const buildConfig = require( './builderConfig' ); 8 | 9 | const shouldNotarize = process.env.SHOULD_NOTARIZE; 10 | module.exports = async function( parameters ) { 11 | // Only notarize the app on Mac OS only & on CI. 12 | if ( process.platform !== 'darwin' || !shouldNotarize ) { 13 | return; 14 | } 15 | 16 | console.log( 'afterSign hook triggered', parameters ); 17 | 18 | // Same appId in electron-builder. 19 | const { appId } = buildConfig; 20 | 21 | const appPath = path.join( 22 | parameters.appOutDir, 23 | `${parameters.packager.appInfo.productFilename}.app` 24 | ); 25 | if ( !fs.existsSync( appPath ) ) { 26 | throw new Error( `Cannot find application at: ${appPath}` ); 27 | } 28 | 29 | console.log( `Notarizing ${appId} found at ${appPath}` ); 30 | 31 | try { 32 | await electronNotarize.notarize( { 33 | appBundleId: appId, 34 | appPath, 35 | appleId: `${process.env.APPLE_ID}`, 36 | appleIdPassword: `${process.env.APPLE_ID_PASSWORD}` 37 | } ); 38 | } catch ( error ) { 39 | console.error( error ); 40 | } 41 | 42 | console.log( `Done notarizing ${appId}` ); 43 | }; 44 | -------------------------------------------------------------------------------- /app/__mocks__/logger.ts: -------------------------------------------------------------------------------- 1 | export const logger = { 2 | info: jest.fn(), 3 | verbose: jest.fn(), 4 | error: jest.fn(), 5 | warn: jest.fn(), 6 | silly: jest.fn() 7 | }; 8 | -------------------------------------------------------------------------------- /app/actions/bookmarks_actions.ts: -------------------------------------------------------------------------------- 1 | import { createActions } from 'redux-actions'; 2 | 3 | export const TYPES = { 4 | ADD_BOOKMARK: 'ADD_BOOKMARK', 5 | REMOVE_BOOKMARK: 'REMOVE_BOOKMARK', 6 | UPDATE_BOOKMARK: 'UPDATE_BOOKMARK', 7 | UPDATE_BOOKMARKS: 'UPDATE_BOOKMARKS' 8 | }; 9 | 10 | export const { 11 | addBookmark, 12 | removeBookmark, 13 | updateBookmark, 14 | updateBookmarks 15 | } = createActions( 16 | TYPES.ADD_BOOKMARK, 17 | TYPES.REMOVE_BOOKMARK, 18 | TYPES.UPDATE_BOOKMARK, 19 | TYPES.UPDATE_BOOKMARKS 20 | ); 21 | -------------------------------------------------------------------------------- /app/actions/history_actions.ts: -------------------------------------------------------------------------------- 1 | import { createActions } from 'redux-actions'; 2 | 3 | export const TYPES = { 4 | UPDATE_HISTORY_STATE: 'UPDATE_HISTORY_STATE' 5 | }; 6 | 7 | export const { updateHistoryState } = createActions( TYPES.UPDATE_HISTORY_STATE ); 8 | -------------------------------------------------------------------------------- /app/actions/notification_actions.ts: -------------------------------------------------------------------------------- 1 | import { createActions } from 'redux-actions'; 2 | 3 | export const TYPES = { 4 | ADD_NOTIFICATION: 'ADD_NOTIFICATION', 5 | UPDATE_NOTIFICATION: 'UPDATE_NOTIFICATION', 6 | CLEAR_NOTIFICATION: 'CLEAR_NOTIFICATION' 7 | }; 8 | 9 | export const { 10 | addNotification, 11 | updateNotification, 12 | clearNotification 13 | } = createActions( 14 | TYPES.ADD_NOTIFICATION, 15 | TYPES.UPDATE_NOTIFICATION, 16 | TYPES.CLEAR_NOTIFICATION 17 | ); 18 | -------------------------------------------------------------------------------- /app/actions/remoteCall_actions.ts: -------------------------------------------------------------------------------- 1 | import { createActions } from 'redux-actions'; 2 | 3 | export const TYPES = { 4 | ADD_REMOTE_CALL: 'ADD_REMOTE_CALL', 5 | REMOVE_REMOTE_CALL: 'REMOVE_REMOTE_CALL', 6 | UPDATE_REMOTE_CALL: 'UPDATE_REMOTE_CALL' 7 | }; 8 | 9 | export const { 10 | addRemoteCall, 11 | removeRemoteCall, 12 | updateRemoteCall 13 | } = createActions( 14 | TYPES.ADD_REMOTE_CALL, 15 | TYPES.REMOVE_REMOTE_CALL, 16 | TYPES.UPDATE_REMOTE_CALL 17 | ); 18 | -------------------------------------------------------------------------------- /app/actions/resetStore_action.ts: -------------------------------------------------------------------------------- 1 | import { ipcRenderer } from 'electron'; 2 | import { createAliasedAction } from 'electron-redux'; 3 | 4 | import * as tabActions from '$Actions/tabs_actions'; 5 | 6 | export const TYPES = { 7 | RESET_STORE: 'RESET_STORE' 8 | }; 9 | 10 | let currentStore; 11 | 12 | export const setCurrentStore = ( passedStore ) => { 13 | passedStore.subscribe( () => { 14 | currentStore = passedStore; 15 | } ); 16 | }; 17 | 18 | const getCurrentStore = () => currentStore; 19 | 20 | const triggerWindowClosingByIPC = ( { 21 | fromWindow, 22 | tabId, 23 | windowsToBeClosed 24 | } ) => { 25 | const store = getCurrentStore(); 26 | store.dispatch( 27 | tabActions.tabsResetStore( { fromWindow, tabId, windowsToBeClosed } ) 28 | ); 29 | if ( windowsToBeClosed.length > 0 ) { 30 | ipcRenderer.send( 'closeWindows', windowsToBeClosed ); 31 | } 32 | }; 33 | 34 | export const resetStore = createAliasedAction( 35 | TYPES.RESET_STORE, 36 | ( freshState ) => ( { 37 | // the real action 38 | type: TYPES.RESET_STORE, 39 | payload: triggerWindowClosingByIPC( freshState ) 40 | } ) 41 | ); 42 | -------------------------------------------------------------------------------- /app/actions/tabs_actions.ts: -------------------------------------------------------------------------------- 1 | import { createActions } from 'redux-actions'; 2 | 3 | export const TYPES = { 4 | ADD_TAB: 'ADD_TAB', 5 | UPDATE_TAB_URL: 'UPDATE_TAB_URL', 6 | UPDATE_TAB_WEB_ID: 'UPDATE_TAB_WEB_ID', 7 | UPDATE_TAB_WEB_CONTENTS_ID: 'UPDATE_TAB_WEB_CONTENTS_ID', 8 | TOGGLE_DEV_TOOLS: 'TOGGLE_DEV_TOOLS', 9 | TAB_SHOULD_RELOAD: 'TAB_SHOULD_RELOAD', 10 | UPDATE_TAB_TITLE: 'UPDATE_TAB_TITLE', 11 | UPDATE_TAB_FAVICON: 'UPDATE_TAB_FAVICON', 12 | TAB_LOAD: 'TAB_LOAD', 13 | TAB_FORWARDS: 'TAB_FORWARDS', 14 | TAB_BACKWARDS: 'TAB_BACKWARDS', 15 | FOCUS_WEBVIEW: 'FOCUS_WEBVIEW', 16 | BLUR_ADDRESS_BAR: 'BLUR_ADDRESS_BAR', 17 | SELECT_ADDRESS_BAR: 'SELECT_ADDRESS_BAR', 18 | DESELECT_ADDRESS_BAR: 'DESELECT_ADDRESS_BAR', 19 | TABS_RESET_STORE: 'TABS_RESET_STORE' 20 | }; 21 | 22 | export const { 23 | addTab, 24 | updateTabUrl, 25 | updateTabWebId, 26 | updateTabWebContentsId, 27 | toggleDevTools, 28 | tabShouldReload, 29 | updateTabTitle, 30 | updateTabFavicon, 31 | tabLoad, 32 | tabForwards, 33 | tabBackwards, 34 | focusWebview, 35 | blurAddressBar, 36 | selectAddressBar, 37 | deselectAddressBar, 38 | tabsResetStore 39 | } = createActions( 40 | TYPES.ADD_TAB, 41 | TYPES.UPDATE_TAB_URL, 42 | TYPES.UPDATE_TAB_WEB_ID, 43 | TYPES.UPDATE_TAB_WEB_CONTENTS_ID, 44 | TYPES.TOGGLE_DEV_TOOLS, 45 | TYPES.TAB_SHOULD_RELOAD, 46 | TYPES.UPDATE_TAB_TITLE, 47 | TYPES.UPDATE_TAB_FAVICON, 48 | TYPES.TAB_LOAD, 49 | TYPES.TAB_FORWARDS, 50 | TYPES.TAB_BACKWARDS, 51 | TYPES.FOCUS_WEBVIEW, 52 | TYPES.BLUR_ADDRESS_BAR, 53 | TYPES.SELECT_ADDRESS_BAR, 54 | TYPES.DESELECT_ADDRESS_BAR, 55 | TYPES.TABS_RESET_STORE 56 | ); 57 | -------------------------------------------------------------------------------- /app/actions/windows_actions.ts: -------------------------------------------------------------------------------- 1 | import { createActions } from 'redux-actions'; 2 | 3 | export const TYPES = { 4 | ADD_WINDOW: 'ADD_WINDOW', 5 | ADD_TAB_NEXT: 'ADD_TAB_NEXT', 6 | ADD_TAB_END: 'ADD_TAB_END', 7 | SET_ACTIVE_TAB: 'SET_ACTIVE_TAB', 8 | WINDOW_CLOSE_TAB: 'WINDOW_CLOSE_TAB', 9 | REOPEN_TAB: 'REOPEN_TAB', 10 | CLOSE_WINDOW: 'CLOSE_WINDOW', 11 | SHOW_SETTINGS_MENU: 'SHOW_SETTINGS_MENU', 12 | HIDE_SETTINGS_MENU: 'HIDE_SETTINGS_MENU', 13 | SET_LAST_FOCUSED_WINDOW: 'SET_LAST_FOCUSED_WINDOW' 14 | }; 15 | 16 | export const { 17 | addWindow, 18 | addTabNext, 19 | addTabEnd, 20 | setActiveTab, 21 | windowCloseTab, 22 | reopenTab, 23 | closeWindow, 24 | showSettingsMenu, 25 | hideSettingsMenu, 26 | setLastFocusedWindow 27 | } = createActions( 28 | TYPES.ADD_WINDOW, 29 | TYPES.ADD_TAB_NEXT, 30 | TYPES.ADD_TAB_END, 31 | TYPES.SET_ACTIVE_TAB, 32 | TYPES.WINDOW_CLOSE_TAB, 33 | TYPES.REOPEN_TAB, 34 | TYPES.CLOSE_WINDOW, 35 | TYPES.SHOW_SETTINGS_MENU, 36 | TYPES.HIDE_SETTINGS_MENU, 37 | TYPES.SET_LAST_FOCUSED_WINDOW 38 | ); 39 | -------------------------------------------------------------------------------- /app/app.global.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | .app, 4 | #root { 5 | height: 100%; 6 | margin: 0; 7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 8 | 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 9 | 'Helvetica Neue', Arial, sans-serif; 10 | box-sizing: border-box; 11 | display: flex; 12 | flex-direction: column; 13 | } 14 | 15 | .tabBar__tabBox, 16 | .tabBar__addTab { 17 | -webkit-app-region: no-drag; 18 | } 19 | 20 | .tabBar__tabBar { 21 | -webkit-app-region: drag; 22 | } 23 | 24 | html { 25 | font-size: 16px; 26 | line-height: 1.2em; 27 | } 28 | 29 | .info_box_details { 30 | display: none; 31 | margin-top: 5%; 32 | } 33 | 34 | .info_box_expander:hover + .info_box_details { 35 | display: block; 36 | } 37 | 38 | .no_display { 39 | display: none; 40 | } 41 | 42 | .reveal_link { 43 | background-color: #fafafa; 44 | position: fixed; 45 | bottom: 0; 46 | left: 0; 47 | padding: 4px; 48 | font-size: 1.3em; 49 | } 50 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SAFE Browser 6 | 27 | 28 | 29 |
30 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/app/app.icns -------------------------------------------------------------------------------- /app/background.ts: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 1 */ 2 | import i18n from 'i18n'; 3 | import { remote } from 'electron'; 4 | import path from 'path'; 5 | 6 | import { manageRemoteCalls } from './background.manageRemoteCalls'; 7 | import { getExtensionReduxMiddleware } from './extensions'; 8 | import { onInitBgProcess } from './extensions/backgroundProcess'; 9 | import { setupServer } from './server'; 10 | 11 | import { logger } from '$Logger'; 12 | import { configureStore } from '$Store/configureStore'; 13 | import { I18N_CONFIG, isRunningTestCafeProcess } from '$Constants'; 14 | import { setCurrentStore } from '$Actions/resetStore_action'; 15 | 16 | const initSafeServer = ( store ) => { 17 | const server = setupServer(); 18 | onInitBgProcess( server, store ); 19 | }; 20 | 21 | const initBgProcess = async () => { 22 | logger.info( 'Background process init.' ); 23 | // Add middleware from extensions here. TODO: this should be be unified somewhere. 24 | const loadMiddlewarePackages = getExtensionReduxMiddleware() || []; 25 | const store = configureStore( undefined, loadMiddlewarePackages, true ); 26 | initSafeServer( store ); 27 | setCurrentStore( store ); 28 | 29 | i18n.configure( I18N_CONFIG ); 30 | i18n.setLocale( 'en' ); 31 | 32 | // store.subscribe( () => { 33 | // manageRemoteCalls( store ); 34 | // } ); 35 | }; 36 | 37 | initBgProcess(); 38 | 39 | window.addEventListener( 'error', function( error ) { 40 | console.error( 'errorInBackgroundWindow', error ); 41 | logger.error( 42 | 'errorInBackgroundWindow', 43 | JSON.stringify( error, [ 44 | 'message', 45 | 'arguments', 46 | 'type', 47 | 'name', 48 | 'file', 49 | 'line' 50 | ] ) 51 | ); 52 | } ); 53 | -------------------------------------------------------------------------------- /app/bg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Background Process of SAFE Browser 6 | 7 | 8 | 9 |
10 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/components/AddressBar/ButtonsLHS/ButtonsLHS.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Row, Col, Button } from 'antd'; 3 | import 'antd/lib/row/style'; 4 | import 'antd/lib/col/style'; 5 | import 'antd/lib/button/style'; 6 | import { I18n } from 'react-redux-i18n'; 7 | import { parse } from 'url'; 8 | 9 | import { logger } from '$Logger'; 10 | import { CLASSES, PROTOCOLS } from '$Constants'; 11 | import { extendComponent } from '$Utils/extendComponent'; 12 | import { wrapAddressBarButtonsLHS } from '$Extensions/components'; 13 | /** 14 | * Left hand side buttons for the Address Bar 15 | * @extends Component 16 | */ 17 | const ButtonsLHS = ( props ) => { 18 | const { 19 | addTabEnd, 20 | updateTabWebId, 21 | activeTab, 22 | handleBack, 23 | handleForward, 24 | handleRefresh, 25 | canGoForwards, 26 | canGoBackwards 27 | } = props; 28 | const activeTabUrl = 29 | activeTab && activeTab.url ? parse( activeTab.url ) : undefined; 30 | 31 | return ( 32 | 38 | 39 |