├── .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 |
48 |
49 |
50 |
58 |
59 |
60 |
73 |
74 |
75 | );
76 | };
77 |
78 | export const ExtendedButtonsLHS = extendComponent(
79 | ButtonsLHS,
80 | wrapAddressBarButtonsLHS
81 | );
82 |
--------------------------------------------------------------------------------
/app/components/AddressBar/ButtonsLHS/buttonsLHS.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/app/components/AddressBar/ButtonsLHS/buttonsLHS.css
--------------------------------------------------------------------------------
/app/components/AddressBar/ButtonsLHS/index.ts:
--------------------------------------------------------------------------------
1 | export { ExtendedButtonsLHS as ButtonsLHS } from './ButtonsLHS';
2 |
--------------------------------------------------------------------------------
/app/components/AddressBar/ButtonsRHS/buttonsRHS.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/app/components/AddressBar/ButtonsRHS/buttonsRHS.css
--------------------------------------------------------------------------------
/app/components/AddressBar/ButtonsRHS/index.ts:
--------------------------------------------------------------------------------
1 | export { ExtendedButtonsRHS as ButtonsRHS } from './ButtonsRHS';
2 |
--------------------------------------------------------------------------------
/app/components/AddressBar/Input/index.ts:
--------------------------------------------------------------------------------
1 | export { ExtendedInput as Input } from './Input';
2 |
--------------------------------------------------------------------------------
/app/components/AddressBar/Input/input.css:
--------------------------------------------------------------------------------
1 | .placeholder
2 | {
3 | /* width: 20px; */
4 | }
5 |
--------------------------------------------------------------------------------
/app/components/AddressBar/addressBar.css:
--------------------------------------------------------------------------------
1 | .container
2 | {
3 | width: 100%;
4 | padding-top: 3px;
5 | padding-bottom: 3px;
6 | background-color: rgba( 246, 246, 246, 1);
7 | border: 1px solid rgba( 127, 127, 127, 1);
8 | display: flex;
9 | flex: none;
10 | align-content: center;
11 | justify-content: space-around;
12 | box-sizing: border-box;
13 | border-right: 0;
14 | border-left: 0;
15 | z-index: 3;
16 | }
17 |
18 | .addressBar
19 | {
20 | width: 100%;
21 | }
22 |
23 | /* let it grow */
24 | .addressBarCol
25 | {
26 | flex: 1;
27 | }
28 |
29 | .input
30 | {
31 | display: block;
32 | border: 1px solid rgba( 127, 127, 127, 1);
33 | padding: 5px;
34 | border-radius: 4px;
35 | font-size: 14px;
36 | max-width: 800px;
37 | width: 100%;
38 | height: 20px;
39 |
40 | }
41 |
42 |
43 | .buttonBlock
44 | {
45 | display: flex;
46 |
47 | }
48 |
49 | .rightButtons,
50 | .leftButtons
51 | {
52 | composes: buttonBlock;
53 | }
54 |
55 |
56 | .button
57 | {
58 | display: block;
59 | width: 32px;
60 | height: 32px;
61 | margin-right: 2px;
62 | margin-left: 2px;
63 | }
64 |
65 | .button.disabled
66 | {
67 | cursor: default;
68 | }
69 |
70 | .addressBarColumn
71 | {
72 | flex-grow: 1;
73 | }
74 | .buttonIcon
75 | {
76 | display: block;
77 | width: 25px;
78 | height: 100%;
79 | padding-left: 0;
80 | fill: rgba( 127, 127, 127, 1);
81 | padding-right: 1rem;
82 |
83 |
84 | }
85 |
86 | .buttonIcon:hover
87 | {
88 | fill: black;
89 | }
90 |
91 | /* buttons themselves */
92 |
93 | /* .back,
94 | .forward,
95 | .reload
96 | {
97 | composes: button;
98 | } */
99 |
100 |
101 |
102 | /* settings menu item */
103 | .menuItem{
104 | cursor: pointer;
105 | padding: 2%;
106 | }
107 |
--------------------------------------------------------------------------------
/app/components/AddressBar/index.ts:
--------------------------------------------------------------------------------
1 | export { AddressBar } from './AddressBar';
2 |
--------------------------------------------------------------------------------
/app/components/Browser/browser.css:
--------------------------------------------------------------------------------
1 | .container {
2 | text-align: center;
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | position: relative;
7 | }
8 |
9 | .container h2 {
10 | font-size: 5rem;
11 | }
12 |
13 | .container a {
14 | font-size: 1.4rem;
15 | }
16 |
17 |
18 | .tab
19 | {
20 | flex-grow: 1;
21 | }
22 |
--------------------------------------------------------------------------------
/app/components/Browser/index.ts:
--------------------------------------------------------------------------------
1 | export { ExtendedBrowser as Browser } from './Browser';
2 |
--------------------------------------------------------------------------------
/app/components/CustomMenu/customMenu.css:
--------------------------------------------------------------------------------
1 | .menuContainer{
2 | position: relative;
3 | }
4 | .menu
5 | {
6 | background-color: white;
7 | border-radius: 10px;
8 | padding: 1.3rem;
9 | box-shadow: 0 4px 15px rgba(51, 51, 51, 0.5);
10 | backdrop-filter: blur(30px);
11 | display: block;
12 | position: absolute;
13 | top: 0.2rem;
14 | right: 0.5rem;
15 | min-width: 260px;
16 | font-weight: 500;
17 | color: rgba(0, 0, 0, 0.65);
18 | }
19 |
20 | .customMenuItem{
21 | cursor: pointer;
22 | }
23 |
--------------------------------------------------------------------------------
/app/components/CustomMenu/index.ts:
--------------------------------------------------------------------------------
1 | export { CustomMenu } from './CustomMenu';
2 |
--------------------------------------------------------------------------------
/app/components/PerusePages/Bookmarks/Bookmarks.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Page, H1, PageHeader } from 'nessie-ui';
3 |
4 | import styles from './bookmarks.css';
5 |
6 | import { UrlList } from '$Components/UrlList';
7 |
8 | interface BookmarksProps {
9 | bookmarks: Array;
10 | addTabEnd?: ( ...args: Array ) => any;
11 | }
12 | export const Bookmarks = ( props: BookmarksProps = { bookmarks: [] } ) => {
13 | const { bookmarks, addTabEnd } = props;
14 | const bookmarkList = bookmarks.map( ( bookmark ) => bookmark.url );
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/app/components/PerusePages/Bookmarks/bookmarks.css:
--------------------------------------------------------------------------------
1 | .placeholder {
2 | /* placeholder */
3 | }
4 |
--------------------------------------------------------------------------------
/app/components/PerusePages/Bookmarks/index.ts:
--------------------------------------------------------------------------------
1 | export { Bookmarks } from './Bookmarks';
2 |
--------------------------------------------------------------------------------
/app/components/PerusePages/Error/error.css:
--------------------------------------------------------------------------------
1 | .page {
2 | width: '100%';
3 | padding-top: '30px';
4 | padding-bottom: '3px';
5 | display: 'flex';
6 | flex: 'none';
7 | align-content: 'center';
8 | box-sizing: 'border-box';
9 | border-right: '0';
10 | overflow: 'auto';
11 | }
12 |
13 | .content {
14 | text-align: center;
15 | }
16 |
--------------------------------------------------------------------------------
/app/components/PerusePages/Error/index.ts:
--------------------------------------------------------------------------------
1 | export { Error, ERROR_TYPES, ERROR_CODES } from './Error';
2 |
--------------------------------------------------------------------------------
/app/components/PerusePages/History/history.css:
--------------------------------------------------------------------------------
1 | .table {
2 | max-width: 800px;
3 | padding-top: 3px;
4 | padding-bottom: 3px;
5 | align-content: center;
6 | box-sizing: border-box;
7 | border-right: 0;
8 | margin: 0 auto;
9 | }
10 |
11 | .date {
12 | margin: 0 auto;
13 | }
14 |
15 | .item {
16 | display: inline;
17 | }
18 |
19 | .url {
20 | padding-left: 15px;
21 | }
22 |
23 | .timeStamp {
24 | opacity: 0.5;
25 | }
26 |
--------------------------------------------------------------------------------
/app/components/PerusePages/History/index.ts:
--------------------------------------------------------------------------------
1 | export { History } from './History';
2 |
--------------------------------------------------------------------------------
/app/components/Tab/index.ts:
--------------------------------------------------------------------------------
1 | export { Tab } from './Tab';
2 |
--------------------------------------------------------------------------------
/app/components/Tab/tab.css:
--------------------------------------------------------------------------------
1 | .tab
2 | {
3 | display: flex;
4 | position: absolute;
5 | background-color: white;
6 | top: 0;
7 | right: 0;
8 | left: 0;
9 | bottom: 0;
10 | visibility: hidden;
11 | }
12 |
13 |
14 |
15 | .activeTab
16 | {
17 | visibility: visible;
18 | composes: tab;
19 | }
20 |
--------------------------------------------------------------------------------
/app/components/TabBar/index.ts:
--------------------------------------------------------------------------------
1 | export { TabBar } from './TabBar';
2 |
--------------------------------------------------------------------------------
/app/components/TabBar/tabBar.css:
--------------------------------------------------------------------------------
1 | .container {
2 | flex: 0;
3 | height: 100%;
4 | padding-left: 10px;
5 | padding-right: 10px;
6 | padding-top: 5px;
7 | background: rgba(207, 208, 209, 1);
8 | z-index: 4;
9 | font-size: 1.4rem;
10 | -webkit-app-region: drag;
11 | }
12 | .tabRow {
13 | display: flex;
14 | justify-content: flex-start;
15 | }
16 | .containerMac {
17 | padding-top: 10px;
18 | padding-left: 100px;
19 | }
20 |
21 | .tabBar {
22 | display: flex;
23 | flex-grow: 1;
24 | align-items: center;
25 | position: relative;
26 | top: 1px;
27 | -webkit-app-region: no-drag;
28 | }
29 |
30 | .tabBox {
31 | padding-right: 7px;
32 | padding-left: 7px;
33 | min-height: 31px;
34 | padding-top: 4px;
35 | box-sizing: border-box;
36 | border-top: 1px solid rgba(127, 127, 127, 1);
37 | border-right: 1px solid rgba(127, 127, 127, 1);
38 | }
39 |
40 | .tabBox:hover:not(.activeTab) {
41 | background-color: rgba(246, 246, 246, 0.5);
42 | }
43 |
44 | .tab {
45 | composes: tabBox; /* stylelint-disable-line */
46 | flex-wrap: nowrap;
47 | overflow: hidden;
48 | position: relative;
49 | max-width: 250px;
50 | text-align: left;
51 | user-select: none;
52 | background-color: transparent;
53 | outline: none;
54 | border-bottom: none;
55 | border-left: none;
56 | font-size: 0.8rem;
57 | }
58 |
59 | .tab .tabRow {
60 | flex-flow: nowrap;
61 | }
62 |
63 | .tab:first-child {
64 | border-left: 1px solid rgba(127, 127, 127, 1);
65 | }
66 |
67 | .addTab {
68 | margin-left: 10px;
69 | }
70 |
71 | .tabText {
72 | flex-grow: 2;
73 | white-space: nowrap;
74 | overflow: hidden;
75 | display: inline-block;
76 | line-height: 1.2;
77 | text-overflow: ellipsis;
78 | user-select: none;
79 | margin-left: 5px;
80 | margin-right: 5px;
81 | }
82 |
83 | .favicon {
84 | width: 20px;
85 | height: 20px;
86 | margin-top: -4px;
87 | }
88 |
89 | .faviconContainer {
90 | flex-wrap: nowrap;
91 | height: 20px;
92 | width: 20px;
93 | }
94 |
95 | .loadingIcon {
96 | font-size: 20px;
97 | }
98 |
99 | .addTab,
100 | .closeTab {
101 | flex-wrap: nowrap;
102 | padding: 4px;
103 | border-radius: 100%;
104 | }
105 |
106 | .addTab:hover {
107 | color: white;
108 | background-color: rgba(54, 125, 62, 0.9);
109 | cursor: default;
110 | }
111 |
112 | .closeTab:hover {
113 | color: white;
114 | cursor: default;
115 | background-color: rgba(187, 45, 0, 0.9);
116 | }
117 |
118 | .activeTab {
119 | composes: tab; /* stylelint-disable-line */
120 | background-color: rgba(246, 246, 246, 1);
121 | }
122 |
--------------------------------------------------------------------------------
/app/components/TabContents/index.ts:
--------------------------------------------------------------------------------
1 | export { TabContents } from './TabContents';
2 |
--------------------------------------------------------------------------------
/app/components/TabContents/tabContents.css:
--------------------------------------------------------------------------------
1 | /* @import '../Tab/tab.css' */
2 |
3 | .page {
4 | background-color: #fff;
5 | min-height: auto; /* override nessie 100vh */
6 | }
7 |
8 | .tab {
9 | /* stylelint-disable-next-line property-no-unknown */
10 | composes: tab from '../Tab/tab.css';
11 | }
12 |
13 | .activeTab {
14 | /* stylelint-disable-next-line property-no-unknown */
15 | composes: activeTab from '../Tab/tab.css';
16 | }
17 |
18 | .container {
19 | flex: auto;
20 | position: relative;
21 | overflow: hidden;
22 | }
23 |
--------------------------------------------------------------------------------
/app/components/UrlList/UrlList.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | // import { logger } from '$Logger';
3 | import { TableRow, TableCell, Table } from 'nessie-ui';
4 |
5 | import styles from './urlList.css';
6 |
7 | interface UrlListProps {
8 | list: Array;
9 | onRemove?: ( ...args: Array ) => any;
10 | addTabEnd: ( ...args: Array ) => any;
11 | windowId: number;
12 | }
13 | export const UrlList = ( props: UrlListProps = { list: [] } ) => {
14 | const { addTabEnd, list, windowId } = props;
15 | const parsedList = [];
16 | list.forEach( ( item ) => {
17 | const handleClick = ( event ) => {
18 | // required to prevent the app navigating by default.
19 | event.preventDefault();
20 | const tabId = Math.random().toString( 36 );
21 | addTabEnd( {
22 | url: item,
23 | tabId,
24 | windowId
25 | } );
26 | };
27 | const listItem = (
28 |
29 |
30 |
31 | {item}
32 |
33 |
34 |
35 | );
36 | parsedList.push( listItem );
37 | } );
38 | return (
39 |
40 | {parsedList}
41 | {!parsedList.length && (
42 |
43 | Nothing to see here yet.
44 |
45 | )}
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/app/components/UrlList/index.ts:
--------------------------------------------------------------------------------
1 | export { UrlList } from './UrlList';
2 |
--------------------------------------------------------------------------------
/app/components/UrlList/urlList.css:
--------------------------------------------------------------------------------
1 | .table
2 | {
3 | max-width: 800px;
4 | padding-top: 3px;
5 | padding-bottom: 3px;
6 | align-content: center;
7 | box-sizing: border-box;
8 | border-right: 0;
9 | margin: 0 auto;
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/app/constants/classes.js:
--------------------------------------------------------------------------------
1 | // TODO. Unify with test lib/constants browser UI?
2 | export const CLASSES = {
3 | ADDRESS_BAR: 'js-address',
4 | ACTIVE_TAB: 'js-tabBar__active-tab',
5 | TAB: 'js-tab',
6 | ADD_TAB: 'js-tabBar__add-tab',
7 | CLOSE_TAB: 'js-tabBar__close-tab',
8 | SAFE_BROWSER_PAGE: 'js-safeBrowser__page',
9 | SPECTRON_AREA: 'js-spectron-area',
10 | SPECTRON_AREA__SPOOF_SAVE: 'js-spectron-area__spoof-save',
11 | SPECTRON_AREA__SPOOF_LOAD: 'js-spectron-area__spoof-read',
12 | NOTIFIER_TEXT: 'js-notifier__text',
13 | BOOKMARK_PAGE: 'js-bookmark-page',
14 | FORWARDS: 'js-address__forwards',
15 | BACKWARDS: 'js-address__backwards',
16 | REFRESH: 'js-address__refresh',
17 | ADDRESS_INPUT: 'js-address__input',
18 | MENU: 'js-address__menu',
19 |
20 | NOTIFICATION__ACCEPT: 'js-notification__accept',
21 | NOTIFICATION__REJECT: 'js-notification__reject',
22 | NOTIFICATION__IGNORE: 'js-notification__ignore',
23 |
24 | SETTINGS_MENU: 'js-settingsMenu',
25 | SETTINGS_MENU__BUTTON: 'js-settingsMenu_button',
26 | SETTINGS_MENU__BOOKMARKS: 'js-settingsMenu_bookmarks',
27 | SETTINGS_MENU__HISTORY: 'js-settingsMenu_history',
28 | SETTINGS_MENU__TOGGLE: 'js-settingsMenu_toggle',
29 | SETTINGS_MENU__TOGGLE_BUTTON: 'js-settingsMenu_toggleButton',
30 | SETTINGS_MENU__TOGGLE_TEXT: 'js-settingsMenu_toggleText',
31 | MOCK_TAG: 'js-addressBar_mockTag'
32 | };
33 |
34 | export const getDomClasses = () => {
35 | const domClasses = {};
36 |
37 | Object.keys( CLASSES ).forEach( ( theClass ) => {
38 | domClasses[theClass] = `.${CLASSES[theClass]}`;
39 | } );
40 |
41 | return domClasses;
42 | };
43 |
44 | export const GET_DOM_EL_CLASS = getDomClasses();
45 |
--------------------------------------------------------------------------------
/app/constants/routes.json:
--------------------------------------------------------------------------------
1 | {
2 | "HOME": "/"
3 | }
4 |
--------------------------------------------------------------------------------
/app/containers/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { SpriteMap } from 'nessie-ui';
3 |
4 | interface Props {
5 | children: React.ReactNode;
6 | }
7 |
8 | export const App = ( props: Props ) => {
9 | const { children } = props;
10 | return (
11 |
12 |
13 | {children}
14 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/app/containers/BrowserWindow.tsx:
--------------------------------------------------------------------------------
1 | import { bindActionCreators } from 'redux';
2 | import { connect } from 'react-redux';
3 | import { remote } from 'electron';
4 |
5 | import * as TabActions from '$Actions/tabs_actions';
6 | import * as NotificationActions from '$Actions/notification_actions';
7 | import * as BookmarksActions from '$Actions/bookmarks_actions';
8 | import * as WindowsActions from '$Actions/windows_actions';
9 | import { Browser } from '$Components/Browser';
10 | import { getActionsForBrowser } from '$Extensions';
11 |
12 | const windowId = remote ? remote.getCurrentWindow().id : undefined;
13 | function mapStateToProperties( state ) {
14 | return {
15 | bookmarks: state.bookmarks,
16 | notifications: state.notifications,
17 | tabs: state.tabs,
18 | history: state.history,
19 | windows: state.windows,
20 | windowId,
21 | safeBrowserApp: state.safeBrowserApp
22 | };
23 | }
24 |
25 | function mapDispatchToProperties( dispatch ) {
26 | const extensionActions = getActionsForBrowser();
27 | const actions = {
28 | ...BookmarksActions,
29 | ...NotificationActions,
30 | ...TabActions,
31 | ...WindowsActions,
32 |
33 | ...extensionActions
34 | };
35 | return bindActionCreators( actions, dispatch );
36 | }
37 |
38 | export const BrowserWindow = connect(
39 | mapStateToProperties,
40 | mapDispatchToProperties
41 | )( Browser );
42 |
--------------------------------------------------------------------------------
/app/containers/app.css:
--------------------------------------------------------------------------------
1 | .container
2 | {
3 | /* overflow: hidden; */
4 | height: 100%;
5 | }
6 |
7 | .titleBar
8 | {
9 | display: block;
10 | font-size: 2rem;
11 | }
12 |
--------------------------------------------------------------------------------
/app/definitions/globals.d.ts:
--------------------------------------------------------------------------------
1 | import { Store } from 'redux';
2 | import { BrowserWindow } from 'electron';
3 | import EventEmitter from 'events';
4 |
5 | declare namespace NodeJS {
6 | interface Global {
7 | appDir: string;
8 | isCI: boolean;
9 | macAllWindowsClosed: boolean;
10 | mainProcessStore: Store;
11 | port: number;
12 | preloadFile: string;
13 | safeExperimentsEnabled: boolean | null;
14 | startedRunningMock: boolean;
15 | shouldStartAsMockFromFlagsOrPackage: boolean;
16 | isRunningSpectronTestProcessingPackagedApp: boolean;
17 | SAFE_NODE_LIB_PATH: string;
18 | SPECTRON_TEST: boolean;
19 | }
20 | }
21 |
22 | declare interface NodeError extends Error {
23 | line: string;
24 | file: string;
25 | }
26 |
27 | // Enable import of css in typescript
28 | declare module '*.css' {
29 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
30 | const content: any;
31 | /* eslint-disable-next-line import/no-default-export */
32 | export default content;
33 | }
34 |
35 | export interface Window {
36 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: () => void;
37 | eval: () => void | boolean;
38 | Safe: () => void;
39 | safeAppGroupId: number;
40 | webIdEventEmitter: EventEmitter;
41 | peruseStore: Store;
42 | safeExperimentsEnabled: boolean | null;
43 | }
44 |
45 | export interface AppWindow extends BrowserWindow {
46 | mainWindow: BrowserWindow;
47 | openWindow: () => void;
48 | store: Store;
49 | openDevTools: () => void;
50 | }
51 |
--------------------------------------------------------------------------------
/app/extensions/__mocks__/components.ts:
--------------------------------------------------------------------------------
1 | export const wrapAddressBarButtonsLHS = ( component ) => component;
2 | export const wrapAddressBarButtonsRHS = ( component ) => component;
3 | export const wrapAddressBarInput = ( component ) => component;
4 |
--------------------------------------------------------------------------------
/app/extensions/backgroundProcess.ts:
--------------------------------------------------------------------------------
1 | import { Store } from 'redux';
2 | import { ReactNode } from 'react';
3 | import { Url } from 'url';
4 |
5 | import * as safeBrowsing from './safe/backgroundProcess';
6 |
7 | import { logger } from '$Logger';
8 | // TODO: This should load all packages either from here or from node_modules etc...
9 |
10 | // here add your packages for extensibility.
11 | // const allPackages = [ ];
12 | const allPackages = [safeBrowsing];
13 |
14 | /**
15 | * To be triggered when a remote call occurs in the main process.
16 | * @param {object} store redux store
17 | */
18 | export const onRemoteCallInBgProcess = ( store, allAPICalls, theCall ) => {
19 | allPackages.forEach( ( extension ) => {
20 | if ( extension.onRemoteCallInBgProcess ) {
21 | extension.onRemoteCallInBgProcess( store, allAPICalls, theCall );
22 | }
23 | } );
24 | };
25 |
26 | export const onInitBgProcess = ( server, store ) => {
27 | allPackages.forEach( ( extension ) => {
28 | if ( extension.setupRoutes ) {
29 | extension.setupRoutes( server, store );
30 | }
31 |
32 | if ( extension.onInitBgProcess ) {
33 | extension.onInitBgProcess( store );
34 | }
35 | } );
36 | };
37 |
--------------------------------------------------------------------------------
/app/extensions/mainProcess.ts:
--------------------------------------------------------------------------------
1 | import { Store } from 'redux';
2 | import { ReactNode } from 'react';
3 | import { Url } from 'url';
4 |
5 | import * as safeBrowsingMainProcesses from './safe/main-process';
6 |
7 | import { logger } from '$Logger';
8 |
9 | // here add your packages for extensibility.
10 |
11 | const allPackages = [safeBrowsingMainProcesses];
12 |
13 | const allMainProcessPackages = [safeBrowsingMainProcesses];
14 |
15 | export const preAppLoad = ( store: Store ): void => {
16 | allMainProcessPackages.forEach( ( extension ): void => {
17 | if ( extension.preAppLoad ) {
18 | extension.preAppLoad( store );
19 | }
20 | } );
21 | };
22 |
23 | export const onAppReady = ( store ): void => {
24 | allMainProcessPackages.forEach( ( extension ): void => {
25 | if ( extension.onAppReady ) {
26 | extension.onAppReady( store );
27 | }
28 | } );
29 | };
30 |
31 | export const onReceiveUrl = ( store, url ): void => {
32 | allMainProcessPackages.forEach( ( extension ): void => {
33 | if ( extension.onReceiveUrl ) {
34 | extension.onReceiveUrl( store, url );
35 | }
36 | } );
37 | };
38 |
39 | export const getExtensionMenuItems = ( store, menusArray ) => {
40 | logger.info( 'Extending menus array' );
41 | let newMenuArray = [];
42 | allPackages.forEach( ( extension ) => {
43 | if ( extension.addExtensionMenuItems ) {
44 | newMenuArray = extension.addExtensionMenuItems( store, menusArray );
45 |
46 | if ( !Array.isArray( newMenuArray ) )
47 | throw new Error( 'Extensions must pass an array of menu items.' );
48 | }
49 | } );
50 |
51 | return newMenuArray;
52 | };
53 |
54 | export const onOpenLoadExtensions = async ( store: Store ): Promise => {
55 | const allExtensionLoading = allPackages.map( ( extension ): any => {
56 | if ( extension.onOpen ) {
57 | return extension.onOpen( store );
58 | }
59 | return null;
60 | } );
61 |
62 | return Promise.all( allExtensionLoading );
63 | };
64 |
--------------------------------------------------------------------------------
/app/extensions/renderProcess.ts:
--------------------------------------------------------------------------------
1 | import { Store } from 'redux';
2 | import { ReactNode } from 'react';
3 | import { Url } from 'url';
4 |
5 | import * as safeRenderer from './safe/renderProcess';
6 |
7 | import { logger } from '$Logger';
8 | // TODO: This should load all packages either from here or from node_modules etc...
9 |
10 | // TODO: Separate all rednerer specifics out.
11 | // here add your packages for extensibility.
12 | // const allPackages = [ ];
13 | const allPackages = [safeRenderer];
14 |
15 | /*
16 | Check for internal pages added by an extension. Naive impl will return _last_
17 | found result
18 | */
19 | export const resolveExtensionInternalPages = (
20 | urlObject,
21 | query: Record,
22 | tab: Record,
23 | props: Record
24 | ): { pageComponent: ReactNode; title: string; tabButtonStyles?: Record } => {
25 | logger.info( 'Extensions: Checking addInternalPages via all extensions.' );
26 |
27 | let result = null;
28 |
29 | allPackages.forEach( ( extension ) => {
30 | if ( extension.addInternalPages ) {
31 | result = extension.addInternalPages( urlObject, query, tab, props );
32 | }
33 | } );
34 |
35 | return result;
36 | };
37 |
--------------------------------------------------------------------------------
/app/extensions/safe/actions/__mocks__/safeBrowserApplication_actions.ts:
--------------------------------------------------------------------------------
1 | import { createActions } from 'redux-actions';
2 | import { createAliasedAction } from 'electron-redux';
3 |
4 | import { getWebIds as getWebIdsFromSafe } from '$Extensions/safe/backgroundProcess/safeBrowserApplication/webIds';
5 | import { logger } from '$Logger';
6 | import { inBgProcess } from '$Constants';
7 |
8 | export const TYPES = {
9 | SET_APP_STATUS: 'SET_APP_STATUS',
10 | SET_NETWORK_STATUS: 'SET_NETWORK_STATUS',
11 | SET_IS_MOCK: 'SET_IS_MOCK',
12 |
13 | // webId
14 | ENABLE_EXPERIMENTS: 'ENABLE_EXPERIMENTS',
15 | DISABLE_EXPERIMENTS: 'DISABLE_EXPERIMENTS',
16 |
17 | // webId
18 | GET_AVAILABLE_WEB_IDS: 'GET_AVAILABLE_WEB_IDS',
19 | SET_AVAILABLE_WEB_IDS: 'SET_AVAILABLE_WEB_IDS',
20 | FETCHING_WEB_IDS: 'FETCHING_WEB_IDS',
21 |
22 | SET_READ_CONFIG_STATUS: 'SET_READ_CONFIG_STATUS',
23 | SET_SAVE_CONFIG_STATUS: 'SET_SAVE_CONFIG_STATUS',
24 |
25 | // read status from network
26 | RECEIVED_AUTH_RESPONSE: 'RECEIVED_AUTH_RESPONSE',
27 |
28 | RECONNECT_SAFE_APP: 'RECONNECT_SAFE_APP',
29 | RESET_STORE: 'RESET_STORE',
30 |
31 | // UI actions.
32 | SHOW_WEB_ID_DROPDOWN: 'SHOW_WEB_ID_DROPDOWN'
33 | };
34 |
35 | export const {
36 | setAppStatus,
37 | setNetworkStatus,
38 | setIsMock,
39 |
40 | enableExperiments,
41 | disableExperiments,
42 |
43 | setAvailableWebIds,
44 | fetchingWebIds,
45 |
46 | setReadConfigStatus,
47 | setSaveConfigStatus,
48 |
49 | receivedAuthResponse,
50 |
51 | reconnectSafeApp,
52 |
53 | resetStore,
54 |
55 | showWebIdDropdown
56 | } = createActions(
57 | TYPES.SET_APP_STATUS,
58 | TYPES.SET_NETWORK_STATUS,
59 | TYPES.SET_IS_MOCK,
60 |
61 | TYPES.ENABLE_EXPERIMENTS,
62 | TYPES.DISABLE_EXPERIMENTS,
63 |
64 | TYPES.SET_AVAILABLE_WEB_IDS,
65 | TYPES.FETCHING_WEB_IDS,
66 |
67 | TYPES.SET_READ_CONFIG_STATUS,
68 | TYPES.SET_SAVE_CONFIG_STATUS,
69 |
70 | TYPES.RECEIVED_AUTH_RESPONSE,
71 |
72 | TYPES.RECONNECT_SAFE_APP,
73 | TYPES.RESET_STORE,
74 |
75 | TYPES.SHOW_WEB_ID_DROPDOWN
76 | );
77 |
78 | const triggerGetWebIds = async () => {
79 | if ( !inBgProcess ) return;
80 |
81 | logger.info( 'BG Process: Retrieving webIds...' );
82 |
83 | await getWebIdsFromSafe();
84 | };
85 |
86 | export const getAvailableWebIds = createAliasedAction(
87 | TYPES.GET_AVAILABLE_WEB_IDS,
88 | // TODO: there is a complaint about not having middleware, despite redux-promise.
89 | () => ( {
90 | // the real action
91 | type: TYPES.GET_AVAILABLE_WEB_IDS,
92 | payload: triggerGetWebIds()
93 | } )
94 | );
95 |
--------------------------------------------------------------------------------
/app/extensions/safe/actions/aliased/index.ts:
--------------------------------------------------------------------------------
1 | import { createAliasedAction } from 'electron-redux';
2 |
3 | import { getWebIds } from '$Extensions/safe/backgroundProcess/safeBrowserApplication/webIds';
4 | import {
5 | registerNrsNameOnNetwork,
6 | uploadFilesToSafe
7 | } from '$Extensions/safe/backgroundProcess/safeBrowserApplication';
8 | import { initAnon } from '$Extensions/safe/backgroundProcess/safeBrowserApplication/init/initAnon';
9 | import { initAuthed } from '$Extensions/safe/backgroundProcess/safeBrowserApplication/init/initAuthed';
10 | import { logger } from '$Logger';
11 | import { TYPES } from '$Extensions/safe/actions/safeBrowserApplication_actions';
12 |
13 | export const getAvailableWebIds = createAliasedAction(
14 | TYPES.ALIAS_GET_AVAILABLE_WEB_IDS,
15 | // TODO: there is a complaint about not having middleware, despite redux-promise.
16 | () => ( {
17 | // the real action
18 | type: TYPES.GET_AVAILABLE_WEB_IDS,
19 | payload: getWebIds()
20 | } )
21 | );
22 |
23 | export const connectUnauthorised = createAliasedAction(
24 | TYPES.ALIAS_CONNECT_ANONYMOUS,
25 | () => ( {
26 | // the real action
27 | type: TYPES.CONNECT_ANONYMOUS,
28 | payload: initAnon()
29 | } )
30 | );
31 |
32 | export const connectAuthorised = createAliasedAction(
33 | TYPES.ALIAS_CONNECT_AUTHORISED,
34 | () => ( {
35 | // the real action
36 | type: TYPES.CONNECT_AUTHORISED,
37 | payload: initAuthed()
38 | } )
39 | );
40 |
41 | export const registerNrsName = createAliasedAction(
42 | TYPES.ALIAS_REGISTER_NRS_NAME,
43 | ( address: string ) => ( {
44 | type: TYPES.REGISTER_NRS_NAME,
45 | payload: registerNrsNameOnNetwork( address )
46 | } )
47 | );
48 |
49 | export const uploadFiles = createAliasedAction(
50 | TYPES.ALIAS_UPLOAD_FILES,
51 | ( folder: string, target: string ) => ( {
52 | // the real action
53 | type: TYPES.UPLOAD_FILES,
54 | payload: uploadFilesToSafe( folder, target )
55 | } )
56 | );
57 |
--------------------------------------------------------------------------------
/app/extensions/safe/actions/pWeb_actions.ts:
--------------------------------------------------------------------------------
1 | import { createActions } from 'redux-actions';
2 |
3 | import { logger } from '$Logger';
4 |
5 | export const TYPES = {
6 | SET_KNOWN_VERSIONS_FOR_URL: 'SET_KNOWN_VERSIONS_FOR_URL',
7 | SET_NAME_AS_MY_SITE: 'SET_NAME_AS_MY_SITE'
8 | };
9 |
10 | export const { setKnownVersionsForUrl, setNameAsMySite } = createActions(
11 | TYPES.SET_KNOWN_VERSIONS_FOR_URL,
12 | TYPES.SET_NAME_AS_MY_SITE
13 | );
14 |
--------------------------------------------------------------------------------
/app/extensions/safe/actions/safeBrowserApplication_actions.ts:
--------------------------------------------------------------------------------
1 | import { createActions } from 'redux-actions';
2 |
3 | import { logger } from '$Logger';
4 |
5 | export const TYPES = {
6 | ALIAS_CONNECT_UNAUTHORISED_BROWSER_APP:
7 | 'ALIAS_CONNECT_UNAUTHORISED_BROWSER_APP',
8 | CONNECT_UNAUTHORISED_BROWSER_APP: 'CONNECT_UNAUTHORISED_BROWSER_APP',
9 |
10 | SET_APP_STATUS: 'SET_APP_STATUS',
11 | SET_NETWORK_STATUS: 'SET_NETWORK_STATUS',
12 | SET_IS_MOCK: 'SET_IS_MOCK',
13 |
14 | // experiments
15 | ENABLE_EXPERIMENTS: 'ENABLE_EXPERIMENTS',
16 | DISABLE_EXPERIMENTS: 'DISABLE_EXPERIMENTS',
17 |
18 | // webId
19 | ALIAS_GET_AVAILABLE_WEB_IDS: 'ALIAS_GET_AVAILABLE_WEB_IDS',
20 | GET_AVAILABLE_WEB_IDS: 'GET_AVAILABLE_WEB_IDS',
21 | SET_AVAILABLE_WEB_IDS: 'SET_AVAILABLE_WEB_IDS',
22 | FETCHING_WEB_IDS: 'FETCHING_WEB_IDS',
23 |
24 | SET_READ_CONFIG_STATUS: 'SET_READ_CONFIG_STATUS',
25 | SET_SAVE_CONFIG_STATUS: 'SET_SAVE_CONFIG_STATUS',
26 |
27 | // read status from network
28 | RECEIVED_AUTH_RESPONSE: 'RECEIVED_AUTH_RESPONSE',
29 |
30 | RECONNECT_SAFE_APP: 'RECONNECT_SAFE_APP',
31 | RESET_STORE: 'RESET_STORE',
32 |
33 | // UI actions.
34 | SHOW_WEB_ID_DROPDOWN: 'SHOW_WEB_ID_DROPDOWN',
35 |
36 | // SAFE connection...
37 | ALIAS_CONNECT_ANONYMOUS: 'ALIAS_CONNECT_ANONYMOUS',
38 | CONNECT_ANONYMOUS: 'CONNECT_ANONYMOUS',
39 | ALIAS_CONNECT_AUTHORISED: 'ALIAS_CONNECT_AUTHORISED',
40 | CONNECT_AUTHORISED: 'CONNECT_AUTHORISED',
41 |
42 | // SAFE NRS
43 | ALIAS_REGISTER_NRS_NAME: 'ALIAS_REGISTER_NRS_NAME',
44 | REGISTER_NRS_NAME: 'REGISTER_NRS_NAME',
45 |
46 | // SAFE FILES
47 | ALIAS_UPLOAD_FILES: 'ALIAS_UPLOAD_FILES',
48 | UPLOAD_FILES: 'UPLOAD_FILES'
49 | };
50 |
51 | export const {
52 | setAppStatus,
53 | setNetworkStatus,
54 | setIsMock,
55 |
56 | enableExperiments,
57 | disableExperiments,
58 |
59 | setAvailableWebIds,
60 | fetchingWebIds,
61 |
62 | setReadConfigStatus,
63 | setSaveConfigStatus,
64 |
65 | receivedAuthResponse,
66 |
67 | reconnectSafeApp,
68 |
69 | resetStore,
70 |
71 | showWebIdDropdown
72 | } = createActions(
73 | TYPES.SET_APP_STATUS,
74 | TYPES.SET_NETWORK_STATUS,
75 | TYPES.SET_IS_MOCK,
76 |
77 | TYPES.ENABLE_EXPERIMENTS,
78 | TYPES.DISABLE_EXPERIMENTS,
79 |
80 | TYPES.SET_AVAILABLE_WEB_IDS,
81 | TYPES.FETCHING_WEB_IDS,
82 |
83 | TYPES.SET_READ_CONFIG_STATUS,
84 | TYPES.SET_SAVE_CONFIG_STATUS,
85 |
86 | TYPES.RECEIVED_AUTH_RESPONSE,
87 |
88 | TYPES.RECONNECT_SAFE_APP,
89 | TYPES.RESET_STORE,
90 |
91 | TYPES.SHOW_WEB_ID_DROPDOWN,
92 |
93 | TYPES.ALIAS_CONNECT_ANONYMOUS,
94 | TYPES.CONNECT_ANONYMOUS
95 | );
96 |
--------------------------------------------------------------------------------
/app/extensions/safe/backgroundProcess/handleRemoteCalls.ts:
--------------------------------------------------------------------------------
1 | import * as resetTabAction from '$Actions/resetStore_action';
2 | import { SAFE } from '$Extensions/safe/constants';
3 | import * as safeBrowserAppActions from '$Extensions/safe/actions/safeBrowserApplication_actions';
4 | import * as remoteCallActions from '$Actions/remoteCall_actions';
5 | import { logger } from '$Logger';
6 |
7 | let theStore;
8 |
9 | export const getResetStoreActionObject = ( state, windowId ) => {
10 | logger.verbose( 'Setting up reset store from window:', windowId );
11 | const tabId = Math.random().toString( 36 );
12 | if ( !state.windows ) {
13 | throw Error(
14 | 'No windows object passed in the app state. Ensure you have called `store.getState()`'
15 | );
16 | }
17 | const windowState = state.windows.openWindows;
18 | const windows = Object.keys( windowState );
19 | const windowsToBeClosed = windows.filter(
20 | ( aWindowId ) => parseInt( aWindowId, 10 ) !== windowId
21 | );
22 |
23 | return { fromWindow: windowId, tabId, windowsToBeClosed };
24 | };
25 |
26 | // export const onRemoteCallInBgProcess = ( store, allAPICalls, theCall ) => {
27 | // theStore = store;
28 | //
29 | // logger.info( 'Handling remote call in extension', theCall );
30 | // if ( theCall && theCall.isListener ) {
31 | // allAPICalls[theCall.name]( ( error, args ) => {
32 | // store.dispatch(
33 | // remoteCallActions.updateRemoteCall( {
34 | // ...theCall,
35 | // done: true,
36 | // response: args
37 | // } )
38 | // );
39 | // } );
40 | // }
41 | // };
42 |
--------------------------------------------------------------------------------
/app/extensions/safe/backgroundProcess/index.ts:
--------------------------------------------------------------------------------
1 | import { Store } from 'redux';
2 |
3 | import { logger } from '$Logger';
4 | import { requestManagement } from '$Extensions/safe/requestManagement';
5 | import { registerSafeProtocol } from '$Extensions/safe/protocols/safe';
6 | import { setCurrentStore as setCurrentStoreForSafe } from '$Extensions/safe/backgroundProcess/safeBrowserApplication/theApplication';
7 | import { connectUnauthorised } from '$Extensions/safe/actions/aliased';
8 |
9 | export { setupRoutes } from '$Extensions/safe/backgroundProcess/server-routes';
10 |
11 | export { getHTTPFriendlyData } from '$App/extensions/safe/backgroundProcess/fetch';
12 |
13 | export const onInitBgProcess = async ( store: Store ): Promise => {
14 | logger.info( 'Registering SAFE Network Protocols' );
15 |
16 | setCurrentStoreForSafe( store );
17 |
18 | try {
19 | registerSafeProtocol();
20 | requestManagement( store );
21 |
22 | store.dispatch( connectUnauthorised() );
23 | } catch ( error ) {
24 | logger.error( 'Load extensions error: ', error );
25 | }
26 |
27 | // store.subscribe( () => {
28 | // handleSafeBrowserStoreChanges( store );
29 | // } );
30 | };
31 |
--------------------------------------------------------------------------------
/app/extensions/safe/backgroundProcess/safeBrowserApplication/init/initAnon.ts:
--------------------------------------------------------------------------------
1 | import { Safe } from 'safe-nodejs';
2 |
3 | import { logger } from '$Logger';
4 | import {
5 | setSafeBrowserAppObject,
6 | getCurrentStore
7 | } from '$App/extensions/safe/backgroundProcess/safeBrowserApplication/theApplication';
8 | import { cleanupNeonError } from '$Extensions/safe/utils/safeHelpers';
9 | import { addNotification } from '$Actions/notification_actions';
10 |
11 | export const initAnon = async (): Safe => {
12 | let safeBrowserAppObject;
13 |
14 | const APP_ID = 'net.maidsafe.safe_browser';
15 | const APP_NAME = 'SAFE Browser';
16 | const APP_VENDOR = 'MaidSafe.net Ltd';
17 |
18 | try {
19 | logger.info( 'Initialising anonymous SAFE App' );
20 |
21 | safeBrowserAppObject = new Safe();
22 |
23 | logger.info( 'Connecting to the Network...' );
24 | await safeBrowserAppObject.connect( APP_ID );
25 |
26 | const isAuthed = false;
27 | setSafeBrowserAppObject( safeBrowserAppObject, { isAuthed } );
28 |
29 | return safeBrowserAppObject;
30 | } catch ( error ) {
31 | const message = cleanupNeonError( error );
32 | if ( message.includes( 'Request has timed out' ) ) {
33 | const store = getCurrentStore();
34 |
35 | // TODO: Try again
36 | store.dispatch(
37 | addNotification( {
38 | title:
39 | 'Network Connection Failed. There was a timeout. Try restarting the browser.',
40 | acceptText: 'Dismiss'
41 | } )
42 | );
43 | }
44 | logger.error( message );
45 | setSafeBrowserAppObject( safeBrowserAppObject, { error } );
46 | return safeBrowserAppObject;
47 | }
48 | };
49 |
--------------------------------------------------------------------------------
/app/extensions/safe/backgroundProcess/safeBrowserApplication/init/initAuthed.ts:
--------------------------------------------------------------------------------
1 | import { Safe } from 'safe-nodejs';
2 |
3 | import { logger } from '$Logger';
4 | import {
5 | setSafeBrowserAppObject,
6 | getCurrentStore
7 | } from '$App/extensions/safe/backgroundProcess/safeBrowserApplication/theApplication';
8 | import { cleanupNeonError } from '$Extensions/safe/utils/safeHelpers';
9 | import { addNotification } from '$Actions/notification_actions';
10 |
11 | export const initAuthed = async (): Safe => {
12 | let safeBrowserAppObject;
13 |
14 | const APP_ID = 'net.maidsafe.safe_browser';
15 | const APP_NAME = 'SAFE Browser';
16 | const APP_VENDOR = 'MaidSafe.net Ltd';
17 |
18 | try {
19 | logger.info( 'Initialising authenticated SAFE App' );
20 |
21 | safeBrowserAppObject = new Safe();
22 |
23 | logger.info( 'Connecting (authed) to the Network...' );
24 | const authCredentials = await safeBrowserAppObject.auth_app(
25 | APP_ID,
26 | APP_NAME,
27 | APP_VENDOR
28 | );
29 |
30 | await safeBrowserAppObject.connect( APP_ID, authCredentials );
31 | logger.info( 'Connected.' );
32 |
33 | const isAuthed = true;
34 | setSafeBrowserAppObject( safeBrowserAppObject, { isAuthed } );
35 |
36 | return safeBrowserAppObject;
37 | } catch ( error ) {
38 | const message = cleanupNeonError( error );
39 | if ( message.includes( 'Failed to send request to Authenticator' ) ) {
40 | const store = getCurrentStore();
41 |
42 | store.dispatch(
43 | addNotification( {
44 | title: 'Authentication Failed. Check an authenticator is running.',
45 | acceptText: 'Dismiss'
46 | } )
47 | );
48 | }
49 | logger.error( message );
50 | setSafeBrowserAppObject( safeBrowserAppObject, { isAuthed: false, error } );
51 | return safeBrowserAppObject;
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/app/extensions/safe/backgroundProcess/safeBrowserApplication/init/networkStateChange.ts:
--------------------------------------------------------------------------------
1 | import { logger } from '$Logger';
2 | import { SAFE } from '$Extensions/safe/constants';
3 | import { attemptReconnect } from '$Extensions/safe/network';
4 | import { setNetworkStatus } from '$Extensions/safe/actions/safeBrowserApplication_actions';
5 | import {
6 | addNotification,
7 | clearNotification
8 | } from '$Actions/notification_actions';
9 | import { getSafeBrowserAppObject } from '$Extensions/safe/backgroundProcess/safeBrowserApplication/theApplication';
10 |
11 | export const onNetworkStateChange = ( store, mockAttemptReconnect ) => (
12 | state
13 | ) => {
14 | logger.info( 'onNetworkStateChange' );
15 | const safeBrowserAppObject = getSafeBrowserAppObject();
16 |
17 | const previousState = store.getState().safeBrowserApp.networkStatus;
18 | logger.info( 'previousState: ', previousState );
19 | store.dispatch( setNetworkStatus( state ) );
20 | const isDisconnected = state === SAFE.NETWORK_STATE.DISCONNECTED;
21 | const notificationID = Math.random().toString( 36 );
22 |
23 | if ( isDisconnected ) {
24 | if ( store ) {
25 | store.dispatch(
26 | addNotification( {
27 | title: `Network state: ${state}`,
28 | body: 'Reconnecting...',
29 | id: notificationID
30 | } )
31 | );
32 |
33 | if ( mockAttemptReconnect ) {
34 | mockAttemptReconnect( store );
35 | } else {
36 | attemptReconnect( store, safeBrowserAppObject );
37 | }
38 | }
39 | }
40 |
41 | if (
42 | state === SAFE.NETWORK_STATE.CONNECTED &&
43 | previousState === SAFE.NETWORK_STATE.DISCONNECTED
44 | ) {
45 | store.dispatch( clearNotification( { id: notificationID } ) );
46 | }
47 | };
48 |
--------------------------------------------------------------------------------
/app/extensions/safe/backgroundProcess/safeBrowserApplication/theApplication.ts:
--------------------------------------------------------------------------------
1 | import { SAFE } from '$Extensions/safe/constants';
2 | import { logger } from '$Logger';
3 |
4 | /*
5 | Handle and maintain the safe browser application object.
6 | Provide handlers to retrieve this from other bg process files.
7 | */
8 |
9 | let safeBrowserAppObject;
10 | let currentStore;
11 | let connectionIsAuthorised = false;
12 | let hasError = false;
13 | let theError = null;
14 |
15 | export const safeIsAuthorised = () => connectionIsAuthorised;
16 |
17 | export const setSafeBrowserAppObject = (
18 | passedApp,
19 | meta: { isAuthed: boolean; error?: Error }
20 | ) => {
21 | safeBrowserAppObject = passedApp;
22 | connectionIsAuthorised = meta.isAuthed;
23 | hasError = false;
24 | theError = null;
25 |
26 | if ( meta.error ) {
27 | hasError = true;
28 | theError = meta.error;
29 | }
30 | };
31 |
32 | export const getSafeBrowserAppObject = () => {
33 | if ( hasError ) throw theError;
34 |
35 | return safeBrowserAppObject;
36 | };
37 |
38 | export const getCurrentStore = () => currentStore;
39 |
40 | export const setCurrentStore = ( passedStore ) => {
41 | currentStore = passedStore;
42 | };
43 |
--------------------------------------------------------------------------------
/app/extensions/safe/backgroundProcess/safeBrowserApplication/webIds/index.ts:
--------------------------------------------------------------------------------
1 | import * as safeBrowserAppActions from '$Extensions/safe/actions/safeBrowserApplication_actions';
2 | import { logger } from '$Logger';
3 | import {
4 | getCurrentStore,
5 | getSafeBrowserAppObject,
6 | safeIsAuthorised
7 | } from '$Extensions/safe/backgroundProcess/safeBrowserApplication/theApplication';
8 |
9 | /**
10 | * Get WebIds for the current user
11 | * @return {Promise} Resolves to Array of webIds
12 | */
13 | export const getWebIds = async () => {
14 | logger.info( 'getWebIds' );
15 | const currentStore = getCurrentStore();
16 |
17 | const safeBrowserApp = getSafeBrowserAppObject();
18 |
19 | if ( !safeBrowserApp ) throw new Error( 'SafeBrowserApp should be initiated.' );
20 |
21 | if ( !safeIsAuthorised() ) throw new Error( 'SafeBrowserApp is not authorised' );
22 |
23 | let webIds = [];
24 |
25 | currentStore.dispatch( safeBrowserAppActions.fetchingWebIds() );
26 | webIds = await safeBrowserApp.web.getWebIds();
27 |
28 | currentStore.dispatch( safeBrowserAppActions.setAvailableWebIds( webIds ) );
29 |
30 | return webIds;
31 | };
32 |
--------------------------------------------------------------------------------
/app/extensions/safe/backgroundProcess/server-routes/index.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import url from 'url';
3 |
4 | import { safeRoute } from './safe';
5 |
6 | import { logger } from '$Logger';
7 |
8 | export const setupRoutes = ( server, store ) => {
9 | const routes = [safeRoute( store )];
10 |
11 | routes.forEach( ( route ) => {
12 | try {
13 | server.get( route.path, route.handler );
14 | } catch ( error ) {
15 | logger.error( 'Problem initing a route.', route, error );
16 | }
17 | } );
18 | };
19 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/FilesContainer/FilesContainer.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { uniqBy } from 'lodash';
3 |
4 | import { logger } from '$Logger';
5 | // import styles from './filesContainer.css';
6 |
7 | interface FilesContainerProps {
8 | filesMap: {
9 | [path: string]: {
10 | created: string;
11 | link: string;
12 | modified: string;
13 | size: number;
14 | type: string;
15 | };
16 | };
17 | currentLocation?: string;
18 | }
19 |
20 | export class FilesContainer extends React.PureComponent<
21 | FilesContainerProps,
22 | Record
23 | > {
24 | render() {
25 | const { filesMap, currentLocation } = this.props;
26 |
27 | const targetFilesArray = Object.keys( filesMap );
28 | const [locationWithoutQuery, version] = currentLocation.split( '?v=' );
29 |
30 | const theList: Array<{
31 | link: string;
32 | text: string;
33 | }> = targetFilesArray.map( ( filesMapPath ): {
34 | link: string;
35 | text: string;
36 | } => {
37 | // get the base url out of the way
38 | let theLinkText = filesMapPath;
39 | // only get the next part of the tree
40 | if ( theLinkText.startsWith( '/' ) ) {
41 | theLinkText = `/${theLinkText.split( '/' )[1]}`;
42 | } else {
43 | theLinkText = theLinkText.split( '/' )[0];
44 | }
45 |
46 | const href = `${theLinkText}${version ? `?v=${version}` : ''}`;
47 |
48 | return {
49 | link: href,
50 | text: theLinkText
51 | };
52 | } );
53 |
54 | const uniqList = uniqBy( theList, ( item ) => item.link );
55 | return (
56 |
57 | {uniqList.length > 0 && (
58 |
59 | {`${locationWithoutQuery} contains:`}
60 |
61 | {uniqList.map( ( linkObject ) => {
62 | return (
63 | -
64 | {linkObject.text}
65 |
66 | );
67 | } )}
68 |
69 |
70 | )}
71 | {uniqList.length < 1 && No content found at this path}
72 |
73 | );
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/FilesContainer/filesContainer.css:
--------------------------------------------------------------------------------
1 | .webIdList {
2 | position: absolute;
3 | font-size: 1.4rem;
4 | top: 3rem;
5 | text-align: left;
6 | list-style: none;
7 | background-color: rgba(246, 246, 246, 1);
8 | border: 1px solid rgba(127, 127, 127, 1);
9 | display: flex;
10 | z-index: 1;
11 | flex-direction: column;
12 | padding: 0;
13 | white-space: nowrap;
14 | }
15 |
16 | .webId {
17 | cursor: pointer;
18 | padding: 1rem;
19 | padding-left: 1.2rem;
20 | padding-right: 1.2rem;
21 | border-bottom: 1px solid rgba(127, 127, 127, 0.3);
22 | }
23 |
24 | .webIdInfo {
25 | /* stylelint-disable-next-line */
26 | composes: webId;
27 | cursor: default;
28 | }
29 |
30 | .webIdManager {
31 | /* stylelint-disable-next-line */
32 | composes: webId;
33 | border-bottom: none;
34 | }
35 |
36 | .openAuth {
37 | /* stylelint-disable-next-line */
38 | composes: webId;
39 | }
40 | .openAuth a {
41 | text-decoration: none;
42 | }
43 |
44 | .webIdManager a {
45 | text-decoration: none;
46 | }
47 |
48 | .selectedWebId {
49 | /* stylelint-disable-next-line */
50 | composes: webId;
51 | font-weight: bold;
52 | }
53 |
54 | .loadingIcon {
55 | font-size: 20px;
56 | }
57 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/FilesContainer/index.tsx:
--------------------------------------------------------------------------------
1 | export { FilesContainer } from './FilesContainer';
2 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/NrsRegistryBar/index.tsx:
--------------------------------------------------------------------------------
1 | export { NrsRegistryBar } from './NrsRegistryBar';
2 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/NrsRegistryBar/nrsRegistryBar.css:
--------------------------------------------------------------------------------
1 | .textField {
2 | border-radius: 200px;
3 | width: 400px;
4 | margin-right: 4rem;
5 | }
6 |
7 | /* TODO move other theBar in here */
8 |
9 | .barLayout {
10 | display: flex;
11 | justify-content: flex-start;
12 | align-items: center;
13 | padding-left: 4rem;
14 | padding-right: 4rem;
15 | }
16 | .buttonSpacer {
17 | display: block;
18 | width: 2rem;
19 | height: 100%;
20 | }
21 | .inputArea {
22 | display: flex;
23 | margin: 0 auto;
24 | align-items: center;
25 | position: relative;
26 | }
27 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/SafePages/Editor/editor.css:
--------------------------------------------------------------------------------
1 | .uploadArea {
2 | width: 100%;
3 | height: auto;
4 | margin-top: 4rem;
5 |
6 | /* background-color: grey; */
7 | }
8 |
9 | .uploadAreaButton,
10 | .targetDir {
11 | display: block;
12 | margin-bottom: 1rem;
13 | }
14 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/SafePages/Editor/index.ts:
--------------------------------------------------------------------------------
1 | export { Editor } from './Editor';
2 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/SafePages/MySites/index.ts:
--------------------------------------------------------------------------------
1 | export { MySites } from './MySites';
2 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/SafePages/MySites/mysites.css:
--------------------------------------------------------------------------------
1 | .dropzone {
2 | width: 100%;
3 | height: 400px;
4 | background-color: grey;
5 | }
6 |
7 | .createSiteBar {
8 | display: block;
9 | height: 90px;
10 | width: 100%;
11 | }
12 |
13 | .createSiteBar .theBar {
14 | background-color: #fafafa;
15 | }
16 |
17 | .mySitesPage {
18 | width: 800px;
19 | display: flex;
20 | flex-direction: column;
21 | margin: 0 auto;
22 | position: relative;
23 | }
24 |
25 | .sitesList {
26 | max-width: 400px;
27 | }
28 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/webIdDropdown/index.tsx:
--------------------------------------------------------------------------------
1 | export { WebIdDropdown } from './WebIdDropdown';
2 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/webIdDropdown/webIdButtons.css:
--------------------------------------------------------------------------------
1 | .webIdList {
2 | position: absolute;
3 | font-size: 1.4rem;
4 | top: 3rem;
5 | text-align: left;
6 | list-style: none;
7 | background-color: rgba(246, 246, 246, 1);
8 | border: 1px solid rgba(127, 127, 127, 1);
9 | display: flex;
10 | z-index: 1;
11 | flex-direction: column;
12 | padding: 0;
13 | white-space: nowrap;
14 | }
15 |
16 | .webId {
17 | cursor: pointer;
18 | padding: 1rem;
19 | padding-left: 1.2rem;
20 | padding-right: 1.2rem;
21 | border-bottom: 1px solid rgba(127, 127, 127, 0.3);
22 | }
23 |
24 | .webIdInfo {
25 | /* stylelint-disable-next-line */
26 | composes: webId;
27 | cursor: default;
28 | }
29 |
30 | .webIdManager {
31 | /* stylelint-disable-next-line */
32 | composes: webId;
33 | border-bottom: none;
34 | }
35 |
36 | .openAuth {
37 | /* stylelint-disable-next-line */
38 | composes: webId;
39 | }
40 | .openAuth a {
41 | text-decoration: none;
42 | }
43 |
44 | .webIdManager a {
45 | text-decoration: none;
46 | }
47 |
48 | .selectedWebId {
49 | /* stylelint-disable-next-line */
50 | composes: webId;
51 | font-weight: bold;
52 | }
53 |
54 | .loadingIcon {
55 | font-size: 20px;
56 | }
57 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/wrapAddressBarButtons.css:
--------------------------------------------------------------------------------
1 | .toggleText {
2 | white-space: nowrap;
3 | }
4 |
5 | .toggleRow {
6 | border-top: 1px solid rgba(191, 191, 191, 0.5);
7 | flex-wrap: nowrap;
8 | padding: 2%;
9 | }
10 |
11 | .editSiteButton {
12 | cursor: pointer;
13 | }
14 |
15 | .versionButton {
16 | cursor: pointer;
17 | }
18 | .versionButton:disabled {
19 | cursor: default;
20 | }
21 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/wrapAddressBarButtonsLHS.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { bindActionCreators } from 'redux';
4 | import { Row, Col } from 'antd';
5 |
6 | import { WebIdDropdown } from '$Extensions/safe/components/webIdDropdown';
7 | import 'antd/lib/row/style';
8 | import 'antd/lib/col/style';
9 | import 'antd/lib/button/style';
10 | import {
11 | showWebIdDropdown,
12 | setAppStatus
13 | } from '$Extensions/safe/actions/safeBrowserApplication_actions';
14 | import { getAvailableWebIds } from '$Extensions/safe/actions/aliased';
15 |
16 | // import styles from './wrapAddressBarButtons.css';
17 |
18 | function mapStateToProperties( state ) {
19 | return {
20 | safeBrowserApp: state.safeBrowserApp
21 | };
22 | }
23 | function mapDispatchToProperties( dispatch ) {
24 | const actions = {
25 | showWebIdDropdown,
26 | getAvailableWebIds,
27 | setAppStatus
28 | };
29 | return bindActionCreators( actions, dispatch );
30 | }
31 | export const wrapAddressBarButtonsLHS = (
32 | AddressBarButtons,
33 | extensionFunctionality = {}
34 | ) => {
35 | const WrappedAddressBarButtonsLHS = ( props ) => {
36 | const { safeBrowserApp } = props;
37 | const { experimentsEnabled } = safeBrowserApp;
38 | return (
39 |
45 |
46 |
47 |
48 | {experimentsEnabled && (
49 |
50 |
51 |
52 | )}
53 |
54 | );
55 | };
56 |
57 | const hookedUpInput = connect(
58 | mapStateToProperties,
59 | mapDispatchToProperties
60 | )( WrappedAddressBarButtonsLHS );
61 | return hookedUpInput;
62 | };
63 |
--------------------------------------------------------------------------------
/app/extensions/safe/components/wrapAddressBarInput.less:
--------------------------------------------------------------------------------
1 | /* less is not parsed for modules */
2 | .addressBar__container .ant-input-group-addon
3 | {
4 | background-color: white;
5 | padding-right: 5px;
6 | }
7 |
--------------------------------------------------------------------------------
/app/extensions/safe/constants/browser_application.ts:
--------------------------------------------------------------------------------
1 | export const APP_ID = 'net.maidsafe.safe_browser';
2 | export const APP_NAME = 'SAFE Browser';
3 | export const APP_VENDOR = 'MaidSafe.net Ltd';
4 |
--------------------------------------------------------------------------------
/app/extensions/safe/constants/index.ts:
--------------------------------------------------------------------------------
1 | // for searchability in your text editor
2 | export * from './safe-constants';
3 | export * from './browser_application';
4 |
--------------------------------------------------------------------------------
/app/extensions/safe/constants/safe-constants.ts:
--------------------------------------------------------------------------------
1 | export const SAFE = {
2 | APP_STATUS: {
3 | TO_AUTH: 'TO_AUTH',
4 | AUTHORISED: 'AUTHORISED',
5 | AUTHORISING: 'AUTHORISING',
6 | AUTHORISATION_FAILED: 'AUTHORISATION_FAILED',
7 | AUTHORISATION_DENIED: 'AUTHORISATION_DENIED',
8 |
9 | TO_LOGOUT: 'TO_LOGOUT',
10 | LOGGING_OUT: 'LOGGING_OUT',
11 | LOGGED_OUT: 'LOGGED_OUT',
12 |
13 | READY: 'READY'
14 | },
15 | ACCESS_CONTAINERS: {
16 | PUBLIC: '_public',
17 | PUBLIC_NAMES: '_publicNames'
18 | },
19 | NETWORK_STATE: {
20 | INIT: 'Init',
21 | CONNECTED: 'Connected',
22 | UNKNOWN: 'Unknown',
23 | DISCONNECTED: 'Disconnected',
24 | LOGGED_IN: 'LOGGED_IN'
25 | },
26 | READ_STATUS: {
27 | READING: 'READING',
28 | READ_SUCCESSFULLY: 'READ_SUCCESSFULLY',
29 | READ_BUT_NONEXISTANT: 'READ_BUT_NONEXISTANT',
30 | FAILED_TO_READ: 'FAILED_TO_READ',
31 | TO_READ: 'TO_READ'
32 | },
33 | SAVE_STATUS: {
34 | SAVING: 'SAVING',
35 | SAVED_SUCCESSFULLY: 'SAVED_SUCCESSFULLY',
36 | FAILED_TO_SAVE: 'FAILED_TO_SAVE',
37 | TO_SAVE: 'TO_SAVE'
38 | }
39 | };
40 |
41 | export const SAFE_APP_ERROR_CODES = {
42 | ERR_AUTH_DENIED: -200,
43 | ENTRY_ALREADY_EXISTS: -107,
44 | ERR_NO_SUCH_ENTRY: -106,
45 | ERR_DATA_EXISTS: -104,
46 | ERR_DATA_NOT_FOUND: -103,
47 | ERR_OPERATION_ABORTED: -14
48 | };
49 |
50 | export const SAFE_MESSAGES = {
51 | INITIALIZE: {
52 | AUTHORISE_APP: 'Authorising Application',
53 | CHECK_CONFIGURATION: 'Checking configuration'
54 | },
55 | AUTHORISATION_ERROR: 'Failed to authorise',
56 | AUTHORISATION_DENIED: 'The authorisation request was denied',
57 | CHECK_CONFIGURATION_ERROR: 'Failed to retrieve configuration'
58 | };
59 |
--------------------------------------------------------------------------------
/app/extensions/safe/defaultNewSite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SAFE Start Site
6 |
7 |
8 | This is a placeholder text for a SAFE site.
9 |
10 | If you are the owner of this site, click above to edit the page.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/extensions/safe/err-constants.ts:
--------------------------------------------------------------------------------
1 | export const errorConstants = {
2 | ERR_ROUTING_INTERFACE_ERROR: {
3 | code: -11,
4 | msg:
5 | 'Unable to navigate to site. Will automatically reload when network is connected...'
6 | },
7 | ERR_OPERATION_ABORTED: {
8 | code: -14
9 | },
10 | ERR_REQUEST_TIMEOUT: {
11 | code: -17
12 | },
13 | ERR_CONNECT_INFO: {
14 | code: 1,
15 | msg: ( error ) => `Unable to get connection information: ${error}`
16 | },
17 |
18 | ERR_AUTH_APP: {
19 | code: 2,
20 | msg: ( error ) => `Unable to authorise the application: ${error}`
21 | },
22 |
23 | INVALID_HANDLE: {
24 | code: 3,
25 | msg: ( handle ) => `Invalid handle: ${handle}`
26 | },
27 |
28 | INVALID_LISTENER: {
29 | code: 4,
30 | msg: 'Invalid listener type'
31 | },
32 |
33 | INVALID_URI: {
34 | code: 4,
35 | msg: 'Invalid URI'
36 | },
37 |
38 | UNAUTHORISED: {
39 | code: 5,
40 | msg: 'Unauthorised'
41 | },
42 |
43 | INVALID_RESPONSE: {
44 | code: 6,
45 | msg: 'Invalid Response while decoding Unregisterd client request'
46 | },
47 |
48 | ERR_SYSTEM_URI: {
49 | code: 7,
50 | msg: 'Exec command must be an array of string arguments'
51 | },
52 |
53 | AUTH_DECISION_RESP: {
54 | code: 8,
55 | msg: ( error ) =>
56 | `Encoded response after authorisation decision was made: ${error}`
57 | },
58 |
59 | CONTAINER_DECISION_RESP: {
60 | code: 9,
61 | msg: ( error ) =>
62 | `Encoded response after container authorisation decision was made: ${error}`
63 | },
64 |
65 | SHAREMD_DECISION_RESP: {
66 | code: 10,
67 | msg: ( error ) =>
68 | `Encoded response after share MD authorisation decision was made: ${error}`
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/app/extensions/safe/index.ts:
--------------------------------------------------------------------------------
1 | import { logger } from '$Logger';
2 | import * as safeBrowserAppActions from '$Extensions/safe/actions/safeBrowserApplication_actions';
3 | import {
4 | startedRunningMock,
5 | isRunningSpectronTestProcess,
6 | APP_INFO,
7 | PROTOCOLS
8 | } from '$Constants';
9 |
10 | // export { onRemoteCallInBgProcess } from '$Extensions/safe/backgroundProcess/handleRemoteCalls';
11 |
12 | export { additionalReducers } from '$Extensions/safe/reducers';
13 | export { onWebviewPreload } from '$Extensions/safe/webviewProcess/webviewPreload';
14 | export { urlIsValid } from '$Extensions/safe/utils/urlIsValid';
15 |
16 | /**
17 | * add actions to the peruse browser container
18 | * @type {Object}
19 | */
20 | export const actionsForBrowser = {
21 | ...safeBrowserAppActions
22 | };
23 |
24 | /**
25 | * Add middleware to Peruse redux store
26 | * @param {Object} store redux store
27 | */
28 | // eslint-disable-next-line unicorn/consistent-function-scoping
29 | export const middleware = ( store ) => ( next ) => ( action ) => {
30 | if ( isRunningSpectronTestProcess ) {
31 | logger.info( 'ACTION:', action );
32 | }
33 |
34 | return next( action );
35 | };
36 |
--------------------------------------------------------------------------------
/app/extensions/safe/main-process/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | startedRunningMock,
3 | isRunningSpectronTestProcess,
4 | APP_INFO,
5 | PROTOCOLS
6 | } from '$Constants';
7 | import { setIsMock } from '$Extensions/safe/actions/safeBrowserApplication_actions';
8 | import { addFileMenus } from '$Extensions/safe/menus';
9 | import { logger } from '$Logger';
10 |
11 | export { onReceiveUrl } from './onReceiveUrl';
12 | export { preAppLoad } from './preAppLoad';
13 | export { onAppReady } from './onAppReady';
14 |
15 | /**
16 | * Adds menu items to the main peruse menus.
17 | * @param {Object} store redux store
18 | * @param {Array} menusArray Array of menu objects to be parsed by electron.
19 | */
20 | export const addExtensionMenuItems = ( store, menusArray ) => {
21 | logger.info( 'Adding SAFE menus to browser' );
22 |
23 | const newMenuArray = [];
24 |
25 | menusArray.forEach( ( menu ) => {
26 | const { label } = menu;
27 | let newMenu = menu;
28 |
29 | if ( label.includes( 'File' ) ) {
30 | newMenu = addFileMenus( store, newMenu );
31 | }
32 |
33 | newMenuArray.push( newMenu );
34 | } );
35 |
36 | return newMenuArray;
37 | };
38 |
39 | /**
40 | * onOpenLoadExtensions
41 | * on open of peruse application
42 | * @param {Object} store redux store
43 | */
44 | export const onOpen = ( store ) =>
45 | new Promise( ( resolve, reject ) => {
46 | logger.info( 'OnOpen: Setting mock in store. ', startedRunningMock );
47 | store.dispatch( setIsMock( startedRunningMock ) );
48 |
49 | resolve();
50 | } );
51 |
--------------------------------------------------------------------------------
/app/extensions/safe/main-process/onAppReady.ts:
--------------------------------------------------------------------------------
1 | import { Store } from 'redux';
2 | import { app } from 'electron';
3 |
4 | import { startedRunningMock } from '$Constants';
5 | import { logger } from '$Logger';
6 | import { setIsMock } from '$Extensions/safe/actions/safeBrowserApplication_actions';
7 |
8 | /**
9 | * on open of peruse application
10 | * @param {Object} store redux store
11 | */
12 | export const onAppReady = ( store: Store ) => {
13 | logger.info( 'OnAppReady: Setting mock in store. ', startedRunningMock );
14 | store.dispatch( setIsMock( startedRunningMock ) );
15 | };
16 |
--------------------------------------------------------------------------------
/app/extensions/safe/main-process/preAppLoad.ts:
--------------------------------------------------------------------------------
1 | import { Store } from 'redux';
2 | import { app, protocol } from 'electron';
3 | import fs from 'fs-extra';
4 | import path from 'path';
5 |
6 | import { isRunningUnpacked } from '$Constants';
7 | import { logger } from '$Logger';
8 | import { setNameAsMySite } from '$Extensions/safe/actions/pWeb_actions';
9 |
10 | export const preAppLoad = ( store: Store ) => {
11 | protocol.registerSchemesAsPrivileged( [
12 | {
13 | scheme: 'safe',
14 | privileges: {
15 | standard: true,
16 | secure: true,
17 | allowServiceWorkers: true,
18 | corsEnabled: true,
19 | supportFetchAPI: true
20 | }
21 | }
22 | ] );
23 |
24 | app.setAsDefaultProtocolClient( 'safe' );
25 |
26 | const isDefaultSafe = app.isDefaultProtocolClient( 'safe' );
27 | logger.info( 'Registered to handle safe: urls ? ', isDefaultSafe );
28 |
29 | // HACK: pseudo mysites until baked into API...
30 | // TODO: remove this once we have storage on the network.
31 | const storeMySitesLocation = path.resolve(
32 | app.getPath( 'userData' ),
33 | 'mySites.json'
34 | );
35 |
36 | logger.info( 'HACK: mysites local location', storeMySitesLocation );
37 |
38 | let isReadingMySites = true;
39 | fs.readJson( storeMySitesLocation, ( error, mySites ) => {
40 | isReadingMySites = false;
41 | if ( error ) logger.error( 'error reading mySites data.', error );
42 |
43 | logger.info( 'Local mysites info found.', mySites );
44 |
45 | if ( mySites != null ) {
46 | mySites.forEach( ( site ) => {
47 | if ( site && site.length > 0 ) {
48 | store.dispatch( setNameAsMySite( { url: `safe://${site}` } ) );
49 | }
50 | } );
51 | }
52 | } );
53 |
54 | // Listen and update the file
55 | let previousSites = [];
56 | store.subscribe( async () => {
57 | const { pWeb } = store.getState();
58 |
59 | if ( pWeb.mySites !== previousSites ) {
60 | previousSites = pWeb.mySites;
61 |
62 | // prevent windows FS errors
63 | if ( isReadingMySites ) return;
64 | // With async/await:
65 | try {
66 | await fs.outputJson( storeMySitesLocation, previousSites );
67 | logger.info( 'HACK: Written mysites.json to', storeMySitesLocation );
68 | } catch ( error ) {
69 | logger.error( 'HACK, error writing mysites.json ', error );
70 | }
71 | }
72 | } );
73 | };
74 |
--------------------------------------------------------------------------------
/app/extensions/safe/menus.ts:
--------------------------------------------------------------------------------
1 | import {
2 | setSaveConfigStatus,
3 | setReadConfigStatus
4 | } from '$Extensions/safe/actions/safeBrowserApplication_actions';
5 | import { SAFE } from '$Extensions/safe/constants';
6 |
7 | // import { logger } from '$Logger';
8 |
9 | const safeSave = ( store ) => ( {
10 | label: 'Save Browser State to SAFE',
11 | accelerator: 'CommandOrControl+Shift+E',
12 | click: ( _item, win ) => {
13 | if ( win ) {
14 | store.dispatch( setSaveConfigStatus( SAFE.SAVE_STATUS.TO_SAVE ) );
15 | }
16 | }
17 | } );
18 |
19 | const safeRead = ( store ) => ( {
20 | label: 'Read Browser State from SAFE',
21 | accelerator: 'CommandOrControl+Alt+F',
22 | click: ( item, win ) => {
23 | if ( win ) {
24 | store.dispatch( setReadConfigStatus( SAFE.READ_STATUS.TO_READ ) );
25 | }
26 | }
27 | } );
28 |
29 | export const addFileMenus = ( store, menu ) => {
30 | if ( !store || typeof store !== 'object' ) {
31 | throw new Error(
32 | 'Must pass the store to enable dispatching actions from the menus.'
33 | );
34 | }
35 |
36 | if ( !menu ) throw new Error( 'Must pass a menu to extend.' );
37 |
38 | const save = safeSave( store );
39 | const read = safeRead( store );
40 |
41 | const newMenu = { ...menu };
42 |
43 | newMenu.submenu.push( { type: 'separator' } );
44 | // newMenu.submenu.push( safeSave( store ) );
45 | // newMenu.submenu.push( safeRead( store ) );
46 | return newMenu;
47 | };
48 |
--------------------------------------------------------------------------------
/app/extensions/safe/network/authenticator-comms.ts:
--------------------------------------------------------------------------------
1 | import { parse as parseURL } from 'url';
2 |
3 | import { logger } from '$Logger';
4 | import { updateRemoteCall } from '$Actions/remoteCall_actions';
5 | import { getCurrentStore } from '$Extensions/safe/backgroundProcess/safeBrowserApplication/theApplication';
6 | import { PROTOCOLS } from '$Constants';
7 | import { SAFE } from '$Extensions/safe/constants';
8 |
9 | export const attemptReconnect = ( passedStore, appObject ) => {
10 | setTimeout( () => {
11 | logger.info( 'Attempting reconnect...' );
12 | appObject.reconnect();
13 |
14 | if (
15 | passedStore.getState().safeBrowserApp.networkStatus ===
16 | SAFE.NETWORK_STATE.DISCONNECTED
17 | ) {
18 | attemptReconnect( passedStore, appObject );
19 | }
20 | }, 5000 );
21 | };
22 |
23 | /**
24 | * Reconnect the application with SAFE Network when disconnected
25 | */
26 | export const reconnect = ( app ) => {
27 | if ( !app ) {
28 | return Promise.reject( new Error( 'Application not initialised' ) );
29 | }
30 | return app.reconnect();
31 | };
32 |
33 | /**
34 | * Reply to a remoteCall requeting auth from a webview DOM API.
35 | * (ClientType === 'WEB' )
36 | * @param {Object} request request object from ipc.js
37 | */
38 | export const replyToRemoteCallFromAuth = ( request ) => {
39 | logger.info( 'Replying to RemoteCall From Auth' );
40 | const store = getCurrentStore();
41 | const state = store.getState();
42 | const { remoteCalls } = state;
43 |
44 | const remoteCallToReply = remoteCalls.find( ( theCall ): boolean => {
45 | if ( theCall.name !== 'authenticateFromUriObject' ) return false;
46 |
47 | const theRequestFromCall = theCall.args[0].uri;
48 |
49 | return theRequestFromCall === request.uri;
50 | } );
51 |
52 | store.dispatch(
53 | updateRemoteCall( {
54 | ...remoteCallToReply,
55 | done: true,
56 | inProgress: true,
57 | response: request.res
58 | } )
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/app/extensions/safe/network/index.ts:
--------------------------------------------------------------------------------
1 | export * from './authenticator-comms';
2 |
--------------------------------------------------------------------------------
/app/extensions/safe/protocols/safe.ts:
--------------------------------------------------------------------------------
1 | import { remote } from 'electron';
2 | import url from 'url';
3 |
4 | import { logger } from '$Logger';
5 | import { CONFIG, PROTOCOLS } from '$Constants';
6 |
7 | export const registerSafeProtocol = () => {
8 | logger.info( `${PROTOCOLS.SAFE} Registering` );
9 | // bind to partition.
10 | const partition = CONFIG.SAFE_PARTITION;
11 | const ses = remote.session.fromPartition( partition );
12 |
13 | // TODO: Is it better to have one safe protocol
14 | // Would ports automatically routing locally make things simpler?
15 | ses.protocol.registerHttpProtocol(
16 | PROTOCOLS.SAFE,
17 | ( request, callback ) => {
18 | logger.info( `safe:// req url being parsed: ${request.url}` );
19 | const parsedUrl = url.parse( request.url );
20 |
21 | const { host, query } = parsedUrl;
22 |
23 | if ( !host ) {
24 | return;
25 | }
26 |
27 | const path = parsedUrl.pathname || '';
28 |
29 | // TODO. Sort out when/where with slash
30 | let newUrl = `http://localhost:${CONFIG.PORT}/safe://${host}${path}${
31 | query ? `?${query}` : ''
32 | }`;
33 |
34 | // Allow localhost to be served as safe://
35 | if ( parsedUrl.hostname === 'localhost' && parsedUrl.port ) {
36 | newUrl = `http://localhost:${parsedUrl.port}${path}${
37 | query ? `?${query}` : ''
38 | }`;
39 | }
40 |
41 | callback( { url: newUrl } );
42 | },
43 | ( error ) => {
44 | if ( !error ) return;
45 |
46 | if ( error.message === 'The scheme has been registered' ) {
47 | logger.info( 'SAFE protocol already registered, so dont worry' );
48 | } else {
49 | throw error;
50 | }
51 | }
52 | );
53 | };
54 |
--------------------------------------------------------------------------------
/app/extensions/safe/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import { safeBrowserApp } from './safeBrowserApp';
2 | import { pWeb } from './pWeb_reducer';
3 |
4 | export const additionalReducers = {
5 | safeBrowserApp,
6 | pWeb
7 | };
8 |
--------------------------------------------------------------------------------
/app/extensions/safe/reducers/initialAppState.ts:
--------------------------------------------------------------------------------
1 | export const initialAppState = {
2 | pWeb: {
3 | versionedUrls: {},
4 | availableNrsUrls: [],
5 | mySites: []
6 | },
7 | safeBrowserApp: {
8 | // appStatus: null,
9 | // networkStatus: null,
10 | app: null,
11 | // readStatus: '',
12 | // authResponseUri: '',
13 | // savedBeforeQuit: false,
14 | // saveStatus: '',
15 | isMock: null,
16 | isAuthorised: false,
17 | experimentsEnabled: false,
18 | showingWebIdDropdown: false,
19 | isFetchingWebIds: false,
20 | webIds: []
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/app/extensions/safe/reducers/pWeb_reducer.ts:
--------------------------------------------------------------------------------
1 | import { parse } from 'url';
2 |
3 | import { initialAppState } from './initialAppState';
4 |
5 | import { TYPES } from '$Extensions/safe/actions/pWeb_actions';
6 | import { logger } from '$Logger';
7 |
8 | const initialState = initialAppState.pWeb;
9 |
10 | export function pWeb( state = initialState, action ) {
11 | if ( action.error ) {
12 | logger.error( 'Error in initializing reducer: ', action, action.error );
13 | return state;
14 | }
15 |
16 | const { payload } = action;
17 |
18 | switch ( action.type ) {
19 | case TYPES.SET_KNOWN_VERSIONS_FOR_URL: {
20 | const newVersionedUrls = { ...state.versionedUrls };
21 |
22 | const newVersion = payload.version;
23 | const previousVersion = newVersionedUrls[payload.url];
24 |
25 | // no entry, or prev version was higher...
26 | if (
27 | !previousVersion ||
28 | ( previousVersion && previousVersion < payload.version )
29 | ) {
30 | newVersionedUrls[payload.url] = payload.version;
31 | }
32 |
33 | return {
34 | ...state,
35 | versionedUrls: newVersionedUrls
36 | };
37 | }
38 | case TYPES.SET_NAME_AS_MY_SITE: {
39 | const { url } = payload;
40 | const newMySites = [...state.mySites];
41 | let host;
42 | try {
43 | // eslint-disable-next-line prefer-destructuring
44 | host = parse( url ).hostname;
45 | } catch ( error ) {
46 | logger.error( 'There was an error parsing the url: ', error );
47 | }
48 |
49 | // if its avialable, add it if we dont already have it
50 | if ( !newMySites.includes( host ) ) {
51 | newMySites.push( host );
52 | }
53 |
54 | return {
55 | ...state,
56 | mySites: newMySites
57 | };
58 | }
59 |
60 | default:
61 | return state;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/extensions/safe/renderProcess/index.ts:
--------------------------------------------------------------------------------
1 | import { logger } from '$Logger';
2 | import * as safeBrowserAppActions from '$Extensions/safe/actions/safeBrowserApplication_actions';
3 | import {
4 | startedRunningMock,
5 | isRunningSpectronTestProcess,
6 | APP_INFO,
7 | PROTOCOLS
8 | } from '$Constants';
9 |
10 | export { additionalReducers } from '$Extensions/safe/reducers';
11 |
12 | export { addInternalPages } from '$Extensions/safe/rendererProcess/internalPages';
13 |
14 | /**
15 | * add actions to the peruse browser container
16 | * @type {Object}
17 | */
18 | export const actionsForBrowser = {
19 | ...safeBrowserAppActions
20 | };
21 |
22 | /**
23 | * Add middleware to Peruse redux store
24 | * @param {Object} store redux store
25 | */
26 | // eslint-disable-next-line unicorn/consistent-function-scoping
27 | export const middleware = ( store ) => ( next ) => ( action ) => {
28 | if ( isRunningSpectronTestProcess ) {
29 | logger.info( 'ACTION:', action );
30 | }
31 |
32 | return next( action );
33 | };
34 |
--------------------------------------------------------------------------------
/app/extensions/safe/rendererProcess/internalPages.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { Url } from 'url';
3 |
4 | import { Editor } from '$Extensions/safe/components/SafePages/Editor';
5 | import { MySites } from '$Extensions/safe/components/SafePages/MySites';
6 | import { STYLE_CONSTANTS } from '$Extensions/safe/rendererProcess/styleConstants';
7 |
8 | export const SAFE_PAGES = {
9 | EDIT_SITE: 'edit-site',
10 | MY_SITES: 'my-sites'
11 | };
12 |
13 | export const addInternalPages = (
14 | urlObject,
15 | query: {
16 | register?: string;
17 | },
18 | tab: {
19 | tabId: string;
20 | },
21 | props: Record
22 | ): null | { pageComponent: ReactNode; title: string; tabButtonStyles?: Record } => {
23 | switch ( urlObject.host ) {
24 | case SAFE_PAGES.EDIT_SITE: {
25 | const targetName = urlObject.path.slice( 1 );
26 | return {
27 | title: `Edit ${targetName}`,
28 | tabButtonStyles: {
29 | // backgroundColor: STYLE_CONSTANTS.editBgColor,
30 | // color: STYLE_CONSTANTS.editFontColor
31 | },
32 | pageComponent:
33 | };
34 | }
35 | case SAFE_PAGES.MY_SITES: {
36 | const { register } = query;
37 | return {
38 | title: 'My Sites',
39 | pageComponent: (
40 |
45 | )
46 | };
47 | }
48 | default: {
49 | return null;
50 | }
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/app/extensions/safe/rendererProcess/styleConstants.ts:
--------------------------------------------------------------------------------
1 | export const STYLE_CONSTANTS = {
2 | editBgColor: '#444444',
3 | editFontColor: '#FFFFFF'
4 | };
5 |
--------------------------------------------------------------------------------
/app/extensions/safe/safe-pages.css:
--------------------------------------------------------------------------------
1 | body
2 | {
3 | display: block;
4 | width: 80%;
5 | margin: 0 auto;
6 | max-width: 30rem;
7 | margin-top: 10rem;
8 | color: rgb( 51, 51, 51);
9 | font-family: system, -apple-system, ".SFNSDisplay-Regular", "Helvetica Neue", Helvetica, "Segoe UI", sans-serif;
10 | }
11 |
12 | h1, h2, h3, h4
13 | {
14 | font-weight: normal;
15 | }
16 |
17 | h1
18 | {
19 | border: 0;
20 | font-size: 36px;
21 | text-align: center;
22 | color: #5592d7;
23 | font-weight: 300;
24 | margin-bottom: 0;
25 | }
26 |
27 | h4
28 | {
29 | font-size: 18px;
30 | }
31 |
32 | .desc {
33 | text-align: center;
34 | margin: 0;
35 | padding: 0;
36 | margin-bottom: 50px;
37 | }
38 |
39 | .desc span::after
40 | {
41 | content: "";
42 | width: 5px;
43 | height: 5px;
44 | background-color: black;
45 | position: absolute;
46 | bottom: 5px;
47 | right: -2.5px;
48 | border-radius: 100%;
49 | }
50 |
51 | .desc span
52 | {
53 | font-size: 14px;
54 | font-weight: normal;
55 | opacity: 0.5;
56 | padding: 0 16px;
57 | position: relative;
58 | }
59 |
60 | .desc span:last-child::after
61 | {
62 | display: none;
63 | }
64 |
65 |
66 |
67 | a
68 | {
69 | cursor: pointer;
70 | color: #016fde;
71 | text-decoration: none;
72 | }
73 |
74 | a:hover
75 | {
76 | text-decoration: underline;
77 | color: #0091ff;
78 | }
79 |
80 | a:active,
81 | a:hover
82 | {
83 | outline: 0;
84 | }
85 |
86 |
87 |
88 |
89 |
90 | ul
91 | {
92 | padding-left: 0;
93 | }
94 |
95 | li
96 | {
97 | list-style-type: none;
98 | margin: 20px 0 0;
99 | padding: 0;
100 | font-size: 14px;
101 | }
102 |
103 |
104 |
105 | .log-body
106 | {
107 | max-width: 60em;
108 | width: 90%;
109 | }
110 |
--------------------------------------------------------------------------------
/app/extensions/safe/safe.d.ts:
--------------------------------------------------------------------------------
1 | type XorUrl = string;
2 |
3 | type SafeDataType = 'PublishedSeqAppendOnlyData' | 'PublishedImmutableData';
4 |
5 | export interface SafeData {
6 | data_type: SafeDataType;
7 | files_map?: {
8 | [path: string]: XorUrl;
9 | };
10 | resolved_from: {
11 | data_type: SafeDataType;
12 | nrs_map: [Record];
13 | public_name?: string;
14 | type_tag: number;
15 | version: number;
16 | xorname: Array;
17 | xorurl: XorUrl;
18 | };
19 | type_tag: number;
20 | version: number;
21 | xorname: Array;
22 | }
23 |
--------------------------------------------------------------------------------
/app/extensions/safe/test/.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 |
--------------------------------------------------------------------------------
/app/extensions/safe/test/actions/pWeb.spec.ts:
--------------------------------------------------------------------------------
1 | import * as pWeb from '$Extensions/safe/actions/pWeb_actions';
2 |
3 | const date = new Date().toLocaleDateString();
4 |
5 | describe( 'pWeb actions', () => {
6 | it( 'should have types', () => {
7 | expect( pWeb.TYPES ).toBeDefined();
8 | } );
9 |
10 | it( 'should set known version', () => {
11 | const payload = {
12 | url: 'x',
13 | version: 2
14 | };
15 | const expectedAction = {
16 | type: pWeb.TYPES.SET_KNOWN_VERSIONS_FOR_URL,
17 | payload
18 | };
19 | expect( pWeb.setKnownVersionsForUrl( payload ) ).toEqual( expectedAction );
20 | } );
21 |
22 | it( 'should set url as owned', () => {
23 | const payload = {
24 | url: 'x'
25 | };
26 |
27 | const expectedAction = {
28 | type: pWeb.TYPES.SET_NAME_AS_MY_SITE,
29 | payload
30 | };
31 | expect( pWeb.setNameAsMySite( payload ) ).toEqual( expectedAction );
32 | } );
33 | } );
34 |
--------------------------------------------------------------------------------
/app/extensions/safe/test/app/saferSafe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SaferSafe } from '$App/extensions/safe/webviewProcess/saferSafe';
2 |
3 | // jest.mock('safe-nodejs');
4 |
5 | describe( 'Filesystem safe SAFE', () => {
6 | let safe;
7 | beforeEach( () => {
8 | safe = new SaferSafe();
9 | } );
10 |
11 | test( 'a SAFE object has needed methods', async () => {
12 | expect( typeof safe.files_container_create ).toBe( 'function' );
13 | expect( typeof safe.files_container_add ).toBe( 'function' );
14 | expect( typeof safe.files_container_sync ).toBe( 'function' );
15 |
16 | // not strictly needed, but lets check it exists on our new class
17 | expect( typeof safe.files_container_add_from_raw ).toBe( 'function' );
18 | } );
19 |
20 | test( 'attempting to use location fails', async () => {
21 | expect( () => {
22 | safe.files_container_create( 'some_location', '', true, true, true );
23 | } ).toThrow( /"location"/ );
24 | expect( () => {
25 | safe.files_container_sync( 'some_location', '', true, true, true );
26 | } ).toThrow( /"location"/ );
27 | expect( () => {
28 | safe.files_container_add( 'some_none_safe_location', '', true, true, true );
29 | } ).toThrow( /"location".+safe:/ );
30 | } );
31 |
32 | test( 'attempting to use safe container add with a safe: url does not fail', async () => {
33 | // it will still fail as args not correct and we're not mocking safe here.
34 | expect( () => {
35 | safe.files_container_add(
36 | 'safe://some_safe_location',
37 | '',
38 | true,
39 | true,
40 | true
41 | );
42 | } ).not.toThrow( /"location".+safe:/ );
43 | } );
44 |
45 | test( 'attempting to use safe container create should fail when a string is passed', async () => {
46 | expect( () => {
47 | safe.files_container_create( 's', '', true, true, true );
48 | } ).toThrow( /File object/ );
49 | } );
50 |
51 | test( 'attempting to use safe container create with locaton object should fail', async () => {
52 | const x = {};
53 |
54 | expect( () => {
55 | safe.files_container_create( x, '', true, true, true );
56 | } ).toThrow( /File object/ );
57 | } );
58 |
59 | test( 'attempting to use safe container create with File object should throw temp error', async () => {
60 | const x = new File( [], 'test' );
61 | expect( () => {
62 | safe.files_container_create( x, '', true, true, true );
63 | } ).toThrow( /location" argument cannot be used/ );
64 | } );
65 | } );
66 |
--------------------------------------------------------------------------------
/app/extensions/safe/test/app/webviewPreload.spec.ts:
--------------------------------------------------------------------------------
1 | import * as webviewPreload from '$App/extensions/safe/webviewProcess/webviewPreload';
2 | import { APP_INFO, startedRunningProduction } from '$Constants';
3 |
4 | // avoid appveyour for its weak.ref issues right now.
5 | const { APPVEYOR } = process.env;
6 |
7 | describe( 'SAFE Webpreload', () => {
8 | const win = {};
9 | const store = {
10 | subscribe: jest.fn(),
11 | getState: jest.fn( () => ( {
12 | safeBrowserApp: { experimentsEnabled: true }
13 | } ) )
14 | };
15 | beforeEach( () => {
16 | // webviewPreload.onPreload( store, win );
17 | } );
18 |
19 | test( 'SAFE API added to the DOM', async () => {
20 | webviewPreload.setupSafeAPIs( store, win );
21 |
22 | expect( typeof win.Safe ).toBe( 'function' );
23 | } );
24 | test( 'SAFE XorUrlEncoder added to the DOM', async () => {
25 | webviewPreload.setupSafeAPIs( store, win );
26 |
27 | expect( typeof win.XorUrlEncoder ).toBe( 'function' );
28 | } );
29 | } );
30 |
31 | // describe( 'SAFE manageWebIdUpdates', () => {
32 | // if ( APPVEYOR ) return;
33 | //
34 | // const win = {};
35 | // // need to mock store. should be called once.
36 | // const store = {
37 | // subscribe: jest.fn(),
38 | // getState: jest.fn( () => ( {
39 | // safeBrowserApp: { experimentsEnabled: true }
40 | // } ) )
41 | // };
42 | //
43 | // beforeEach( () => {
44 | // // webviewPreload.onPreload( store, win );
45 | // } );
46 | //
47 | // test( 'webIdEventEmitter should not exist with experiments disabled', () => {
48 | // const noExpStore = {
49 | // subscribe: jest.fn(),
50 | // getState: jest.fn( () => ( {
51 | // safeBrowserApp: { experimentsEnabled: false }
52 | // } ) )
53 | // };
54 | //
55 | // // webviewPreload.onPreload( noExpStore, win );
56 | //
57 | // expect( win.webIdEventEmitter ).toBeNull();
58 | // } );
59 | //
60 | // test( 'webIdEventEmitter should exist', () => {
61 | // expect( win.webIdEventEmitter ).not.toBeNull();
62 | // } );
63 | //
64 | // test( 'webIdEventEmitter should emit events', async () => {
65 | // expect.assertions( 1 );
66 | // const theData = 'webId!!!';
67 | // win.webIdEventEmitter.on( 'update', ( data ) => {
68 | // expect( data ).toBe( theData );
69 | // } );
70 | //
71 | // win.webIdEventEmitter.emit( 'update', theData );
72 | // } );
73 | //
74 | // /* xtest( 'Check response to store change?' ); */
75 | // } );
76 |
--------------------------------------------------------------------------------
/app/extensions/safe/utils/isInEditor.ts:
--------------------------------------------------------------------------------
1 | import { parse } from 'url';
2 |
3 | import { PROTOCOLS } from '$Constants';
4 | import { SAFE_PAGES } from '$Extensions/safe/rendererProcess/internalPages';
5 |
6 | export const inEditor = ( address ): boolean => {
7 | const parsedAddress = parse( address );
8 |
9 | return (
10 | parsedAddress.protocol === `${PROTOCOLS.INTERNAL_PAGES}:` &&
11 | parsedAddress.host === SAFE_PAGES.EDIT_SITE
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/app/extensions/safe/utils/safeHelpers.ts:
--------------------------------------------------------------------------------
1 | export const cleanupNeonError = ( error: Error ): string => {
2 | const neonError = 'internal error in Neon module:';
3 | let { message } = error;
4 |
5 | if ( message && message.startsWith( neonError ) ) {
6 | message = message.replace( neonError, '' );
7 | }
8 | return message;
9 | };
10 |
11 | export const generateBoundaryString = () => {
12 | let text = '';
13 | const charSet = 'abcdefghijklmnopqrstuvwxyz0123456789';
14 |
15 | for ( let i = 0; i < 13; i += 1 ) {
16 | text += charSet.charAt( Math.floor( Math.random() * charSet.length ) );
17 | }
18 |
19 | return text;
20 | };
21 |
22 | export const rangeStringToArray = ( rangeString ) => {
23 | const BYTES = 'bytes=';
24 | return rangeString
25 | .slice( BYTES.length, rangeString.length )
26 | .split( ',' )
27 | .map( ( part ) => {
28 | const partObject = {};
29 | part.split( '-' ).forEach( ( int, i ) => {
30 | if ( i === 0 ) {
31 | if ( Number.isInteger( parseInt( int, 10 ) ) ) {
32 | partObject.start = parseInt( int, 10 );
33 | } else {
34 | partObject.start = null;
35 | }
36 | } else if ( i === 1 ) {
37 | if ( Number.isInteger( parseInt( int, 10 ) ) ) {
38 | partObject.end = parseInt( int, 10 );
39 | } else {
40 | partObject.end = null;
41 | }
42 | }
43 | } );
44 | return partObject;
45 | } );
46 | };
47 |
48 | export const generateResponseString = ( data ) => {
49 | const boundaryString = generateBoundaryString();
50 | const crlf = '\r\n';
51 | let responseString = `HTTP/1.1 206 Partial Content${crlf}`;
52 | responseString += `Content-Type: multipart/byteranges; boundary=${boundaryString}${crlf}`;
53 | responseString += `Content-Length:${data.headers['Content-Length']}${crlf}`;
54 | data.parts.forEach( ( part ) => {
55 | responseString += `--${boundaryString}${crlf}`;
56 | responseString += `Content-Type:${part.headers['Content-Type']}${crlf}`;
57 | responseString += `Content-Range: ${part.headers['Content-Range']}${crlf}`;
58 | responseString += `${part.body}${crlf}`;
59 | } );
60 | responseString += `--${boundaryString}--`;
61 | return responseString;
62 | };
63 |
--------------------------------------------------------------------------------
/app/extensions/safe/utils/urlIsValid.ts:
--------------------------------------------------------------------------------
1 | import { parse, Url } from 'url';
2 |
3 | import { CONFIG } from '$Constants';
4 | import { logger } from '$Logger';
5 | import buildConfig from '$BuilderConfig';
6 |
7 | const isForSafeServer = ( parsedUrlObject: Url ): boolean =>
8 | parsedUrlObject.host === `localhost:${CONFIG.PORT}`;
9 |
10 | export const urlIsValid = ( testUrl ): boolean => {
11 | logger.info( 'Checking urlIsValid', testUrl );
12 | const urlObject = parse( testUrl );
13 |
14 | const validProtocols = buildConfig.protocols.schemes || ['http'];
15 | const adaptedProtocols = validProtocols.map( ( proto ) => `${proto}:` );
16 |
17 | if ( testUrl === 'about:blank' ) return true;
18 |
19 | // TODO: locally server appspot files to avoid reqs thereto.
20 | if (
21 | adaptedProtocols.includes( urlObject.protocol ) ||
22 | isForSafeServer( urlObject ) ||
23 | urlObject.protocol === 'devtools:' ||
24 | urlObject.protocol === 'file:' ||
25 | urlObject.protocol === 'blob:' ||
26 | urlObject.protocol === 'chrome-extension:'
27 | // ||
28 | // urlObject.host === 'chrome-devtools-frontend.appspot.com'
29 | ) {
30 | return true;
31 | }
32 |
33 | if (
34 | urlObject.hostname === '127.0.0.1' ||
35 | urlObject.hostname === 'localhost'
36 | ) {
37 | return true;
38 | }
39 |
40 | return false;
41 | };
42 |
--------------------------------------------------------------------------------
/app/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import { Provider } from 'react-redux';
5 |
6 | import { configureStore } from './store/configureStore';
7 | import { BrowserWindow } from './containers/BrowserWindow';
8 | import { App } from './containers/App';
9 |
10 | import { logger } from '$Logger';
11 | import './app.global.css';
12 |
13 | logger.info( 'Starting render process' );
14 |
15 | window.addEventListener( 'error', function( error ) {
16 | console.error( 'error in UI:', error );
17 | logger.error(
18 | 'errorInUI',
19 | JSON.stringify( error, [
20 | 'message',
21 | 'arguments',
22 | 'type',
23 | 'name',
24 | 'file',
25 | 'line'
26 | ] )
27 | );
28 | } );
29 |
30 | const store = configureStore();
31 |
32 | // for execution via BrowserWindow later
33 | window.peruseStore = store;
34 |
35 | // if ( window.perusePendingNavigation && window.perusePendingNavigation.length !== 0 ) {
36 | // store.dispatch( push( window.perusePendingNavigation ) );
37 | // }
38 |
39 | render(
40 |
41 |
42 |
43 |
44 |
45 |
46 | ,
47 | document.getElementById( 'root' )
48 | );
49 |
50 | if ( module.hot ) {
51 | module.hot.accept( './containers/App', () => {
52 | // eslint-disable-next-line @typescript-eslint/no-var-requires
53 | const NextRoot = require( './containers/App' ).default;
54 | render(
55 |
56 |
57 | ,
58 | document.getElementById( 'root' )
59 | );
60 | } );
61 | }
62 |
--------------------------------------------------------------------------------
/app/locales/en.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/app/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import { bookmarks } from './bookmarks';
4 | import { notifications } from './notifications';
5 | import { tabs } from './tabs';
6 | import { remoteCalls } from './remoteCalls';
7 | import { windows } from './windows';
8 | import { history } from './history';
9 |
10 | import { logger } from '$Logger';
11 | import { getExtensionReducers } from '$Extensions';
12 |
13 | const additionalReducers = getExtensionReducers();
14 |
15 | export function createRootReducer() {
16 | return combineReducers( {
17 | history,
18 | bookmarks,
19 | notifications,
20 | remoteCalls,
21 | tabs,
22 | windows,
23 | ...additionalReducers
24 | } );
25 | }
26 |
--------------------------------------------------------------------------------
/app/reducers/initialAppState.ts:
--------------------------------------------------------------------------------
1 | export const initialAppState = {
2 | history: {},
3 | bookmarks: [{ url: 'safe://hello' }],
4 | remoteCalls: [],
5 | notifications: [],
6 | tabs: {},
7 | windows: {
8 | openWindows: {},
9 | closedWindows: {}
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/app/reducers/notifications.ts:
--------------------------------------------------------------------------------
1 | import { initialAppState } from './initialAppState';
2 |
3 | import { logger } from '$Logger';
4 | import { TYPES } from '$Actions/notification_actions';
5 |
6 | const initialState = initialAppState.notifications;
7 | const findNotificationIndexById = ( theState, theCall ) => {
8 | if ( !theCall.id ) {
9 | logger.error( 'Noticications cannot be removed without an ID property' );
10 | }
11 |
12 | return theState.findIndex( ( c ) => c.id === theCall.id );
13 | };
14 |
15 | export const notifications = ( state: Array = initialState, action ) => {
16 | const notification = action.payload;
17 |
18 | switch ( action.type ) {
19 | case TYPES.ADD_NOTIFICATION: {
20 | const id = notification.id || Math.random().toString( 36 );
21 | const notificationToAdd = { ...notification, id };
22 | return [...state, notificationToAdd];
23 | }
24 | case TYPES.UPDATE_NOTIFICATION: {
25 | if ( !notification.id ) {
26 | throw new Error( 'To update a notification requires passing the "id"' );
27 | }
28 | const notificationId = findNotificationIndexById( state, notification );
29 | const updatedState = [...state];
30 | const oldNotification = updatedState[notificationId];
31 |
32 | updatedState[notificationId] = {
33 | ...oldNotification,
34 | ...notification
35 | };
36 | return updatedState;
37 | }
38 | case TYPES.CLEAR_NOTIFICATION: {
39 | const updatedState = [...state];
40 | if ( notification && notification.id ) {
41 | const newArray = updatedState.filter(
42 | ( element ) => element.id !== notification.id
43 | );
44 | return [...newArray];
45 | }
46 | updatedState.shift();
47 | return updatedState;
48 | }
49 |
50 | default:
51 | return state;
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/app/reducers/remoteCalls.ts:
--------------------------------------------------------------------------------
1 | import { initialAppState } from './initialAppState';
2 |
3 | import { logger } from '$Logger';
4 | import { TYPES } from '$Actions/remoteCall_actions';
5 |
6 | const initialState = initialAppState.remoteCalls;
7 |
8 | const findCallIndexById = ( theState, theCall ) => {
9 | if ( !theCall.id ) {
10 | logger.error( 'Remote calls cannot be removed without an ID property' );
11 | }
12 |
13 | return theState.findIndex( ( c ) => c.id === theCall.id );
14 | };
15 |
16 | export function remoteCalls( state: Array = initialState, action ) {
17 | const theCall = action.payload;
18 |
19 | switch ( action.type ) {
20 | case TYPES.ADD_REMOTE_CALL: {
21 | const updatedState = [...state];
22 | updatedState.push( theCall );
23 | // TODO: Do we need to add an ID here?
24 | // DO we fail if no windowIdProvided?
25 | // Do we need to remove calls after X time?
26 | return updatedState;
27 | }
28 | case TYPES.REMOVE_REMOTE_CALL: {
29 | const updatedState = [...state];
30 |
31 | const removalIndex = findCallIndexById( updatedState, theCall );
32 | updatedState.splice( removalIndex, 1 );
33 |
34 | return updatedState;
35 | }
36 | case TYPES.UPDATE_REMOTE_CALL: {
37 | const updatedState = [...state];
38 |
39 | const callIndex = findCallIndexById( updatedState, theCall );
40 | const callToUpdate = updatedState[callIndex];
41 |
42 | updatedState[callIndex] = {
43 | ...callToUpdate,
44 | ...theCall
45 | };
46 |
47 | return updatedState;
48 | }
49 |
50 | default: {
51 | return state;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/server/index.ts:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 |
3 | import { CONFIG } from '$Constants';
4 | import { logger } from '$Logger';
5 |
6 | const app = express();
7 |
8 | export const setupServer = () => {
9 | app.listen( CONFIG.PORT, () =>
10 | logger.info( `Peruse internal server listening on port ${CONFIG.PORT}!` )
11 | );
12 |
13 | return app;
14 | };
15 |
--------------------------------------------------------------------------------
/app/store/addMiddlewares.ts:
--------------------------------------------------------------------------------
1 | import thunk from 'redux-thunk';
2 | import promiseMiddleware from 'redux-promise';
3 | import { forwardToRenderer, forwardToMain, triggerAlias } from 'electron-redux';
4 |
5 | import { inRendererProcess, inBgProcess } from '$Constants';
6 |
7 | export const addMiddlewares = ( middleware: Array<()=>void > ) => {
8 | middleware.push( thunk );
9 |
10 | middleware.unshift( promiseMiddleware );
11 |
12 | if ( inBgProcess ) {
13 | middleware.push( triggerAlias );
14 | }
15 |
16 | if ( inRendererProcess ) {
17 | // must be first
18 | middleware.unshift( forwardToMain );
19 | }
20 |
21 | if ( !inRendererProcess ) {
22 | // must be last
23 | middleware.push( forwardToRenderer );
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/app/store/configureStore.ts:
--------------------------------------------------------------------------------
1 | import * as configureStoreDevelopment from './configureStore.dev';
2 | import * as configureStoreProduction from './configureStore.prod';
3 |
4 | const selectedConfigureStore =
5 | process.env.NODE_ENV === 'production'
6 | ? configureStoreProduction
7 | : configureStoreDevelopment;
8 |
9 | export const { configureStore } = selectedConfigureStore;
10 |
--------------------------------------------------------------------------------
/app/utils/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/app/utils/.gitkeep
--------------------------------------------------------------------------------
/app/utils/__mocks__/extendComponent.tsx:
--------------------------------------------------------------------------------
1 | export const extendComponent = ( WrappedComponent, extensionWrapperApi ) =>
2 | WrappedComponent;
3 |
--------------------------------------------------------------------------------
/app/utils/extendComponent.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import { logger } from '$Logger';
4 |
5 | export const extendComponent = ( WrappedComponent, extensionWrapperApi ) => {
6 | if ( !WrappedComponent ) throw new Error( 'Must pass a component to wrap.' );
7 |
8 | if ( typeof extensionWrapperApi !== 'function' )
9 | throw new Error( 'extensionWrapperApi must be an executable function.' );
10 |
11 | const componentClassName = WrappedComponent.name;
12 | logger.info( `Extending ${componentClassName} via the extensions Api` );
13 |
14 | class Extended extends Component {
15 | static getDerivedStateFromError( error ) {
16 | // Update state so the next render will show the fallback UI.
17 | return { hasError: true, theError: error };
18 | }
19 |
20 | constructor( props ) {
21 | super( props );
22 | this.state = { hasError: false };
23 | this.EnWrappedComponent = extensionWrapperApi( WrappedComponent );
24 | }
25 |
26 | render() {
27 | if ( this.state && this.state.hasError ) {
28 | const error = this.state.theError;
29 |
30 | // You can render any custom fallback UI
31 | return (
32 |
33 |
Something went wrong extending {componentClassName}
34 |
35 | {JSON.stringify( error, ['message', 'arguments', 'type', 'name'] )}
36 |
37 |
38 | );
39 | }
40 |
41 | const { EnWrappedComponent } = this;
42 | return ;
43 | }
44 | }
45 |
46 | // set our wrapped class name to be the standard class name.
47 | Object.defineProperty( Extended, 'name', {
48 | value: `Extended${componentClassName}`
49 | } );
50 |
51 | return Extended;
52 | };
53 |
--------------------------------------------------------------------------------
/app/utils/getMostRecentlyActiveWindow.ts:
--------------------------------------------------------------------------------
1 | import { BrowserWindow } from 'electron';
2 | import { Store } from 'redux';
3 |
4 | import { logger } from '$Logger';
5 |
6 | export const getMostRecentlyActiveWindow = ( aStore: Store ): BrowserWindow => {
7 | if ( !aStore ) {
8 | throw new Error( 'A store must be passed.' );
9 | }
10 | const { openWindows } = aStore.getState().windows;
11 | const openWindowArray = Object.keys( openWindows );
12 |
13 | const windowInFocusId = Number(
14 | openWindowArray.find(
15 | ( aWindowId ): boolean => openWindows[aWindowId].wasLastInFocus
16 | )
17 | );
18 |
19 | let targetWindow;
20 |
21 | // fallback (mostly for during boot)
22 | try {
23 | targetWindow = BrowserWindow.fromId( windowInFocusId );
24 | } catch ( error ) {
25 | targetWindow = BrowserWindow.getAllWindows();
26 |
27 | targetWindow.forEach( ( w ) => logger.info( 'a window id:', w.id ) );
28 |
29 | // eslint-disable-next-line prefer-destructuring
30 | targetWindow = targetWindow[0];
31 | }
32 |
33 | return targetWindow;
34 | };
35 |
--------------------------------------------------------------------------------
/app/utils/reactNodeToElement.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export interface NodeObject {
4 | type: string;
5 | key?: string;
6 | props: {
7 | className?: string;
8 | children: Array | string;
9 | [prop: string]: any;
10 | };
11 | }
12 |
13 | interface NodeDescription {
14 | type: string;
15 | children: Array | string;
16 | props?: {
17 | [prop: string]: any;
18 | };
19 | }
20 |
21 | export const reactNodeToElement = ( nodeObject: NodeObject ) => {
22 | const nodeDescription: NodeDescription = {
23 | type: ''
24 | };
25 | Object.keys( nodeObject ).forEach( ( key ) => {
26 | if ( key === 'type' ) {
27 | nodeDescription[key] = nodeObject[key];
28 | return;
29 | }
30 | if ( key === 'props' ) {
31 | Object.keys( nodeObject[key] ).forEach( ( property ) => {
32 | if ( property === 'children' ) {
33 | nodeDescription.children = nodeObject.props.children;
34 | return;
35 | }
36 | if ( nodeDescription.props ) {
37 | nodeDescription.props = {
38 | ...nodeDescription.props,
39 | [property]: nodeObject[key][property]
40 | };
41 | } else {
42 | nodeDescription.props = {
43 | [property]: nodeObject[key][property]
44 | };
45 | }
46 | } );
47 | }
48 | if ( key === 'key' && nodeObject.key ) {
49 | if ( !nodeDescription.props ) {
50 | nodeDescription.props = {};
51 | }
52 | nodeDescription.props.key = nodeObject.key;
53 | }
54 | } );
55 | if ( Array.isArray( nodeDescription.children ) ) {
56 | nodeDescription.children = nodeDescription.children
57 | .reduce( ( accumulator, value ) => accumulator.concat( value ), [] )
58 | .map( ( child ) => reactNodeToElement( child ) );
59 | } else if (
60 | nodeDescription.children instanceof Object &&
61 | !Array.isArray( nodeDescription.children )
62 | ) {
63 | nodeDescription.children = reactNodeToElement( nodeDescription.children );
64 | }
65 | const elementType = nodeDescription.type;
66 | const elementProperties = nodeDescription.props || [];
67 | const elementChildren = nodeDescription.children || null;
68 | return React.createElement( elementType, elementProperties, elementChildren );
69 | };
70 |
--------------------------------------------------------------------------------
/codeowners:
--------------------------------------------------------------------------------
1 | * @joshuef
2 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ['@commitlint/config-conventional'] };
2 |
--------------------------------------------------------------------------------
/configs/webpack.config.background.prod.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack config for production electron main process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import { merge } from 'webpack-merge';
8 | import TerserPlugin from 'terser-webpack-plugin';
9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
10 |
11 | import baseConfig from './webpack.config.base';
12 | import CheckNodeEnvironment from '../internals/scripts/CheckNodeEnv';
13 |
14 | CheckNodeEnvironment( 'production' );
15 |
16 | export default merge( baseConfig, {
17 | devtool: 'source-map',
18 |
19 | mode: 'development',
20 |
21 | target: 'electron-renderer',
22 |
23 | entry: './app/background',
24 |
25 | output: {
26 | path: path.join( __dirname, '..' ),
27 | filename: './app/background.prod.js',
28 | },
29 |
30 | optimization: {
31 | minimizer: process.env.E2E_BUILD
32 | ? []
33 | : [
34 | new TerserPlugin( {
35 | parallel: true,
36 | sourceMap: true,
37 | cache: true,
38 | } ),
39 | ],
40 | },
41 |
42 | plugins: [
43 | new BundleAnalyzerPlugin( {
44 | analyzerMode:
45 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
46 | openAnalyzer: process.env.OPEN_ANALYZER === 'true',
47 | } ),
48 |
49 | /**
50 | * Create global constants which can be configured at compile time.
51 | *
52 | * Useful for allowing different behaviour between development builds and
53 | * release builds
54 | *
55 | * NODE_ENV should be production so that modules do not perform certain
56 | * development checks
57 | */
58 | new webpack.EnvironmentPlugin( {
59 | NODE_ENV: 'production',
60 | DEBUG_PROD: false,
61 | START_MINIMIZED: false,
62 | } ),
63 | ],
64 |
65 | /**
66 | * Disables webpack processing of __dirname and __filename.
67 | * If you run the bundle in node.js it falls back to these values of node.js.
68 | * https://github.com/webpack/webpack/issues/2010
69 | */
70 | node: {
71 | __dirname: false,
72 | __filename: false,
73 | },
74 | } );
75 |
--------------------------------------------------------------------------------
/configs/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Base webpack config used across other specific configs
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 |
8 | import { dependencies } from '../package';
9 |
10 | export default {
11 | externals: [...Object.keys( dependencies || {} )],
12 | module: {
13 | rules: [
14 | {
15 | test: /\.[jt|]sx?$/,
16 | exclude: /node_modules/,
17 | use: {
18 | loader: 'babel-loader',
19 | options: {
20 | cacheDirectory: true
21 | }
22 | }
23 | },
24 | // NODE Files
25 | {
26 | test: /\.node(\?v=\d+\.\d+\.\d+)?$/,
27 | use: {
28 | loader: 'native-ext-loader'
29 | }
30 | }
31 | ]
32 | },
33 |
34 | output: {
35 | path: path.join( __dirname, '..', 'app' ),
36 | // https://github.com/webpack/webpack/issues/1114
37 | libraryTarget: 'commonjs2'
38 | },
39 |
40 | /**
41 | * Determine the array of extensions that should be used to resolve modules.
42 | */
43 | resolve: {
44 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx']
45 | },
46 |
47 | plugins: [
48 | new webpack.EnvironmentPlugin( {
49 | NODE_ENV: 'production'
50 | } ),
51 |
52 | new webpack.NamedModulesPlugin()
53 | ]
54 | };
55 |
--------------------------------------------------------------------------------
/configs/webpack.config.eslint.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-unresolved: off, import/no-self-import: off */
2 | require( '@babel/register' );
3 |
4 | module.exports = require( './webpack.config.renderer.dev.babel' ).default;
5 |
--------------------------------------------------------------------------------
/configs/webpack.config.main.prod.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack config for production electron main process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import { merge } from 'webpack-merge';
8 | import TerserPlugin from 'terser-webpack-plugin';
9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
10 |
11 | import baseConfig from './webpack.config.base';
12 | import CheckNodeEnvironment from '../internals/scripts/CheckNodeEnv';
13 |
14 | CheckNodeEnvironment( 'production' );
15 |
16 | export default merge( baseConfig, {
17 | devtool: 'source-map',
18 |
19 | mode: 'production',
20 |
21 | target: 'electron-main',
22 |
23 | entry: './app/main.dev.ts',
24 |
25 | output: {
26 | path: path.join( __dirname, '..' ),
27 | filename: './app/main.prod.js',
28 | },
29 |
30 | optimization: {
31 | minimizer: process.env.E2E_BUILD
32 | ? []
33 | : [
34 | new TerserPlugin( {
35 | parallel: true,
36 | sourceMap: true,
37 | cache: true,
38 | } ),
39 | ],
40 | },
41 |
42 | module: {
43 | rules: [
44 | // NODE Files
45 | {
46 | test: /\.node(\?v=\d+\.\d+\.\d+)?$/,
47 | use: {
48 | loader: 'native-ext-loader',
49 | options: {
50 | emit: false,
51 | checkResourcesPath: true,
52 | },
53 | },
54 | },
55 | ],
56 | },
57 |
58 | plugins: [
59 | new BundleAnalyzerPlugin( {
60 | analyzerMode:
61 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
62 | openAnalyzer: process.env.OPEN_ANALYZER === 'true',
63 | } ),
64 |
65 | /**
66 | * Create global constants which can be configured at compile time.
67 | *
68 | * Useful for allowing different behaviour between development builds and
69 | * release builds
70 | *
71 | * NODE_ENV should be production so that modules do not perform certain
72 | * development checks
73 | */
74 | new webpack.EnvironmentPlugin( {
75 | NODE_ENV: 'production',
76 | DEBUG_PROD: false,
77 | START_MINIMIZED: false,
78 | } ),
79 | ],
80 |
81 | /**
82 | * Disables webpack processing of __dirname and __filename.
83 | * If you run the bundle in node.js it falls back to these values of node.js.
84 | * https://github.com/webpack/webpack/issues/2010
85 | */
86 | node: {
87 | __dirname: false,
88 | __filename: false,
89 | },
90 | } );
91 |
--------------------------------------------------------------------------------
/configs/webpack.config.renderer.dev.dll.babel.js:
--------------------------------------------------------------------------------
1 | /* eslint global-require: off, import/no-dynamic-require: off */
2 |
3 | /**
4 | * Builds the DLL for development electron renderer process
5 | */
6 |
7 | import webpack from 'webpack';
8 | import path from 'path';
9 | import { merge } from 'webpack-merge';
10 |
11 | import baseConfig from './webpack.config.base';
12 | import { dependencies } from '../package';
13 | import CheckNodeEnvironment from '../internals/scripts/CheckNodeEnv';
14 |
15 | CheckNodeEnvironment( 'development' );
16 |
17 | const dist = path.join( __dirname, '..', 'dll' );
18 |
19 | export default merge( baseConfig, {
20 | context: path.join( __dirname, '..' ),
21 |
22 | devtool: 'eval',
23 |
24 | mode: 'development',
25 |
26 | target: 'electron-renderer',
27 |
28 | externals: ['fsevents', 'crypto-browserify'],
29 |
30 | /**
31 | * Use `module` from `webpack.config.renderer.dev.js`
32 | */
33 | module: require( './webpack.config.renderer.dev.babel' ).module,
34 |
35 | entry: {
36 | renderer: Object.keys( dependencies || {} ),
37 | },
38 |
39 | output: {
40 | library: 'renderer',
41 | path: dist,
42 | filename: '[name].dev.dll.js',
43 | libraryTarget: 'var',
44 | },
45 |
46 | plugins: [
47 | new webpack.DllPlugin( {
48 | path: path.join( dist, '[name].json' ),
49 | name: '[name]',
50 | } ),
51 |
52 | /**
53 | * Create global constants which can be configured at compile time.
54 | *
55 | * Useful for allowing different behaviour between development builds and
56 | * release builds
57 | *
58 | * NODE_ENV should be production so that modules do not perform certain
59 | * development checks
60 | */
61 | new webpack.EnvironmentPlugin( {
62 | NODE_ENV: 'development',
63 | } ),
64 |
65 | new webpack.LoaderOptionsPlugin( {
66 | debug: true,
67 | options: {
68 | context: path.join( __dirname, '..', 'app' ),
69 | output: {
70 | path: path.join( __dirname, '..', 'dll' ),
71 | },
72 | },
73 | } ),
74 | ],
75 | } );
76 |
--------------------------------------------------------------------------------
/configs/webpack.config.web-preload.prod.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack config for production electron main process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import { merge } from 'webpack-merge';
8 | import TerserPlugin from 'terser-webpack-plugin';
9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
10 |
11 | import baseConfig from './webpack.config.base';
12 | import CheckNodeEnvironment from '../internals/scripts/CheckNodeEnv';
13 |
14 | CheckNodeEnvironment( 'production' );
15 |
16 | export default merge( baseConfig, {
17 | devtool: 'source-map',
18 |
19 | mode: 'development',
20 |
21 | target: 'electron-renderer',
22 |
23 | entry: './app/webPreload.ts',
24 |
25 | output: {
26 | path: path.join( __dirname, '..' ),
27 | filename: './app/webPreload.prod.js',
28 | },
29 |
30 | optimization: {
31 | minimizer: process.env.E2E_BUILD
32 | ? []
33 | : [
34 | new TerserPlugin( {
35 | parallel: true,
36 | sourceMap: true,
37 | cache: true,
38 | } ),
39 | ],
40 | },
41 |
42 | plugins: [
43 | new BundleAnalyzerPlugin( {
44 | analyzerMode:
45 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
46 | openAnalyzer: process.env.OPEN_ANALYZER === 'true',
47 | } ),
48 |
49 | /**
50 | * Create global constants which can be configured at compile time.
51 | *
52 | * Useful for allowing different behaviour between development builds and
53 | * release builds
54 | *
55 | * NODE_ENV should be production so that modules do not perform certain
56 | * development checks
57 | */
58 | new webpack.EnvironmentPlugin( {
59 | NODE_ENV: 'production',
60 | DEBUG_PROD: false,
61 | START_MINIMIZED: false,
62 | } ),
63 | ],
64 |
65 | /**
66 | * Disables webpack processing of __dirname and __filename.
67 | * If you run the bundle in node.js it falls back to these values of node.js.
68 | * https://github.com/webpack/webpack/issues/2010
69 | */
70 | node: {
71 | __dirname: false,
72 | __filename: false,
73 | },
74 | } );
75 |
--------------------------------------------------------------------------------
/install-libs.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const { spawn } = require( 'child_process' );
3 |
4 | const environment = process.env.NODE_ENV || 'production';
5 |
6 | const isRunningDevelopment = environment.startsWith( 'dev' );
7 |
8 | if ( isRunningDevelopment ) {
9 | spawn( 'yarn', ['run', 'install-mock'], {
10 | shell: true,
11 | env: process.env,
12 | stdio: 'inherit'
13 | } );
14 | }
15 |
--------------------------------------------------------------------------------
/internals/img/eslint-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/eslint-padded-90.png
--------------------------------------------------------------------------------
/internals/img/eslint-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/eslint-padded.png
--------------------------------------------------------------------------------
/internals/img/eslint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/eslint.png
--------------------------------------------------------------------------------
/internals/img/flow-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/flow-padded-90.png
--------------------------------------------------------------------------------
/internals/img/flow-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/flow-padded.png
--------------------------------------------------------------------------------
/internals/img/flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/flow.png
--------------------------------------------------------------------------------
/internals/img/jest-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/jest-padded-90.png
--------------------------------------------------------------------------------
/internals/img/jest-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/jest-padded.png
--------------------------------------------------------------------------------
/internals/img/jest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/jest.png
--------------------------------------------------------------------------------
/internals/img/js-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/js-padded.png
--------------------------------------------------------------------------------
/internals/img/js.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/js.png
--------------------------------------------------------------------------------
/internals/img/npm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/npm.png
--------------------------------------------------------------------------------
/internals/img/react-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/react-padded-90.png
--------------------------------------------------------------------------------
/internals/img/react-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/react-padded.png
--------------------------------------------------------------------------------
/internals/img/react-router-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/react-router-padded-90.png
--------------------------------------------------------------------------------
/internals/img/react-router-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/react-router-padded.png
--------------------------------------------------------------------------------
/internals/img/react-router.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/react-router.png
--------------------------------------------------------------------------------
/internals/img/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/react.png
--------------------------------------------------------------------------------
/internals/img/redux-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/redux-padded-90.png
--------------------------------------------------------------------------------
/internals/img/redux-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/redux-padded.png
--------------------------------------------------------------------------------
/internals/img/redux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/redux.png
--------------------------------------------------------------------------------
/internals/img/webpack-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/webpack-padded-90.png
--------------------------------------------------------------------------------
/internals/img/webpack-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/webpack-padded.png
--------------------------------------------------------------------------------
/internals/img/webpack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/webpack.png
--------------------------------------------------------------------------------
/internals/img/yarn-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/yarn-padded-90.png
--------------------------------------------------------------------------------
/internals/img/yarn-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/yarn-padded.png
--------------------------------------------------------------------------------
/internals/img/yarn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/img/yarn.png
--------------------------------------------------------------------------------
/internals/mocks/fileMock.ts:
--------------------------------------------------------------------------------
1 | export default 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/internals/scripts/CheckBuiltsExist.ts:
--------------------------------------------------------------------------------
1 | // Check if the renderer and main bundles are built
2 | import path from 'path';
3 | import chalk from 'chalk';
4 | import fs from 'fs';
5 |
6 | function CheckBuildsExist() {
7 | const mainPath = path.join( __dirname, '..', '..', 'app', 'main.prod.js' );
8 | const rendererPath = path.join(
9 | __dirname,
10 | '..',
11 | '..',
12 | 'app',
13 | 'dist',
14 | 'renderer.prod.js'
15 | );
16 |
17 | if ( !fs.existsSync( mainPath ) ) {
18 | throw new Error(
19 | chalk.whiteBright.bgRed.bold(
20 | 'The main process is not built yet. Build it by running "yarn build-main"'
21 | )
22 | );
23 | }
24 |
25 | if ( !fs.existsSync( rendererPath ) ) {
26 | throw new Error(
27 | chalk.whiteBright.bgRed.bold(
28 | 'The renderer process is not built yet. Build it by running "yarn build-renderer"'
29 | )
30 | );
31 | }
32 | }
33 |
34 | CheckBuildsExist();
35 |
--------------------------------------------------------------------------------
/internals/scripts/CheckNodeEnv.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 |
3 | // eslint-disable-next-line import/no-default-export
4 | export default function CheckNodeEnvironment( expectedEnvironment ) {
5 | if ( !expectedEnvironment ) {
6 | throw new Error( '"expectedEnv" not set' );
7 | }
8 |
9 | if ( process.env.NODE_ENV !== expectedEnvironment ) {
10 | // eslint-disable-next-line no-console
11 | console.log(
12 | chalk.whiteBright.bgRed.bold(
13 | `"process.env.NODE_ENV" must be "${expectedEnvironment}" to use this webpack config`
14 | )
15 | );
16 | // eslint-disable-next-line unicorn/no-process-exit
17 | process.exit( 2 );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/internals/scripts/CheckPortInUse.ts:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import detectPort from 'detect-port';
3 |
4 | ( function CheckPortInUse() {
5 | const port = process.env.PORT || '1212';
6 |
7 | detectPort( port, ( error, availablePort ) => {
8 | if ( port !== String( availablePort ) ) {
9 | throw new Error(
10 | chalk.whiteBright.bgRed.bold(
11 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 yarn dev`
12 | )
13 | );
14 | } else {
15 | // eslint-disable-next-line unicorn/no-process-exit
16 | process.exit( 0 );
17 | }
18 | } );
19 | } )();
20 |
--------------------------------------------------------------------------------
/internals/testsite/css/style.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | font-weight: bold;
3 | font-size: 48px;
4 | font-style: italic;
5 | }
6 |
--------------------------------------------------------------------------------
/internals/testsite/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/internals/testsite/img/logo.png
--------------------------------------------------------------------------------
/internals/testsite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TestSite
6 |
7 |
8 |
9 | This is a test site. And it has changed.
10 |
11 | truth?
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/internals/testsite/sub/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TestSite
6 |
7 |
8 |
9 | This is a sub test site. And it has changed.
10 |
11 | truth?
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testURL: 'http://localhost/',
3 | verbose: true,
4 | moduleFileExtensions: ['js', 'jsx', 'json', 'ts', 'tsx', 'node'],
5 | setupFiles: ['raf/polyfill', '/tests_setup.js'],
6 | testPathIgnorePatterns: ['node_modules'],
7 | moduleDirectories: ['app', 'test', 'node_modules'],
8 | moduleNameMapper: {
9 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
10 | '/mocks/fileMock.ts',
11 | '\\.(css|less|scss)$': '/mocks/fileMock.ts'
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/mac.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/mac.zip
--------------------------------------------------------------------------------
/mocks/fileMock.ts:
--------------------------------------------------------------------------------
1 | const fileStub = 'test-file-stub';
2 |
3 | // eslint-disable-next-line import/no-default-export
4 | export default fileStub;
5 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [require( 'precss' ), require( 'autoprefixer' )]
3 | };
4 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base"],
3 | "rangeStrategy": "bump",
4 | "baseBranches": ["next"],
5 | "automerge": true,
6 | "major": {
7 | "automerge": false
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/resources/Electron Helper.safe_core.config:
--------------------------------------------------------------------------------
1 | {
2 | "dev": null
3 | }
--------------------------------------------------------------------------------
/resources/crust.config:
--------------------------------------------------------------------------------
1 | {
2 | "hard_coded_contacts": [
3 | "178.62.76.8:5483",
4 | "138.68.185.218:5483",
5 | "138.68.181.57:5483",
6 | "138.68.181.60:5483",
7 | "138.68.181.86:5483",
8 | "138.68.181.87:5483",
9 | "138.68.181.168:5483",
10 | "138.68.181.176:5483",
11 | "138.68.181.179:5483",
12 | "138.68.181.180:5483",
13 | "138.68.181.182:5483",
14 | "138.68.181.242:5483",
15 | "138.68.181.243:5483",
16 | "138.68.181.249:5483",
17 | "138.68.189.14:5483",
18 | "138.68.189.15:5483",
19 | "138.68.189.17:5483",
20 | "138.68.189.18:5483",
21 | "138.68.189.19:5483",
22 | "138.68.189.31:5483",
23 | "138.68.189.34:5483",
24 | "138.68.189.36:5483",
25 | "138.68.189.38:5483",
26 | "138.68.189.39:5483",
27 | "46.101.5.179:5483"
28 | ],
29 | "whitelisted_node_ips": null,
30 | "whitelisted_client_ips": null,
31 | "tcp_acceptor_port": 5483,
32 | "service_discovery_port": null,
33 | "bootstrap_cache_name": null,
34 | "force_acceptor_port_in_ext_ep": false,
35 | "network_name": "alpha_2",
36 | "dev": null
37 | }
38 |
--------------------------------------------------------------------------------
/resources/entitlements.mac.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.cs.allow-jit
6 |
7 | com.apple.security.cs.allow-unsigned-executable-memory
8 |
9 | com.apple.security.cs.allow-dyld-environment-variables
10 |
11 | com.apple.security.device.audio-input
12 |
13 | com.apple.security.device.camera
14 |
15 | com.apple.security.files.user-selected.read-write
16 |
17 | com.apple.security.network.client
18 |
19 | com.apple.security.network.server
20 |
21 | com.apple.security.files.user-selected.read-only
22 |
23 | com.apple.security.cs.disable-library-validation
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/resources/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/favicon.ico
--------------------------------------------------------------------------------
/resources/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icon.icns
--------------------------------------------------------------------------------
/resources/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icon.ico
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icon.png
--------------------------------------------------------------------------------
/resources/icons/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icons/1024x1024.png
--------------------------------------------------------------------------------
/resources/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icons/128x128.png
--------------------------------------------------------------------------------
/resources/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icons/16x16.png
--------------------------------------------------------------------------------
/resources/icons/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icons/24x24.png
--------------------------------------------------------------------------------
/resources/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icons/256x256.png
--------------------------------------------------------------------------------
/resources/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icons/32x32.png
--------------------------------------------------------------------------------
/resources/icons/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icons/48x48.png
--------------------------------------------------------------------------------
/resources/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icons/512x512.png
--------------------------------------------------------------------------------
/resources/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icons/64x64.png
--------------------------------------------------------------------------------
/resources/icons/96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icons/96x96.png
--------------------------------------------------------------------------------
/resources/icons/heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/icons/heart.png
--------------------------------------------------------------------------------
/resources/icons/make-sizes.sh:
--------------------------------------------------------------------------------
1 | sips -z 16 16 1024x1024.png --out 16x16.png
2 | sips -z 24 24 1024x1024.png --out 24x24.png
3 | sips -z 32 32 1024x1024.png --out 32x32.png
4 | sips -z 48 48 1024x1024.png --out 48x48.png
5 | sips -z 64 64 1024x1024.png --out 64x64.png
6 | sips -z 96 96 1024x1024.png --out 96x96.png
7 | sips -z 128 128 1024x1024.png --out 128x128.png
8 | sips -z 256 256 1024x1024.png --out 256x256.png
9 | sips -z 512 512 1024x1024.png --out 512x512.png
--------------------------------------------------------------------------------
/resources/locales/en.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/resources/readme/experiments-toggle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/readme/experiments-toggle.png
--------------------------------------------------------------------------------
/resources/readme/experiments-visual-indicator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/readme/experiments-visual-indicator.png
--------------------------------------------------------------------------------
/resources/readme/md-viewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/readme/md-viewer.png
--------------------------------------------------------------------------------
/resources/readme/mock-visual-indicator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/readme/mock-visual-indicator.png
--------------------------------------------------------------------------------
/resources/readme/webid-selector.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/readme/webid-selector.png
--------------------------------------------------------------------------------
/resources/readme/xorurl-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/readme/xorurl-screenshot.png
--------------------------------------------------------------------------------
/resources/safeicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maidsafe/sn_browser/9621ae96ca85d127406a1cc7da667f2034b85321/resources/safeicon.png
--------------------------------------------------------------------------------
/tests_setup.js:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | Enzyme.configure( { adapter: new Adapter() } );
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "moduleResolution": "node",
5 | "target": "esnext",
6 | "allowJs": true,
7 | "noEmit": true,
8 | "jsx": "react",
9 | "resolveJsonModule": true,
10 | "esModuleInterop": true,
11 | "paths": {
12 | "*": ["app/definitions"],
13 | "$App/*": ["app/*"],
14 | "$Tests/*": ["__tests__/*"],
15 | "$BuilderConfig": ["builderConfig.js"],
16 | "$Actions/*": ["app/actions/*"],
17 | "$Store/*": ["app/store/*"],
18 | "$Extensions/*": ["app/extensions/*"],
19 | "$Reducers/*": ["app/reducers/*"],
20 | "$Logger": ["app/logger.ts"],
21 | "$Components/*": ["app/components/*"],
22 | "$Constants": ["app/constants.ts"],
23 | "$Utils/*": ["app/utils/*"],
24 | "$Package": ["package.json"],
25 | "$TestCafeHelpers": ["__testcafe__/helpers.js"]
26 | }
27 | },
28 | "exclude": ["app/*.prod.js", "app/dist", "node_modules", "release"]
29 | }
30 |
--------------------------------------------------------------------------------