1
0
mirror of https://github.com/XFox111/PasswordGeneratorExtension.git synced 2026-04-22 08:08:01 +03:00

Major 2.0 (#8)

* Migrated to React 18 and FluentUI 9

* Added Ukranian translation

* Updated GitHub templates

* Updated CI/CD
- Added CodeQL and Dependabot pipelines
- Removed Whitesource Bolt integration
- Added PR pipeline
- Update release pipeline to meet ReactJS
- Added Edge publish to pipeline
- Updated PR checklist

* Updated repo docs

* Moved dependabot yml to the right place

* Update README.md

* Added path filters to pipelines
This commit is contained in:
Eugene Fox
2022-09-06 19:12:02 +03:00
committed by GitHub
parent 8991884494
commit 03d74f93d6
56 changed files with 11817 additions and 1134 deletions
+4 -3
View File
@@ -3,8 +3,7 @@ name: Bug report
about: Create a report to help us improve the extension about: Create a report to help us improve the extension
title: '' title: ''
labels: bug labels: bug
assignees: '' assignees: @XFox111
--- ---
### Description ### Description
@@ -30,4 +29,6 @@ Please provide the following information:
- Extension version: [e.g. 1.5] - Extension version: [e.g. 1.5]
### Additional context ### Additional context
Add any other context about the problem here. Add any other context about the problem here.
- [ ] Are you willing to submit a pull request to implement this feature yourself?
+4 -3
View File
@@ -3,8 +3,7 @@ name: Feature request
about: Suggest an idea for this project about: Suggest an idea for this project
title: '' title: ''
labels: enhancement labels: enhancement
assignees: '' assignees: @XFox111
--- ---
**Is your feature request related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**
@@ -17,4 +16,6 @@ A clear and concise description of what you want to happen.
A clear and concise description of any alternative solutions or features you've considered. A clear and concise description of any alternative solutions or features you've considered.
**Additional context** **Additional context**
Add any other context or screenshots about the feature request here. Add any other context or screenshots about the feature request here.
- [ ] Are you willing to submit a pull request to implement this feature yourself?
+29
View File
@@ -0,0 +1,29 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# yaml-language-server: $schema=https://json.schemastore.org/dependabot-2.0.json
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
target-branch: "main"
assignees:
- "xfox111"
reviewers:
- "xfox111"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "main"
assignees:
- "xfox111"
reviewers:
- "xfox111"
schedule:
interval: "weekly"
+8 -3
View File
@@ -1,9 +1,14 @@
fixes: ## Description
> Put short description of the pull request here
fixes: #issue_number
## Changelog ## Changelog
- Item 1 - Item 1
- Item 2 - Item 2
- Item 3 - Item 3
## PR Checklist ## PR Checklist (main branch)
- [ ] Change extension version in the manifest - [ ] Update extension version in `package.json`
- [ ] [Post-merge] Create a release to publish the new version
- [ ] [Post-deploy] Update changelog for Firefox webstore
+147
View File
@@ -0,0 +1,147 @@
name: Release pipeline
on:
release:
types: [published]
workflow_dispatch:
jobs:
Build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Update manifest version
uses: Amadevus/pwsh-script@v2
with:
script: |
[PSCustomObject] $package = Get-Content "package.json" | ConvertFrom-Json;
[PSCustomObject] $manifest = Get-Content "public/manifest.json" | ConvertFrom-Json;
$manifest.version = $package.version;
$manifest | ConvertTo-Json -Depth 10 | Out-File "public/manifest.json"
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '16.x'
cache: 'yarn'
- run: yarn install
- run: yarn build
- name: 'Drop artifacts'
uses: actions/upload-artifacts@v3.0.0
with:
name: 'Build'
path: 'build'
Firefox:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v3
with:
name: 'Build'
- name: Build Extension for Firefox
id: web-ext-build
uses: kewisch/action-web-ext@v1
with:
cmd: build
source: Build
- name: 'Sign & publish'
id: web-ext-sign
uses: kewisch/action-web-ext@v1
with:
cmd: sign
channel: listed
source: ${{ steps.web-ext-build.outputs.target }}
apiKey: ${{ secrets.FIREFOX_API_KEY }}
apiSecret: ${{ secrets.FIREFOX_CLIENT_SECRET }}
- name: Drop artifacts
uses: actions/upload-artifact@v2
with:
name: 'Firefox build'
path: ${{ steps.web-ext-sign.outputs.target }}
- name: Upload artifact
uses: xresloader/upload-to-github-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
file: ${{ steps.web-ext-sign.outputs.target }}
tags: true
draft: false
Chrome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v3
with:
name: 'Build'
- name: Pack extension
uses: TheDoctor0/zip-release@0.4.1
with:
directory: Build
filename: ./PasswordGenerator.zip
- name: Publish to Chrome Webstore
uses: SebastienGllmt/chrome-addon@v3
with:
extension: jnjobgjobffgmgfnkpkjfjkkfhfikmfl
zip: ./PasswordGenerator.zip
client-id: ${{ secrets.CHROME_CLIENT_ID }}
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
- name: Drop artifacts
uses: actions/upload-artifact@v2
with:
name: 'Chrome Artifacts'
path: ./PasswordGenerator.zip
- name: Upload artifact
uses: xresloader/upload-to-github-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
file: ./PasswordGenerator.zip
tags: true
draft: false
Edge:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v3
with:
name: 'Build'
- name: Pack extension
uses: TheDoctor0/zip-release@0.4.1
with:
directory: Build
filename: ./PasswordGenerator.zip
- name: Publish to Edge Addons
uses: wdzeng/edge-addon@v1
with:
product-id: 0RDCKDGH3MWT
zip-path: ./PasswordGenerator.zip
client-id: ${{ secrets.EDGE_CLIENT_ID }}
client-secret: ${{ secrets.EDGE_CLIENT_SECRET }}
access-token-url: ${{ secrets.EDGE_ACCESS_TOKEN_URL }}
-70
View File
@@ -1,70 +0,0 @@
name: CI
on:
release:
types: [published]
jobs:
Firefox:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build Extension for Firefox
id: web-ext-build
uses: kewisch/action-web-ext@v1
with:
cmd: build
- name: 'Sign & publish'
id: web-ext-sign
uses: kewisch/action-web-ext@v1
with:
cmd: sign
channel: listed
source: ${{ steps.web-ext-build.outputs.target }}
apiKey: ${{ secrets.FIREFOX_API_KEY }}
apiSecret: ${{ secrets.FIREFOX_CLIENT_SECRET }}
- name: Drop artifacts
uses: actions/upload-artifact@v2
with:
name: 'Firefox Artefacts'
path: ${{ steps.web-ext-build.outputs.target }}
Chrome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Pack extension
uses: TheDoctor0/zip-release@0.4.1
with:
filename: ./PasswordGenerator.zip
exclusions: '.git/* .vscode/* .github/* *.md'
- name: Publish to Chrome Webstore
uses: SebastienGllmt/chrome-addon@v3
with:
extension: jnjobgjobffgmgfnkpkjfjkkfhfikmfl
zip: ./PasswordGenerator.zip
client-id: ${{ secrets.CHROME_CLIENT_ID }}
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
- name: Upload artifact
uses: xresloader/upload-to-github-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
file: ./PasswordGenerator.zip
tags: true
draft: false
- name: Drop artifacts
uses: actions/upload-artifact@v2
with:
name: 'Chrome Artifacts'
path: ./PasswordGenerator.zip
+86
View File
@@ -0,0 +1,86 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main" ]
paths-ignore:
- '**.md'
- 'LICENSE'
- 'PRIVACY'
- '**/cd_pipeline.yaml'
- '**/dependabot.yml'
- '**/pr_pipeline.yaml'
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
paths-ignore:
- '**.md'
- 'LICENSE'
- 'PRIVACY'
- '**/cd_pipeline.yaml'
- '**/dependabot.yml'
- '**/pr_pipeline.yaml'
schedule:
- cron: '24 7 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'typescript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+36
View File
@@ -0,0 +1,36 @@
name: PR check pipeline
on:
pull_request:
branches:
- main
paths-ignore:
- '**.md'
- 'LICENSE'
- 'PRIVACY'
- '**/cd_pipeline.yaml'
- '**/dependabot.yml'
- '**/codeql-analysis.yml'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '16.x'
cache: 'yarn'
- run: yarn install
- run: yarn build
- name: 'Drop artifacts'
uses: actions/upload-artifact@v3.0.0
with:
name: 'Package'
path: 'build'
+26 -1
View File
@@ -1 +1,26 @@
.vscode # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# IDE files
.vscode
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-12
View File
@@ -1,12 +0,0 @@
{
"scanSettings": {
"baseBranches": []
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure",
"displayMode": "diff"
},
"issueSettings": {
"minSeverityLevel": "LOW"
}
}
+19 -8
View File
@@ -68,9 +68,9 @@ Please include the following with each issue:
### Final Checklist ### Final Checklist
Please remember to do the following: Please remember to do the following:
- [ ] Search the issue repository to ensure your report is a new issue - [*] Search the issue repository to ensure your report is a new issue
- [ ] Separate issues reports - [*] Separate issues reports
- [ ] Include as much information as you can to your report - [*] Include as much information as you can to your report
Don't feel bad if the developers can't reproduce the issue right away. They will simply ask for more information! Don't feel bad if the developers can't reproduce the issue right away. They will simply ask for more information!
@@ -85,23 +85,34 @@ If you are interested in writing code to fix issues or implement new awesome fea
``` ```
git clone https://github.com/xfox111/PasswordGeneratorExtension.git git clone https://github.com/xfox111/PasswordGeneratorExtension.git
``` ```
2. Install [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/)
3. Open terminal in project directory and run
```bash
yarn install
```
4. Build project
```bash
yarn build
```
2. Enable Developers mode on your browser extensions page 2. Enable Developers mode on your browser extensions page
3. Click "Load unpacked" button and navigate to the extension root folder (contains `manifest.json`) 3. Click "Load unpacked" button and navigate to the `build` folder of the repo (contains `manifest.json`)
4. Done! 4. Done!
Next time you make any changes to the codebase, reload extension by toggling it off and on or by pressing "Reload" button on extensions list page To run extension as standalone web application you can use `yarn start` command
Next time you make any changes to the codebase, rebuild extension with `yarn build` and reload extension by toggling it off and on or by pressing "Reload" button on extensions list page
### Development workflow ### Development workflow
This section represents how contributors should interact with codebase implementing features and fixing bugs This section represents how contributors should interact with codebase implementing features and fixing bugs
1. Getting assigned to the issue 1. Getting assigned to the issue
2. Creating a repository fork 2. Creating a repository fork
3. Making changes to codebase 3. Making changes to codebase
5. Creating a pull request to `master` 5. Creating a pull request to `main`
6. Reviewing & completing PR 6. Reviewing & completing PR
7. Done 7. Done
#### Release #### Release
Next stage is release. Release performs on every push to master (which makes functional changes to the source code). Release performs manually by @XFox111 into: Chrome webstore, Edge webstore and GitHub releases Next stage is release. Release performs on every push to main (which makes functional changes to the source code). Release performs manually by @XFox111 into: Chrome webstore, Edge webstore and GitHub releases
### Coding guidelines ### Coding guidelines
#### Indentation #### Indentation
@@ -201,4 +212,4 @@ Pull requests that fix typos are welcomed but please make sure it doesn't touch
Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute. Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute.
## Attribution ## Attribution
This Contribution Guidelines are adapted from the [Contributing to VS Code](https://github.com/microsoft/vscode/blob/master/CONTRIBUTING.md) This Contribution Guidelines are adapted from the [Contributing to VS Code](https://github.com/microsoft/vscode/blob/master/CONTRIBUTING.md)
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020 Michael Gordeev Copyright (c) 2022 Eugene Fox
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+3 -6
View File
@@ -1,7 +1,4 @@
Password generator Extension Privacy Policy # Password Generator Extension Privacy Policy
1. Developers of the extension don't affiliate with Google LLC, Mozilla Foundation or Microsoft Corporation in any way 1. Developers of the extension don't affiliate with Google LLC, Mozilla Foundation or Microsoft Corporation in any way
2. This extension doesn't transfer any personal data (data which may be used to track your location or reveal your identity) to any remote or local server 2. This extension doesn't store any personal data (data which may be used to track your location or reveal your identity)
3. This extension doesn't share any personal data with third parties 3. This extension uses build-in cloud storage of your browser to store extension settings
4. This extension stores following personal data:
- Browser tabs which have been saved for later by user via this extension (if user click "Set current tabs aside" button). This includes titles, favicons and web links
5. User can delete all saved personal data by removing this extension from his Browser
+13 -16
View File
@@ -1,21 +1,12 @@
# Password generator # Password generator
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/xfox111/PasswordGeneratorExtension)](https://github.com/xfox111/PasswordGeneratorExtension/releases/latest) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/xfox111/PasswordGeneratorExtension)](https://github.com/xfox111/PasswordGeneratorExtension/releases/latest)
![CI](https://github.com/XFox111/PasswordGeneratorExtension/workflows/CI/badge.svg) [![GitHub last commit](https://img.shields.io/github/last-commit/xfox111/PasswordGeneratorExtension?label=Last+update)](https://github.com/xfox111/PasswordGeneratorExtension/commits/master)
[![Mozilla Add-on](https://img.shields.io/amo/rating/easy-password-generator?label=Firefox%20rating)](https://addons.mozilla.org/firefox/addon/easy-password-generator/)
[![Chrome Web Store](https://img.shields.io/chrome-web-store/users/jnjobgjobffgmgfnkpkjfjkkfhfikmfl?label=Chrome%20Webstore%20downloads)](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl)
[![Mozilla Add-on](https://img.shields.io/amo/users/easy-password-generator?label=Firefox%20Webstore%20downloads)](https://addons.mozilla.org/firefox/addon/easy-password-generator/)
[![GitHub issues](https://img.shields.io/github/issues/xfox111/PasswordGeneratorExtension)](https://github.com/xfox111/PasswordGeneratorExtension/issues)
[![GitHub last commit](https://img.shields.io/github/last-commit/xfox111/PasswordGeneratorExtension)](https://github.com/xfox111/PasswordGeneratorExtension/commits/master)
[![GitHub repo size](https://img.shields.io/github/repo-size/xfox111/PasswordGeneratorExtension?label=repo%20size)](https://github.com/xfox111/PasswordGeneratorExtension)
[![MIT License](https://img.shields.io/github/license/xfox111/PasswordGeneratorExtension)](https://opensource.org/licenses/MIT)
[![Twitter Follow](https://img.shields.io/twitter/follow/xfox111?style=social)](https://twitter.com/xfox111) [![Twitter Follow](https://img.shields.io/twitter/follow/xfox111?style=social)](https://twitter.com/xfox111)
[![GitHub followers](https://img.shields.io/github/followers/xfox111?label=Follow%20@xfox111&style=social)](https://github.com/xfox111) [![GitHub followers](https://img.shields.io/github/followers/xfox111?label=Follow%20@xfox111&style=social)](https://github.com/xfox111)
[![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-%40xfox111-orange)](https://buymeacoffee.com/xfox111) [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-%40xfox111-orange)](https://buymeacoffee.com/xfox111)
![Password generator](https://xfox111.net/ma4rop) ![Password generator](https://img1.teletype.in/files/4d/93/4d935519-814b-41b7-a3c0-54503eb4eac7.png)
Extension for web browsers which helps you to easily generate strong passwords in one click Extension for web browsers which helps you to easily generate strong passwords in one click
@@ -25,15 +16,23 @@ Extension for web browsers which helps you to easily generate strong passwords i
- Clean and simple UI - Clean and simple UI
- Dark mode - Dark mode
![Password generator demo](https://xfox111.net/1cj439) <img width="1136" alt="Password generator v2.0" src="https://user-images.githubusercontent.com/28831743/188680034-a673b1b4-3153-4054-bb0d-949568de1748.png">
## Download ## Download
[![Chrome Web Store](https://img.shields.io/chrome-web-store/users/jnjobgjobffgmgfnkpkjfjkkfhfikmfl?label=Chrome%20Webstore%20downloads)](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl)
[![Mozilla Add-on](https://img.shields.io/amo/users/easy-password-generator?label=Firefox%20Webstore%20downloads)](https://addons.mozilla.org/firefox/addon/easy-password-generator/)
[![Mozilla Add-on](https://img.shields.io/amo/rating/easy-password-generator?label=Firefox%20rating)](https://addons.mozilla.org/firefox/addon/easy-password-generator/)
- [Google Chrome Webstore](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl) - [Google Chrome Webstore](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl)
- [Microsoft Edge Add-ons Webstore](https://microsoftedge.microsoft.com/addons/detail/manimdhobjbkfpeeehlhhneookiokpbj) - [Microsoft Edge Add-ons Webstore](https://microsoftedge.microsoft.com/addons/detail/manimdhobjbkfpeeehlhhneookiokpbj)
- [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/easy-password-generator/) - [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/easy-password-generator/)
- [GitHub Releases](https://github.com/xfox111/PasswordGeneratorExtension/releases/latest) - [GitHub Releases](https://github.com/xfox111/PasswordGeneratorExtension/releases/latest)
## Contributing ## Contributing
[![GitHub issues](https://img.shields.io/github/issues/xfox111/PasswordGeneratorExtension)](https://github.com/xfox111/PasswordGeneratorExtension/issues)
![CI](https://github.com/XFox111/PasswordGeneratorExtension/workflows/CI/badge.svg)
[![GitHub repo size](https://img.shields.io/github/repo-size/xfox111/PasswordGeneratorExtension?label=repo%20size)](https://github.com/xfox111/PasswordGeneratorExtension)
There are many ways in which you can participate in the project, for example: There are many ways in which you can participate in the project, for example:
- [Submit bugs and feature requests](https://github.com/xfox111/PasswordGeneratorExtension/issues), and help us verify as they are checked in - [Submit bugs and feature requests](https://github.com/xfox111/PasswordGeneratorExtension/issues), and help us verify as they are checked in
- Review [source code changes](https://github.com/xfox111/PasswordGeneratorExtension/pulls) - Review [source code changes](https://github.com/xfox111/PasswordGeneratorExtension/pulls)
@@ -51,8 +50,6 @@ If you are interested in fixing issues and contributing directly to the code bas
This project has adopted the Contributor Covenant. For more information see the [Code of Conduct](https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CODE_OF_CONDUCT.md) This project has adopted the Contributor Covenant. For more information see the [Code of Conduct](https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CODE_OF_CONDUCT.md)
## Copyrights ## Copyrights
> ©2020 Michael "XFox" Gordeev > ©2022 Eugene Fox
Font copyrights: Microsoft Corportation ©2019 (Additional ELUA applied) [![MIT License](https://img.shields.io/github/license/xfox111/PasswordGeneratorExtension)](https://opensource.org/licenses/MIT)
Licensed under [MIT License](https://opensource.org/licenses/MIT)
+21
View File
@@ -0,0 +1,21 @@
# Security Policy
## Supported Versions
Password generator extension has a linear versioning system. The latest version is always the most secure one. If you are using an older version, please update it to the latest one.
We regularly run security audits and fix any security issues that are found. If you find a security issue, please report it to us as described below.
### 1.x EoL
With 2.0 release bringing substantial changes to the extension, 1.x versions are no longer supported. Please update to the latest version.
If you are willing to continue using 1.x version, you can sideload it from the [releases page](https://github.com/XFox111/PasswordGeneratorExtension/releases/tag/v1.3). Use outdated versions at your own risk.
| Version | Supported |
| ------- | ------------------ |
| 2.x.x | :white_check_mark: |
| < 2.0.0 | :x: |
## Reporting a Vulnerability
In case you find a security issue, please report it to us by [creating an issue](https://github.com/XFox111/PasswordGeneratorExtension/issues/new?assignees=xfox111&labels=bug&template=bug_report.md&title=)
-160
View File
@@ -1,160 +0,0 @@
{
"name":
{
"message": "Password generator",
"description": "Extension name. Displayed in the manifest and pane header"
},
"description":
{
"message": "Password generator extension allows you to easily generate long and secure password in one click",
"description": "Extension description"
},
"author":
{
"message": "Michael \"XFox\" Gordeev",
"description": "Author's name"
},
"generate":
{
"message": "Generate password",
"description": "Main action label"
},
"success":
{
"message": "Password has been generated and copied to your clipboard",
"description": "Message which is shown after successful creation of a password"
},
"fail":
{
"message": "Invalid generator settings. No password was generated",
"description": "Message which is shown after a creation of password failed"
},
"notEnoughChars":
{
"message": "With your current settings, your password length should not exceed %MIN_CHARS% characters",
"description": "Message which is shown if there's no enough characters to create a password"
},
"lengthPrompt":
{
"message": "Set password length. Press OK to use default password length (%LEN% characters)",
"description": "A prompt message when user generates a password"
},
"generatorOptions":
{
"message": "Generator options",
"description": "Generator options group header"
},
"length":
{
"message": "Password length",
"description": "Input field header"
},
"hint":
{
"message": "Reccommended password length",
"description": "Password length field hint"
},
"includeSymbols":
{
"message": "Include symbols (e.g.",
"description": "Option checkbox label"
},
"includeNumbers":
{
"message": "Include numbers (e.g.",
"description": "Option checkbox label"
},
"includeLowercase":
{
"message": "Include lowercase characters (e.g.",
"description": "Option checkbox label"
},
"includeUppercase":
{
"message": "Include uppercase characters (e.g.",
"description": "Option checkbox label"
},
"excludeSimilar":
{
"message": "Exclude similar characters (e.g.",
"description": "Option checkbox label"
},
"excludeSpecial":
{
"message": "Exclude ambiguous characters (e.g.",
"description": "Option checkbox label"
},
"dontRepeatChars":
{
"message": "Do not repeat characters",
"description": "Option checkbox label"
},
"extOptions":
{
"message": "Extension options",
"description": "Extension options group header"
},
"showButton":
{
"message": "Insert \"Generate password\" button below password fields",
"description": "Option checkbox label"
},
"showContext":
{
"message": "Add \"Generate password\" action to context menu",
"description": "Option checkbox label"
},
"hideAlert":
{
"message": "Hide successful password generation message",
"description": "Option checkbox label"
},
"promptForLength":
{
"message": "Prompt for password length every time before generating",
"description": "Option checkbox label"
},
"more":
{
"message": "More",
"description": "About section toggle button caption"
},
"about":
{
"message": "About",
"description": "About info header"
},
"developed":
{
"message": "Developed by",
"description": ""
},
"website":
{
"message": "My website",
"description": "My website link caption"
},
"github":
{
"message": "Source code",
"description": "GitHub repo link caption"
},
"changelog":
{
"message": "Changelog",
"description": "Changelog link caption"
},
"feedback":
{
"message": "Leave feedback",
"description": "Feedback CTA button"
},
"bmc":
{
"message": "Buy me a coffee",
"description": "BMC CTA button cation"
}
}
-160
View File
@@ -1,160 +0,0 @@
{
"name":
{
"message": "Генератор паролей",
"description": "Extension name. Displayed in the manifest and pane header"
},
"description":
{
"message": "Расширение, позволяющее вам легко генерировать сложные и надежные пароли в один клик",
"description": "Extension description"
},
"author":
{
"message": "Михаил \"XFox\" Гордеев",
"description": "Author's name"
},
"generate":
{
"message": "Сгенерировать пароль",
"description": "Main action label"
},
"success":
{
"message": "Пароль сгенерирован и скопирован в ваш буфер обмена",
"description": "Message which is shown after successful creation of a password"
},
"fail":
{
"message": "Установлены неправильные настройки генератора. Ничего не было сгенерировано",
"description": "Message which is shown after a creation of password failed"
},
"notEnoughChars":
{
"message": "При текущих параметрах длина создаваемого пароля должна быть не больше %MIN_CHARS% знаков",
"description": "Message which is shown if there's no enough characters to create a password"
},
"lengthPrompt":
{
"message": "Укажите длину пароля. Нажмите ОК чтобы использовать длину по умолчанию (%LEN% символов)",
"description": "A prompt message when user generates a password"
},
"generatorOptions":
{
"message": "Настройки генератора",
"description": "Generator options group header"
},
"length":
{
"message": "Длина пароля",
"description": "Input field header"
},
"hint":
{
"message": "Рекомендованная длина пароля",
"description": "Password length field hint"
},
"includeSymbols":
{
"message": "Включить символы (например:",
"description": "Option checkbox label"
},
"includeNumbers":
{
"message": "Включить цифры (например:",
"description": "Option checkbox label"
},
"includeLowercase":
{
"message": "Включить буквы нижнего регистра (например:",
"description": "Option checkbox label"
},
"includeUppercase":
{
"message": "Включить буквы верхнего регистра (например:",
"description": "Option checkbox label"
},
"excludeSimilar":
{
"message": "Исключить похожие символы (например:",
"description": "Option checkbox label"
},
"excludeSpecial":
{
"message": "Исключить специальные символы (например:",
"description": "Option checkbox label"
},
"dontRepeatChars":
{
"message": "Не повторять символы",
"description": "Option checkbox label"
},
"extOptions":
{
"message": "Настройки расширения",
"description": "Extension options group header"
},
"showButton":
{
"message": "Добавлять кнопку \"Сгенерировать пароль\" рядом с каждым полем ввода пароля",
"description": "Option checkbox label"
},
"showContext":
{
"message": "Добавить действие \"Сгенерировать пароль\" в контекстное меню браузера",
"description": "Option checkbox label"
},
"hideAlert":
{
"message": "Не показывать сообщение при генерации пароля",
"description": "Option checkbox label"
},
"promptForLength":
{
"message": "Спрашивать длину пароля перед генерацией",
"description": "Option checkbox label"
},
"more":
{
"message": "Больше",
"description": "About section toggle button caption"
},
"about":
{
"message": "О расширении",
"description": "About info header"
},
"developed":
{
"message": "Разработал:",
"description": ""
},
"website":
{
"message": "Мой веб-сайт",
"description": "My website link caption"
},
"github":
{
"message": "Исходный код",
"description": "GitHub repo link caption"
},
"changelog":
{
"message": "Список изменений",
"description": "Changelog link caption"
},
"feedback":
{
"message": "Оставить отзыв",
"description": "Feedback CTA button"
},
"bmc":
{
"message": "Поддержать автора",
"description": "BMC CTA button cation"
}
}
+35
View File
@@ -0,0 +1,35 @@
// Craco config file
// Craco is used to separate content and background scripts from the main JS bundle
export default
{
webpack:
{
configure: (webpackConfig : any, { env, paths } : IEnvironment) =>
{
return {
...webpackConfig,
entry:
{
main: [ env === "development" && require.resolve("react-dev-utils/webpackHotDevClient"), paths.appIndexJs ].filter(Boolean),
background: "./src/Services/BackgroundService.ts",
contentScript: "./src/Services/ContentService.ts"
},
output:
{
...webpackConfig.output,
filename: "static/js/[name].js",
}
}
}
}
}
interface IEnvironment
{
env: string;
paths:
{
[key: string]: string | string[]
};
}
-238
View File
@@ -1,238 +0,0 @@
/* Fluent UI styles */
@font-face
{
font-family: "Segoe UI";
src: local("Segoe UI"),
url("fonts/segoeui.ttf") format("truetype"),
}
@font-face
{
font-family: "Segoe MDL2 Assets";
src: local("Segoe MDL2 Assets"),
url("fonts/segoemdl2.ttf") format("truetype"),
}
body
{
font-family: "Segoe UI";
user-select: none;
white-space: nowrap;
font-size: 14px;
line-height: 20px;
color: rgb(50, 49, 48);
border-radius: 2px;
}
h1, h2, h3
{
font-weight: 400;
margin: 0px;
}
h1
{
font-size: 28px;
font-weight: 400;
line-height: 36px;
}
h2
{
font-size: 20px;
font-weight: 400;
line-height: 28px;
}
.group
{
margin-bottom: 10px;
}
.group > *
{
display: block;
}
button.accent, a.button
{
font-weight: 600;
background-color: #0078d4;
cursor: pointer;
color: white !important;
border: 1px solid #0078d4;
border-radius: 2px;
padding: 0px 16px;
height: 32px;
min-width: 80px;
text-align: center;
outline: none !important;
text-decoration: none !important;
}
button.accent:hover, a.button:hover
{
background-color: rgb(16, 110, 190);
border: 1px solid rgb(16, 110, 190);
}
button.accent:active, a.button:active
{
background-color: rgb(0, 90, 158);
border: 1px solid rgb(0, 90, 158);
}
a.button
{
display: inline-block;
line-height: 28px;
vertical-align: bottom;
box-sizing: border-box;
}
button.link, a, a:visited
{
color: rgb(0, 120, 212);
background: none;
outline: none;
border: none;
cursor: pointer;
text-decoration: none;
}
button.link:hover, a:hover, a:visited:hover
{
color: rgb(0, 69, 120);
text-decoration: underline;
}
button.link:hover
{
text-decoration: none;
}
button.link:hover span
{
text-decoration: underline;
}
button.link i
{
font-family: "Segoe MDL2 Assets";
font-style: normal;
font-size: 16px;
margin-left: 4px;
vertical-align: bottom;
}
input[type=number]
{
padding-left: 9px;
padding-right: 9px;
height: 32px;
width: 100%;
border: 1px solid rgb(96, 94, 92);
box-sizing: border-box;
outline: none;
cursor: text;
}
input[type=number]:active, input[type=number]:focus
{
border: 2px solid rgb(0, 120, 212);
}
p
{
margin: 0px;
}
.control.checkbox
{
position: relative;
padding-left: 32px;
cursor: pointer;
margin-bottom: 5px;
}
.control.checkbox input
{
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.control.checkbox input + span
{
position: absolute;
left: 0;
height: 20px;
width: 20px;
border: 1px solid rgb(50, 49, 48);
border-radius: 2px;
transition-property: background, border, border-color;
transition-duration: 200ms;
transition-timing-function: cubic-bezier(0.4, 0, 0.23, 1);
}
.control.checkbox input + span::after
{
position: absolute;
color: rgb(96, 94, 92);
font-family: "Segoe MDL2 Assets";
content: "\E001";
width: 20px;
height: 20px;
text-align: center;
display: none;
}
.control.checkbox input:checked + span::after
{
display: block;
color: white;
}
.control.checkbox:hover input + span::after
{
display: block;
}
.control.checkbox input:checked + span
{
background-color: rgb(0, 120, 212);
border-color: rgb(0, 120, 212);
}
.control.checkbox:hover input:checked + span
{
background-color: rgb(0, 90, 158);
border-color: rgb(0, 90, 158);
}
/* Custom CSS */
.bmc
{
display: inline-grid !important;
grid-template-columns: auto 1fr;
grid-column-gap: 10px;
align-items: center;
background-color: #FF813F !important;
border-color: #FF813F !important;
color: white !important;
text-decoration: none;
height: 32px;
}
.bmc svg
{
height: 20px;
}
.bmc span
{
margin-bottom: 2px;
}
-42
View File
@@ -1,42 +0,0 @@
body, input
{
color: #EDEBE9;
background-color: rgb(51, 51, 51);
}
input[type=number]:active, input[type=number]:focus
{
border-color: rgb(40, 153, 245);
}
button.link, a, a:visited
{
color: rgb(40, 153, 245);
}
button.link:hover, a:hover, a:visited:hover
{
color: #7DBAE9;
}
.control.checkbox input + span
{
border-color: rgb(243, 242, 241);
}
.control.checkbox input:checked + span
{
background-color: rgb(40, 153, 245);
border-color: rgb(40, 153, 245);
}
.control.checkbox:hover input:checked + span
{
background-color: rgb(108, 184, 246);
border-color: rgb(108, 184, 246);
}
.control.checkbox:hover input + span:after
{
color: rgb(161, 159, 157);
}
.control.checkbox input:checked + span:after
{
color: rgb(27, 26, 25);
}
-1
View File
@@ -1 +0,0 @@
You may use the Segoe MDL2 Assets and Segoe UI fonts or glyphs included in this file (“Software”) solely to design, develop and test your programs for Microsoft Office. Microsoft Office includes but is not limited to any software product or service branded by trademark, trade dress, copyright or some other recognized means, as a product or service of Microsoft Office. This license does not grant you the right to distribute or sublicense all or part of the Software to any third party. By using the Software you agree to these terms. If you do not agree to these terms, do not use the Segoe MDL2 and Segoe UI Fonts or Glyphs.
Binary file not shown.
BIN
View File
Binary file not shown.
-35
View File
@@ -1,35 +0,0 @@
function AddContextMenu()
{
chrome.contextMenus.create(
{
id: "generate",
contexts: [ "all" ],
title: chrome.i18n.getMessage("generate")
}
);
}
// Setting up context menu event listener
chrome.contextMenus.onClicked.addListener(() => GeneratePassword(null));
// Adding context menu entry if needed
chrome.runtime.onInstalled.addListener(() =>
{
// Adding context menu option
chrome.storage.sync.get({ showContext: true }, (settings) =>
{
if (settings.showContext !== false)
AddContextMenu();
});
});
chrome.storage.onChanged.addListener((changes, area) =>
{
if (area != "sync" || changes["showContext"] == null)
return;
if (changes["showContext"].newValue === false)
chrome.contextMenus.removeAll();
else
AddContextMenu();
});
-73
View File
@@ -1,73 +0,0 @@
// Loading input fields states
chrome.storage.sync.get(
{
// Set of settings keys we retrieve and their default values
// Generator settings
length: 16,
includeSymbols: true,
includeNumbers: true,
includeLowercase: true,
includeUppercase: true,
excludeSimilar: true,
excludeSpecial: true,
dontRepeatChars: false,
// Extension settings
showButton: true,
showContext: true,
hideAlert: false,
promptForLength: false
},
(settings) =>
{
if (window.matchMedia("(prefers-color-scheme: dark)").matches) // Doesn't work on Fiefox
document.querySelector("#darkStylesheet").removeAttribute("disabled");
document.querySelector("#length").value = settings.length; // Setting length value
// Setting checkboxes
[
"includeSymbols",
"includeNumbers",
"includeLowercase",
"includeUppercase",
"excludeSimilar",
"excludeSpecial",
"dontRepeatChars",
"showButton",
"showContext",
"hideAlert",
"promptForLength"
].forEach(i => document.querySelector("#" + i).checked = settings[i]);
SetupEventHandlers();
document.querySelector("#version").textContent = "v" + chrome.runtime.getManifest()["version"]; // Updating display version
document.querySelectorAll("*[loc]").forEach(i => i.textContent = chrome.i18n.getMessage(i.getAttribute("loc"))); // Updating localization
});
function SetupEventHandlers()
{
document.querySelectorAll("input").forEach(i =>
i.addEventListener(
"input",
() => chrome.storage.sync.set(JSON.parse("{ \"" + i.id + "\": " + (i.type == "checkbox" ? i.checked : i.value) + " }"))
)
);
document.querySelector("#generate").addEventListener("click", () => GeneratePassword(null, true));
document.querySelector("#more").addEventListener("click", (s) =>
{
let group = document.querySelector("#about");
if (group.hasAttribute("hidden"))
{
group.removeAttribute("hidden");
s.currentTarget.querySelector("i").textContent = "\uE010";
}
else
{
group.setAttribute("hidden", "");
s.currentTarget.querySelector("i").textContent = "\uE011";
}
});
}
-156
View File
@@ -1,156 +0,0 @@
// Some constants
const upperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const lowerCase = upperCase.toLowerCase();
const numbers = "1234567890";
const specialCharacters = "!#$%&*+-=?@^_";
const ambiguousCharacters = "{}[]()/\\'\"`~,;:.<>";
const similarCharacters = "il1Lo0O";
chrome.storage.sync.get({ showButton: true },
(settings) =>
{
if (settings.showButton)
InsertButtons();
}
);
// Adding button below every password field
function InsertButtons()
{
document.querySelectorAll("input[type=password]").forEach(i =>
{
let actionLink = document.createElement("a");
actionLink.innerText = chrome.i18n.getMessage("generate");
actionLink.style.margin = "5px";
actionLink.style.display = "block";
// Since anchor without 'href' attrubute isn't shown as a hyperlink and '#' link potentially can break some sites logic, we add empty JS function
actionLink.href = "javascript:void(0);";
actionLink.addEventListener("click", GeneratePassword);
i.insertAdjacentElement("afterend", actionLink); // Adding button after a password field
});
}
function GeneratePassword(e, useDefaultLength = false)
{
// Generating password
let availableCharacters = ""; // Set of available characters to generate a password from
chrome.storage.sync.get(
{
length: 16,
includeSymbols: true,
includeNumbers: true,
includeLowercase: true,
includeUppercase: true,
excludeSimilar: true,
excludeSpecial: true,
hideAlert: false,
promptForLength: false,
dontRepeatChars: false,
},
(settings) =>
{
// Adding or excluding characters from the set
if (settings.includeSymbols)
availableCharacters += specialCharacters;
if (settings.includeNumbers)
availableCharacters += numbers;
if (settings.includeLowercase)
availableCharacters += lowerCase;
if (settings.includeUppercase)
availableCharacters += upperCase;
if (settings.excludeSimilar)
similarCharacters.split("").forEach(i => availableCharacters = availableCharacters.replace(i, ""));
if (settings.excludeSpecial === false)
availableCharacters += ambiguousCharacters;
if (availableCharacters.length < 1)
{
alert(chrome.i18n.getMessage("fail"));
return;
}
var pwdLength = settings.length;
if (settings.promptForLength && !useDefaultLength)
while(true)
{
var response = prompt(chrome.i18n.getMessage("lengthPrompt").replace("%LEN%", settings.length));
if (response === null) // If user clicked 'Cancel'
return;
if (parseInt(response) && response >= 6)
{
pwdLength = response;
break;
}
else if (!response) // Continue with default length if no response is provided. Try again if input is invalid
break;
}
if (settings.dontRepeatChars && availableCharacters.length < pwdLength)
{
alert(chrome.i18n.getMessage("notEnoughChars").replace("%MIN_CHARS%", availableCharacters.length));
return;
}
do
{
var password = "";
var leftCharacters = availableCharacters;
for (k = 0; k < pwdLength; k++)
{
password += leftCharacters[GetRandomInt(0, leftCharacters.length)]; // Picking random characters
if (settings.dontRepeatChars)
leftCharacters = leftCharacters.replace(password[k], "");
}
}
while (!((!settings.includeSymbols || ContainsAny(password, specialCharacters)) &&
(!settings.includeNumbers || ContainsAny(password, numbers)) &&
(!settings.includeLowercase || ContainsAny(password, lowerCase)) &&
(!settings.includeUppercase || ContainsAny(password, upperCase))));
let field = e?.target.previousElementSibling;
// Creating a hidden field if called as standalone
if (!field)
{
field = document.createElement("input");
document.body.appendChild(field);
}
field.value = password; // Setting generated password to the field
field.setAttribute("type", "text"); // Since we cannot copy text from a password field, we'll make it temporarly simple field
// Some JS clipboard copying stuff
field.select();
document.execCommand("copy");
// Setting field type back to 'password'
field.setAttribute("type", "password");
if (!e)
field.remove();
if (settings.hideAlert === false)
alert(chrome.i18n.getMessage("success"));
});
}
// See https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/random
function GetRandomInt(min, max)
{
return Math.floor(Math.random() * (max - min)) + min;
}
function ContainsAny(array1, array2)
{
for(var k = 0; k < array2.length; k++)
if (array1.includes(array2[k]))
return true;
return false;
}
-134
View File
File diff suppressed because one or more lines are too long
+57
View File
@@ -0,0 +1,57 @@
{
"name": "password-generator",
"version": "2.0.0",
"private": true,
"dependencies":
{
"@craco/craco": "^6.4.5",
"@fluentui/react-components": "^9.2.0",
"@fluentui/react-icons": "^2.0.179",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.54.5",
"typescript": "^4.4.2"
},
"devDependencies":
{
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"@types/chrome": "^0.0.195",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"react-scripts": "5.0.1"
},
"scripts":
{
"start": "react-scripts start",
"build": "INLINE_RUNTIME_CHUNK=false craco build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig":
{
"extends":
[
"react-app",
"react-app/jest"
]
},
"browserslist":
{
"production":
[
">0.2%",
"not dead",
"not op_mini all"
],
"development":
[
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
+217
View File
@@ -0,0 +1,217 @@
{
"name":
{
"message": "Password Generator",
"description": "manifest.json"
},
"description":
{
"message": "Password generator extension allows you to easily generate long and secure password in one click",
"description": "manifest.json"
},
"author":
{
"message": "Eugene Fox",
"description": "manifest.json"
},
"Password_generator":
{
"message": "Password generator",
"description": "App.tsx"
},
"Copy":
{
"message": "Copy",
"description": "PasswordView.tsx"
},
"Generate_new":
{
"message": "Generate new",
"description": "PasswordView.tsx"
},
"Exclude_special_symbols_one_time":
{
"message": "Generate password without special symbols",
"description": "PasswordView.tsx"
},
"Include_special_symbols_one_time":
{
"message": "Generate password with special symbols",
"description": "PasswordView.tsx"
},
"Settings":
{
"message": "Settings",
"description": "SettingsSection.tsx"
},
"Password_length":
{
"message": "Password length",
"description": "SettingsSection.tsx"
},
"Recommended_password_length":
{
"message": "Recommended password length",
"description": "SettingsSection.tsx"
},
"Character_options":
{
"message": "Character options",
"description": "SettingsSection.tsx"
},
"Include":
{
"message": "Include",
"description": "SettingsSection.tsx"
},
"Special_symbols":
{
"message": "Special symbols",
"description": "SettingsSection.tsx"
},
"Numeric":
{
"message": "Numeric",
"description": "SettingsSection.tsx"
},
"Uppercase":
{
"message": "Uppercase",
"description": "SettingsSection.tsx"
},
"Lowercase":
{
"message": "Lowercase",
"description": "SettingsSection.tsx"
},
"Exclude":
{
"message": "Exclude",
"description": "SettingsSection.tsx"
},
"Similar":
{
"message": "Similar",
"description": "SettingsSection.tsx"
},
"Ambiguous":
{
"message": "Ambiguous",
"description": "SettingsSection.tsx"
},
"Repeating":
{
"message": "Repeating",
"description": "SettingsSection.tsx"
},
"Add_shortcut_to_context_menu":
{
"message": "Add shortcut to context menu",
"description": "SettingsSection.tsx"
},
"Right_click_password_field_to_quickly_generate_password":
{
"message": "Right-click password field to quickly generate password",
"description": "SettingsSection.tsx"
},
"Automatically_copy_to_clipboard":
{
"message": "Automatically copy to clipboard",
"description": "SettingsSection.tsx"
},
"About":
{
"message": "About",
"description": "AboutSection.tsx"
},
"Developed_by_Eugene_Fox":
{
"message": "Developed by Eugene Fox",
"description": "AboutSection.tsx"
},
"Licensed_under":
{
"message": "Licensed under",
"description": "AboutSection.tsx"
},
"MIT_license":
{
"message": "MIT license",
"description": "AboutSection.tsx"
},
"Want_to_contribute_translation_for_your_language_":
{
"message": "Want to contribute translation for your language?",
"description": "AboutSection.tsx"
},
"Read_this_to_get_started":
{
"message": "Read this to get started",
"description": "AboutSection.tsx"
},
"My_website":
{
"message": "My website",
"description": "AboutSection.tsx"
},
"Source_code":
{
"message": "Source code",
"description": "AboutSection.tsx"
},
"Changelog":
{
"message": "Changelog",
"description": "AboutSection.tsx"
},
"Leave_feedback":
{
"message": "Leave feedback",
"description": "AboutSection.tsx"
},
"Buy_me_a_coffee":
{
"message": "Buy me a coffee",
"description": "AboutSection.tsx"
},
"Set_name":
{
"message": "Set name",
"description": "CharacterHelpDialog.tsx"
},
"Characters":
{
"message": "Characters",
"description": "CharacterHelpDialog.tsx"
},
"__etc_":
{
"message": ", etc.",
"description": "CharacterHelpDialog.tsx"
},
"OK":
{
"message": "OK",
"description": "CharacterHelpDialog.tsx"
},
"Either_lowercase_or_uppercase_characters_must_be_included":
{
"message": "Either lowercase or uppercase characters must be included",
"description": "Generator.tsx"
},
"Selected_length_is_too_long_to_exclude_repeating_characters":
{
"message": "Selected length is too long to exclude repeating characters",
"description": "Generator.tsx"
},
"Quick_generator_is_only_available_on_password_fields":
{
"message": "Quick generator is only available on password fields",
"description": "ContentService.tsx"
},
"Quick_generate_password":
{
"message": "Quick generate password",
"description": "BackgroundService.tsx"
}
}
+217
View File
@@ -0,0 +1,217 @@
{
"name":
{
"message": "Генератор паролей",
"description": "manifest.json"
},
"description":
{
"message": "Расширение, позволяющее легко генерировать сложные и надежные пароли в один клик",
"description": "manifest.json"
},
"author":
{
"message": "Евгений Лис",
"description": "manifest.json"
},
"Password_generator":
{
"message": "Генератор паролей",
"description": "App.tsx"
},
"Copy":
{
"message": "Копировать",
"description": "PasswordView.tsx"
},
"Generate_new":
{
"message": "Создать новый",
"description": "PasswordView.tsx"
},
"Exclude_special_symbols_one_time":
{
"message": "Сгенерировать пароль без спецсимволов",
"description": "PasswordView.tsx"
},
"Include_special_symbols_one_time":
{
"message": "Сгенерировать пароль со спецсимволами",
"description": "PasswordView.tsx"
},
"Settings":
{
"message": "Настройки",
"description": "SettingsSection.tsx"
},
"Password_length":
{
"message": "Длина пароля",
"description": "SettingsSection.tsx"
},
"Recommended_password_length":
{
"message": "Рекомендуемая длина пароля",
"description": "SettingsSection.tsx"
},
"Character_options":
{
"message": "Настройки символов",
"description": "SettingsSection.tsx"
},
"Include":
{
"message": "Включить",
"description": "SettingsSection.tsx"
},
"Special_symbols":
{
"message": "Специальные символы",
"description": "SettingsSection.tsx"
},
"Numeric":
{
"message": "Цифры",
"description": "SettingsSection.tsx"
},
"Uppercase":
{
"message": "Прописные буквы",
"description": "SettingsSection.tsx"
},
"Lowercase":
{
"message": "Строчные буквы",
"description": "SettingsSection.tsx"
},
"Exclude":
{
"message": "Исключить",
"description": "SettingsSection.tsx"
},
"Similar":
{
"message": "Похожие",
"description": "SettingsSection.tsx"
},
"Ambiguous":
{
"message": "Особые",
"description": "SettingsSection.tsx"
},
"Repeating":
{
"message": "Повторяющиеся",
"description": "SettingsSection.tsx"
},
"Add_shortcut_to_context_menu":
{
"message": "Добавить расширение в контекстное меню",
"description": "SettingsSection.tsx"
},
"Right_click_password_field_to_quickly_generate_password":
{
"message": "Щелкните правой кнопкой мыши по полю ввода пароля, чтобы быстро сгенерировать пароль",
"description": "SettingsSection.tsx"
},
"Automatically_copy_to_clipboard":
{
"message": "Автоматически копировать в буфер обмена",
"description": "SettingsSection.tsx"
},
"About":
{
"message": "О расширении",
"description": "AboutSection.tsx"
},
"Developed_by_Eugene_Fox":
{
"message": "Разработчик Евгений Лис",
"description": "AboutSection.tsx"
},
"Licensed_under":
{
"message": "Лицензия",
"description": "AboutSection.tsx"
},
"MIT_license":
{
"message": "MIT",
"description": "AboutSection.tsx"
},
"Want_to_contribute_translation_for_your_language_":
{
"message": "Хотите помочь с переводом на свой язык?",
"description": "AboutSection.tsx"
},
"Read_this_to_get_started":
{
"message": "Прочтите эту статью",
"description": "AboutSection.tsx"
},
"My_website":
{
"message": "Мой сайт",
"description": "AboutSection.tsx"
},
"Source_code":
{
"message": "Исходный код",
"description": "AboutSection.tsx"
},
"Changelog":
{
"message": "Что нового",
"description": "AboutSection.tsx"
},
"Leave_feedback":
{
"message": "Оставить отзыв",
"description": "AboutSection.tsx"
},
"Buy_me_a_coffee":
{
"message": "Спонсировать",
"description": "AboutSection.tsx"
},
"Set_name":
{
"message": "Название набора",
"description": "CharacterHelpDialog.tsx"
},
"Characters":
{
"message": "Символы",
"description": "CharacterHelpDialog.tsx"
},
"__etc_":
{
"message": " и т.д.",
"description": "CharacterHelpDialog.tsx"
},
"OK":
{
"message": "ОК",
"description": "CharacterHelpDialog.tsx"
},
"Either_lowercase_or_uppercase_characters_must_be_included":
{
"message": "Должны быть включены строчные или прописные буквы",
"description": "Generator.tsx"
},
"Selected_length_is_too_long_to_exclude_repeating_characters":
{
"message": "Выбранная длина слишком велика для исключения повторяющихся символов",
"description": "Generator.tsx"
},
"Quick_generator_is_only_available_on_password_fields":
{
"message": "Быстрый генератор доступен только для полей ввода пароля",
"description": "ContentService.tsx"
},
"Quick_generate_password":
{
"message": "Сгенерировать пароль",
"description": "BackgroundService.tsx"
}
}
+217
View File
@@ -0,0 +1,217 @@
{
"name":
{
"message": "Генератор паролів",
"description": "manifest.json"
},
"description":
{
"message": "Розширення, яке дозволяє легко генерувати складні та надійні паролі в один клік",
"description": "manifest.json"
},
"author":
{
"message": "Євген Лис",
"description": "manifest.json"
},
"Password_generator":
{
"message": "Генератор паролів",
"description": "App.tsx"
},
"Copy":
{
"message": "Копіювати",
"description": "PasswordView.tsx"
},
"Generate_new":
{
"message": "Генерувати новий",
"description": "PasswordView.tsx"
},
"Exclude_special_symbols_one_time":
{
"message": "Генерувати пароль без спеціальних символів",
"description": "PasswordView.tsx"
},
"Include_special_symbols_one_time":
{
"message": "Генерувати пароль з спеціальними символами",
"description": "PasswordView.tsx"
},
"Settings":
{
"message": "Налаштування",
"description": "SettingsSection.tsx"
},
"Password_length":
{
"message": "Довжина паролю",
"description": "SettingsSection.tsx"
},
"Recommended_password_length":
{
"message": "Рекомендована довжина паролю",
"description": "SettingsSection.tsx"
},
"Character_options":
{
"message": "Параметри символів",
"description": "SettingsSection.tsx"
},
"Include":
{
"message": "Включити",
"description": "SettingsSection.tsx"
},
"Special_symbols":
{
"message": "Спеціальні символи",
"description": "SettingsSection.tsx"
},
"Numeric":
{
"message": "Цифри",
"description": "SettingsSection.tsx"
},
"Uppercase":
{
"message": "Великі літери",
"description": "SettingsSection.tsx"
},
"Lowercase":
{
"message": "Малі літери",
"description": "SettingsSection.tsx"
},
"Exclude":
{
"message": "Виключити",
"description": "SettingsSection.tsx"
},
"Similar":
{
"message": "Схожі",
"description": "SettingsSection.tsx"
},
"Ambiguous":
{
"message": "Особливі",
"description": "SettingsSection.tsx"
},
"Repeating":
{
"message": "Повторювані",
"description": "SettingsSection.tsx"
},
"Add_shortcut_to_context_menu":
{
"message": "Додати розширення до контекстного меню",
"description": "SettingsSection.tsx"
},
"Right_click_password_field_to_quickly_generate_password":
{
"message": "Правий клік на поле вводу паролю для швидкого генерування паролю",
"description": "SettingsSection.tsx"
},
"Automatically_copy_to_clipboard":
{
"message": "Автоматично копіювати в буфер обміну",
"description": "SettingsSection.tsx"
},
"About":
{
"message": "Про розширення",
"description": "AboutSection.tsx"
},
"Developed_by_Eugene_Fox":
{
"message": "Розроблено Євгеном Лисом",
"description": "AboutSection.tsx"
},
"Licensed_under":
{
"message": "Ліцензовано під",
"description": "AboutSection.tsx"
},
"MIT_license":
{
"message": "MIT",
"description": "AboutSection.tsx"
},
"Want_to_contribute_translation_for_your_language_":
{
"message": "Хочете допомогти перекласти розширення на свою мову?",
"description": "AboutSection.tsx"
},
"Read_this_to_get_started":
{
"message": "Прочитайте цю статтю",
"description": "AboutSection.tsx"
},
"My_website":
{
"message": "Моя веб-сторінка",
"description": "AboutSection.tsx"
},
"Source_code":
{
"message": "Вихідний код",
"description": "AboutSection.tsx"
},
"Changelog":
{
"message": "Що нового",
"description": "AboutSection.tsx"
},
"Leave_feedback":
{
"message": "Залишити відгук",
"description": "AboutSection.tsx"
},
"Buy_me_a_coffee":
{
"message": "Підтримати",
"description": "AboutSection.tsx"
},
"Set_name":
{
"message": "Назва набору",
"description": "CharacterHelpDialog.tsx"
},
"Characters":
{
"message": "Символи",
"description": "CharacterHelpDialog.tsx"
},
"__etc_":
{
"message": " і т.д.",
"description": "CharacterHelpDialog.tsx"
},
"OK":
{
"message": "OK",
"description": "CharacterHelpDialog.tsx"
},
"Either_lowercase_or_uppercase_characters_must_be_included":
{
"message": "Повинні бути включені малі або великі літери",
"description": "Generator.tsx"
},
"Selected_length_is_too_long_to_exclude_repeating_characters":
{
"message": "Вибрана довжина занадто велика для виключення повторюваних символів",
"description": "Generator.tsx"
},
"Quick_generator_is_only_available_on_password_fields":
{
"message": "Швидкий генератор доступний тільки для полів вводу паролів",
"description": "ContentService.tsx"
},
"Quick_generate_password":
{
"message": "Згенерувати пароль",
"description": "BackgroundService.tsx"
}
}

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Before

Width:  |  Height:  |  Size: 641 B

After

Width:  |  Height:  |  Size: 641 B

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

+15
View File
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Password Generator</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf-8" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
+15 -12
View File
@@ -1,32 +1,40 @@
{ {
"$schema": "https://json.schemastore.org/chrome-manifest.json",
"manifest_version": 3,
"name": "__MSG_name__", "name": "__MSG_name__",
"version": "1.3",
"manifest_version": 2,
"description": "__MSG_description__", "description": "__MSG_description__",
"author": "__MSG_author__", "author": "__MSG_author__",
"version": "2.0.0",
"default_locale": "en", "default_locale": "en",
"permissions": "permissions":
[ [
"storage", "storage",
"<all_urls>",
"contextMenus", "contextMenus",
"clipboardWrite" "clipboardWrite"
], ],
"background":
{
"service_worker": "./static/js/background.js",
"type": "module"
},
"content_scripts": "content_scripts":
[ [
{ {
"matches": [ "<all_urls>" ], "matches": [ "<all_urls>" ],
"js": [ "js/script.js" ], "js": [ "./static/js/contentScript.js" ],
"run_at": "document_idle", "run_at": "document_idle",
"all_frames": true "all_frames": true
} }
], ],
"background":
"action":
{ {
"scripts": [ "js/background.js", "js/script.js" ], "default_popup": "index.html",
"persistent": false "default_title": "__MSG_name__"
}, },
"icons": "icons":
@@ -36,11 +44,6 @@
"32": "icons/icon-32.png", "32": "icons/icon-32.png",
"16": "icons/icon-16.png" "16": "icons/icon-16.png"
}, },
"browser_action":
{
"default_icon": "icons/icon-32.png",
"default_popup": "options.html"
},
"browser_specific_settings": "browser_specific_settings":
{ {
+118
View File
@@ -0,0 +1,118 @@
main
{
width: 400px;
h1, h2, h3, h4, h5, h6, p
{
margin: 0;
}
// Buy me a coffee button style
.fui-Button.bmc
{
background-color: var(--colorPaletteDarkOrangeBorder2) !important;
&:hover
{
background-color: var(--colorPaletteDarkOrangeForeground1) !important;
}
&:active
{
background-color: var(--colorPaletteDarkOrangeBackground2) !important;
}
}
// Overrides for default FluentUI styles
a.fui-Button
{
text-decoration: none;
}
.fui-Accordion
{
section
{
margin-bottom: 10px;
}
.fui-AccordionHeader__expandIcon > svg
{
transition-duration: .5s;
transition-timing-function: var(--curveDecelerateMax);
}
}
.scaleUpIn
{
animation-name: scaleUpInAnim;
animation-timing-function: var(--curveEasyEaseMax);
animation-duration: .5s;
}
@keyframes scaleUpInAnim
{
from
{
transform: scale(.5);
opacity: 0;
}
to
{
transform: scale(1);
opacity: 1;
}
}
.spin
{
animation-name: spinAnim;
animation-timing-function: var(--curveEasyEaseMax);
animation-duration: .5s;
}
@keyframes spinAnim
{
from
{
transform: rotate(0deg);
}
to
{
transform: rotate(360deg);
}
}
.fadeIn
{
animation-name: fadeInAnim;
animation-timing-function: var(--curveDecelerateMin);
animation-duration: .5s;
}
@keyframes fadeInAnim
{
from
{
opacity: 0;
}
to
{
opacity: 1;
}
}
}
.stack
{
display: flex;
flex-direction: column;
&.horizontal
{
flex-direction: row;
flex-wrap: wrap;
}
&.gap
{
gap: 10px;
}
}
+83
View File
@@ -0,0 +1,83 @@
import React from "react";
import { Accordion, FluentProvider, Text, Theme, Title2, webDarkTheme, webLightTheme } from "@fluentui/react-components";
import "./App.scss";
import SettingsSection from "./Components/SettingsSection";
import AboutSection from "./Components/AboutSection";
import Package from "../package.json";
import PasswordView from "./Components/PasswordView";
import Settings from "./Utils/Settings";
import GeneratorOptions from "./Utils/GeneratorOptions";
import { loc } from "./Utils/Localization";
interface IStates
{
theme: Theme;
generatorOptions: GeneratorOptions;
settings: Settings;
}
interface IProps
{
settings: Settings;
}
export default class App extends React.Component<IProps, IStates>
{
constructor(props: IProps)
{
super(props);
this.state =
{
theme: this.UpdateTheme(),
generatorOptions: new GeneratorOptions(),
settings: props.settings
};
Settings.OnChanged = (changes) => this.setState({ settings: { ...this.state.settings, ...changes } });
GeneratorOptions.OnChanged = (changes) => this.setState({ generatorOptions: { ...this.state.generatorOptions, ...changes } });
}
public async componentDidMount(): Promise<void>
{
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change",
(arg: MediaQueryListEvent) =>
this.setState({ theme: this.UpdateTheme(arg.matches) })
);
this.setState({ generatorOptions: await GeneratorOptions.Init() });
}
private UpdateTheme(isDark?: boolean): Theme
{
let theme: Theme = (isDark ?? window.matchMedia("(prefers-color-scheme: dark)").matches) ? webDarkTheme : webLightTheme;
document.body.style.backgroundColor = theme.colorNeutralBackground1;
return theme;
}
public render(): JSX.Element
{
return (
<FluentProvider theme={ this.state.theme }>
<main className="stack gap">
<header className="stack horizontal gap">
<Title2 as="h1">{ loc("Password generator") }</Title2>
<Text as="span">v{ Package.version }</Text>
</header>
<PasswordView settings={ this.state.settings } generatorOptions={ this.state.generatorOptions } />
<Accordion collapsible>
<SettingsSection
generatorOptions={ this.state.generatorOptions }
settings={ this.state.settings } />
<AboutSection />
</Accordion>
</main>
</FluentProvider>
);
}
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.1 KiB

+54
View File
@@ -0,0 +1,54 @@
import { AccordionItem, AccordionHeader, AccordionPanel, Link, Text, Button } from "@fluentui/react-components";
import { InfoRegular, PersonFeedbackRegular } from "@fluentui/react-icons";
import { ReactComponent as BuyMeACoffee } from "../Assets/BuyMeACoffee.svg";
import React from "react";
import { loc } from "../Utils/Localization";
export default class AboutSection extends React.Component
{
public render(): JSX.Element
{
return (
<AccordionItem value="about">
<AccordionHeader as="h2" icon={ <InfoRegular /> }>{ loc("About") }</AccordionHeader>
<AccordionPanel>
<section className="stack gap fadeIn">
<Text as="p">
{ loc("Developed by Eugene Fox") } (<Link href="https://twitter.com/xfox111" target="_blank">@xfox111</Link>)
<br />
{ loc("Licensed under") } <Link href="https://github.com/XFox111/PasswordGeneratorExtension/blob/master/LICENSE" target="_blank">{ loc("MIT license") }</Link>
</Text>
<Text as="p">
{ loc("Want to contribute translation for your language?") } <Link href="https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CONTRIBUTING.md" target="_blank">{ loc("Read this to get started") }</Link>
</Text>
<Text as="p">
<Link href="https://xfox111.net/" target="_blank">{ loc("My website") }</Link>
<br />
<Link href="https://github.com/xfox111/PasswordGeneratorExtension" target="_blank">{ loc("Source code") }</Link>
<br />
<Link href="https://github.com/XFox111/PasswordGeneratorExtension/releases/latest" target="_blank">{ loc("Changelog") }</Link>
</Text>
<div className="stack horizontal gap">
<Button
as="a" target="_blank"
href="mailto:feedback@xfox111.net"
appearance="primary" icon={ <PersonFeedbackRegular /> }>
{ loc("Leave feedback") }
</Button>
<Button
as="a" target="_blank"
href="https://buymeacoffee.com/xfox111"
className="bmc" appearance="primary" icon={ <BuyMeACoffee /> }>
{ loc("Buy me a coffee") }
</Button>
</div>
</section>
</AccordionPanel>
</AccordionItem>
);
}
}
+76
View File
@@ -0,0 +1,76 @@
import { Button, Text } from "@fluentui/react-components";
import { Dialog, DialogTrigger, DialogSurface, DialogTitle, DialogBody, Table, TableHeader, TableRow, TableHeaderCell, TableBody, TableCell, DialogActions } from "@fluentui/react-components/unstable";
import { QuestionCircleRegular } from "@fluentui/react-icons";
import React from "react";
import Generator from "../Utils/Generator";
import { loc } from "../Utils/Localization";
export default class CharacterHelpDialog extends React.Component
{
public render(): JSX.Element
{
return (
<Dialog>
<DialogTrigger>
<Button appearance="subtle" style={ { marginLeft: 5 } } icon={ <QuestionCircleRegular /> } />
</DialogTrigger>
<DialogSurface aria-label="label">
<DialogTitle>{ loc("Character options") }</DialogTitle>
<DialogBody>
<Table>
<TableHeader>
<TableRow>
<TableHeaderCell>{ loc("Set_name") }</TableHeaderCell>
<TableHeaderCell>{ loc("Characters") }</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>{ loc("Lowercase") }</TableCell>
<TableCell>
<Text font="monospace">{ Generator.Lowercase.substring(0, 10) }{ loc(", etc.") }</Text>
</TableCell>
</TableRow>
<TableRow>
<TableCell>{ loc("Uppercase") }</TableCell>
<TableCell>
<Text font="monospace">{ Generator.Uppercase.substring(0, 10) }{ loc(", etc.") }</Text>
</TableCell>
</TableRow>
<TableRow>
<TableCell>{ loc("Numeric") }</TableCell>
<TableCell>
<Text font="monospace">{ Generator.Numeric }</Text>
</TableCell>
</TableRow>
<TableRow>
<TableCell>{ loc("Special symbols") }</TableCell>
<TableCell>
<Text font="monospace">{ Generator.SpecialCharacters }</Text>
</TableCell>
</TableRow>
<TableRow>
<TableCell>{ loc("Ambiguous") }</TableCell>
<TableCell>
<Text font="monospace">{ Generator.AmbiguousCharacters }</Text>
</TableCell>
</TableRow>
<TableRow>
<TableCell>{ loc("Similar") }</TableCell>
<TableCell>
<Text font="monospace">{ Generator.SimilarCharacters }</Text>
</TableCell>
</TableRow>
</TableBody>
</Table>
</DialogBody>
<DialogActions>
<DialogTrigger>
<Button appearance="secondary">{ loc("OK") }</Button>
</DialogTrigger>
</DialogActions>
</DialogSurface>
</Dialog>
);
}
}
+129
View File
@@ -0,0 +1,129 @@
import { Input, Button, Link, Text, Tooltip } from "@fluentui/react-components";
import { Alert } from "@fluentui/react-components/unstable";
import { ArrowClockwiseRegular, CheckmarkRegular, CopyRegular } from "@fluentui/react-icons";
import React from "react";
import Generator from "../Utils/Generator";
import GeneratorOptions from "../Utils/GeneratorOptions";
import { loc } from "../Utils/Localization";
import Settings from "../Utils/Settings";
interface IStates
{
password: string;
error?: string;
copyIcon: JSX.Element;
}
interface IProps
{
generatorOptions: GeneratorOptions;
settings: Settings;
}
export default class PasswordView extends React.Component<IProps, IStates>
{
constructor(props: IProps)
{
super(props);
this.state =
{
password: Generator.GeneratePassword(props.generatorOptions),
error: Generator.ValidateProps(props.generatorOptions),
copyIcon: <CopyRegular className="scaleUpIn" />,
};
}
private OnCopyPassword(password : string): void
{
console.log("PasswordView.OnCopyPassword");
if (!document.hasFocus())
return;
window.navigator.clipboard.writeText(password);
this.setState({ copyIcon: <CheckmarkRegular className="scaleUpIn" /> });
setTimeout(() => this.setState({ copyIcon: <CopyRegular className="scaleUpIn" /> }), 3000);
}
private OnRefreshPassword(): void
{
console.log("PasswordView.OnRefreshPassword");
let password : string = Generator.GeneratePassword(this.props.generatorOptions);
this.setState({ password });
document.querySelector("svg#refresh-btn")?.classList.add("spin");
setTimeout(() => document.querySelector("svg#refresh-btn")?.classList.remove("spin"), 600);
if (this.props.settings.Autocopy)
this.OnCopyPassword(password);
}
public componentDidUpdate(prevProps: Readonly<IProps>): void
{
console.log("PasswordView.componentDidUpdate");
// Converting to JSON is the easiest way to compare objects
if (JSON.stringify(prevProps.generatorOptions) === JSON.stringify(this.props.generatorOptions))
return;
let error : string = Generator.ValidateProps(this.props.generatorOptions);
let password = Generator.GeneratePassword(this.props.generatorOptions);
this.setState({ password, error });
if (!error && this.props.settings.Autocopy)
this.OnCopyPassword(password);
}
private AlterSpecialsOnce(useSpecials : boolean) : void
{
console.log("PasswordView.AlterSpecialsOnce", `useSpecials: ${useSpecials}`);
let options : GeneratorOptions = { ...this.props.generatorOptions, Special: useSpecials, ExcludeAmbiguous: true };
let error : string = Generator.ValidateProps(options);
let password : string = Generator.GeneratePassword(options);
this.setState({ password, error });
if (error)
setTimeout(() => this.OnRefreshPassword(), 3000);
}
public render(): JSX.Element
{
return this.state.error ?
<Alert intent="error" className="fadeIn">{ this.state.error }</Alert>
:
<section className="stack fadeIn">
<Input
value={ this.state.password } readOnly
contentAfter={
<>
<Tooltip content={ loc("Copy") } relationship="label">
<Button onClick={ () => this.OnCopyPassword(this.state.password) } appearance="subtle" icon={ this.state.copyIcon } />
</Tooltip>
<Tooltip content={ loc("Generate new") } relationship="label">
<Button onClick={ () => this.OnRefreshPassword() } appearance="subtle" icon={ <ArrowClockwiseRegular id="refresh-btn" /> } />
</Tooltip>
</>
} />
<Text>
{ this.props.generatorOptions.Special ?
<Link onClick={ () => this.AlterSpecialsOnce(false) }>
{ loc("Exclude special symbols one time") }
</Link>
:
<Link onClick={ () => this.AlterSpecialsOnce(true) }>
{ loc("Include special symbols one time") }
</Link>
}
</Text>
</section>
;
}
}
+81
View File
@@ -0,0 +1,81 @@
import { AccordionItem, AccordionHeader, AccordionPanel, Label, Text, Input, Divider, Checkbox, Tooltip } from "@fluentui/react-components";
import { QuestionCircleRegular, SettingsRegular } from "@fluentui/react-icons";
import React from "react";
import GeneratorOptions from "../Utils/GeneratorOptions";
import { loc } from "../Utils/Localization";
import Settings from "../Utils/Settings";
import CharacterHelpDialog from "./CharacterHelpDialog";
interface IProps
{
generatorOptions: GeneratorOptions;
settings: Settings;
}
export default class SettingsSection extends React.Component<IProps>
{
public render(): JSX.Element
{
let options : GeneratorOptions = this.props.generatorOptions;
let settings : Settings = this.props.settings;
return (
<AccordionItem value="settings">
<AccordionHeader as="h2" icon={ <SettingsRegular /> }>{ loc("Settings") }</AccordionHeader>
<AccordionPanel>
<section className="stack gap fadeIn">
<Label weight="semibold" htmlFor="pwd-length">{ loc("Password length") }</Label>
<div className="stack">
<Input
id="pwd-length"
value={ options.Length.toString() }
onChange={ (_, e) => GeneratorOptions.Update({ Length: parseInt(e.value) }) }
type="number" min={ 4 } />
<Text size={ 200 }>{ loc("Recommended password length") } <b>16-32</b></Text>
</div>
<Divider />
<div className="stack">
<Text as="h3" weight="semibold">
{ loc("Character options") }
<CharacterHelpDialog />
</Text>
<Text as="h4">{ loc("Include") }</Text>
<div className="stack horizontal">
<Checkbox label={ loc("Special symbols") }
checked={ options.Special } onChange={ (_, e) => GeneratorOptions.Update({ Special: e.checked as boolean }) } />
<Checkbox label={ loc("Numeric") }
checked={ options.Numeric } onChange={ (_, e) => GeneratorOptions.Update({ Numeric: e.checked as boolean }) } />
<Checkbox label={ loc("Uppercase") }
checked={ options.Uppercase } onChange={ (_, e) => GeneratorOptions.Update({ Uppercase: e.checked as boolean }) } />
<Checkbox label={ loc("Lowercase") }
checked={ options.Lowercase } onChange={ (_, e) => GeneratorOptions.Update({ Lowercase: e.checked as boolean }) } />
</div>
<Text as="h4">{ loc("Exclude") }</Text>
<div className="stack horizontal">
<Checkbox label={ loc("Similar") }
checked={ options.ExcludeSimilar } onChange={ (_, e) => GeneratorOptions.Update({ ExcludeSimilar: e.checked as boolean }) } />
<Checkbox label={ loc("Ambiguous") }
checked={ options.ExcludeAmbiguous } onChange={ (_, e) => GeneratorOptions.Update({ ExcludeAmbiguous: e.checked as boolean }) } />
<Checkbox label={ loc("Repeating") }
checked={ options.ExcludeRepeating } onChange={ (_, e) => GeneratorOptions.Update({ ExcludeRepeating: e.checked as boolean }) } />
</div>
</div>
<Divider />
<div className="stack">
<div>
<Tooltip content={ loc("Right-click password field to quickly generate password") } relationship="description">
<Checkbox label={ <Text>{loc("Add shortcut to context menu")} <QuestionCircleRegular /></Text> }
checked={ settings.AddContext } onChange={ (_, e) => Settings.Update({ AddContext: e.checked as boolean }) } />
</Tooltip>
</div>
<Checkbox label={ loc("Automatically copy to clipboard") }
checked={ settings.Autocopy } onChange={ (_, e) => Settings.Update({ Autocopy: e.checked as boolean }) } />
</div>
</section>
</AccordionPanel>
</AccordionItem>
);
}
}
+51
View File
@@ -0,0 +1,51 @@
// BackgroundService.ts
// Background script that handles the context menu visibility
import { loc } from "../Utils/Localization";
function UpdateContextMenu(isEnabled: boolean) : void
{
console.log("BackgroundService.UpdateContextMenu", isEnabled);
chrome.contextMenus.update("generatePassword", { visible: isEnabled });
}
async function OnContextClick(info : chrome.contextMenus.OnClickData) : Promise<void>
{
console.log("BackgroundService.OnContextClick", info);
let tabInfo : chrome.tabs.Tab[] = await chrome.tabs.query({ active: true, currentWindow: true });
console.log("BackgroundService.OnContextClick", tabInfo);
chrome.tabs.sendMessage<string>(tabInfo[0].id, info.menuItemId as string);
}
if (!chrome.runtime.onInstalled.hasListeners())
chrome.runtime.onInstalled.addListener(async () =>
{
console.log("[BackgroundService] chrome.runtime.onInstalled");
chrome.contextMenus.removeAll();
chrome.contextMenus.create(
{
title: loc("Quick generate password"),
contexts: [ "editable" ],
id: "generatePassword"
}
);
let settings : { [key : string]: any } = await chrome.storage.sync.get({ AddContext: true });
UpdateContextMenu(settings.AddContext);
});
if (!chrome.contextMenus.onClicked.hasListeners())
chrome.contextMenus.onClicked.addListener(OnContextClick);
if (!chrome.storage.sync.onChanged.hasListeners())
chrome.storage.sync.onChanged.addListener(changes =>
{
console.log("[BackgroundService] chrome.storage.sync.onChanged", changes);
if (changes.AddContext?.newValue !== undefined)
UpdateContextMenu(changes.AddContext.newValue);
});
+37
View File
@@ -0,0 +1,37 @@
// ContentService.ts
// Content script that handles quick password generation through context menu
import Generator from "../Utils/Generator";
import GeneratorOptions from "../Utils/GeneratorOptions";
import { loc } from "../Utils/Localization";
if (!chrome.runtime.onMessage.hasListeners())
chrome.runtime.onMessage.addListener(async message =>
{
console.log("[ContentService] chrome.runtime.onMessage", message);
if (message === "generatePassword")
{
let generatorOptions : GeneratorOptions = await GeneratorOptions.Init();
let password : string = Generator.GeneratePassword(generatorOptions);
let input : HTMLInputElement = document.activeElement as HTMLInputElement;
if (![ "INPUT", "TEXTAREA" ].includes(input.tagName))
return;
console.log("[ContentService] chrome.runtime.onMessage", input);
if (input.tagName !== "INPUT" || input.readOnly || ![ "text", "password" ].includes(input.type))
{
window.alert(loc("Quick generator is only available on password fields"));
return;
}
input.focus();
input.value = password;
window.navigator.clipboard.writeText(password);
}
});
console.log("[ContentService] Loaded");
+113
View File
@@ -0,0 +1,113 @@
import GeneratorOptions from "./GeneratorOptions";
import { loc } from "./Localization";
export default class Generator
{
public static Uppercase = "ABCDEFGHJKMNPQRSTUVWXYZ";
public static Lowercase = this.Uppercase.toLowerCase();
public static Numeric = "23456789";
public static SpecialCharacters = "!#$%&*+-=?@^_";
public static AmbiguousCharacters = "{}[]()/\\'\"`~,;:.<>";
public static SimilarCharacters = "il1Lo0O";
public static GeneratePassword(props : GeneratorOptions) : string
{
// Validating parameters
if (this.ValidateProps(props))
return "";
// Generating password
let availableCharacters : string = this.GetAvailableCharacters(props);
let requiredCharacters : string = this.GetRequiredCharacters(props);
let password : string = "";
for (let i = 0; i < props.Length; i++)
{
let char : string = this.PickRandomFromArray(availableCharacters);
if (props.ExcludeRepeating && password.includes(char))
i--;
else
password += char;
}
for (let i = 0; i < requiredCharacters.length; i++)
{
if (props.ExcludeRepeating && password.includes(requiredCharacters[i]))
continue;
password = password.replace(this.PickRandomFromArray(password), requiredCharacters[i]);
}
return password;
}
public static ValidateProps(props : GeneratorOptions): string
{
if (!props.Lowercase && !props.Uppercase)
return loc("Either lowercase or uppercase characters must be included");
let availableCharacters : string = this.GetAvailableCharacters(props);
if (props.ExcludeRepeating && availableCharacters.length < props.Length)
return loc("Selected length is too long to exclude repeating characters");
return "";
}
private static GetAvailableCharacters(props : GeneratorOptions) : string
{
let availableCharacters : string = "";
if (props.Special)
availableCharacters += this.SpecialCharacters;
if (props.Numeric)
availableCharacters += this.Numeric;
if (props.Lowercase)
availableCharacters += this.Lowercase;
if (props.Uppercase)
availableCharacters += this.Uppercase;
if (!props.ExcludeAmbiguous)
availableCharacters += this.AmbiguousCharacters;
if (!props.ExcludeSimilar)
availableCharacters += this.SimilarCharacters;
return availableCharacters;
}
private static GetRequiredCharacters(props : GeneratorOptions) : string
{
let requiredCharacters : string = "";
if (props.Special)
requiredCharacters += this.PickRandomFromArray(this.SpecialCharacters);
if (props.Numeric)
requiredCharacters += this.PickRandomFromArray(this.Numeric);
if (props.Lowercase)
requiredCharacters += this.PickRandomFromArray(this.Lowercase);
if (props.Uppercase)
requiredCharacters += this.PickRandomFromArray(this.Uppercase);
if (!props.ExcludeAmbiguous)
requiredCharacters += this.PickRandomFromArray(this.AmbiguousCharacters);
if (!props.ExcludeSimilar)
requiredCharacters += this.PickRandomFromArray(this.SimilarCharacters);
return requiredCharacters;
}
// See https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/random
// min is inclusive, max is exclusive
private static GetRandomInt(min : number, max : number) : number
{
return Math.floor(Math.random() * (max - min)) + min;
}
private static PickRandomFromArray(array : string) : string
{
return array[this.GetRandomInt(0, array.length)];
}
}
+50
View File
@@ -0,0 +1,50 @@
export default class GeneratorOptions
{
public Length: number = 16;
public Special: boolean = true;
public Numeric: boolean = true;
public Lowercase: boolean = true;
public Uppercase: boolean = true;
public ExcludeSimilar: boolean = true;
public ExcludeAmbiguous: boolean = true;
public ExcludeRepeating: boolean = false;
public static OnChanged : (changes : Partial<GeneratorOptions>) => void;
public static async Init() : Promise<GeneratorOptions>
{
let fallbackOptions : GeneratorOptions = new GeneratorOptions();
if (!chrome?.storage?.sync) // Extension is running as a standalone app
return fallbackOptions;
let props : { [key: string]: any } = await chrome.storage.sync.get(fallbackOptions);
chrome.storage.sync.onChanged.addListener(GeneratorOptions.OnStorageChanged);
return props as GeneratorOptions;
}
public static async Update(changes : Partial<GeneratorOptions>) : Promise<void>
{
if (chrome?.storage?.sync)
await chrome?.storage?.sync?.set(changes);
else
GeneratorOptions.OnChanged(changes);
}
private static OnStorageChanged(changes : { [key: string]: chrome.storage.StorageChange }) : void
{
let propsList : string[] = Object.keys(new GeneratorOptions());
let options : { [key: string]: any } = { };
Object.entries(changes)
.filter(i => propsList.includes(i[0]))
.map(i => options[i[0]] = i[1].newValue);
if (GeneratorOptions.OnChanged)
GeneratorOptions.OnChanged(options as Partial<GeneratorOptions>);
}
}
+22
View File
@@ -0,0 +1,22 @@
export default class Localization
{
public static GetString(key : string) : string
{
let sanitizedKey : string = key
.replaceAll(".", "_")
.replaceAll(",", "_")
.replaceAll(" ", "_")
.replaceAll("-", "_")
.replaceAll("?", "_")
.replaceAll("!", "_");
let str : string = chrome?.i18n?.getMessage(sanitizedKey);
return str ?? key;
}
}
export function loc(key : string) : string
{
return Localization.GetString(key);
}
+42
View File
@@ -0,0 +1,42 @@
export default class Settings
{
public AddContext : boolean = true;
public Autocopy : boolean = true;
public static OnChanged : (changes : Partial<Settings>) => void;
public static async Init() : Promise<Settings>
{
let fallbackOptions = new Settings();
if (!chrome?.storage?.sync)
return fallbackOptions;
let props : { [key: string]: any } = await chrome.storage.sync.get(fallbackOptions);
chrome.storage.sync.onChanged.addListener(Settings.OnStorageChanged);
return props as Settings;
}
public static async Update(changes : Partial<Settings>) : Promise<void>
{
if (chrome?.storage?.sync)
await chrome?.storage?.sync?.set(changes);
else
Settings.OnChanged(changes);
}
private static OnStorageChanged(changes : { [key: string]: chrome.storage.StorageChange }) : void
{
let propsList : string[] = Object.keys(new Settings());
let settings : { [key: string]: any } = { };
Object.entries(changes)
.filter(i => propsList.includes(i[0]))
.map(i => settings[i[0]] = i[1].newValue);
if (Settings.OnChanged)
Settings.OnChanged(settings as Partial<Settings>);
}
}
+8
View File
@@ -0,0 +1,8 @@
import ReactDOM from "react-dom/client";
import App from "./App";
import Settings from "./Utils/Settings";
Settings.Init().then(settings =>
ReactDOM
.createRoot(document.querySelector("#root") as HTMLElement)
.render(<App settings={ settings } />));
+1
View File
@@ -0,0 +1 @@
/// <reference types="react-scripts" />
+27
View File
@@ -0,0 +1,27 @@
{
"compilerOptions":
{
"target": "es5",
"lib":
[
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"strictNullChecks": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [ "src" ]
}
+9721
View File
File diff suppressed because it is too large Load Diff