├── .github
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE
│ ├── BUG_REPORT.md
│ └── FEATURE_REQUEST.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── README.md
├── banner.png
├── docs
├── README.md
├── Theme.md
└── images
│ └── browser-theme-protocol-example.png
├── package-lock.json
├── package.json
├── public
├── icon.icns
└── icon.png
└── src
├── actions.js
├── auto-updater.js
├── context-menu.js
├── find-menu.js
├── main.js
├── pages
├── 404.html
├── about.html
├── clock.js
├── home.html
├── index.html
├── nav-box.js
├── p2p
│ ├── chat
│ │ ├── app.js
│ │ ├── index.html
│ │ ├── send.svg
│ │ └── styles.css
│ ├── editor
│ │ ├── codeEditor.js
│ │ ├── common.js
│ │ ├── dweb.js
│ │ ├── index.html
│ │ └── styles.css
│ ├── upload
│ │ └── index.html
│ └── wiki
│ │ ├── index.html
│ │ ├── script.js
│ │ └── static
│ │ ├── assets
│ │ ├── favicon.ico
│ │ └── wikipedia-on-ipfs.png
│ │ └── styles.css
├── peer-bar.js
├── plan1.html
├── preload.js
├── static
│ ├── assets
│ │ ├── logo.png
│ │ ├── redwoods.jpg
│ │ └── svg
│ │ │ ├── build.svg
│ │ │ ├── chat.svg
│ │ │ ├── close.svg
│ │ │ ├── down.svg
│ │ │ ├── home.svg
│ │ │ ├── left.svg
│ │ │ ├── people.svg
│ │ │ ├── plus.svg
│ │ │ ├── reload.svg
│ │ │ ├── right.svg
│ │ │ ├── up.svg
│ │ │ ├── upload.svg
│ │ │ └── wikipedia.svg
│ ├── elves
│ │ ├── elf.js
│ │ ├── goodbye-world.js
│ │ ├── hello-world.js
│ │ └── qr-code.js
│ └── js
│ │ └── vendor
│ │ └── qr-creator
│ │ ├── LICENSE
│ │ └── qr-creator.js
├── theme
│ ├── base.css
│ ├── home.css
│ ├── index.css
│ ├── plan1.css
│ ├── style.css
│ └── vars.css
└── track-box.js
├── protocols
├── config.js
├── helia
│ ├── directoryListingTemplate.js
│ ├── helia.js
│ └── libp2p.js
├── hyper-handler.js
├── ipfs-handler.js
├── peersky-protocol.js
├── theme-handler.js
└── web3-handler.js
├── renderer.js
├── utils.js
└── window-manager.js
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at contact@p2plabs.xyz.
63 | All complaints will be reviewed and investigated promptly and fairly.
64 |
65 | All community leaders are obligated to respect the privacy and security of the
66 | reporter of any incident.
67 |
68 | ## Enforcement Guidelines
69 |
70 | Community leaders will follow these Community Impact Guidelines in determining
71 | the consequences for any action they deem in violation of this Code of Conduct:
72 |
73 | ### 1. Correction
74 |
75 | **Community Impact**: Use of inappropriate language or other behavior deemed
76 | unprofessional or unwelcome in the community.
77 |
78 | **Consequence**: A private, written warning from community leaders, providing
79 | clarity around the nature of the violation and an explanation of why the
80 | behavior was inappropriate. A public apology may be requested.
81 |
82 | ### 2. Warning
83 |
84 | **Community Impact**: A violation through a single incident or series
85 | of actions.
86 |
87 | **Consequence**: A warning with consequences for continued behavior. No
88 | interaction with the people involved, including unsolicited interaction with
89 | those enforcing the Code of Conduct, for a specified period of time. This
90 | includes avoiding interactions in community spaces as well as external channels
91 | like social media. Violating these terms may lead to a temporary or
92 | permanent ban.
93 |
94 | ### 3. Temporary Ban
95 |
96 | **Community Impact**: A serious violation of community standards, including
97 | sustained inappropriate behavior.
98 |
99 | **Consequence**: A temporary ban from any sort of interaction or public
100 | communication with the community for a specified period of time. No public or
101 | private interaction with the people involved, including unsolicited interaction
102 | with those enforcing the Code of Conduct, is allowed during this period.
103 | Violating these terms may lead to a permanent ban.
104 |
105 | ### 4. Permanent Ban
106 |
107 | **Community Impact**: Demonstrating a pattern of violation of community
108 | standards, including sustained inappropriate behavior, harassment of an
109 | individual, or aggression toward or disparagement of classes of individuals.
110 |
111 | **Consequence**: A permanent ban from any sort of public interaction within
112 | the community.
113 |
114 | ## Attribution
115 |
116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
117 | version 2.0, available at
118 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
119 |
120 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
121 | enforcement ladder](https://github.com/mozilla/diversity).
122 |
123 | [homepage]: https://www.contributor-covenant.org
124 |
125 | For answers to common questions about this code of conduct, see the FAQ at
126 | https://www.contributor-covenant.org/faq. Translations are available at
127 | https://www.contributor-covenant.org/translations.
128 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | > Contributions are always welcome!
4 |
5 | ## Issues
6 |
7 | * Do not hesitate and [create a new Issue](https://github.com/p2plabsxyz/peersky-browser/issues/new/choose) if you see a bug, room for improvement or simply have a question.
8 | * Feel free to work on issues that are [not assigned yet](https://github.com/p2plabsxyz/peersky-browser/issues?utf8=✓&q=is%3Aissue+is%3Aopen+no%3Aassignee).
9 | * Do not create a pull request without an issue before discussing the problem.
10 |
11 | ## Pull Requests
12 |
13 | * Make sure your PR comes with its own tests.
14 | * Always be descriptive in your PR -> add screenshots, explain in detail what improvements you did, or bugs you solved.
15 |
16 | ## Commits
17 |
18 | * Your commit messages "should" follow the conventional commits guidelines. Learn more about it [here](https://www.conventionalcommits.org/en/v1.0.0/).
19 |
20 | ## Code Style
21 |
22 | * Make sure to commit in the same style that we are committing until now on the project.
23 | * Run `prettier` in each code file.
24 |
25 | ## Questions
26 | * Please reach out to us at contact@p2plabs.xyz.
27 |
28 | *Hope to see your username on our list of [contributors](https://github.com/p2plabsxyz/peersky-browser/graphs/contributors) 🎉*
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG_REPORT.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Issue Title
3 | about: Create a bug-report to help us address errors in the repo.
4 | name: Bug
5 | label: Bug
6 | Assignee: ''
7 |
8 | ---
9 |
10 | Define You:
11 |
12 | - [ ] Contributor
13 |
14 |
15 | **Describe the Bug**
16 |
17 |
18 |
19 | **Steps to Reproduce**
20 |
21 | Steps to reproduce the behavior:
22 |
23 | 1.
24 | 2.
25 | 3.
26 | 4.
27 |
28 | **Expected Behavior**
29 |
30 |
31 |
32 | **Actual Behavior**
33 |
34 |
35 |
36 | **Screenshots**
37 |
38 |
39 |
40 | **Additional Details**
41 |
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Issue Title
3 | about: Suggest an idea for this project
4 | name: Feature Request
5 | label: Feature Request
6 | Assignee: ''
7 |
8 | ---
9 |
10 | Define You:
11 |
12 | - [ ] Contributor
13 |
14 |
15 | **Is your feature request related to a problem? Please describe.**
16 |
17 |
18 |
19 | **Describe the solution you'd like...**
20 |
21 |
22 |
23 | **Describe alternatives you've considered?**
24 |
25 |
26 |
27 | **Approach to be followed (optional):**
28 |
29 |
30 |
31 | **Additional context**
32 |
33 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Related Issue (if any)
2 |
3 |
4 |
5 | Closes: #[issue number that will be closed through this PR]
6 |
7 | ### Describe the add-ons or changes you've made
8 |
9 |
10 |
11 | ## Type of change
12 |
13 |
14 |
18 | - [ ] Bug fix (non-breaking change which fixes an issue)
19 | - [ ] New feature (non-breaking change which adds functionality)
20 | - [ ] Code style update (formatting, local variables)
21 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
22 | - [ ] This change requires a documentation update
23 |
24 | ## How Has This Been Tested?
25 |
26 |
28 |
29 | ## Checklist:
30 |
34 | - [ ] My code follows the "contribution guidelines" of this project.
35 | - [ ] I have performed a self-review of my own code.
36 | - [ ] I have commented my code, particularly wherever it was hard to understand.
37 | - [ ] My changes generate no new warnings.
38 | - [ ] Any dependent changes have been merged and published in downstream modules.
39 |
40 | ## Screenshots (Only for Front End and UI/UX Designers)
41 |
42 | Original | Updated
43 | :--------------------: |:--------------------:
44 | Original Screenshot | Updated Screenshot |
45 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # https://github.com/AgregoreWeb/agregore-browser/blob/master/.github/workflows/build.yml
2 |
3 | name: Build/release
4 |
5 | on:
6 | push:
7 | ## Run on tags starting with `v*`
8 | tags:
9 | - 'v*'
10 |
11 | # Allows you to run this workflow manually from the Actions tab
12 | workflow_dispatch:
13 |
14 | jobs:
15 | release:
16 | continue-on-error: true
17 | runs-on: ${{ matrix.os }}
18 |
19 | strategy:
20 | matrix:
21 | os: [macos-latest, ubuntu-latest, windows-latest]
22 |
23 | steps:
24 | - name: Install libarchive-tools for pacman build # Related https://github.com/electron-userland/electron-builder/issues/4181
25 | if: startsWith(matrix.os, 'ubuntu')
26 | run: sudo apt-get install libarchive-tools
27 |
28 | - name: Check out Git repository
29 | uses: actions/checkout@v3
30 | with:
31 | submodules: true
32 |
33 | - name: Install Node.js, NPM and Yarn
34 | uses: actions/setup-node@v3
35 | with:
36 | node-version: 'lts/*'
37 |
38 | - name: Non-tag specific build step
39 | if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
40 | run: echo "This build was triggered without a tag."
41 |
42 | - name: Build binaries with electron-builder
43 | uses: coparse-inc/action-electron-builder@29a7606c7d726b5b0f4dc2f334026f58bea0e1bb # v1.6.0 but safer than a tag that can be changed
44 | with:
45 | max_attempts: 2
46 | github_token: ${{ secrets.github_token }}
47 | release: ${{ startsWith(github.ref, 'refs/tags/v') }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
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 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 |
110 | # Serverless directories
111 | .serverless/
112 |
113 | # FuseBox cache
114 | .fusebox/
115 |
116 | # DynamoDB Local files
117 | .dynamodb/
118 |
119 | # TernJS port file
120 | .tern-port
121 |
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 |
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 |
132 | # Electron-builder output
133 | build/
134 |
135 | # macOS system files
136 | .DS_Store
137 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 p2plabs.xyz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Peersky Browser
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 💻 [Download](https://peersky.p2plabs.xyz/)
17 |
18 | ## Roadmap
19 |
20 | - [x] Basic browser navigation:
21 |
22 | - [x] Back
23 | - [x] Forward
24 | - [x] Reload
25 | - [x] Browser protocol (peersky://)
26 | - [x] Home page (peersky://home)
27 | - [x] Cross browser themeing ([browser://theme/](https://github.com/p2plabsxyz/peersky-browser/blob/main/docs/Theme.md))
28 | - [x] Search engine
29 | - DuckDuckGo (default)
30 | - Ecosia
31 | - [ ] [Tabs](https://github.com/p2plabsxyz/peersky-browser/issues/11)
32 |
33 | - [x] IPFS protocol handler:
34 |
35 | - [x] Run a local [Helia](https://helia.io/) node
36 | - [x] `ipfs://` / `ipns://` native URLs support
37 | - [x] Directory listings support
38 | - [x] Native ENS domain resolution:
39 | - [x] Resolve `.eth` domains directly to IPFS/IPNS content without centralized gateways (e.g., `ipfs://vitalik.eth`).
40 | - [x] Local caching for resolved ENS content to enhance performance and reduce RPC calls.
41 |
42 | - [x] Hypercore protocol handler:
43 |
44 | - [x] Run a local [hyper](https://holepunch.to/) node
45 | - [x] `hyper://` native URLs support
46 |
47 | - [x] Web3 protocol handler:
48 |
49 | - [x] Run a local [web3 protocol](https://docs.web3url.io/) node
50 | - [x] Access on-chain websites.
51 | - [x] Fetch data from smart contracts using auto, manual, and resource request resolve modes.
52 | - [x] Query account balances or other data directly from smart contracts.
53 |
54 | - [x] P2P Applications:
55 |
56 | - [x] `peersky://p2p/chat/`
57 | - Peer-to-peer messaging over Hyper
58 | - [x] `peersky://p2p/upload/`
59 | - Decentralized file storage
60 | - [x] `peersky://p2p/editor/`
61 | - Build and publish websites
62 | - [x] `peersky://p2p/wiki/`
63 | - Browse Wikipedia over IPFS
64 | - [x] [reader.p2plabs.xyz](https://reader.distributed.press/)
65 | - A p2p offline ActivityPub client for reading and following microblogs on the fediverse.
66 |
67 | - [x] Electron’s Auto-updater:
68 |
69 | - [x] Download and install the latest release from Github automatically
70 |
71 | - [x] Context menu:
72 |
73 | - [x] Back / Forward
74 | - [x] Reload
75 | - [x] Inspect
76 | - [x] Undo / Redo
77 | - [x] Cut / Copy / Paste
78 | - [x] Copy Link Address
79 | - [x] Open Link in New Tab
80 |
81 | - [x] Find in page:
82 | - [x] Search for text within a document or web page
83 |
84 | - [x] Window state persistence:
85 | - [x] Save and restore open windows on app launch
86 |
87 | - [x] Keyboard shortcuts:
88 |
89 | - [x] New Window: `CommandOrControl+N`
90 | - [x] Back: `CommandOrControl+[`
91 | - [x] Forward: `CommandOrControl+]`
92 | - [x] Reload: `CommandOrControl+R`
93 | - [x] Find in Page: `CommandOrControl+F`
94 | - [x] Open Dev Tools: `CommandOrControl+Shift+I`
95 | - [x] Focus URL Bar: `CommandOrControl+L`
96 | - [x] Minimize: `CommandOrControl+M`
97 | - [x] Close: `CommandOrControl+W`
98 | - [x] Toggle Full Screen: `F11`
99 |
100 | - [ ] Settings (peersky://settings):
101 |
102 | - [ ] Switch search engines
103 | - [ ] Set custom home page wallpapers
104 | - [ ] Hide/show the home page clock
105 | - [ ] Change themes
106 | - [ ] Clear browser cache
107 |
108 | - [ ] [Web extensions](https://github.com/p2plabsxyz/peersky-browser/issues/19):
109 | - [ ] Ability to add and manage extensions
110 | - [ ] Default extensions
111 | - [Ad-blocker](https://github.com/gorhill/uBlock)
112 | - [DScan](https://github.com/p2plabsxyz/dscan)
113 | - ..
114 |
115 | - [ ] History (peersky://history):
116 |
117 | - [ ] Suggestions based on the browser history when typing in URL prompt
118 |
119 | - [ ] QR Code generator:
120 |
121 | - [ ] Option to generate QR Code for every page in the URL prompt
122 |
123 | - [ ] Bookmarks (peersky://bookmarks):
124 |
125 | - [ ] Option to add favourite pages in the nav bar (peersky://bookmarks)
126 |
127 | - [ ] Archive (peersky://archive):
128 |
129 | - [ ] List and showcase published content from `peersky://p2p/` apps for enhanced discoverability.
130 | - [ ] Provide metadata (e.g., creation date, content type) to improve navigation and usability.
131 | - [ ] Ability to download all the hashes of published data in a .json file.
132 |
133 | ## Development
134 |
135 | ### Node.js and npm Setup
136 |
137 | Please refer to the [Node.js official documentation](https://nodejs.org/) to install Node.js. Once installed, npm (Node Package Manager) will be available, allowing you to run commands like `npx` and `npm`.
138 |
139 | - **npm**: Comes bundled with Node.js. Verify installation by running:
140 | ```bash
141 | node -v
142 | npm -v
143 | ```
144 |
145 | ### Install dependencies
146 |
147 | ```bash
148 | npm install
149 | ```
150 |
151 | ### Start the app
152 |
153 | ```bash
154 | npm start
155 | ```
156 |
157 | ### Build
158 | After development of the browser, run the following command. This will create a production build.
159 |
160 | ```bash
161 | npm run build
162 | # For Intel and M1 macs
163 | ```
164 |
165 | Now, the `dist` folder will appear in the root directory.
166 |
167 | ```bash
168 | npm run build-all
169 | # For macOS, Linux, and Windows
170 | ```
171 |
172 | ## Contribute
173 |
174 | - Thanks for your interest in contributing to Peersky Browser. There are many ways you can contribute to the project.
175 | - To start, take a few minutes to read the "[contribution guide](https://github.com/p2plabsxyz/peersky-browser/blob/main/.github/CONTRIBUTING.md)".
176 | - We look forward to your [pull requests](https://github.com/p2plabsxyz/peersky-browser/pulls) and / or involvement in our [issues page](https://github.com/p2plabsxyz/peersky-browser/issues).
177 |
178 | ## License
179 |
180 | Peersky Browser is licensed under the [MIT License](https://github.com/p2plabsxyz/peersky-browser/blob/main/LICENSE).
181 |
--------------------------------------------------------------------------------
/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p2plabsxyz/peersky-browser/884f0e70e38540494e2d541ea092a19fe4622239/banner.png
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Peersky Browser Documentation
2 |
3 | - [Theme Protocol](Theme.md)
--------------------------------------------------------------------------------
/docs/Theme.md:
--------------------------------------------------------------------------------
1 | # Theme Protocol (`browser://theme/`)
2 |
3 | ## Overview
4 |
5 | The `browser://theme/` protocol provides a standardized way for web applications to access browser-level CSS styles and theme variables in [Peersky](https://peersky.p2plabs.xyz/) and other compatible browsers, such as [Agregore](https://agregore.mauve.moe/). This protocol ensures consistent theming across different browsers by serving CSS files with a common set of variables. It allows developers to build applications that adapt to the browser's theme without needing browser-specific code.
6 |
7 | 
8 |
9 | ## Purpose
10 |
11 | The goal of the `browser://theme/` protocol is to:
12 |
13 | - Enable cross-browser compatibility for theming in any browser, including p2p browsers like Peersky and Agregore.
14 | - Provide a unified set of theme variables using standardized `--browser-theme-` prefixes.
15 | - Allow web applications to import styles or variables without hardcoding browser-specific protocols (e.g., `peersky://` or `agregore://`).
16 |
17 | ## Implementation
18 |
19 | ### Protocol Handler
20 |
21 | The `browser://theme/` protocol is implemented in Peersky via a custom Electron protocol handler (`theme-handler.js`). It serves CSS files from the `src/pages/theme/` directory when requests are made to URLs like `browser://theme/vars.css` or `browser://theme/style.css`.
22 |
23 | - **Location**: Files are stored in `src/pages/theme/` (e.g., `vars.css`, `style.css`, `base.css`, `index.css`).
24 | - **URL Structure**: Requests to `browser://theme/` map to `src/pages/theme/`.
25 | - **Example**: `browser://theme/vars.css` serves `src/pages/theme/vars.css`.
26 |
27 | ### Theme Variable Standardization
28 |
29 | The `browser://theme/` protocol provides standardized theme variables prefixed with `--browser-theme-`, such as `--browser-theme-font-family`, `--browser-theme-background`, `--browser-theme-text-color`, `--browser-theme-primary-highlight`, and `--browser-theme-secondary-highlight`. These variables allow web applications to adapt to the host browser's theme without needing browser-specific code.
30 |
31 | Each browser implements these standardized variables by mapping them to their internal theme variables. For example:
32 |
33 | - In Peersky, `--browser-theme-background` is mapped to `--base01`, which is part of the Base16 color palette [Base16 Framework](https://github.com/chriskempson/base16).
34 | - In Agregore, `--browser-theme-background` is mapped to `--ag-theme-background`, which is defined in Agregore's theme configuration.
35 |
36 | This ensures that applications built for one browser can work seamlessly in another, as long as they use the standardized `--browser-theme-` variables.
37 |
38 | ### Cross-Browser Compatibility
39 |
40 | The `browser://theme/` protocol enables apps built for one browser to work seamlessly in another by providing standardized theme variables prefixed with `--browser-theme-`. These variables are mapped to each browser's internal theme variables, ensuring consistent theming across different browsers.
41 |
42 | For example:
43 |
44 | - In Peersky, `--browser-theme-background` is mapped to `--base01`, which is part of the Base16 color palette.
45 | - In Agregore, `--browser-theme-background` is mapped to `--ag-theme-background`, which is defined in Agregore's theme configuration.
46 |
47 | As a result, an app using `--browser-theme-background` will render with the appropriate background color for each browser, whether it's based on Base16 (as in Peersky) or another theme system (as in Agregore).
48 |
49 | Additionally, apps can use the full set of variables provided by each browser for more advanced theming, but for cross-browser compatibility, it's recommended to use the standardized `--browser-theme-` variables.
50 |
51 | ## Usage
52 |
53 | ### Importing Theme Styles
54 |
55 | Web applications can import theme styles or variables using `
67 | ```
68 |
69 | - **Import Default Styles**:
70 |
71 | ```html
72 |
73 | ```
74 |
75 | - **Use Browser-Specific Variables** (for Agregore apps in Peersky):
76 | ```html
77 |
85 | ```
86 |
87 | ## Theme Files (`browser://theme/`)
88 |
89 | - `vars.css`: Defines standardized `--browser-theme-`, Base16, and Peersky-specific CSS variables for theming.
90 | - `base.css`: Applies minimal default styles for unstyled pages, auto-injected by preload.
91 | - `style.css`: Opt-in comprehensive styling for web apps
92 | - `index.css`: Styles Peersky’s browser UI (e.g., navigation bar, URL input).
93 | - `home.css`: Styles Peersky’s home page with a background image and sidebar.
94 |
--------------------------------------------------------------------------------
/docs/images/browser-theme-protocol-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p2plabsxyz/peersky-browser/884f0e70e38540494e2d541ea092a19fe4622239/docs/images/browser-theme-protocol-example.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peersky-browser",
3 | "version": "1.0.0-beta.4",
4 | "description": "A minimal local-first p2p web browser: access, communicate, and publish offline.",
5 | "keywords": [
6 | "peersky",
7 | "ipfs",
8 | "hyper",
9 | "web3",
10 | "browser",
11 | "dweb"
12 | ],
13 | "license": "MIT",
14 | "author": "Akhilesh Thite (https://akhilesh.art/)",
15 | "main": "src/main.js",
16 | "type": "module",
17 | "repository": "https://github.com/p2plabsxyz/peersky-browser",
18 | "scripts": {
19 | "test": "echo \"Error: no test specified\" && exit 1",
20 | "start": "electron .",
21 | "build": "electron-builder build --publish never",
22 | "build-all": "electron-builder build -mwl",
23 | "postinstall": "electron-builder install-app-deps"
24 | },
25 | "build": {
26 | "buildDependenciesFromSource": true,
27 | "npmRebuild": true,
28 | "asar": true,
29 | "appId": "peersky.p2plabs.xyz",
30 | "productName": "Peersky Browser",
31 | "directories": {
32 | "output": "dist"
33 | },
34 | "files": [
35 | "node_modules/**/*",
36 | "package.json",
37 | "public/*",
38 | "src/**/*",
39 | "src/*"
40 | ],
41 | "mac": {
42 | "artifactName": "${name}-${version}-${os}-${arch}.${ext}",
43 | "gatekeeperAssess": false,
44 | "target": [
45 | {
46 | "target": "default",
47 | "arch": [
48 | "x64",
49 | "arm64"
50 | ]
51 | }
52 | ],
53 | "icon": "./public/icon.icns"
54 | },
55 | "win": {
56 | "target": [
57 | "nsis",
58 | "portable"
59 | ],
60 | "icon": "./public/icon.png"
61 | },
62 | "linux": {
63 | "artifactName": "${name}-${version}-${os}-${arch}.${ext}",
64 | "executableArgs": [
65 | "--enable-accelerated-video"
66 | ],
67 | "target": [
68 | "deb",
69 | "AppImage",
70 | "apk",
71 | "pacman"
72 | ],
73 | "category": "Network;WebBrowser"
74 | },
75 | "protocols": [
76 | {
77 | "name": "webpages",
78 | "schemes": [
79 | "http",
80 | "https"
81 | ],
82 | "role": "Viewer"
83 | },
84 | {
85 | "name": "ipfs",
86 | "schemes": [
87 | "ipfs",
88 | "ipns",
89 | "ipld"
90 | ],
91 | "role": "Viewer"
92 | },
93 | {
94 | "name": "hyper",
95 | "schemes": [
96 | "hyper",
97 | "dat"
98 | ],
99 | "role": "Viewer"
100 | },
101 | {
102 | "name": "web3",
103 | "schemes": [
104 | "web3"
105 | ],
106 | "role": "Viewer"
107 | }
108 | ],
109 | "publish": [
110 | {
111 | "provider": "github",
112 | "owner": "p2plabsxyz",
113 | "repo": "peersky-browser",
114 | "releaseType": "release"
115 | }
116 | ]
117 | },
118 | "dependencies": {
119 | "@chainsafe/libp2p-gossipsub": "^13.0.0",
120 | "@chainsafe/libp2p-noise": "^15.0.0",
121 | "@chainsafe/libp2p-yamux": "^6.0.2",
122 | "@helia/ipns": "^8.1.0",
123 | "@helia/unixfs": "^3.0.6",
124 | "@libp2p/bootstrap": "^10.0.24",
125 | "@libp2p/circuit-relay-v2": "^3.1.12",
126 | "@libp2p/identify": "^3.0.18",
127 | "@libp2p/kad-dht": "^12.0.17",
128 | "@libp2p/mdns": "^11.0.8",
129 | "@libp2p/mplex": "^10.0.24",
130 | "@libp2p/peer-id": "^5.0.8",
131 | "@libp2p/tcp": "^9.0.26",
132 | "@libp2p/webrtc": "^5.0.23",
133 | "@libp2p/websockets": "^8.0.24",
134 | "b4a": "^1.6.7",
135 | "content-hash": "^2.5.2",
136 | "content-type": "^1.0.5",
137 | "electron-find": "^1.0.7",
138 | "electron-log": "^5.3.0",
139 | "electron-updater": "^6.2.1",
140 | "ethers": "^6.13.4",
141 | "find-process": "^1.4.7",
142 | "fs-extra": "^11.2.0",
143 | "helia": "^5.2.0",
144 | "hyper-sdk": "^5.1.0",
145 | "hypercore-crypto": "^3.4.2",
146 | "hypercore-fetch": "^9.9.1",
147 | "hyperdht": "^6.20.1",
148 | "hyperswarm": "^4.8.4",
149 | "jquery": "^3.7.1",
150 | "libp2p": "^1.6.0",
151 | "libp2p-gossipsub": "^0.13.0",
152 | "mime-types": "^2.1.35",
153 | "multiformats": "^13.3.2",
154 | "node-cache": "^5.1.2",
155 | "scoped-fs": "^1.4.1",
156 | "web3protocol": "^0.6.0"
157 | },
158 | "devDependencies": {
159 | "electron": "^29.0.1",
160 | "electron-builder": "^24.12.0"
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/public/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p2plabsxyz/peersky-browser/884f0e70e38540494e2d541ea092a19fe4622239/public/icon.icns
--------------------------------------------------------------------------------
/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p2plabsxyz/peersky-browser/884f0e70e38540494e2d541ea092a19fe4622239/public/icon.png
--------------------------------------------------------------------------------
/src/actions.js:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow, globalShortcut } from "electron";
2 | import WindowManager from './window-manager.js';
3 |
4 | export function createActions(windowManager) {
5 | const actions = {
6 | OpenDevTools: {
7 | label: "Open Dev Tools",
8 | accelerator: "CommandOrControl+Shift+I",
9 | click: (focusedWindow) => {
10 | if (focusedWindow) {
11 | focusedWindow.webContents.openDevTools({ mode: "detach" });
12 | }
13 | },
14 | },
15 | NewWindow: {
16 | label: "New Window",
17 | accelerator: "CommandOrControl+N",
18 | click: () => {
19 | windowManager.open();
20 | },
21 | },
22 | Forward: {
23 | label: "Forward",
24 | accelerator: "CommandOrControl+]",
25 | click: (focusedWindow) => {
26 | if (focusedWindow) {
27 | focusedWindow.webContents.executeJavaScript(`{
28 | const webview = document.querySelector('webview');
29 | if (webview && webview.canGoForward()) {
30 | webview.goForward();
31 | }
32 | }`);
33 | }
34 | },
35 | },
36 | Back: {
37 | label: "Back",
38 | accelerator: "CommandOrControl+[",
39 | click: (focusedWindow) => {
40 | if (focusedWindow) {
41 | focusedWindow.webContents.executeJavaScript(`{
42 | const webview = document.querySelector('webview');
43 | if (webview && webview.canGoBack()) {
44 | webview.goBack();
45 | }
46 | }`);
47 | }
48 | },
49 | },
50 | FocusURLBar: {
51 | label: "Focus URL Bar",
52 | accelerator: "CommandOrControl+L",
53 | click: (focusedWindow) => {
54 | if (focusedWindow) {
55 | focusedWindow.webContents.executeJavaScript(`
56 | document.getElementById('url').focus();
57 | `);
58 | }
59 | },
60 | },
61 | Reload: {
62 | label: "Reload",
63 | accelerator: "CommandOrControl+R",
64 | click: (focusedWindow) => {
65 | if (focusedWindow) {
66 | focusedWindow.webContents.executeJavaScript(`{
67 | const webview = document.querySelector('webview');
68 | if (webview) {
69 | webview.reload();
70 | }
71 | }`);
72 | }
73 | },
74 | },
75 | Minimize: {
76 | label: "Minimize",
77 | accelerator: "CommandOrControl+M",
78 | click: (focusedWindow) => {
79 | if (focusedWindow) {
80 | focusedWindow.minimize();
81 | }
82 | },
83 | },
84 | Close: {
85 | label: "Close",
86 | accelerator: "CommandOrControl+W",
87 | click: (focusedWindow) => {
88 | if (focusedWindow) {
89 | focusedWindow.close();
90 | }
91 | },
92 | },
93 | FullScreen: {
94 | label: "Toggle Full Screen",
95 | accelerator: "F11",
96 | click: (focusedWindow) => {
97 | if (focusedWindow) {
98 | focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
99 | }
100 | },
101 | },
102 | FindInPage: {
103 | label: "Find in Page",
104 | accelerator: "CommandOrControl+F",
105 | click: (focusedWindow) => {
106 | if (focusedWindow) {
107 | focusedWindow.webContents.executeJavaScript(`
108 | var findMenu = document.querySelector('find-menu');
109 | if (findMenu) {
110 | findMenu.toggle();
111 | setTimeout(() => {
112 | var input = findMenu.querySelector('.find-menu-input');
113 | if (input) {
114 | input.focus();
115 | }
116 | }, 100); // Timeout to ensure the menu is visible and ready to receive focus
117 | }
118 | `);
119 | }
120 | },
121 | },
122 | };
123 |
124 | return actions;
125 | }
126 |
127 | export function registerShortcuts(windowManager) {
128 | const actions = createActions(windowManager);
129 |
130 | const registerFindShortcut = (focusedWindow) => {
131 | if (focusedWindow) {
132 | globalShortcut.register("CommandOrControl+F", () => {
133 | actions.FindInPage.click(focusedWindow);
134 | });
135 | }
136 | };
137 |
138 | const unregisterFindShortcut = () => {
139 | globalShortcut.unregister("CommandOrControl+F");
140 | };
141 |
142 | // Register and unregister `Ctrl+F` based on focus
143 | app.on("browser-window-focus", (event, win) => {
144 | registerFindShortcut(win);
145 | });
146 |
147 | app.on("browser-window-blur", () => {
148 | unregisterFindShortcut();
149 | });
150 |
151 | // Register remaining shortcuts
152 | Object.keys(actions).forEach((key) => {
153 | const action = actions[key];
154 | if (key !== "FindInPage") {
155 | // Register other shortcuts except `Ctrl+F`
156 | globalShortcut.register(action.accelerator, () => {
157 | const focusedWindow = BrowserWindow.getFocusedWindow();
158 | if (focusedWindow) action.click(focusedWindow);
159 | });
160 | }
161 | });
162 | }
163 |
--------------------------------------------------------------------------------
/src/auto-updater.js:
--------------------------------------------------------------------------------
1 | import { app, dialog } from 'electron';
2 | import pkg from 'electron-updater';
3 | const { autoUpdater } = pkg;
4 | import log from 'electron-log';
5 |
6 | // Uncomment while locally testing the AutoUpdater
7 | // Object.defineProperty(app, 'isPackaged', {
8 | // value: true
9 | // });
10 |
11 | function setupAutoUpdater() {
12 | autoUpdater.setFeedURL({
13 | provider: 'github',
14 | repo: 'peersky-browser',
15 | owner: 'p2plabsxyz',
16 | });
17 |
18 | // Allow pre-release updates
19 | autoUpdater.allowPrerelease = true;
20 |
21 | // Configure electron-log
22 | log.transports.file.level = 'info';
23 | log.transports.console.level = 'info';
24 | autoUpdater.logger = log;
25 |
26 | autoUpdater.on('checking-for-update', () => {
27 | log.info('Checking for update...');
28 | });
29 |
30 | autoUpdater.on('update-available', (info) => {
31 | log.info('Update available:', info);
32 | });
33 |
34 | autoUpdater.on('update-not-available', (info) => {
35 | log.info('Update not available:', info);
36 | });
37 |
38 | autoUpdater.on('download-progress', (progressObj) => {
39 | log.info(`Download speed: ${progressObj.bytesPerSecond} - Downloaded ${progressObj.percent}%`);
40 | });
41 |
42 | autoUpdater.on('update-downloaded', (info) => {
43 | const message = `Version ${info.version} has been downloaded. Restart now to install it or select Later to postpone the update.`;
44 | const response = dialog.showMessageBoxSync({
45 | type: 'info',
46 | buttons: ['Restart Now', 'Later'],
47 | title: 'Update Ready',
48 | message: message,
49 | });
50 | if (response === 0) {
51 | autoUpdater.quitAndInstall();
52 | }
53 | });
54 |
55 | autoUpdater.on('error', (error) => {
56 | log.error('Auto-update error:', error);
57 | });
58 |
59 | // Initiate update check after 10 seconds
60 | setTimeout(() => {
61 | autoUpdater.checkForUpdates();
62 | }, 10000);
63 | }
64 |
65 | export { setupAutoUpdater };
66 |
--------------------------------------------------------------------------------
/src/context-menu.js:
--------------------------------------------------------------------------------
1 | import { Menu, MenuItem, clipboard } from "electron";
2 | import WindowManager from "./window-manager.js";
3 |
4 | const isMac = process.platform === "darwin";
5 |
6 | // Ensure a single instance of WindowManager is used
7 | let windowManagerInstance = null;
8 |
9 | export function setWindowManager(instance) {
10 | windowManagerInstance = instance;
11 | }
12 |
13 | export function attachContextMenus(browserWindow, windowManager) {
14 | // Assign the WindowManager instance if not already set
15 | if (!windowManagerInstance) {
16 | windowManagerInstance = windowManager;
17 | }
18 |
19 | const attachMenuToWebContents = (webContents) => {
20 | webContents.on("context-menu", (event, params) => {
21 | const menu = new Menu();
22 |
23 | // Add Undo, Redo for editable text fields with platform-specific accelerators
24 | if (params.isEditable) {
25 | menu.append(
26 | new MenuItem({
27 | label: "Undo",
28 | role: "undo",
29 | accelerator: "CommandOrControl+Z",
30 | })
31 | );
32 | menu.append(
33 | new MenuItem({
34 | label: "Redo",
35 | role: "redo",
36 | accelerator: isMac ? "Command+Shift+Z" : "Control+Y",
37 | })
38 | );
39 | menu.append(new MenuItem({ type: "separator" }));
40 | }
41 |
42 | // Cut, Copy, Paste, Delete, and Select All with accelerators
43 | if (params.isEditable || params.selectionText.trim().length > 0) {
44 | menu.append(
45 | new MenuItem({
46 | label: "Cut",
47 | role: "cut",
48 | accelerator: "CommandOrControl+X",
49 | enabled: params.editFlags.canCut,
50 | })
51 | );
52 | menu.append(
53 | new MenuItem({
54 | label: "Copy",
55 | role: "copy",
56 | accelerator: "CommandOrControl+C",
57 | enabled: params.editFlags.canCopy,
58 | })
59 | );
60 | menu.append(
61 | new MenuItem({
62 | label: "Paste",
63 | role: "paste",
64 | accelerator: "CommandOrControl+V",
65 | enabled: params.editFlags.canPaste,
66 | })
67 | );
68 | menu.append(
69 | new MenuItem({
70 | label: "Delete",
71 | role: "delete",
72 | })
73 | );
74 | menu.append(
75 | new MenuItem({
76 | label: "Select All",
77 | role: "selectAll",
78 | accelerator: "CommandOrControl+A",
79 | })
80 | );
81 | menu.append(new MenuItem({ type: "separator" }));
82 | }
83 |
84 | // Navigation controls with no accelerators
85 | menu.append(
86 | new MenuItem({
87 | label: "Back",
88 | enabled: webContents.canGoBack(),
89 | click: () => webContents.goBack(),
90 | })
91 | );
92 | menu.append(
93 | new MenuItem({
94 | label: "Forward",
95 | enabled: webContents.canGoForward(),
96 | click: () => webContents.goForward(),
97 | })
98 | );
99 | menu.append(
100 | new MenuItem({
101 | label: "Reload",
102 | click: () => webContents.reload(),
103 | })
104 | );
105 |
106 | // Element inspection
107 | menu.append(
108 | new MenuItem({
109 | label: "Inspect",
110 | click: () => {
111 | if (!webContents.isDevToolsOpened()) {
112 | webContents.openDevTools({ mode: "detach" });
113 | }
114 | webContents.inspectElement(params.x, params.y);
115 | },
116 | })
117 | );
118 |
119 | // Link handling
120 | if (params.linkURL) {
121 | menu.append(
122 | new MenuItem({
123 | label: "Copy Link Address",
124 | click: () => clipboard.writeText(params.linkURL),
125 | })
126 | );
127 | menu.append(
128 | new MenuItem({
129 | label: "Open Link in New Window",
130 | click: () => {
131 | if (windowManagerInstance) {
132 | windowManagerInstance.open({ url: params.linkURL });
133 | } else {
134 | console.error("WindowManager instance not set.");
135 | }
136 | },
137 | })
138 | );
139 | }
140 |
141 | menu.popup();
142 | });
143 | };
144 |
145 | // Attach to main window's webContents
146 | attachMenuToWebContents(browserWindow.webContents);
147 |
148 | // Attach to all existing webviews
149 | browserWindow.webContents.on(
150 | "did-attach-webview",
151 | (event, webviewWebContents) => {
152 | attachMenuToWebContents(webviewWebContents);
153 |
154 | webviewWebContents.setWindowOpenHandler(({ url }) => {
155 | if (windowManagerInstance) {
156 | windowManagerInstance.open({ url });
157 | } else {
158 | console.error("WindowManager instance not set.");
159 | }
160 | return { action: "deny" };
161 | });
162 | }
163 | );
164 | }
165 |
--------------------------------------------------------------------------------
/src/find-menu.js:
--------------------------------------------------------------------------------
1 | class FindMenu extends HTMLElement {
2 | constructor() {
3 | super();
4 |
5 | this.currentSearchValue = '';
6 | this.matchCase = false;
7 | this.currentRequestId = null;
8 | this.matchesCount = 0;
9 | this.currentMatchIndex = 0;
10 | this.isPdf = false; // Track if current document is PDF
11 | this.wrappingBackward = false; // Tracks if we're wrapping around to the end
12 | this.updateTimeout = null;
13 |
14 | this.addEventListener('keydown', ({ key }) => {
15 | if (key === 'Escape') this.hide();
16 | });
17 | }
18 |
19 | async connectedCallback() {
20 | this.innerHTML = `
21 |
22 |
23 |
24 |
25 |
26 | `;
27 |
28 | this.input = this.querySelector('.find-menu-input');
29 | this.matchCountDisplay = this.querySelector('.match-count');
30 | this.previousButton = this.querySelector('.find-menu-previous');
31 | this.nextButton = this.querySelector('.find-menu-next');
32 | this.hideButton = this.querySelector('.find-menu-hide');
33 |
34 | await this.loadSVG(this.previousButton, 'peersky://static/assets/svg/up.svg');
35 | await this.loadSVG(this.nextButton, 'peersky://static/assets/svg/down.svg');
36 | await this.loadSVG(this.hideButton, 'peersky://static/assets/svg/close.svg');
37 |
38 | // Setup foundInPage listener on webview
39 | this.setupFoundInPageListener();
40 |
41 | // Setup webview navigation events to detect PDFs
42 | this.setupWebviewNavigationListener();
43 |
44 | this.input.addEventListener('input', (e) => {
45 | const { value } = this;
46 | if (!value) {
47 | this.stopFindInPage('clearSelection');
48 | return;
49 | }
50 |
51 | this.findInWebview(value, { forward: true });
52 | });
53 |
54 | this.input.addEventListener('keydown', ({ keyCode, shiftKey }) => {
55 | if (keyCode === 13) {
56 | const { value } = this;
57 | if (!value) return this.hide();
58 |
59 | const forward = !shiftKey;
60 | this.findInWebview(value, { forward, findNext: true });
61 | }
62 | });
63 |
64 | this.previousButton.addEventListener('click', () => {
65 | const { value } = this;
66 | if (!value) return;
67 | this.findInWebview(value, { forward: false, findNext: true });
68 | });
69 |
70 | this.nextButton.addEventListener('click', () => {
71 | const { value } = this;
72 | if (!value) return;
73 | this.findInWebview(value, { forward: true, findNext: true });
74 | });
75 |
76 | this.hideButton.addEventListener('click', () => this.hide());
77 | }
78 |
79 | setupWebviewNavigationListener() {
80 | const webview = this.getWebviewElement();
81 | if (!webview) return;
82 |
83 | // Listen for did-navigate events to detect content type
84 | webview.addEventListener('did-navigate', () => {
85 | this.detectContentType();
86 | });
87 |
88 | webview.addEventListener('did-navigate-in-page', () => {
89 | this.detectContentType();
90 | });
91 |
92 | // Also check when loading finishes
93 | webview.addEventListener('did-finish-load', () => {
94 | this.detectContentType();
95 | });
96 | }
97 |
98 | async detectContentType() {
99 | if (this.isPdf !== null) return; // detect only once
100 | const webview = this.getWebviewElement();
101 | if (!webview) return;
102 |
103 | try {
104 | // Check if current page is a PDF by examining the URL or content
105 | const url = await webview.getURL();
106 | this.isPdf = url.toLowerCase().endsWith('.pdf') ||
107 | url.toLowerCase().includes('application/pdf');
108 |
109 | // If we need more precise detection, we can use executeJavaScript
110 | if (!this.isPdf) {
111 | const contentType = await webview.executeJavaScript(`
112 | document.contentType ||
113 | (document.querySelector('embed[type="application/pdf"]') ? 'application/pdf' : '')
114 | `);
115 | this.isPdf = contentType === 'application/pdf';
116 | }
117 | } catch (error) {
118 | console.error('Error detecting content type:', error);
119 | }
120 | }
121 | // fix (orignal code)
122 | setupFoundInPageListener() {
123 | // Get the webview element
124 | const webview = this.getWebviewElement();
125 | if (!webview) return;
126 |
127 | // Listen for found-in-page events
128 | webview.addEventListener('found-in-page', (event) => {
129 | const { requestId, matches, activeMatchOrdinal } = event.result;
130 | console.log('found-in-page', requestId, matches, activeMatchOrdinal);
131 |
132 | // Ensure this is a response to our current request
133 | if (requestId !== this.currentRequestId) return;
134 |
135 | // updates the match count display only if
136 | // 1. matchesCount is 0 (first search) or
137 | // 2. search value has changed
138 | if (this.matchesCount === 0 || this.currentSearchValue !== this.input.value) {
139 | this.matchesCount = matches || 0;
140 | }
141 | if(matches > 0) {
142 | this.currentMatchIndex = activeMatchOrdinal;
143 | if(this.currentMatchIndex > this.matchesCount) {
144 | console.log('wrapping', this.currentMatchIndex, this.matchesCount);
145 | this.currentMatchIndex = 1;
146 | }
147 | else if(this.currentMatchIndex < 1 && this.matchesCount > 0) {
148 | this.currentMatchIndex = this.matchesCount
149 | }
150 | }
151 | else{
152 | this.currentMatchIndex = 0;
153 | }
154 |
155 | if (this.matchesCount > 0) {
156 | this.matchCountDisplay.textContent = `${this.currentMatchIndex} of ${this.matchesCount}`;
157 | } else {
158 | this.matchCountDisplay.textContent = 'No matches';
159 | }
160 | });
161 | }
162 |
163 |
164 | findInWebview(value, options = {}) {
165 | const webview = this.getWebviewElement();
166 | if (!webview) return;
167 |
168 | // If search value changed, reset the search
169 | if (value !== this.currentSearchValue) {
170 | this.stopFindInPage('clearSelection');
171 | this.currentSearchValue = value;
172 | options.findNext = false;
173 |
174 | // Reset counters when starting a new search
175 | this.matchesCount = 0;
176 | this.currentMatchIndex = 0;
177 | }
178 |
179 | // Use Electron's findInPage API for both HTML and PDF content
180 | try {
181 | this.currentRequestId = webview.findInPage(value, {
182 | forward: options.forward !== false,
183 | findNext: options.findNext || false,
184 | matchCase: this.matchCase,
185 | });
186 | } catch (error) {
187 | console.error('Error using findInPage:', error);
188 | }
189 | }
190 |
191 | stopFindInPage(action = 'keepSelection') {
192 | const webview = this.getWebviewElement();
193 | if (webview) {
194 | webview.stopFindInPage(action);
195 | if (action === 'clearSelection') {
196 | this.currentSearchValue = '';
197 | this.matchCountDisplay.textContent = '';
198 | this.matchesCount = 0;
199 | this.currentMatchIndex = 0;
200 | }
201 | }
202 | }
203 |
204 | getWebviewElement() {
205 | // First try getting tracked-box's webviewElement property
206 | const trackedBox = document.querySelector('tracked-box');
207 | if (trackedBox && trackedBox.webviewElement) {
208 | return trackedBox.webviewElement;
209 | }
210 | // Fallback to direct webview element
211 | return document.querySelector('webview');
212 | }
213 |
214 | async loadSVG(button, svgPath) {
215 | const response = await fetch(svgPath);
216 | const svgContent = await response.text();
217 | const svgContainer = document.createElement("div");
218 | svgContainer.innerHTML = svgContent;
219 | svgContainer.querySelector("svg").setAttribute("width", "14");
220 | svgContainer.querySelector("svg").setAttribute("height", "14");
221 | svgContainer.querySelector("svg").setAttribute("fill", "currentColor");
222 | button.appendChild(svgContainer.firstChild);
223 | }
224 |
225 | resetSearch() {
226 | this.stopFindInPage('clearSelection');
227 | }
228 |
229 | get value() {
230 | return this.input.value;
231 | }
232 |
233 | show() {
234 | this.classList.toggle('hidden', false);
235 | // Check content type when showing search
236 | this.detectContentType();
237 | setTimeout(() => {
238 | this.focus();
239 | }, 10);
240 | }
241 |
242 | hide() {
243 | this.stopFindInPage('clearSelection');
244 | this.classList.toggle('hidden', true);
245 | this.dispatchEvent(new CustomEvent('hide'));
246 | }
247 |
248 | toggle() {
249 | const isHidden = this.classList.contains('hidden');
250 | this.classList.toggle('hidden');
251 | if (isHidden) {
252 | this.detectContentType();
253 | this.focus();
254 | } else {
255 | this.stopFindInPage('clearSelection');
256 | this.dispatchEvent(new CustomEvent('hide'));
257 | }
258 | }
259 |
260 | focus() {
261 | this.input.focus();
262 | this.input.select();
263 | }
264 | }
265 |
266 | customElements.define('find-menu', FindMenu);
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { app, session, protocol as globalProtocol } from "electron";
2 | import { createHandler as createBrowserHandler } from "./protocols/peersky-protocol.js";
3 | import { createHandler as createBrowserThemeHandler } from "./protocols/theme-handler.js";
4 | import { createHandler as createIPFSHandler } from "./protocols/ipfs-handler.js";
5 | import { createHandler as createHyperHandler } from "./protocols/hyper-handler.js";
6 | import { createHandler as createWeb3Handler } from "./protocols/web3-handler.js";
7 | import { ipfsOptions, hyperOptions } from "./protocols/config.js";
8 | import { registerShortcuts } from "./actions.js";
9 | import WindowManager from "./window-manager.js";
10 | import { attachContextMenus, setWindowManager } from "./context-menu.js";
11 | // import { setupAutoUpdater } from "./auto-updater.js";
12 |
13 | const P2P_PROTOCOL = {
14 | standard: true,
15 | secure: true,
16 | allowServiceWorkers: true,
17 | supportFetchAPI: true,
18 | bypassCSP: false,
19 | corsEnabled: true,
20 | stream: true,
21 | };
22 |
23 | const BROWSER_PROTOCOL = {
24 | standard: false,
25 | secure: true,
26 | allowServiceWorkers: false,
27 | supportFetchAPI: true,
28 | bypassCSP: false,
29 | corsEnabled: true,
30 | };
31 |
32 | let windowManager;
33 |
34 | globalProtocol.registerSchemesAsPrivileged([
35 | { scheme: "peersky", privileges: BROWSER_PROTOCOL },
36 | { scheme: "browser", privileges: BROWSER_PROTOCOL },
37 | { scheme: "ipfs", privileges: P2P_PROTOCOL },
38 | { scheme: "ipns", privileges: P2P_PROTOCOL },
39 | { scheme: "pubsub", privileges: P2P_PROTOCOL },
40 | { scheme: "hyper", privileges: P2P_PROTOCOL },
41 | { scheme: "web3", privileges: P2P_PROTOCOL },
42 | ]);
43 |
44 | app.whenReady().then(async () => {
45 | windowManager = new WindowManager();
46 |
47 | // Set the WindowManager instance in context-menu.js
48 | setWindowManager(windowManager);
49 | await setupProtocols(session.defaultSession);
50 |
51 | // Load saved windows or open a new one
52 | await windowManager.openSavedWindows();
53 | if (windowManager.all.length === 0) {
54 | windowManager.open({ isMainWindow: true });
55 | }
56 |
57 | registerShortcuts(windowManager); // Pass windowManager to registerShortcuts
58 |
59 | windowManager.startSaver();
60 |
61 | // Initialize AutoUpdater after windowManager is ready
62 | // console.log("App is prepared, setting up AutoUpdater...");
63 | // setupAutoUpdater();
64 | });
65 |
66 | // Introduce a flag to prevent multiple 'before-quit' handling
67 | let isQuitting = false;
68 |
69 | app.on("before-quit", (event) => {
70 | if (isQuitting) {
71 | return;
72 | }
73 | event.preventDefault(); // Prevent the default quit behavior
74 |
75 | console.log("Before quit: Saving window states...");
76 |
77 | isQuitting = true; // Set the quitting flag
78 |
79 | windowManager.setQuitting(true); // Inform WindowManager that quitting is happening
80 |
81 | windowManager
82 | .saveOpened()
83 | .then(() => {
84 | console.log("Window states saved successfully.");
85 | windowManager.stopSaver();
86 | app.quit(); // Proceed to quit the app
87 | })
88 | .catch((error) => {
89 | console.error("Error saving window states on quit:", error);
90 | windowManager.stopSaver();
91 | app.quit(); // Proceed to quit the app even if saving fails
92 | });
93 | });
94 |
95 | async function setupProtocols(session) {
96 | const { protocol: sessionProtocol } = session;
97 |
98 | app.setAsDefaultProtocolClient("peersky");
99 | app.setAsDefaultProtocolClient("browser");
100 | app.setAsDefaultProtocolClient("ipfs");
101 | app.setAsDefaultProtocolClient("ipns");
102 | app.setAsDefaultProtocolClient("hyper");
103 | app.setAsDefaultProtocolClient("web3");
104 |
105 | const browserProtocolHandler = await createBrowserHandler();
106 | sessionProtocol.registerStreamProtocol("peersky", browserProtocolHandler, BROWSER_PROTOCOL);
107 |
108 | const browserThemeHandler = await createBrowserThemeHandler();
109 | sessionProtocol.registerStreamProtocol("browser", browserThemeHandler, BROWSER_PROTOCOL);
110 |
111 | const ipfsProtocolHandler = await createIPFSHandler(ipfsOptions, session);
112 | sessionProtocol.registerStreamProtocol("ipfs", ipfsProtocolHandler, P2P_PROTOCOL);
113 | sessionProtocol.registerStreamProtocol("ipns", ipfsProtocolHandler, P2P_PROTOCOL);
114 | sessionProtocol.registerStreamProtocol("pubsub", ipfsProtocolHandler, P2P_PROTOCOL);
115 |
116 | const hyperProtocolHandler = await createHyperHandler(hyperOptions, session);
117 | sessionProtocol.registerStreamProtocol("hyper", hyperProtocolHandler, P2P_PROTOCOL);
118 |
119 | const web3ProtocolHandler = await createWeb3Handler();
120 | sessionProtocol.registerStreamProtocol("web3", web3ProtocolHandler, P2P_PROTOCOL);
121 | }
122 |
123 | app.on("window-all-closed", () => {
124 | if (process.platform !== "darwin") {
125 | app.quit();
126 | }
127 | });
128 |
129 | app.on("activate", () => {
130 | if (windowManager.all.length === 0) {
131 | windowManager.open({ isMainWindow: true });
132 | }
133 | });
134 |
135 | export { windowManager };
136 |
--------------------------------------------------------------------------------
/src/pages/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Peersky Browser | 404
6 |
15 |
74 | Elves tuck information away in what humans have categorized as "JSON", better known as Jason.
75 |
76 |
77 |
const data = $.learn()
78 |
79 |
Draw
80 |
81 | When elves draw, they have the full hypertext target to work with. Hypertext returned from the callback will be drawn automatically. The target is available in the draw callback, as well as optional hooks before and after the draw callback is updated, when Jason has new information to learn.
82 |