Showing preview only (420K chars total). Download the full file or copy to clipboard to get everything.
Repository: jackluson/sync-your-cookie
Branch: main
Commit: faf31f67eafa
Files: 204
Total size: 370.7 KB
Directory structure:
gitextract_67wvu_tp/
├── .eslintignore
├── .eslintrc
├── .github/
│ ├── CODEOWNERS
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── auto_assign.yml
│ ├── dependabot.yml
│ ├── stale.yml
│ └── workflows/
│ ├── auto-assign.yml
│ ├── build-zip.yml
│ ├── claude.yml
│ └── greetings.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .vscode/
│ └── settings.json
├── LICENSE
├── README.md
├── README_ZH.md
├── chrome-extension/
│ ├── lib/
│ │ └── background/
│ │ ├── badge.ts
│ │ ├── contextMenu.ts
│ │ ├── index.ts
│ │ ├── listen.ts
│ │ └── subscribe.ts
│ ├── manifest.js
│ ├── package.json
│ ├── public/
│ │ ├── _locales/
│ │ │ └── en/
│ │ │ └── messages.json
│ │ └── content.css
│ ├── tsconfig.json
│ ├── utils/
│ │ └── plugins/
│ │ └── make-manifest-plugin.ts
│ └── vite.config.ts
├── googlef759ff453695209f.html
├── how-to-use.md
├── package.json
├── packages/
│ ├── dev-utils/
│ │ ├── index.ts
│ │ ├── lib/
│ │ │ ├── logger.ts
│ │ │ └── manifest-parser/
│ │ │ ├── impl.ts
│ │ │ ├── index.ts
│ │ │ └── type.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── hmr/
│ │ ├── index.ts
│ │ ├── lib/
│ │ │ ├── constant.ts
│ │ │ ├── debounce.ts
│ │ │ ├── initClient.ts
│ │ │ ├── initReloadServer.ts
│ │ │ ├── injections/
│ │ │ │ ├── refresh.ts
│ │ │ │ └── reload.ts
│ │ │ ├── interpreter/
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ └── plugins/
│ │ │ ├── index.ts
│ │ │ ├── make-entry-point-plugin.ts
│ │ │ └── watch-rebuild-plugin.ts
│ │ ├── package.json
│ │ ├── rollup.config.mjs
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── protobuf/
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── lib/
│ │ │ └── protobuf/
│ │ │ ├── code.ts
│ │ │ ├── index.ts
│ │ │ └── proto/
│ │ │ ├── cookie.d.ts
│ │ │ └── cookie.js
│ │ ├── package.json
│ │ ├── proto/
│ │ │ └── cookie.proto
│ │ ├── tsconfig.json
│ │ ├── tsup.config.ts
│ │ ├── utils/
│ │ │ ├── base64.ts
│ │ │ ├── compress.ts
│ │ │ ├── encryption.test.ts
│ │ │ ├── encryption.ts
│ │ │ └── index.ts
│ │ └── vitest.config.ts
│ ├── shared/
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── lib/
│ │ │ ├── Providers/
│ │ │ │ ├── ThemeProvider.tsx
│ │ │ │ ├── hooks/
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── useTheme.ts
│ │ │ │ └── index.tsx
│ │ │ ├── cloudflare/
│ │ │ │ ├── api.ts
│ │ │ │ ├── enum.ts
│ │ │ │ └── index.ts
│ │ │ ├── cookie/
│ │ │ │ ├── index.ts
│ │ │ │ ├── withCloudflare.ts
│ │ │ │ └── withStorage.ts
│ │ │ ├── github/
│ │ │ │ ├── api.ts
│ │ │ │ └── index.ts
│ │ │ ├── hoc/
│ │ │ │ ├── index.ts
│ │ │ │ ├── withErrorBoundary.tsx
│ │ │ │ └── withSuspense.tsx
│ │ │ ├── hooks/
│ │ │ │ ├── index.ts
│ │ │ │ ├── useCookieAction.ts
│ │ │ │ ├── useStorage.ts
│ │ │ │ └── useStorageSuspense.tsx
│ │ │ ├── message/
│ │ │ │ └── index.ts
│ │ │ └── utils/
│ │ │ └── index.ts
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── tsup.config.ts
│ ├── storage/
│ │ ├── index.ts
│ │ ├── lib/
│ │ │ ├── accountStorage.ts
│ │ │ ├── base.ts
│ │ │ ├── cookieStorage.ts
│ │ │ ├── domainConfigStorage.ts
│ │ │ ├── domainStatusStorage.ts
│ │ │ ├── index.ts
│ │ │ ├── settingsStorage.ts
│ │ │ └── themeStorage.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── tailwind-config/
│ │ ├── package.json
│ │ └── tailwind.config.js
│ ├── tsconfig/
│ │ ├── app.json
│ │ ├── base.json
│ │ ├── package.json
│ │ └── utils.json
│ ├── ui/
│ │ ├── components.json
│ │ ├── globals.css
│ │ ├── package.json
│ │ ├── postcss.config.js
│ │ ├── src/
│ │ │ ├── components/
│ │ │ │ ├── DateTable/
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── Image/
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── Spinner/
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── ThemeDropdown/
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── Tooltip/
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── ui/
│ │ │ │ ├── alert-dialog.tsx
│ │ │ │ ├── alert.tsx
│ │ │ │ ├── avatar.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── input.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── popover.tsx
│ │ │ │ ├── select.tsx
│ │ │ │ ├── sonner.tsx
│ │ │ │ ├── switch.tsx
│ │ │ │ ├── table.tsx
│ │ │ │ ├── toggle.tsx
│ │ │ │ └── tooltip.tsx
│ │ │ ├── index.ts
│ │ │ └── libs/
│ │ │ ├── index.ts
│ │ │ └── utils.ts
│ │ ├── tailwind.config.js
│ │ ├── tsconfig.json
│ │ └── tsup.config.ts
│ └── zipper/
│ ├── index.mts
│ ├── lib/
│ │ └── index.ts
│ ├── package.json
│ └── tsconfig.json
├── pages/
│ ├── content/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── listener.ts
│ │ │ ├── localStorage.ts
│ │ │ └── observer.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.mts
│ ├── options/
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── postcss.config.js
│ │ ├── src/
│ │ │ ├── Options.tsx
│ │ │ ├── components/
│ │ │ │ ├── SettingsPopover.tsx
│ │ │ │ └── StorageSelect.tsx
│ │ │ ├── hooks/
│ │ │ │ └── useGithub.ts
│ │ │ ├── index.css
│ │ │ └── index.tsx
│ │ ├── tailwind.config.js
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── popup/
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── postcss.config.js
│ │ ├── src/
│ │ │ ├── Popup.tsx
│ │ │ ├── components/
│ │ │ │ └── AutoSwtich/
│ │ │ │ └── index.tsx
│ │ │ ├── hooks/
│ │ │ │ └── useDomainConfig.ts
│ │ │ ├── index.css
│ │ │ ├── index.tsx
│ │ │ └── utils/
│ │ │ └── index.ts
│ │ ├── tailwind.config.js
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ └── sidepanel/
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── src/
│ │ ├── SidePanel.tsx
│ │ ├── components/
│ │ │ └── CookieTable/
│ │ │ ├── SearchInput.tsx
│ │ │ ├── hooks/
│ │ │ │ ├── useAction.ts
│ │ │ │ ├── useCookieItem.ts
│ │ │ │ └── useSelected.tsx
│ │ │ └── index.tsx
│ │ ├── index.css
│ │ ├── index.html
│ │ └── index.tsx
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ └── vite.config.ts
├── pnpm-workspace.yaml
├── private-policy.md
└── turbo.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintignore
================================================
dist
node_modules
tailwind.config.js
================================================
FILE: .eslintrc
================================================
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"plugin:import/recommended",
"plugin:jsx-a11y/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint", "react-hooks", "import", "jsx-a11y", "prettier"],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"prettier/prettier": "error",
"react/react-in-jsx-scope": "off",
"import/no-unresolved": "off",
"jsx-a11y/click-events-have-key-events": "warn"
},
"globals": {
"chrome": "readonly"
},
"ignorePatterns": ["watch.js", "dist/**"]
}
================================================
FILE: .github/CODEOWNERS
================================================
* @jackluson
================================================
FILE: .github/FUNDING.yml
================================================
github: jackluson
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: jackluson
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Mac, Window, Linux]
- Browser [e.g. chrome, firefox]
- Node Version [e.g. 18.12.0]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: jackluson
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/auto_assign.yml
================================================
# Set to true to add reviewers to pull requests
addReviewers: true
# Set to true to add assignees to pull requests
addAssignees: author
# A list of reviewers to be added to pull requests (GitHub user name)
reviewers:
- jackluson
# A number of reviewers added to the pull request
# Set 0 to add all the reviewers (default: 0)
numberOfReviewers: 0
# A list of assignees, overrides reviewers if set
# assignees:
# - assigneeA
# A number of assignees to add to the pull request
# Set to 0 to add all of the assignees.
# Uses numberOfReviewers if unset.
# numberOfAssignees: 2
# A list of keywords to be skipped the process that add reviewers if pull requests include it
# skipKeywords:
# - wip
filterLabels:
exclude:
- dependencies
================================================
FILE: .github/dependabot.yml
================================================
# 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
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
================================================
FILE: .github/stale.yml
================================================
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 90
# Number of days of inactivity before a stale Issue or Pull Request is closed
daysUntilClose: 30
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- pinned
- security
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when removing the stale label. Set to `false` to disable
unmarkComment: false
# Comment to post when closing a stale Issue or Pull Request. Set to `false` to disable
closeComment: true
# Limit to only `issues` or `pulls`
only: issues
================================================
FILE: .github/workflows/auto-assign.yml
================================================
name: 'Auto Assign'
on:
pull_request:
types: [opened, ready_for_review]
jobs:
add-reviews:
runs-on: ubuntu-latest
steps:
- uses: kentaro-m/auto-assign-action@v1.2.5
with:
configuration-path: '.github/auto_assign.yml'
================================================
FILE: .github/workflows/build-zip.yml
================================================
name: Build And Upload Extension Zip Via Artifact
on:
push:
branches: [ main ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: ".nvmrc"
- uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.OS }}-build-${{ hashFiles('**/pnpm-lock.yaml') }}
- uses: pnpm/action-setup@v4
- run: pnpm install --frozen-lockfile
- run: pnpm build
- uses: actions/upload-artifact@v4
with:
path: dist/*
================================================
FILE: .github/workflows/claude.yml
================================================
name: Claude Code Integration
on:
# 当 Issue 或 PR 中有新评论时触发(支持 @claude 唤醒)
issue_comment:
types: [created]
# 当代码推送或创建拉取请求时自动触发功能
pull_request:
types: [opened, synchronize, reopened]
permissions:
id-token: write # 必须添加这个权限,才能生成 ID Token 并消除报错
contents: write # 允许工作流读写代码(Claude Code 常用)
pull-requests: write # 允许在 PR 下回复评论
issues: write # 如果你在 issue comment 里触发,需要这个
jobs:
claude-code-action:
runs-on: ubuntu-latest
# 限制仅在需要时运行,节省资源
if: >
github.event_name == 'pull_request' ||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude'))
steps:
# 1. 检出代码
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # 获取完整历史记录以便 Claude 分析上下文
# 2. 调用 Claude Code 官方 Action
- name: Run Claude Code
uses: anthropics/claude-code-action@v1 # 请以实际官方最新版本号为准
with:
# 提供 GitHub token 以便 Claude 能回复评论和修改代码
github-token: ${{ secrets.GITHUB_TOKEN }}
claude_code_oauth_token: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
env:
# 以下环境变量在 Github Secrets 中提前配置好
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }} # 例如填写 OpenRouter 地址
ANTHROPIC_AUTH_TOKEN: ${{ secrets.ANTHROPIC_AUTH_TOKEN }} # 填写中转 Key
ANTHROPIC_API_KEY: "" # 必须为空
ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5"
ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-5"
ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5"
================================================
FILE: .github/workflows/greetings.yml
================================================
name: Greetings
on: [pull_request_target, issues]
jobs:
greeting:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: 'Thank you for your contribution. We will check and reply to you as soon as possible.'
pr-message: 'Thank you for your contribution. We will check and reply to you as soon as possible.'
================================================
FILE: .gitignore
================================================
# dependencies
**/node_modules
# testing
**/coverage
# build
**/dist
**/dist-zip
**/build
# env
**/.env.local
**/.env
# etc
.DS_Store
.idea
**/.turbo
# compiled
apps/chrome-extension/public/manifest.json
================================================
FILE: .npmrc
================================================
public-hoist-pattern[]=@testing-library/dom
================================================
FILE: .nvmrc
================================================
20.13.1
================================================
FILE: .prettierignore
================================================
dist
node_modules
proto
.gitignore
.github
.eslintignore
.husky
.nvmrc
.prettierignore
LICENSE
*.md
pnpm-lock.yaml
================================================
FILE: .prettierrc
================================================
{
"trailingComma": "all",
"semi": true,
"singleQuote": true,
"arrowParens": "avoid",
"printWidth": 120,
"bracketSameLine": true,
"htmlWhitespaceSensitivity": "strict"
}
================================================
FILE: .vscode/settings.json
================================================
{
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"editor.codeActionsOnSave": {
"source.formatDocument": "explicit",
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
},
"files.insertFinalNewline": true,
"deepscan.enable": true
}
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2024 Jack Lu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<div align="center">
<img src="chrome-extension/public/icon-128.png" alt="logo"/>
<h1> Sync your cookie to Your Cloudflare or Github Gist</h1>




<!-- <img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https://github.com/jackluson/sync-your-cookieFactions&count_bg=%23#222222&title_bg=%23#454545&title=😀&edge_flat=true" alt="hits"/> -->
</div>
[English](./README.md) | [中文](./README_ZH.md)
`Sync your cookie` is a chrome extension that helps you to sync your cookie to Cloudflare or Github Gist. It's a useful tool for web developers to share cookies between different devices.
### Install
Chrome: [Sync Your Cookie](https://chromewebstore.google.com/detail/sync-your-cookie/bcegpckmgklcpcapnbigfdadedcneopf)
Edge: [Sync Your Cookie](https://microsoftedge.microsoft.com/addons/detail/sync-your-cookie/ohlcghldllgnmkegocpcphdbbphikgfm)
### Features
- Supports syncing cookies to Cloudflare or Github Gist (Include LocalStorage)
- Supports configuring `Auto Merge` and `Auto Push` rules for different sites
- Cookie data is transmitted via protobuf encoding
- Provides a management panel to facilitate viewing, copying, and managing synchronized cookie data
- Multi-account synchronization based on Storage-key
### Project Screenshots
Account Settings Page
<img width="600" src="./screenshots/settings_v2.png" alt="account settings"/>
Cookie Sync Popup Page
<img width="600" src="./screenshots/sync.png" alt="cookie sync popup"/>
Cookie Manager Sidebar Panel
<img width="600" src="./screenshots/panel.png" alt="cookie manager sidebar panel"/>
Cookie Detail
<img width="600" src="./screenshots/panel_item.png" alt="cookie manager sidebar panel"/>
LocalStorage Detail
<img width="600" src="./screenshots/panel_item_localStorage.png" alt="cookie manager sidebar panel"/>
Pushed Cookie on Github Gist
<img width="600" src="./screenshots/gist.png" alt="Pushed Cookie on Github Gist"/>
Pushed Cookie on Cloudflare
<img width="600" src="./screenshots/key_value.png" alt="Pushed Cookie on Cloudflare"/>
### Usage
[How to use](./how-to-use.md)
### TODO
- [x] Custom Save Configure
- [x] Multi-account synchronization based on Storage-key
- [x] Sync LocalStorage
- [x] More Cloud Platform (First github gist)
### Privacy Policy
Please refer to [Privacy Policy](./private-policy.md) for more information.
### Support
If you find this project helpful, you can support the development by:
- Starring the repository ⭐
- [Sponsoring via Ko-fi](https://ko-fi.com/jacklu) 💖
- Sponsor via Wechat
<div>
<img src="./screenshots/wechat_sponsor.jpg" alt="微信支付" style="width: 150px;">
</div>
- Sharing it with others 🚀
================================================
FILE: README_ZH.md
================================================
<div align="center">
<img src="chrome-extension/public/icon-128.png" alt="logo"/>
<h1> Sync your cookie to Cloudflare or Github Gist</h1>
</div>
[English](./README.md) | [中文](./README_ZH.md)
`Sync your cookie` 是一个 Chrome 扩展程序,它可以帮助您将 Cookie 同步到 Cloudflare。它是一个有用的工具,用于在不同设备之间共享 Cookie, 免去了登录流程的烦恼,此外也提供了cookie管理面板查看,管理已经过同步的 cookie。
### 安装
Chrome: [Sync Your Cookie](https://chromewebstore.google.com/detail/sync-your-cookie/bcegpckmgklcpcapnbigfdadedcneopf)
Edge: [Sync Your Cookie](https://microsoftedge.microsoft.com/addons/detail/sync-your-cookie/ohlcghldllgnmkegocpcphdbbphikgfm)
### 功能
- 支持同步 Cookie 到 Cloudflare 或者 github Gist (支持LocalStorage)
- 支持为不同站点配置`Auto Merge`和`Auto Push`规则
- Cookie数据经过 protobuf 编码传输
- 提供了一个管理面板,方便查看、复制、管理已经同步的 Cookie 数据
- 可配置多个Key,支持多账户同步
### 项目截图
账号设置页面
<img width="600" src="./screenshots/settings_v2.png" alt="account settings"/>
Cookie 同步页面
<img width="600" src="./screenshots/sync.png" alt="cookie sync popup"/>
Cookie 管理侧边栏面板
<img width="600" src="./screenshots/panel.png" alt="cookie manager sidebar panel"/>
Cookie 详情
<img width="600" src="./screenshots/panel_item.png" alt="cookie manager sidebar panel"/>
Github Gist上传的cookie
<img width="600" src="./screenshots/gist.png" alt="Pushed Cookie on Github Gist"/>
Cloudflare上传的cookie
<img width="600" src="./screenshots/key_value.png" alt="Pushed Cookie on Cloudflare"/>
### 使用指引
[How to use](./how-to-use.md)
### Privacy Policy
Please refer to [Privacy Policy](./private-policy.md) for more information.
### 赞赏
如果你觉得这个项目对你有帮助,欢迎通过以下方式支持我:
- 给项目点个 Star ⭐
- [通过 Ko-fi 赞助](https://ko-fi.com/jacklu) 💖
- 通过 微信支付 赞助
<div>
<img src="./screenshots/wechat_sponsor.jpg" alt="微信支付" style="width: 150px;">
</div>
- 分享给更多需要的人 🚀
================================================
FILE: chrome-extension/lib/background/badge.ts
================================================
export function setBadge(text: string, color: string = '#7246e4') {
chrome.action.setBadgeText({ text });
chrome.action.setBadgeBackgroundColor({ color });
}
export function clearBadge() {
chrome.action.setBadgeText({ text: '' });
}
export function setPullingBadge() {
setBadge('↓');
}
export function setPushingBadge() {
setBadge('↑');
}
export function setPushingAndPullingBadge() {
// badge('↓↑');
setBadge('⇅');
}
================================================
FILE: chrome-extension/lib/background/contextMenu.ts
================================================
let globalMenuId: number | string = '';
export const initContextMenu = () => {
globalMenuId = chrome.contextMenus.create({
id: 'openSidePanel',
title: 'Open Cookie Manager',
contexts: ['all'],
});
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === 'openSidePanel' && tab?.windowId) {
// This will open the panel in all the pages on the current window.
console.log('openSidePanel->tab', tab);
chrome.sidePanel.open({ windowId: tab.windowId });
}
});
};
export const removeContextMenu = () => {
if (globalMenuId) {
chrome.contextMenus.remove(globalMenuId);
}
};
================================================
FILE: chrome-extension/lib/background/index.ts
================================================
// sort-imports-ignore
import 'webextension-polyfill';
import { initGithubApi, pullAndSetCookies, pullCookies, pushMultipleDomainCookies } from '@sync-your-cookie/shared';
import { cookieStorage } from '@sync-your-cookie/storage/lib/cookieStorage';
import { domainConfigStorage } from '@sync-your-cookie/storage/lib/domainConfigStorage';
import { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';
import { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';
import { initContextMenu } from './contextMenu';
import { refreshListen } from './listen';
import { initSubscribe } from './subscribe';
const ping = () => {
chrome.tabs.query({ active: true, currentWindow: true }, async function (tabs) {
if (tabs.length === 0) {
// const allOpendTabs = await chrome.tabs.query({});
console.log('No active tab found, try alternative way');
// reject({ isOk: false, msg: 'No active tab found' } as SendResponse);
return;
}
chrome.tabs.sendMessage(tabs[0].id!, 'ping', function (result) {
console.log('result->', result);
});
});
// setTimeout(ping, 4000);
};
const init = async () => {
try {
await refreshListen();
console.log('initListen finish');
await initSubscribe(); // await state reset finish
console.log('initSubscribe finish');
await pullCookies(true);
console.log('initPullCookies finish');
// ping();
} catch (error) {
console.log('init-->error', error);
}
};
chrome.runtime.onInstalled.addListener(async () => {
init();
console.log('onInstalled');
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: false });
const settingsSnapShot = await settingsStorage.get();
if (settingsSnapShot?.contextMenu) {
initContextMenu();
}
});
let delayTimer: NodeJS.Timeout | null = null;
let checkDelayTimer: NodeJS.Timeout | null = null;
let timeoutFlag = false;
const changedDomainSet = new Set<string>();
chrome.cookies.onChanged.addListener(async changeInfo => {
const domainConfigSnapShot = await domainConfigStorage.getSnapshot();
const domain = changeInfo.cookie.domain;
const domainMap = domainConfigSnapShot?.domainMap || {};
const removeHeadDomain = domain.startsWith('.') ? domain.slice(1) : domain;
let flag = false;
for (const key in domainMap) {
if (key.endsWith(removeHeadDomain) && domainMap[key]?.autoPush) {
flag = true;
break;
}
}
if (!flag) return;
if (delayTimer && timeoutFlag) {
return;
}
delayTimer && clearTimeout(delayTimer);
changedDomainSet.add(removeHeadDomain);
delayTimer = setTimeout(async () => {
timeoutFlag = false;
if (checkDelayTimer) {
clearTimeout(checkDelayTimer);
}
const domainConfig = await domainConfigStorage.get();
// const pushDomainSet = new Set<string>();
const pushDomainHostMap = new Map<string, string[]>();
for (const domain of changedDomainSet) {
for (const key in domainConfig.domainMap) {
if (key.endsWith(domain) && domainConfig.domainMap[key]?.autoPush) {
// pushDomainSet.add(domain);
const existedHost = pushDomainHostMap.get(domain) || [];
pushDomainHostMap.set(domain, [key, ...existedHost]);
}
}
}
const uploadDomainCookies = [];
const cookieMap = await cookieStorage.getSnapshot();
const userAgent = navigator?.userAgent || '';
console.log('pushDomainHostMap', pushDomainHostMap);
for (const domain of pushDomainHostMap.keys()) {
const hosts = pushDomainHostMap.get(domain) || [];
// const [domain] = await extractDomainAndPort(host);
const cookies = await chrome.cookies.getAll({
domain: domain,
});
for (const host of hosts) {
uploadDomainCookies.push({
domain: host,
cookies,
localStorageItems: cookieMap?.domainCookieMap?.[host]?.localStorageItems || [],
userAgent,
});
}
}
if (uploadDomainCookies.length) {
await pushMultipleDomainCookies(uploadDomainCookies);
changedDomainSet.clear();
}
}, 10000);
if (!checkDelayTimer) {
checkDelayTimer = setTimeout(() => {
if (delayTimer) {
console.info('checkDelayTimer timeout');
timeoutFlag = true;
delayTimer = null;
}
checkDelayTimer = null;
}, 30000);
}
});
let previousActiveTabList: chrome.tabs.Tab[] = [];
chrome.tabs.onUpdated.addListener(async function (tabId, changeInfo, tab) {
// 1. current tab not exist in the tabMap
// read changeInfo data and do something with it (like read the url)
if (changeInfo.status === 'loading' && changeInfo.url) {
const domainConfig = await domainConfigStorage.get();
let pullDomain = '';
let needPull = false;
for (const key in domainConfig.domainMap) {
if (new URL(changeInfo.url).host.endsWith(key) && domainConfig.domainMap[key]?.autoPull) {
needPull = true;
pullDomain = key;
break;
// await pullCookies();
}
}
if (needPull) {
const allOpendTabs = await chrome.tabs.query({});
const otherExistedTabs = allOpendTabs.filter(itemTab => tab.id !== itemTab.id);
for (const itemTab of otherExistedTabs) {
if (itemTab.url && new URL(itemTab.url).host === new URL(changeInfo.url).host) {
needPull = false;
break;
}
}
}
if (needPull) {
for (const itemTab of previousActiveTabList) {
if (itemTab.url && new URL(itemTab.url).host === new URL(changeInfo.url).host) {
needPull = false;
break;
}
}
}
if (needPull) {
await pullAndSetCookies(changeInfo.url, pullDomain);
}
const allActiveTabs = await chrome.tabs.query({
active: true,
});
previousActiveTabList = allActiveTabs;
}
});
// let previousUrl = '';
// chrome.webNavigation?.onBeforeNavigate.addListener(function (object) {
// chrome.tabs.get(object.tabId, function (tab) {
// previousUrl = tab.url || '';
// console.log('previousUrl', previousUrl);
// });
// });
// chrome.tabs.onRemoved.addListener(async function (tabId, removeInfo) {
// const allActiveTabs = await chrome.tabs.query({
// active: true,
// });
// previousActiveTabList = allActiveTabs;
// });
chrome.tabs.onActivated.addListener(async function () {
const allActiveTabs = await chrome.tabs.query({
active: true,
});
previousActiveTabList = allActiveTabs;
console.log('refreshListen', previousActiveTabList);
const domainStatus = await domainStatusStorage.get();
const settingsStorageInfo = await settingsStorage.get();
if (!domainStatus.pulling && !domainStatus.pushing && !settingsStorageInfo.localStorageGetting) {
refreshListen();
}
});
initGithubApi(true);
================================================
FILE: chrome-extension/lib/background/listen.ts
================================================
import {
check,
checkResponseAndCallback,
CookieOperator,
extractDomainAndPort,
ICookie,
Message,
MessageType,
pullAndSetCookies,
PushCookieMessagePayload,
pushCookies,
removeCookieItem,
removeCookies,
sendGetLocalStorageMessage,
SendResponse,
} from '@sync-your-cookie/shared';
import { cookieStorage } from '@sync-your-cookie/storage/lib/cookieStorage';
import { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';
import { domainConfigStorage } from '@sync-your-cookie/storage/lib/domainConfigStorage';
import { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';
type HandleCallback = (response?: SendResponse) => void;
const handlePush = async (payload: PushCookieMessagePayload, callback: HandleCallback) => {
const { sourceUrl, host, favIconUrl } = payload || {};
const userAgent = navigator?.userAgent || '';
try {
await check();
await domainConfigStorage.updateItem(host, {
sourceUrl: sourceUrl,
favIconUrl,
});
await domainStatusStorage.updateItem(host, {
pushing: true,
});
const [domain, port, hostname] = await extractDomainAndPort(host);
const condition = sourceUrl ? { url: sourceUrl } : { domain: domain };
const cookies = await chrome.cookies.getAll(condition);
let localStorageItems: NonNullable<Parameters<typeof pushCookies>[2]> = [];
const includeLocalStorage = settingsStorage.getSnapshot()?.includeLocalStorage;
if (includeLocalStorage) {
try {
const hostname = sourceUrl ? new URL(sourceUrl).hostname : host;
localStorageItems = await sendGetLocalStorageMessage(hostname);
} catch (error) {
console.error('sendGetLocalStorageMessage error', error);
const cookieMap = await cookieStorage.getSnapshot();
localStorageItems = cookieMap?.domainCookieMap?.[host]?.localStorageItems || [];
}
} else {
const cookieMap = await cookieStorage.getSnapshot();
localStorageItems = cookieMap?.domainCookieMap?.[host]?.localStorageItems || [];
}
if (cookies?.length || localStorageItems.length) {
const res = await pushCookies(host, cookies, localStorageItems, userAgent);
checkResponseAndCallback(res, 'push', callback);
} else {
callback({ isOk: false, msg: 'no cookies and localStorageItems found', result: cookies });
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
checkResponseAndCallback(err, 'push', callback);
} finally {
await domainStatusStorage.togglePushingState(host, false);
}
};
const handlePull = async (activeTabUrl: string, domain: string, isReload: boolean, callback: HandleCallback) => {
try {
await check();
await domainStatusStorage.togglePullingState(domain, true);
const cookieMap = await pullAndSetCookies(activeTabUrl, domain, isReload);
console.log('handlePull->cookieMap', cookieMap);
callback({ isOk: true, msg: 'Pull success', result: cookieMap });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
checkResponseAndCallback(err, 'pull', callback);
} finally {
await domainStatusStorage.togglePullingState(domain, false);
}
};
const handleRemove = async (domain: string, callback: HandleCallback) => {
try {
await check();
const res = await removeCookies(domain);
checkResponseAndCallback(res, 'remove', callback);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
checkResponseAndCallback(err, 'remove', callback);
// callback({ isOk: false, msg: (err as Error).message || 'remove fail, please try again ', result: err });
}
};
const handleRemoveItem = async (domain: string, id: string, callback: HandleCallback) => {
try {
await check();
const res = await removeCookieItem(domain, id);
checkResponseAndCallback(res, 'delete', callback);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
checkResponseAndCallback(err, 'delete', callback);
}
};
const handleEditItem = async (domain: string, oldItem: ICookie, newItem: ICookie, callback: HandleCallback) => {
try {
await check();
const res = await CookieOperator.editCookieItem(domain, oldItem, newItem);
checkResponseAndCallback(res, 'edit', callback);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
checkResponseAndCallback(err, 'edit', callback);
}
};
function handleMessage(
message: Message,
sender: chrome.runtime.MessageSender,
callback: (response?: SendResponse) => void,
) {
const type = message.type;
switch (type) {
case MessageType.PushCookie:
handlePush(message.payload, callback);
break;
case MessageType.PullCookie:
// eslint-disable-next-line no-case-declarations, @typescript-eslint/no-non-null-asserted-optional-chain
const activeTabUrl = message.payload.activeTabUrl || sender.tab?.url!;
handlePull(activeTabUrl!, message.payload.domain, message.payload.reload, callback);
break;
case MessageType.RemoveCookie:
handleRemove(message.payload.domain, callback);
break;
case MessageType.RemoveCookieItem:
handleRemoveItem(message.payload.domain, message.payload.id, callback);
break;
case MessageType.EditCookieItem:
handleEditItem(message.payload.domain, message.payload.oldItem, message.payload.newItem, callback);
break;
default:
break;
}
return true;
}
export const refreshListen = async () => {
chrome.runtime.onMessage.removeListener(handleMessage);
chrome.runtime.onMessage.addListener(handleMessage);
};
================================================
FILE: chrome-extension/lib/background/subscribe.ts
================================================
import { accountStorage } from '@sync-your-cookie/storage/lib/accountStorage';
import { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';
import { pullCookies } from '@sync-your-cookie/shared';
import { cookieStorage } from '@sync-your-cookie/storage/lib/cookieStorage';
import { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';
import { clearBadge, setPullingBadge, setPushingAndPullingBadge, setPushingBadge } from './badge';
import { initContextMenu, removeContextMenu } from './contextMenu';
export const initSubscribe = async () => {
await domainStatusStorage.resetState();
domainStatusStorage.subscribe(async () => {
const domainStatus = await domainStatusStorage.get();
if (domainStatus?.pulling && domainStatus.pushing) {
setPushingAndPullingBadge();
} else if (domainStatus?.pushing) {
setPushingBadge();
} else if (domainStatus?.pulling) {
setPullingBadge();
} else {
clearBadge();
}
});
accountStorage.subscribe(async () => {
await domainStatusStorage.resetState();
await cookieStorage.reset();
await pullCookies();
console.log('reset finished');
});
let previousContextMenu: boolean | undefined = undefined;
settingsStorage.subscribe(async () => {
const settingsSnapShot = await settingsStorage.getSnapshot();
if (previousContextMenu === settingsSnapShot?.contextMenu) {
return;
}
previousContextMenu = settingsSnapShot?.contextMenu;
if (settingsSnapShot?.contextMenu) {
initContextMenu();
} else {
removeContextMenu();
}
});
};
================================================
FILE: chrome-extension/manifest.js
================================================
import fs from 'node:fs';
const packageJson = JSON.parse(fs.readFileSync('../package.json', 'utf8'));
const isFirefox = process.env.__FIREFOX__ === 'true';
const sidePanelConfig = {
side_panel: {
default_path: 'sidepanel/index.html',
},
permissions: !isFirefox ? ['sidePanel', 'contextMenus'] : [],
};
/**
* After changing, please reload the extension at `chrome://extensions`
* @type {chrome.runtime.ManifestV3}
*/
const manifest = Object.assign(
{
manifest_version: 3,
default_locale: 'en',
/**
* if you want to support multiple languages, you can use the following reference
* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Internationalization
*/
name: 'Sync Your Cookie',
version: packageJson.version,
description: 'A browser extension for syncing cookies and localStorage to Cloudflare KV or GitHub Gist',
permissions: ['cookies', 'activeTab', 'tabs', 'storage', 'identity'].concat(sidePanelConfig.permissions),
host_permissions: ['<all_urls>'],
options_page: 'options/index.html',
background: {
service_worker: 'background.iife.js',
type: 'module',
},
action: {
default_popup: 'popup/index.html',
default_icon: 'icon-34.png',
},
// key: key,
// chrome_url_overrides: {
// newtab: 'newtab/index.html',
// },
icons: {
128: 'icon-128.png',
},
content_scripts: [
{
matches: ['*://*/*'],
js: ['content/index.iife.js'],
run_at: 'document_start',
},
],
// devtools_page: 'devtools/index.html',
web_accessible_resources: [
{
resources: ['*.js', '*.css', '*.svg', 'icon-128.png', 'icon-34.png'],
matches: ['*://*/*'],
},
],
},
!isFirefox && { side_panel: { ...sidePanelConfig.side_panel } },
);
export default manifest;
================================================
FILE: chrome-extension/package.json
================================================
{
"name": "chrome-extension",
"version": "1.4.2",
"description": "chrome extension",
"scripts": {
"clean": "rimraf ../../dist && rimraf .turbo",
"build": "tsc --noEmit && vite build",
"build:firefox": "tsc --noEmit && cross-env __FIREFOX__=true vite build",
"build:watch": "cross-env __DEV__=true vite build -w --mode development",
"build:firefox:watch": "cross-env __DEV__=true __FIREFOX__=true vite build -w --mode development",
"dev": "pnpm build:watch",
"dev:firefox": "pnpm build:firefox:watch",
"test": "vitest run",
"lint": "eslint ./ --ext .ts,.js,.tsx,.jsx",
"lint:fix": "pnpm lint --fix",
"prettier": "prettier . --write",
"type-check": "tsc --noEmit"
},
"type": "module",
"dependencies": {
"@sync-your-cookie/shared": "workspace:*",
"@sync-your-cookie/storage": "workspace:*",
"p-timeout": "^6.1.4",
"webextension-polyfill": "^0.12.0"
},
"devDependencies": {
"@laynezh/vite-plugin-lib-assets": "^0.5.21",
"@sync-your-cookie/dev-utils": "workspace:*",
"@sync-your-cookie/hmr": "workspace:*",
"@sync-your-cookie/tsconfig": "workspace:*",
"@types/ws": "^8.5.10",
"magic-string": "^0.30.12",
"ts-loader": "^9.5.1"
}
}
================================================
FILE: chrome-extension/public/_locales/en/messages.json
================================================
{
"extensionDescription": {
"description": "sync your cookie free",
"message": "Sync your cookies to cloudlfare "
},
"extensionName": {
"description": "Sync your cookie",
"message": "Sync your cookie"
}
}
================================================
FILE: chrome-extension/public/content.css
================================================
================================================
FILE: chrome-extension/tsconfig.json
================================================
{
"extends": "@sync-your-cookie/tsconfig/app.json",
"compilerOptions": {
"baseUrl": "./",
"declaration": true,
"declarationMap": true,
"types": ["vite/client", "node", "chrome"],
"paths": {
"@root/*": ["./*"],
"@lib/*": ["lib/*"]
}
},
"include": ["lib", "utils", "vite.config.ts", "node_modules/@types"]
}
================================================
FILE: chrome-extension/utils/plugins/make-manifest-plugin.ts
================================================
import * as fs from 'fs';
import * as path from 'path';
import { ManifestParser, colorLog } from '@sync-your-cookie/dev-utils';
import type { PluginOption } from 'vite';
import { pathToFileURL } from 'url';
import * as process from 'process';
const { resolve } = path;
const rootDir = resolve(__dirname, '..', '..');
const manifestFile = resolve(rootDir, 'manifest.js');
const getManifestWithCacheBurst = (): Promise<{ default: chrome.runtime.ManifestV3 }> => {
const withCacheBurst = (path: string) => `${path}?${Date.now().toString()}`;
/**
* In Windows, import() doesn't work without file:// protocol.
* So, we need to convert path to file:// protocol. (url.pathToFileURL)
*/
if (process.platform === 'win32') {
return import(withCacheBurst(pathToFileURL(manifestFile).href));
}
return import(withCacheBurst(manifestFile));
};
export default function makeManifestPlugin(config: { outDir: string }): PluginOption {
function makeManifest(manifest: chrome.runtime.ManifestV3, to: string) {
if (!fs.existsSync(to)) {
fs.mkdirSync(to);
}
const manifestPath = resolve(to, 'manifest.json');
const isFirefox = process.env.__FIREFOX__;
fs.writeFileSync(manifestPath, ManifestParser.convertManifestToString(manifest, isFirefox ? 'firefox' : 'chrome'));
colorLog(`Manifest file copy complete: ${manifestPath}`, 'success');
}
return {
name: 'make-manifest',
buildStart() {
this.addWatchFile(manifestFile);
},
async writeBundle() {
const outDir = config.outDir;
const manifest = await getManifestWithCacheBurst();
makeManifest(manifest.default, outDir);
},
};
}
================================================
FILE: chrome-extension/vite.config.ts
================================================
import libAssetsPlugin from '@laynezh/vite-plugin-lib-assets';
import { watchRebuildPlugin } from '@sync-your-cookie/hmr';
import { resolve } from 'path';
import { defineConfig } from 'vite';
import makeManifestPlugin from './utils/plugins/make-manifest-plugin';
const rootDir = resolve(__dirname);
const libDir = resolve(rootDir, 'lib');
const isDev = process.env.__DEV__ === 'true';
const isProduction = !isDev;
const outDir = resolve(rootDir, '..', 'dist');
export default defineConfig({
resolve: {
alias: {
'@root': rootDir,
'@lib': libDir,
'@assets': resolve(libDir, 'assets'),
},
},
define: {
'process.env.NODE_ENV': isDev ? `"development"` : `"production"`,
},
plugins: [
libAssetsPlugin({
outputPath: outDir,
}),
makeManifestPlugin({ outDir }),
isDev && watchRebuildPlugin({ reload: true }),
],
publicDir: resolve(rootDir, 'public'),
build: {
lib: {
formats: ['iife'],
entry: resolve(__dirname, 'lib/background/index.ts'),
name: 'BackgroundScript',
fileName: 'background',
},
outDir,
sourcemap: isDev,
minify: isProduction,
reportCompressedSize: isProduction,
modulePreload: true,
rollupOptions: {
external: ['chrome'],
output: {
inlineDynamicImports: true,
},
},
},
});
================================================
FILE: googlef759ff453695209f.html
================================================
google-site-verification: googlef759ff453695209f.html
================================================
FILE: how-to-use.md
================================================
## How to use
`Sync-Your-Cookie` uses Cloudflare [KV](https://developers.cloudflare.com/kv/) to store cookie data. Here is a tutorial on how to configure KV and Token:
## Create Namespace

Input

Your NamespaceId

## Your AccountId

## Create Token
1. Enter Profile Page

2. Custom Permission

3. Select KV Read and Write Permission

4. Confirm Create

5. Copy Token

6. Your Token List

7. Paste Your Account Info And Save

8. Push Your Cookie

9. Check Your Cookie
The uploaded cookie is a protobuf-encoded string

## Reference
- [create-token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/)
- [account-owned-tokens](https://developers.cloudflare.com/fundamentals/api/get-started/account-owned-tokens/)
================================================
FILE: package.json
================================================
{
"name": "sync-your-cookie",
"version": "1.4.2",
"description": "sync your cookie extension monorepo",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/jackluson/sync-your-cookie.git"
},
"scripts": {
"clean": "rimraf dist && rimraf .turbo && turbo clean",
"build": "turbo build",
"build:firefox": "cross-env __FIREFOX__=true turbo build",
"dev-server": "pnpm -F hmr build && pnpm -F hmr dev-server",
"dev:apps": "cross-env __DEV__=true turbo run dev --filter=./pages/* --filter=chrome-extension --filter=@sync-your-cookie/hmr --concurrency 15",
"dev": "cross-env __DEV__=true turbo run dev --concurrency 25",
"dev:firefox": "cross-env __DEV__=true __FIREFOX__=true turbo dev --concurrency 20",
"zip": "pnpm build && pnpm -F zipper zip",
"test": "turbo test",
"type-check": "turbo type-check",
"lint": "turbo lint",
"lint:fix": "turbo lint:fix",
"prettier": "turbo prettier"
},
"type": "module",
"dependencies": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@types/chrome": "^0.0.268",
"@types/node": "^20.12.11",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react-swc": "^3.6.0",
"autoprefixer": "^10.4.19",
"concurrently": "^8.2.2",
"cross-env": "^7.0.3",
"deepmerge": "^4.3.1",
"eslint": "8.56.0",
"eslint-config-airbnb-typescript": "17.1.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jsx-a11y": "6.8.0",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-react": "7.33.2",
"eslint-plugin-react-hooks": "4.6.2",
"postcss": "^8.4.38",
"prettier": "^3.2.5",
"rimraf": "^5.0.7",
"tailwindcss": "^3.4.3",
"tslib": "^2.6.2",
"turbo": "^2.0.3",
"typescript": "5.2.2",
"vite": "^5.2.11"
},
"packageManager": "pnpm@9.1.1",
"engines": {
"node": ">=20.12.0"
}
}
================================================
FILE: packages/dev-utils/index.ts
================================================
export * from './lib/manifest-parser';
export * from './lib/logger';
================================================
FILE: packages/dev-utils/lib/logger.ts
================================================
type ColorType = 'success' | 'info' | 'error' | 'warning' | keyof typeof COLORS;
type ValueOf<T> = T[keyof T];
export function colorLog(message: string, type: ColorType) {
let color: ValueOf<typeof COLORS>;
switch (type) {
case 'success':
color = COLORS.FgGreen;
break;
case 'info':
color = COLORS.FgBlue;
break;
case 'error':
color = COLORS.FgRed;
break;
case 'warning':
color = COLORS.FgYellow;
break;
default:
color = COLORS[type];
break;
}
console.log(color, message);
}
const COLORS = {
Reset: '\x1b[0m',
Bright: '\x1b[1m',
Dim: '\x1b[2m',
Underscore: '\x1b[4m',
Blink: '\x1b[5m',
Reverse: '\x1b[7m',
Hidden: '\x1b[8m',
FgBlack: '\x1b[30m',
FgRed: '\x1b[31m',
FgGreen: '\x1b[32m',
FgYellow: '\x1b[33m',
FgBlue: '\x1b[34m',
FgMagenta: '\x1b[35m',
FgCyan: '\x1b[36m',
FgWhite: '\x1b[37m',
BgBlack: '\x1b[40m',
BgRed: '\x1b[41m',
BgGreen: '\x1b[42m',
BgYellow: '\x1b[43m',
BgBlue: '\x1b[44m',
BgMagenta: '\x1b[45m',
BgCyan: '\x1b[46m',
BgWhite: '\x1b[47m',
} as const;
================================================
FILE: packages/dev-utils/lib/manifest-parser/impl.ts
================================================
import { ManifestParserInterface, Manifest } from './type';
export const ManifestParserImpl: ManifestParserInterface = {
convertManifestToString: (manifest, env) => {
if (env === 'firefox') {
manifest = convertToFirefoxCompatibleManifest(manifest);
}
return JSON.stringify(manifest, null, 2);
},
};
function convertToFirefoxCompatibleManifest(manifest: Manifest) {
const manifestCopy = {
...manifest,
} as { [key: string]: unknown };
manifestCopy.background = {
scripts: [manifest.background?.service_worker],
type: 'module',
};
manifestCopy.options_ui = {
page: manifest.options_page,
browser_style: false,
};
manifestCopy.content_security_policy = {
extension_pages: "script-src 'self'; object-src 'self'",
};
delete manifestCopy.options_page;
return manifestCopy as Manifest;
}
================================================
FILE: packages/dev-utils/lib/manifest-parser/index.ts
================================================
import { ManifestParserImpl } from './impl';
export const ManifestParser = ManifestParserImpl;
================================================
FILE: packages/dev-utils/lib/manifest-parser/type.ts
================================================
export type Manifest = chrome.runtime.ManifestV3;
export interface ManifestParserInterface {
convertManifestToString: (manifest: Manifest, env: 'chrome' | 'firefox') => string;
}
================================================
FILE: packages/dev-utils/package.json
================================================
{
"name": "@sync-your-cookie/dev-utils",
"version": "0.0.1",
"description": "chrome extension dev utils",
"private": true,
"sideEffects": false,
"files": [
"dist/**"
],
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"clean": "rimraf ./dist && rimraf ./build && rimraf .turbo",
"build": "pnpm run clean && tsc",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "pnpm lint --fix",
"prettier": "prettier . --write",
"type-check": "tsc --noEmit"
},
"dependencies": {},
"devDependencies": {
"@sync-your-cookie/tsconfig": "workspace:*"
}
}
================================================
FILE: packages/dev-utils/tsconfig.json
================================================
{
"extends": "@sync-your-cookie/tsconfig/utils",
"compilerOptions": {
"outDir": "dist",
"types": ["chrome"]
},
"include": ["index.ts", "lib"]
}
================================================
FILE: packages/hmr/index.ts
================================================
export * from './lib/plugins';
================================================
FILE: packages/hmr/lib/constant.ts
================================================
export const LOCAL_RELOAD_SOCKET_PORT = 8081;
export const LOCAL_RELOAD_SOCKET_URL = `ws://localhost:${LOCAL_RELOAD_SOCKET_PORT}`;
================================================
FILE: packages/hmr/lib/debounce.ts
================================================
export function debounce<A extends unknown[]>(callback: (...args: A) => void, delay: number) {
let timer: NodeJS.Timeout;
return function (...args: A) {
clearTimeout(timer);
timer = setTimeout(() => callback(...args), delay);
};
}
================================================
FILE: packages/hmr/lib/initClient.ts
================================================
import { LOCAL_RELOAD_SOCKET_URL } from './constant';
import MessageInterpreter from './interpreter';
export default function initReloadClient({ id, onUpdate }: { id: string; onUpdate: () => void }) {
let ws: WebSocket | null = null;
try {
ws = new WebSocket(LOCAL_RELOAD_SOCKET_URL);
ws.onopen = () => {
ws?.addEventListener('message', event => {
const message = MessageInterpreter.receive(String(event.data));
if (message.type === 'ping') {
console.log('[HMR] Client OK');
}
if (message.type === 'do_update' && message.id === id) {
sendUpdateCompleteMessage();
onUpdate();
return;
}
});
};
ws.onclose = () => {
console.log(
`Reload server disconnected.\nPlease check if the WebSocket server is running properly on ${LOCAL_RELOAD_SOCKET_URL}. This feature detects changes in the code and helps the browser to reload the extension or refresh the current tab.`,
);
setTimeout(() => {
initReloadClient({ onUpdate, id });
}, 1000);
};
} catch (e) {
setTimeout(() => {
initReloadClient({ onUpdate, id });
}, 1000);
}
function sendUpdateCompleteMessage() {
ws?.send(MessageInterpreter.send({ type: 'done_update' }));
}
}
================================================
FILE: packages/hmr/lib/initReloadServer.ts
================================================
#!/usr/bin/env node
import { WebSocket, WebSocketServer } from 'ws';
import { LOCAL_RELOAD_SOCKET_PORT, LOCAL_RELOAD_SOCKET_URL } from './constant';
import MessageInterpreter from './interpreter';
const clientsThatNeedToUpdate: Set<WebSocket> = new Set();
function initReloadServer() {
try {
const wss = new WebSocketServer({ port: LOCAL_RELOAD_SOCKET_PORT });
wss.on('listening', () => console.log(`[HMR] Server listening at ${LOCAL_RELOAD_SOCKET_URL}`));
wss.on('connection', ws => {
clientsThatNeedToUpdate.add(ws);
ws.addEventListener('close', () => clientsThatNeedToUpdate.delete(ws));
ws.addEventListener('message', event => {
if (typeof event.data !== 'string') return;
const message = MessageInterpreter.receive(event.data);
if (message.type === 'done_update') {
ws.close();
}
if (message.type === 'build_complete') {
clientsThatNeedToUpdate.forEach((ws: WebSocket) =>
ws.send(MessageInterpreter.send({ type: 'do_update', id: message.id })),
);
}
});
});
ping();
} catch {
console.error(`[HMR] Failed to start server at ${LOCAL_RELOAD_SOCKET_URL}`);
console.error('PLEASE MAKE SURE YOU ARE RUNNING `pnpm dev-server`');
}
}
initReloadServer();
function ping() {
clientsThatNeedToUpdate.forEach(ws => ws.send(MessageInterpreter.send({ type: 'ping' })));
setTimeout(() => {
ping();
}, 15_000);
}
================================================
FILE: packages/hmr/lib/injections/refresh.ts
================================================
import initClient from '../initClient';
function addRefresh() {
let pendingReload = false;
initClient({
// eslint-disable-next-line
// @ts-ignore
id: __HMR_ID,
onUpdate: () => {
// disable reload when tab is hidden
if (document.hidden) {
pendingReload = true;
return;
}
reload();
},
});
// reload
function reload(): void {
pendingReload = false;
window.location.reload();
}
// reload when tab is visible
function reloadWhenTabIsVisible(): void {
!document.hidden && pendingReload && reload();
}
document.addEventListener('visibilitychange', reloadWhenTabIsVisible);
}
addRefresh();
================================================
FILE: packages/hmr/lib/injections/reload.ts
================================================
import initClient from '../initClient';
function addReload() {
const reload = () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
chrome.runtime.reload();
};
initClient({
// eslint-disable-next-line
// @ts-ignore
id: __HMR_ID,
onUpdate: reload,
});
}
addReload();
================================================
FILE: packages/hmr/lib/interpreter/index.ts
================================================
import type { WebSocketMessage, SerializedMessage } from './types';
export default class MessageInterpreter {
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
static send(message: WebSocketMessage): SerializedMessage {
return JSON.stringify(message);
}
static receive(serializedMessage: SerializedMessage): WebSocketMessage {
return JSON.parse(serializedMessage);
}
}
================================================
FILE: packages/hmr/lib/interpreter/types.ts
================================================
type UpdateRequestMessage = {
type: 'do_update';
id: string;
};
type UpdateCompleteMessage = { type: 'done_update' };
type PingMessage = { type: 'ping' };
type BuildCompletionMessage = { type: 'build_complete'; id: string };
export type SerializedMessage = string;
export type WebSocketMessage = UpdateCompleteMessage | UpdateRequestMessage | BuildCompletionMessage | PingMessage;
================================================
FILE: packages/hmr/lib/plugins/index.ts
================================================
export * from './watch-rebuild-plugin';
export * from './make-entry-point-plugin';
================================================
FILE: packages/hmr/lib/plugins/make-entry-point-plugin.ts
================================================
import * as fs from 'fs';
import path from 'path';
import type { PluginOption } from 'vite';
/**
* make entry point file for content script cache busting
*/
export function makeEntryPointPlugin(): PluginOption {
const cleanupTargets = new Set<string>();
const isFirefox = process.env.__FIREFOX__ === 'true';
return {
name: 'make-entry-point-plugin',
generateBundle(options, bundle) {
const outputDir = options.dir;
if (!outputDir) {
throw new Error('Output directory not found');
}
for (const module of Object.values(bundle)) {
const fileName = path.basename(module.fileName);
const newFileName = fileName.replace('.js', '_dev.js');
switch (module.type) {
case 'asset':
// map file
if (fileName.endsWith('.map')) {
cleanupTargets.add(path.resolve(outputDir, fileName));
const originalFileName = fileName.replace('.map', '');
const replacedSource = String(module.source).replaceAll(originalFileName, newFileName);
module.source = '';
fs.writeFileSync(path.resolve(outputDir, newFileName), replacedSource);
break;
}
break;
case 'chunk': {
fs.writeFileSync(path.resolve(outputDir, newFileName), module.code);
console.log('newFileName', newFileName);
if (isFirefox) {
const contentDirectory = extractContentDir(outputDir);
module.code = `import(browser.runtime.getURL("${contentDirectory}/${newFileName}"));`;
} else {
module.code = `import('./${newFileName}');`;
}
break;
}
}
}
},
closeBundle() {
cleanupTargets.forEach(target => {
fs.unlinkSync(target);
});
},
};
}
/**
* Extract content directory from output directory for Firefox
* @param outputDir
*/
function extractContentDir(outputDir: string) {
const parts = outputDir.split(path.sep);
const distIndex = parts.indexOf('dist');
if (distIndex !== -1 && distIndex < parts.length - 1) {
return parts.slice(distIndex + 1);
}
throw new Error('Output directory does not contain "dist"');
}
================================================
FILE: packages/hmr/lib/plugins/watch-rebuild-plugin.ts
================================================
import type { PluginOption } from 'vite';
import { WebSocket } from 'ws';
import MessageInterpreter from '../interpreter';
import { LOCAL_RELOAD_SOCKET_URL } from '../constant';
import * as fs from 'fs';
import path from 'path';
type PluginConfig = {
onStart?: () => void;
reload?: boolean;
refresh?: boolean;
};
const injectionsPath = path.resolve(__dirname, '..', '..', '..', 'build', 'injections');
const refreshCode = fs.readFileSync(path.resolve(injectionsPath, 'refresh.js'), 'utf-8');
const reloadCode = fs.readFileSync(path.resolve(injectionsPath, 'reload.js'), 'utf-8');
export function watchRebuildPlugin(config: PluginConfig): PluginOption {
let ws: WebSocket | null = null;
const id = Math.random().toString(36);
const { refresh, reload } = config;
const hmrCode = (refresh ? refreshCode : '') + (reload ? reloadCode : '');
function initializeWebSocket() {
if (!ws) {
ws = new WebSocket(LOCAL_RELOAD_SOCKET_URL);
ws.onopen = () => {
console.log(`[HMR] Connected to dev-server at ${LOCAL_RELOAD_SOCKET_URL}`);
};
ws.onerror = () => {
console.error(`[HMR] Failed to start server at ${LOCAL_RELOAD_SOCKET_URL}`);
console.error('PLEASE MAKE SURE YOU ARE RUNNING `pnpm dev-server`');
console.warn('Retrying in 5 seconds...');
ws = null;
setTimeout(() => initializeWebSocket(), 5_000);
};
}
}
return {
name: 'watch-rebuild',
writeBundle() {
config.onStart?.();
if (!ws) {
initializeWebSocket();
return;
}
/**
* When the build is complete, send a message to the reload server.
* The reload server will send a message to the client to reload or refresh the extension.
*/
if (!ws) {
throw new Error('WebSocket is not initialized');
}
ws.send(MessageInterpreter.send({ type: 'build_complete', id }));
},
generateBundle(_options, bundle) {
for (const module of Object.values(bundle)) {
if (module.type === 'chunk') {
module.code = `(function() {let __HMR_ID = "${id}";\n` + hmrCode + '\n' + '})();' + '\n' + module.code;
}
}
},
};
}
================================================
FILE: packages/hmr/package.json
================================================
{
"name": "@sync-your-cookie/hmr",
"version": "0.0.1",
"description": "chrome extension hot module reload or refresh",
"private": true,
"sideEffects": true,
"files": [
"dist/**"
],
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"clean": "rimraf ./dist && rimraf ./build && rimraf .turbo",
"build:tsc": "tsc -b tsconfig.build.json",
"build:rollup": "rollup --config rollup.config.mjs",
"build": "pnpm run build:tsc && pnpm run build:rollup",
"dev": "node dist/lib/initReloadServer.js",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "pnpm lint --fix",
"prettier": "prettier . --write",
"type-check": "tsc --noEmit"
},
"dependencies": {
"ws": "8.17.0"
},
"devDependencies": {
"@sync-your-cookie/tsconfig": "workspace:*",
"@rollup/plugin-sucrase": "^5.0.2",
"@types/ws": "^8.5.10",
"esm": "^3.2.25",
"rollup": "^4.17.2",
"ts-node": "^10.9.2"
}
}
================================================
FILE: packages/hmr/rollup.config.mjs
================================================
import sucrase from '@rollup/plugin-sucrase';
const plugins = [
sucrase({
exclude: ['node_modules/**'],
transforms: ['typescript'],
}),
];
/**
* @type {import("rollup").RollupOptions[]}
*/
export default [
{
plugins,
input: 'lib/injections/reload.ts',
output: {
format: 'iife',
file: 'build/injections/reload.js',
},
},
{
plugins,
input: 'lib/injections/refresh.ts',
output: {
format: 'iife',
file: 'build/injections/refresh.js',
},
},
];
================================================
FILE: packages/hmr/tsconfig.build.json
================================================
{
"extends": "@sync-your-cookie/tsconfig/utils",
"compilerOptions": {
"outDir": "dist"
},
"exclude": ["lib/injections/**/*"],
"include": ["lib", "index.ts"]
}
================================================
FILE: packages/hmr/tsconfig.json
================================================
{
"extends": "@sync-your-cookie/tsconfig/utils",
"compilerOptions": {
"outDir": "dist"
},
"include": ["lib", "index.ts", "rollup.config.mjs"]
}
================================================
FILE: packages/protobuf/README.md
================================================
# Shared Package
This package contains code shared with other packages.
To use the code in the package, you need to add the following to the package.json file.
```json
{
"dependencies": {
"@sync-your-cookie/shared": "workspace:*"
}
}
```
After building this package, real-time cache busting does not occur in the code of other packages that reference this package.
You need to rerun it from the root path with `pnpm dev`, etc. (This will be improved in the future.)
If the type does not require compilation, there is no problem, but if the implementation requiring compilation is changed, a problem may occur.
Therefore, it is recommended to extract and use it in each context if it is easier to manage by extracting overlapping or business logic from the code that changes frequently in this package.
================================================
FILE: packages/protobuf/index.ts
================================================
export * from './lib/protobuf';
export * from './utils';
import pako from 'pako';
export { pako };
================================================
FILE: packages/protobuf/lib/protobuf/code.ts
================================================
import pako from 'pako';
import { compress, decompress } from './../../utils/compress';
import type { ICookiesMap } from './proto/cookie';
import { CookiesMap } from './proto/cookie';
export const encodeCookiesMap = async (
cookiesMap: ICookiesMap = {},
isCompress: boolean = true,
): Promise<Uint8Array> => {
// verify 只会校验数据的类型是否合法,并不会校验是否缺少或增加了数据项。
const invalid = CookiesMap.verify(cookiesMap);
if (invalid) {
throw Error(invalid);
}
const message = CookiesMap.create(cookiesMap);
const buffer = CookiesMap.encode(message).finish();
if (isCompress) {
const compressedBuf = pako.deflate(buffer);
return await compress(compressedBuf);
}
return buffer;
};
export const decodeCookiesMap = async (buffer: Uint8Array, isDeCompress: boolean = true) => {
let buf = buffer;
if (isDeCompress) {
buf = await decompress(buf);
buf = pako.inflate(buf);
}
const message = CookiesMap.decode(buf);
return message;
};
export type { ICookie, ICookiesMap, ILocalStorageItem } from './proto/cookie';
================================================
FILE: packages/protobuf/lib/protobuf/index.ts
================================================
export * from './code';
================================================
FILE: packages/protobuf/lib/protobuf/proto/cookie.d.ts
================================================
import * as $protobuf from "protobufjs";
import Long = require("long");
/** Properties of a Cookie. */
export interface ICookie {
/** Cookie domain */
domain?: (string|null);
/** Cookie name */
name?: (string|null);
/** Cookie storeId */
storeId?: (string|null);
/** Cookie value */
value?: (string|null);
/** Cookie session */
session?: (boolean|null);
/** Cookie hostOnly */
hostOnly?: (boolean|null);
/** Cookie expirationDate */
expirationDate?: (number|null);
/** Cookie path */
path?: (string|null);
/** Cookie httpOnly */
httpOnly?: (boolean|null);
/** Cookie secure */
secure?: (boolean|null);
/** Cookie sameSite */
sameSite?: (string|null);
}
/** Represents a Cookie. */
export class Cookie implements ICookie {
/**
* Constructs a new Cookie.
* @param [properties] Properties to set
*/
constructor(properties?: ICookie);
/** Cookie domain. */
public domain: string;
/** Cookie name. */
public name: string;
/** Cookie storeId. */
public storeId: string;
/** Cookie value. */
public value: string;
/** Cookie session. */
public session: boolean;
/** Cookie hostOnly. */
public hostOnly: boolean;
/** Cookie expirationDate. */
public expirationDate: number;
/** Cookie path. */
public path: string;
/** Cookie httpOnly. */
public httpOnly: boolean;
/** Cookie secure. */
public secure: boolean;
/** Cookie sameSite. */
public sameSite: string;
/**
* Creates a new Cookie instance using the specified properties.
* @param [properties] Properties to set
* @returns Cookie instance
*/
public static create(properties?: ICookie): Cookie;
/**
* Encodes the specified Cookie message. Does not implicitly {@link Cookie.verify|verify} messages.
* @param message Cookie message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encode(message: ICookie, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Encodes the specified Cookie message, length delimited. Does not implicitly {@link Cookie.verify|verify} messages.
* @param message Cookie message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encodeDelimited(message: ICookie, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Decodes a Cookie message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns Cookie
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): Cookie;
/**
* Decodes a Cookie message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns Cookie
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): Cookie;
/**
* Verifies a Cookie message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a Cookie message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns Cookie
*/
public static fromObject(object: { [k: string]: any }): Cookie;
/**
* Creates a plain object from a Cookie message. Also converts values to other types if specified.
* @param message Cookie
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: Cookie, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this Cookie to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
/**
* Gets the default type url for Cookie
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
/** Properties of a LocalStorageItem. */
export interface ILocalStorageItem {
/** LocalStorageItem key */
key?: (string|null);
/** LocalStorageItem value */
value?: (string|null);
}
/** Represents a LocalStorageItem. */
export class LocalStorageItem implements ILocalStorageItem {
/**
* Constructs a new LocalStorageItem.
* @param [properties] Properties to set
*/
constructor(properties?: ILocalStorageItem);
/** LocalStorageItem key. */
public key: string;
/** LocalStorageItem value. */
public value: string;
/**
* Creates a new LocalStorageItem instance using the specified properties.
* @param [properties] Properties to set
* @returns LocalStorageItem instance
*/
public static create(properties?: ILocalStorageItem): LocalStorageItem;
/**
* Encodes the specified LocalStorageItem message. Does not implicitly {@link LocalStorageItem.verify|verify} messages.
* @param message LocalStorageItem message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encode(message: ILocalStorageItem, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Encodes the specified LocalStorageItem message, length delimited. Does not implicitly {@link LocalStorageItem.verify|verify} messages.
* @param message LocalStorageItem message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encodeDelimited(message: ILocalStorageItem, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Decodes a LocalStorageItem message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns LocalStorageItem
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): LocalStorageItem;
/**
* Decodes a LocalStorageItem message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns LocalStorageItem
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): LocalStorageItem;
/**
* Verifies a LocalStorageItem message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a LocalStorageItem message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns LocalStorageItem
*/
public static fromObject(object: { [k: string]: any }): LocalStorageItem;
/**
* Creates a plain object from a LocalStorageItem message. Also converts values to other types if specified.
* @param message LocalStorageItem
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: LocalStorageItem, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this LocalStorageItem to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
/**
* Gets the default type url for LocalStorageItem
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
/** Properties of a DomainCookie. */
export interface IDomainCookie {
/** DomainCookie createTime */
createTime?: (number|Long|null);
/** DomainCookie updateTime */
updateTime?: (number|Long|null);
/** DomainCookie cookies */
cookies?: (ICookie[]|null);
/** DomainCookie localStorageItems */
localStorageItems?: (ILocalStorageItem[]|null);
/** DomainCookie userAgent */
userAgent?: (string|null);
}
/** Represents a DomainCookie. */
export class DomainCookie implements IDomainCookie {
/**
* Constructs a new DomainCookie.
* @param [properties] Properties to set
*/
constructor(properties?: IDomainCookie);
/** DomainCookie createTime. */
public createTime: (number|Long);
/** DomainCookie updateTime. */
public updateTime: (number|Long);
/** DomainCookie cookies. */
public cookies: ICookie[];
/** DomainCookie localStorageItems. */
public localStorageItems: ILocalStorageItem[];
/** DomainCookie userAgent. */
public userAgent: string;
/**
* Creates a new DomainCookie instance using the specified properties.
* @param [properties] Properties to set
* @returns DomainCookie instance
*/
public static create(properties?: IDomainCookie): DomainCookie;
/**
* Encodes the specified DomainCookie message. Does not implicitly {@link DomainCookie.verify|verify} messages.
* @param message DomainCookie message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encode(message: IDomainCookie, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Encodes the specified DomainCookie message, length delimited. Does not implicitly {@link DomainCookie.verify|verify} messages.
* @param message DomainCookie message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encodeDelimited(message: IDomainCookie, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Decodes a DomainCookie message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns DomainCookie
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): DomainCookie;
/**
* Decodes a DomainCookie message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns DomainCookie
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): DomainCookie;
/**
* Verifies a DomainCookie message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a DomainCookie message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns DomainCookie
*/
public static fromObject(object: { [k: string]: any }): DomainCookie;
/**
* Creates a plain object from a DomainCookie message. Also converts values to other types if specified.
* @param message DomainCookie
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: DomainCookie, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this DomainCookie to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
/**
* Gets the default type url for DomainCookie
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
/** Properties of a CookiesMap. */
export interface ICookiesMap {
/** CookiesMap createTime */
createTime?: (number|Long|null);
/** CookiesMap updateTime */
updateTime?: (number|Long|null);
/** CookiesMap domainCookieMap */
domainCookieMap?: ({ [k: string]: IDomainCookie }|null);
}
/** Represents a CookiesMap. */
export class CookiesMap implements ICookiesMap {
/**
* Constructs a new CookiesMap.
* @param [properties] Properties to set
*/
constructor(properties?: ICookiesMap);
/** CookiesMap createTime. */
public createTime: (number|Long);
/** CookiesMap updateTime. */
public updateTime: (number|Long);
/** CookiesMap domainCookieMap. */
public domainCookieMap: { [k: string]: IDomainCookie };
/**
* Creates a new CookiesMap instance using the specified properties.
* @param [properties] Properties to set
* @returns CookiesMap instance
*/
public static create(properties?: ICookiesMap): CookiesMap;
/**
* Encodes the specified CookiesMap message. Does not implicitly {@link CookiesMap.verify|verify} messages.
* @param message CookiesMap message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encode(message: ICookiesMap, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Encodes the specified CookiesMap message, length delimited. Does not implicitly {@link CookiesMap.verify|verify} messages.
* @param message CookiesMap message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encodeDelimited(message: ICookiesMap, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Decodes a CookiesMap message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns CookiesMap
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): CookiesMap;
/**
* Decodes a CookiesMap message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns CookiesMap
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): CookiesMap;
/**
* Verifies a CookiesMap message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a CookiesMap message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns CookiesMap
*/
public static fromObject(object: { [k: string]: any }): CookiesMap;
/**
* Creates a plain object from a CookiesMap message. Also converts values to other types if specified.
* @param message CookiesMap
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: CookiesMap, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this CookiesMap to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
/**
* Gets the default type url for CookiesMap
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
================================================
FILE: packages/protobuf/lib/protobuf/proto/cookie.js
================================================
/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/
import * as $protobuf from "protobufjs/minimal";
// Common aliases
const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
// Exported root namespace
const $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {});
export const Cookie = $root.Cookie = (() => {
/**
* Properties of a Cookie.
* @exports ICookie
* @interface ICookie
* @property {string|null} [domain] Cookie domain
* @property {string|null} [name] Cookie name
* @property {string|null} [storeId] Cookie storeId
* @property {string|null} [value] Cookie value
* @property {boolean|null} [session] Cookie session
* @property {boolean|null} [hostOnly] Cookie hostOnly
* @property {number|null} [expirationDate] Cookie expirationDate
* @property {string|null} [path] Cookie path
* @property {boolean|null} [httpOnly] Cookie httpOnly
* @property {boolean|null} [secure] Cookie secure
* @property {string|null} [sameSite] Cookie sameSite
*/
/**
* Constructs a new Cookie.
* @exports Cookie
* @classdesc Represents a Cookie.
* @implements ICookie
* @constructor
* @param {ICookie=} [properties] Properties to set
*/
function Cookie(properties) {
if (properties)
for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)
if (properties[keys[i]] != null)
this[keys[i]] = properties[keys[i]];
}
/**
* Cookie domain.
* @member {string} domain
* @memberof Cookie
* @instance
*/
Cookie.prototype.domain = "";
/**
* Cookie name.
* @member {string} name
* @memberof Cookie
* @instance
*/
Cookie.prototype.name = "";
/**
* Cookie storeId.
* @member {string} storeId
* @memberof Cookie
* @instance
*/
Cookie.prototype.storeId = "";
/**
* Cookie value.
* @member {string} value
* @memberof Cookie
* @instance
*/
Cookie.prototype.value = "";
/**
* Cookie session.
* @member {boolean} session
* @memberof Cookie
* @instance
*/
Cookie.prototype.session = false;
/**
* Cookie hostOnly.
* @member {boolean} hostOnly
* @memberof Cookie
* @instance
*/
Cookie.prototype.hostOnly = false;
/**
* Cookie expirationDate.
* @member {number} expirationDate
* @memberof Cookie
* @instance
*/
Cookie.prototype.expirationDate = 0;
/**
* Cookie path.
* @member {string} path
* @memberof Cookie
* @instance
*/
Cookie.prototype.path = "";
/**
* Cookie httpOnly.
* @member {boolean} httpOnly
* @memberof Cookie
* @instance
*/
Cookie.prototype.httpOnly = false;
/**
* Cookie secure.
* @member {boolean} secure
* @memberof Cookie
* @instance
*/
Cookie.prototype.secure = false;
/**
* Cookie sameSite.
* @member {string} sameSite
* @memberof Cookie
* @instance
*/
Cookie.prototype.sameSite = "";
/**
* Creates a new Cookie instance using the specified properties.
* @function create
* @memberof Cookie
* @static
* @param {ICookie=} [properties] Properties to set
* @returns {Cookie} Cookie instance
*/
Cookie.create = function create(properties) {
return new Cookie(properties);
};
/**
* Encodes the specified Cookie message. Does not implicitly {@link Cookie.verify|verify} messages.
* @function encode
* @memberof Cookie
* @static
* @param {ICookie} message Cookie message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
Cookie.encode = function encode(message, writer) {
if (!writer)
writer = $Writer.create();
if (message.domain != null && Object.hasOwnProperty.call(message, "domain"))
writer.uint32(/* id 1, wireType 2 =*/10).string(message.domain);
if (message.name != null && Object.hasOwnProperty.call(message, "name"))
writer.uint32(/* id 2, wireType 2 =*/18).string(message.name);
if (message.storeId != null && Object.hasOwnProperty.call(message, "storeId"))
writer.uint32(/* id 3, wireType 2 =*/26).string(message.storeId);
if (message.value != null && Object.hasOwnProperty.call(message, "value"))
writer.uint32(/* id 4, wireType 2 =*/34).string(message.value);
if (message.session != null && Object.hasOwnProperty.call(message, "session"))
writer.uint32(/* id 5, wireType 0 =*/40).bool(message.session);
if (message.hostOnly != null && Object.hasOwnProperty.call(message, "hostOnly"))
writer.uint32(/* id 6, wireType 0 =*/48).bool(message.hostOnly);
if (message.expirationDate != null && Object.hasOwnProperty.call(message, "expirationDate"))
writer.uint32(/* id 7, wireType 5 =*/61).float(message.expirationDate);
if (message.path != null && Object.hasOwnProperty.call(message, "path"))
writer.uint32(/* id 8, wireType 2 =*/66).string(message.path);
if (message.httpOnly != null && Object.hasOwnProperty.call(message, "httpOnly"))
writer.uint32(/* id 9, wireType 0 =*/72).bool(message.httpOnly);
if (message.secure != null && Object.hasOwnProperty.call(message, "secure"))
writer.uint32(/* id 10, wireType 0 =*/80).bool(message.secure);
if (message.sameSite != null && Object.hasOwnProperty.call(message, "sameSite"))
writer.uint32(/* id 11, wireType 2 =*/90).string(message.sameSite);
return writer;
};
/**
* Encodes the specified Cookie message, length delimited. Does not implicitly {@link Cookie.verify|verify} messages.
* @function encodeDelimited
* @memberof Cookie
* @static
* @param {ICookie} message Cookie message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
Cookie.encodeDelimited = function encodeDelimited(message, writer) {
return this.encode(message, writer).ldelim();
};
/**
* Decodes a Cookie message from the specified reader or buffer.
* @function decode
* @memberof Cookie
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @param {number} [length] Message length if known beforehand
* @returns {Cookie} Cookie
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
Cookie.decode = function decode(reader, length) {
if (!(reader instanceof $Reader))
reader = $Reader.create(reader);
let end = length === undefined ? reader.len : reader.pos + length, message = new $root.Cookie();
while (reader.pos < end) {
let tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
message.domain = reader.string();
break;
}
case 2: {
message.name = reader.string();
break;
}
case 3: {
message.storeId = reader.string();
break;
}
case 4: {
message.value = reader.string();
break;
}
case 5: {
message.session = reader.bool();
break;
}
case 6: {
message.hostOnly = reader.bool();
break;
}
case 7: {
message.expirationDate = reader.float();
break;
}
case 8: {
message.path = reader.string();
break;
}
case 9: {
message.httpOnly = reader.bool();
break;
}
case 10: {
message.secure = reader.bool();
break;
}
case 11: {
message.sameSite = reader.string();
break;
}
default:
reader.skipType(tag & 7);
break;
}
}
return message;
};
/**
* Decodes a Cookie message from the specified reader or buffer, length delimited.
* @function decodeDelimited
* @memberof Cookie
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @returns {Cookie} Cookie
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
Cookie.decodeDelimited = function decodeDelimited(reader) {
if (!(reader instanceof $Reader))
reader = new $Reader(reader);
return this.decode(reader, reader.uint32());
};
/**
* Verifies a Cookie message.
* @function verify
* @memberof Cookie
* @static
* @param {Object.<string,*>} message Plain object to verify
* @returns {string|null} `null` if valid, otherwise the reason why it is not
*/
Cookie.verify = function verify(message) {
if (typeof message !== "object" || message === null)
return "object expected";
if (message.domain != null && message.hasOwnProperty("domain"))
if (!$util.isString(message.domain))
return "domain: string expected";
if (message.name != null && message.hasOwnProperty("name"))
if (!$util.isString(message.name))
return "name: string expected";
if (message.storeId != null && message.hasOwnProperty("storeId"))
if (!$util.isString(message.storeId))
return "storeId: string expected";
if (message.value != null && message.hasOwnProperty("value"))
if (!$util.isString(message.value))
return "value: string expected";
if (message.session != null && message.hasOwnProperty("session"))
if (typeof message.session !== "boolean")
return "session: boolean expected";
if (message.hostOnly != null && message.hasOwnProperty("hostOnly"))
if (typeof message.hostOnly !== "boolean")
return "hostOnly: boolean expected";
if (message.expirationDate != null && message.hasOwnProperty("expirationDate"))
if (typeof message.expirationDate !== "number")
return "expirationDate: number expected";
if (message.path != null && message.hasOwnProperty("path"))
if (!$util.isString(message.path))
return "path: string expected";
if (message.httpOnly != null && message.hasOwnProperty("httpOnly"))
if (typeof message.httpOnly !== "boolean")
return "httpOnly: boolean expected";
if (message.secure != null && message.hasOwnProperty("secure"))
if (typeof message.secure !== "boolean")
return "secure: boolean expected";
if (message.sameSite != null && message.hasOwnProperty("sameSite"))
if (!$util.isString(message.sameSite))
return "sameSite: string expected";
return null;
};
/**
* Creates a Cookie message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof Cookie
* @static
* @param {Object.<string,*>} object Plain object
* @returns {Cookie} Cookie
*/
Cookie.fromObject = function fromObject(object) {
if (object instanceof $root.Cookie)
return object;
let message = new $root.Cookie();
if (object.domain != null)
message.domain = String(object.domain);
if (object.name != null)
message.name = String(object.name);
if (object.storeId != null)
message.storeId = String(object.storeId);
if (object.value != null)
message.value = String(object.value);
if (object.session != null)
message.session = Boolean(object.session);
if (object.hostOnly != null)
message.hostOnly = Boolean(object.hostOnly);
if (object.expirationDate != null)
message.expirationDate = Number(object.expirationDate);
if (object.path != null)
message.path = String(object.path);
if (object.httpOnly != null)
message.httpOnly = Boolean(object.httpOnly);
if (object.secure != null)
message.secure = Boolean(object.secure);
if (object.sameSite != null)
message.sameSite = String(object.sameSite);
return message;
};
/**
* Creates a plain object from a Cookie message. Also converts values to other types if specified.
* @function toObject
* @memberof Cookie
* @static
* @param {Cookie} message Cookie
* @param {$protobuf.IConversionOptions} [options] Conversion options
* @returns {Object.<string,*>} Plain object
*/
Cookie.toObject = function toObject(message, options) {
if (!options)
options = {};
let object = {};
if (options.defaults) {
object.domain = "";
object.name = "";
object.storeId = "";
object.value = "";
object.session = false;
object.hostOnly = false;
object.expirationDate = 0;
object.path = "";
object.httpOnly = false;
object.secure = false;
object.sameSite = "";
}
if (message.domain != null && message.hasOwnProperty("domain"))
object.domain = message.domain;
if (message.name != null && message.hasOwnProperty("name"))
object.name = message.name;
if (message.storeId != null && message.hasOwnProperty("storeId"))
object.storeId = message.storeId;
if (message.value != null && message.hasOwnProperty("value"))
object.value = message.value;
if (message.session != null && message.hasOwnProperty("session"))
object.session = message.session;
if (message.hostOnly != null && message.hasOwnProperty("hostOnly"))
object.hostOnly = message.hostOnly;
if (message.expirationDate != null && message.hasOwnProperty("expirationDate"))
object.expirationDate = options.json && !isFinite(message.expirationDate) ? String(message.expirationDate) : message.expirationDate;
if (message.path != null && message.hasOwnProperty("path"))
object.path = message.path;
if (message.httpOnly != null && message.hasOwnProperty("httpOnly"))
object.httpOnly = message.httpOnly;
if (message.secure != null && message.hasOwnProperty("secure"))
object.secure = message.secure;
if (message.sameSite != null && message.hasOwnProperty("sameSite"))
object.sameSite = message.sameSite;
return object;
};
/**
* Converts this Cookie to JSON.
* @function toJSON
* @memberof Cookie
* @instance
* @returns {Object.<string,*>} JSON object
*/
Cookie.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
/**
* Gets the default type url for Cookie
* @function getTypeUrl
* @memberof Cookie
* @static
* @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns {string} The default type url
*/
Cookie.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
if (typeUrlPrefix === undefined) {
typeUrlPrefix = "type.googleapis.com";
}
return typeUrlPrefix + "/Cookie";
};
return Cookie;
})();
export const LocalStorageItem = $root.LocalStorageItem = (() => {
/**
* Properties of a LocalStorageItem.
* @exports ILocalStorageItem
* @interface ILocalStorageItem
* @property {string|null} [key] LocalStorageItem key
* @property {string|null} [value] LocalStorageItem value
*/
/**
* Constructs a new LocalStorageItem.
* @exports LocalStorageItem
* @classdesc Represents a LocalStorageItem.
* @implements ILocalStorageItem
* @constructor
* @param {ILocalStorageItem=} [properties] Properties to set
*/
function LocalStorageItem(properties) {
if (properties)
for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)
if (properties[keys[i]] != null)
this[keys[i]] = properties[keys[i]];
}
/**
* LocalStorageItem key.
* @member {string} key
* @memberof LocalStorageItem
* @instance
*/
LocalStorageItem.prototype.key = "";
/**
* LocalStorageItem value.
* @member {string} value
* @memberof LocalStorageItem
* @instance
*/
LocalStorageItem.prototype.value = "";
/**
* Creates a new LocalStorageItem instance using the specified properties.
* @function create
* @memberof LocalStorageItem
* @static
* @param {ILocalStorageItem=} [properties] Properties to set
* @returns {LocalStorageItem} LocalStorageItem instance
*/
LocalStorageItem.create = function create(properties) {
return new LocalStorageItem(properties);
};
/**
* Encodes the specified LocalStorageItem message. Does not implicitly {@link LocalStorageItem.verify|verify} messages.
* @function encode
* @memberof LocalStorageItem
* @static
* @param {ILocalStorageItem} message LocalStorageItem message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
LocalStorageItem.encode = function encode(message, writer) {
if (!writer)
writer = $Writer.create();
if (message.key != null && Object.hasOwnProperty.call(message, "key"))
writer.uint32(/* id 1, wireType 2 =*/10).string(message.key);
if (message.value != null && Object.hasOwnProperty.call(message, "value"))
writer.uint32(/* id 2, wireType 2 =*/18).string(message.value);
return writer;
};
/**
* Encodes the specified LocalStorageItem message, length delimited. Does not implicitly {@link LocalStorageItem.verify|verify} messages.
* @function encodeDelimited
* @memberof LocalStorageItem
* @static
* @param {ILocalStorageItem} message LocalStorageItem message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
LocalStorageItem.encodeDelimited = function encodeDelimited(message, writer) {
return this.encode(message, writer).ldelim();
};
/**
* Decodes a LocalStorageItem message from the specified reader or buffer.
* @function decode
* @memberof LocalStorageItem
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @param {number} [length] Message length if known beforehand
* @returns {LocalStorageItem} LocalStorageItem
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
LocalStorageItem.decode = function decode(reader, length) {
if (!(reader instanceof $Reader))
reader = $Reader.create(reader);
let end = length === undefined ? reader.len : reader.pos + length, message = new $root.LocalStorageItem();
while (reader.pos < end) {
let tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
message.key = reader.string();
break;
}
case 2: {
message.value = reader.string();
break;
}
default:
reader.skipType(tag & 7);
break;
}
}
return message;
};
/**
* Decodes a LocalStorageItem message from the specified reader or buffer, length delimited.
* @function decodeDelimited
* @memberof LocalStorageItem
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @returns {LocalStorageItem} LocalStorageItem
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
LocalStorageItem.decodeDelimited = function decodeDelimited(reader) {
if (!(reader instanceof $Reader))
reader = new $Reader(reader);
return this.decode(reader, reader.uint32());
};
/**
* Verifies a LocalStorageItem message.
* @function verify
* @memberof LocalStorageItem
* @static
* @param {Object.<string,*>} message Plain object to verify
* @returns {string|null} `null` if valid, otherwise the reason why it is not
*/
LocalStorageItem.verify = function verify(message) {
if (typeof message !== "object" || message === null)
return "object expected";
if (message.key != null && message.hasOwnProperty("key"))
if (!$util.isString(message.key))
return "key: string expected";
if (message.value != null && message.hasOwnProperty("value"))
if (!$util.isString(message.value))
return "value: string expected";
return null;
};
/**
* Creates a LocalStorageItem message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof LocalStorageItem
* @static
* @param {Object.<string,*>} object Plain object
* @returns {LocalStorageItem} LocalStorageItem
*/
LocalStorageItem.fromObject = function fromObject(object) {
if (object instanceof $root.LocalStorageItem)
return object;
let message = new $root.LocalStorageItem();
if (object.key != null)
message.key = String(object.key);
if (object.value != null)
message.value = String(object.value);
return message;
};
/**
* Creates a plain object from a LocalStorageItem message. Also converts values to other types if specified.
* @function toObject
* @memberof LocalStorageItem
* @static
* @param {LocalStorageItem} message LocalStorageItem
* @param {$protobuf.IConversionOptions} [options] Conversion options
* @returns {Object.<string,*>} Plain object
*/
LocalStorageItem.toObject = function toObject(message, options) {
if (!options)
options = {};
let object = {};
if (options.defaults) {
object.key = "";
object.value = "";
}
if (message.key != null && message.hasOwnProperty("key"))
object.key = message.key;
if (message.value != null && message.hasOwnProperty("value"))
object.value = message.value;
return object;
};
/**
* Converts this LocalStorageItem to JSON.
* @function toJSON
* @memberof LocalStorageItem
* @instance
* @returns {Object.<string,*>} JSON object
*/
LocalStorageItem.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
/**
* Gets the default type url for LocalStorageItem
* @function getTypeUrl
* @memberof LocalStorageItem
* @static
* @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns {string} The default type url
*/
LocalStorageItem.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
if (typeUrlPrefix === undefined) {
typeUrlPrefix = "type.googleapis.com";
}
return typeUrlPrefix + "/LocalStorageItem";
};
return LocalStorageItem;
})();
export const DomainCookie = $root.DomainCookie = (() => {
/**
* Properties of a DomainCookie.
* @exports IDomainCookie
* @interface IDomainCookie
* @property {number|Long|null} [createTime] DomainCookie createTime
* @property {number|Long|null} [updateTime] DomainCookie updateTime
* @property {Array.<ICookie>|null} [cookies] DomainCookie cookies
* @property {Array.<ILocalStorageItem>|null} [localStorageItems] DomainCookie localStorageItems
* @property {string|null} [userAgent] DomainCookie userAgent
*/
/**
* Constructs a new DomainCookie.
* @exports DomainCookie
* @classdesc Represents a DomainCookie.
* @implements IDomainCookie
* @constructor
* @param {IDomainCookie=} [properties] Properties to set
*/
function DomainCookie(properties) {
this.cookies = [];
this.localStorageItems = [];
if (properties)
for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)
if (properties[keys[i]] != null)
this[keys[i]] = properties[keys[i]];
}
/**
* DomainCookie createTime.
* @member {number|Long} createTime
* @memberof DomainCookie
* @instance
*/
DomainCookie.prototype.createTime = $util.Long ? $util.Long.fromBits(0,0,false) : 0;
/**
* DomainCookie updateTime.
* @member {number|Long} updateTime
* @memberof DomainCookie
* @instance
*/
DomainCookie.prototype.updateTime = $util.Long ? $util.Long.fromBits(0,0,false) : 0;
/**
* DomainCookie cookies.
* @member {Array.<ICookie>} cookies
* @memberof DomainCookie
* @instance
*/
DomainCookie.prototype.cookies = $util.emptyArray;
/**
* DomainCookie localStorageItems.
* @member {Array.<ILocalStorageItem>} localStorageItems
* @memberof DomainCookie
* @instance
*/
DomainCookie.prototype.localStorageItems = $util.emptyArray;
/**
* DomainCookie userAgent.
* @member {string} userAgent
* @memberof DomainCookie
* @instance
*/
DomainCookie.prototype.userAgent = "";
/**
* Creates a new DomainCookie instance using the specified properties.
* @function create
* @memberof DomainCookie
* @static
* @param {IDomainCookie=} [properties] Properties to set
* @returns {DomainCookie} DomainCookie instance
*/
DomainCookie.create = function create(properties) {
return new DomainCookie(properties);
};
/**
* Encodes the specified DomainCookie message. Does not implicitly {@link DomainCookie.verify|verify} messages.
* @function encode
* @memberof DomainCookie
* @static
* @param {IDomainCookie} message DomainCookie message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
DomainCookie.encode = function encode(message, writer) {
if (!writer)
writer = $Writer.create();
if (message.createTime != null && Object.hasOwnProperty.call(message, "createTime"))
writer.uint32(/* id 1, wireType 0 =*/8).int64(message.createTime);
if (message.updateTime != null && Object.hasOwnProperty.call(message, "updateTime"))
writer.uint32(/* id 2, wireType 0 =*/16).int64(message.updateTime);
if (message.cookies != null && message.cookies.length)
for (let i = 0; i < message.cookies.length; ++i)
$root.Cookie.encode(message.cookies[i], writer.uint32(/* id 5, wireType 2 =*/42).fork()).ldelim();
if (message.localStorageItems != null && message.localStorageItems.length)
for (let i = 0; i < message.localStorageItems.length; ++i)
$root.LocalStorageItem.encode(message.localStorageItems[i], writer.uint32(/* id 6, wireType 2 =*/50).fork()).ldelim();
if (message.userAgent != null && Object.hasOwnProperty.call(message, "userAgent"))
writer.uint32(/* id 7, wireType 2 =*/58).string(message.userAgent);
return writer;
};
/**
* Encodes the specified DomainCookie message, length delimited. Does not implicitly {@link DomainCookie.verify|verify} messages.
* @function encodeDelimited
* @memberof DomainCookie
* @static
* @param {IDomainCookie} message DomainCookie message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
DomainCookie.encodeDelimited = function encodeDelimited(message, writer) {
return this.encode(message, writer).ldelim();
};
/**
* Decodes a DomainCookie message from the specified reader or buffer.
* @function decode
* @memberof DomainCookie
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @param {number} [length] Message length if known beforehand
* @returns {DomainCookie} DomainCookie
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
DomainCookie.decode = function decode(reader, length) {
if (!(reader instanceof $Reader))
reader = $Reader.create(reader);
let end = length === undefined ? reader.len : reader.pos + length, message = new $root.DomainCookie();
while (reader.pos < end) {
let tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
message.createTime = reader.int64();
break;
}
case 2: {
message.updateTime = reader.int64();
break;
}
case 5: {
if (!(message.cookies && message.cookies.length))
message.cookies = [];
message.cookies.push($root.Cookie.decode(reader, reader.uint32()));
break;
}
case 6: {
if (!(message.localStorageItems && message.localStorageItems.length))
message.localStorageItems = [];
message.localStorageItems.push($root.LocalStorageItem.decode(reader, reader.uint32()));
break;
}
case 7: {
message.userAgent = reader.string();
break;
}
default:
reader.skipType(tag & 7);
break;
}
}
return message;
};
/**
* Decodes a DomainCookie message from the specified reader or buffer, length delimited.
* @function decodeDelimited
* @memberof DomainCookie
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @returns {DomainCookie} DomainCookie
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
DomainCookie.decodeDelimited = function decodeDelimited(reader) {
if (!(reader instanceof $Reader))
reader = new $Reader(reader);
return this.decode(reader, reader.uint32());
};
/**
* Verifies a DomainCookie message.
* @function verify
* @memberof DomainCookie
* @static
* @param {Object.<string,*>} message Plain object to verify
* @returns {string|null} `null` if valid, otherwise the reason why it is not
*/
DomainCookie.verify = function verify(message) {
if (typeof message !== "object" || message === null)
return "object expected";
if (message.createTime != null && message.hasOwnProperty("createTime"))
if (!$util.isInteger(message.createTime) && !(message.createTime && $util.isInteger(message.createTime.low) && $util.isInteger(message.createTime.high)))
return "createTime: integer|Long expected";
if (message.updateTime != null && message.hasOwnProperty("updateTime"))
if (!$util.isInteger(message.updateTime) && !(message.updateTime && $util.isInteger(message.updateTime.low) && $util.isInteger(message.updateTime.high)))
return "updateTime: integer|Long expected";
if (message.cookies != null && message.hasOwnProperty("cookies")) {
if (!Array.isArray(message.cookies))
return "cookies: array expected";
for (let i = 0; i < message.cookies.length; ++i) {
let error = $root.Cookie.verify(message.cookies[i]);
if (error)
return "cookies." + error;
}
}
if (message.localStorageItems != null && message.hasOwnProperty("localStorageItems")) {
if (!Array.isArray(message.localStorageItems))
return "localStorageItems: array expected";
for (let i = 0; i < message.localStorageItems.length; ++i) {
let error = $root.LocalStorageItem.verify(message.localStorageItems[i]);
if (error)
return "localStorageItems." + error;
}
}
if (message.userAgent != null && message.hasOwnProperty("userAgent"))
if (!$util.isString(message.userAgent))
return "userAgent: string expected";
return null;
};
/**
* Creates a DomainCookie message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof DomainCookie
* @static
* @param {Object.<string,*>} object Plain object
* @returns {DomainCookie} DomainCookie
*/
DomainCookie.fromObject = function fromObject(object) {
if (object instanceof $root.DomainCookie)
return object;
let message = new $root.DomainCookie();
if (object.createTime != null)
if ($util.Long)
(message.createTime = $util.Long.fromValue(object.createTime)).unsigned = false;
else if (typeof object.createTime === "string")
message.createTime = parseInt(object.createTime, 10);
else if (typeof object.createTime === "number")
message.createTime = object.createTime;
else if (typeof object.createTime === "object")
message.createTime = new $util.LongBits(object.createTime.low >>> 0, object.createTime.high >>> 0).toNumber();
if (object.updateTime != null)
if ($util.Long)
(message.updateTime = $util.Long.fromValue(object.updateTime)).unsigned = false;
else if (typeof object.updateTime === "string")
message.updateTime = parseInt(object.updateTime, 10);
else if (typeof object.updateTime === "number")
message.updateTime = object.updateTime;
else if (typeof object.updateTime === "object")
message.updateTime = new $util.LongBits(object.updateTime.low >>> 0, object.updateTime.high >>> 0).toNumber();
if (object.cookies) {
if (!Array.isArray(object.cookies))
throw TypeError(".DomainCookie.cookies: array expected");
message.cookies = [];
for (let i = 0; i < object.cookies.length; ++i) {
if (typeof object.cookies[i] !== "object")
throw TypeError(".DomainCookie.cookies: object expected");
message.cookies[i] = $root.Cookie.fromObject(object.cookies[i]);
}
}
if (object.localStorageItems) {
if (!Array.isArray(object.localStorageItems))
throw TypeError(".DomainCookie.localStorageItems: array expected");
message.localStorageItems = [];
for (let i = 0; i < object.localStorageItems.length; ++i) {
if (typeof object.localStorageItems[i] !== "object")
throw TypeError(".DomainCookie.localStorageItems: object expected");
message.localStorageItems[i] = $root.LocalStorageItem.fromObject(object.localStorageItems[i]);
}
}
if (object.userAgent != null)
message.userAgent = String(object.userAgent);
return message;
};
/**
* Creates a plain object from a DomainCookie message. Also converts values to other types if specified.
* @function toObject
* @memberof DomainCookie
* @static
* @param {DomainCookie} message DomainCookie
* @param {$protobuf.IConversionOptions} [options] Conversion options
* @returns {Object.<string,*>} Plain object
*/
DomainCookie.toObject = function toObject(message, options) {
if (!options)
options = {};
let object = {};
if (options.arrays || options.defaults) {
object.cookies = [];
object.localStorageItems = [];
}
if (options.defaults) {
if ($util.Long) {
let long = new $util.Long(0, 0, false);
object.createTime = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
} else
object.createTime = options.longs === String ? "0" : 0;
if ($util.Long) {
let long = new $util.Long(0, 0, false);
object.updateTime = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
} else
object.updateTime = options.longs === String ? "0" : 0;
object.userAgent = "";
}
if (message.createTime != null && message.hasOwnProperty("createTime"))
if (typeof message.createTime === "number")
object.createTime = options.longs === String ? String(message.createTime) : message.createTime;
else
object.createTime = options.longs === String ? $util.Long.prototype.toString.call(message.createTime) : options.longs === Number ? new $util.LongBits(message.createTime.low >>> 0, message.createTime.high >>> 0).toNumber() : message.createTime;
if (message.updateTime != null && message.hasOwnProperty("updateTime"))
if (typeof message.updateTime === "number")
object.updateTime = options.longs === String ? String(message.updateTime) : message.updateTime;
else
object.updateTime = options.longs === String ? $util.Long.prototype.toString.call(message.updateTime) : options.longs === Number ? new $util.LongBits(message.updateTime.low >>> 0, message.updateTime.high >>> 0).toNumber() : message.updateTime;
if (message.cookies && message.cookies.length) {
object.cookies = [];
for (let j = 0; j < message.cookies.length; ++j)
object.cookies[j] = $root.Cookie.toObject(message.cookies[j], options);
}
if (message.localStorageItems && message.localStorageItems.length) {
object.localStorageItems = [];
for (let j = 0; j < message.localStorageItems.length; ++j)
object.localStorageItems[j] = $root.LocalStorageItem.toObject(message.localStorageItems[j], options);
}
if (message.userAgent != null && message.hasOwnProperty("userAgent"))
object.userAgent = message.userAgent;
return object;
};
/**
* Converts this DomainCookie to JSON.
* @function toJSON
* @memberof DomainCookie
* @instance
* @returns {Object.<string,*>} JSON object
*/
DomainCookie.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
/**
* Gets the default type url for DomainCookie
* @function getTypeUrl
* @memberof DomainCookie
* @static
* @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns {string} The default type url
*/
DomainCookie.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
if (typeUrlPrefix === undefined) {
typeUrlPrefix = "type.googleapis.com";
}
return typeUrlPrefix + "/DomainCookie";
};
return DomainCookie;
})();
export const CookiesMap = $root.CookiesMap = (() => {
/**
* Properties of a CookiesMap.
* @exports ICookiesMap
* @interface ICookiesMap
* @property {number|Long|null} [createTime] CookiesMap createTime
* @property {number|Long|null} [updateTime] CookiesMap updateTime
* @property {Object.<string,IDomainCookie>|null} [domainCookieMap] CookiesMap domainCookieMap
*/
/**
* Constructs a new CookiesMap.
* @exports CookiesMap
* @classdesc Represents a CookiesMap.
* @implements ICookiesMap
* @constructor
* @param {ICookiesMap=} [properties] Properties to set
*/
function CookiesMap(properties) {
this.domainCookieMap = {};
if (properties)
for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)
if (properties[keys[i]] != null)
this[keys[i]] = properties[keys[i]];
}
/**
* CookiesMap createTime.
* @member {number|Long} createTime
* @memberof CookiesMap
* @instance
*/
CookiesMap.prototype.createTime = $util.Long ? $util.Long.fromBits(0,0,false) : 0;
/**
* CookiesMap updateTime.
* @member {number|Long} updateTime
* @memberof CookiesMap
* @instance
*/
CookiesMap.prototype.updateTime = $util.Long ? $util.Long.fromBits(0,0,false) : 0;
/**
* CookiesMap domainCookieMap.
* @member {Object.<string,IDomainCookie>} domainCookieMap
* @memberof CookiesMap
* @instance
*/
CookiesMap.prototype.domainCookieMap = $util.emptyObject;
/**
* Creates a new CookiesMap instance using the specified properties.
* @function create
* @memberof CookiesMap
* @static
* @param {ICookiesMap=} [properties] Properties to set
* @returns {CookiesMap} CookiesMap instance
*/
CookiesMap.create = function create(properties) {
return new CookiesMap(properties);
};
/**
* Encodes the specified CookiesMap message. Does not implicitly {@link CookiesMap.verify|verify} messages.
* @function encode
* @memberof CookiesMap
* @static
* @param {ICookiesMap} message CookiesMap message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
CookiesMap.encode = function encode(message, writer) {
if (!writer)
writer = $Writer.create();
if (message.createTime != null && Object.hasOwnProperty.call(message, "createTime"))
writer.uint32(/* id 1, wireType 0 =*/8).int64(message.createTime);
if (message.updateTime != null && Object.hasOwnProperty.call(message, "updateTime"))
writer.uint32(/* id 2, wireType 0 =*/16).int64(message.updateTime);
if (message.domainCookieMap != null && Object.hasOwnProperty.call(message, "domainCookieMap"))
for (let keys = Object.keys(message.domainCookieMap), i = 0; i < keys.length; ++i) {
writer.uint32(/* id 5, wireType 2 =*/42).fork().uint32(/* id 1, wireType 2 =*/10).string(keys[i]);
$root.DomainCookie.encode(message.domainCookieMap[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim();
}
return writer;
};
/**
* Encodes the specified CookiesMap message, length delimited. Does not implicitly {@link CookiesMap.verify|verify} messages.
* @function encodeDelimited
* @memberof CookiesMap
* @static
* @param {ICookiesMap} message CookiesMap message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
CookiesMap.encodeDelimited = function encodeDelimited(message, writer) {
return this.encode(message, writer).ldelim();
};
/**
* Decodes a CookiesMap message from the specified reader or buffer.
* @function decode
* @memberof CookiesMap
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @param {number} [length] Message length if known beforehand
* @returns {CookiesMap} CookiesMap
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
CookiesMap.decode = function decode(reader, length) {
if (!(reader instanceof $Reader))
reader = $Reader.create(reader);
let end = length === undefined ? reader.len : reader.pos + length, message = new $root.CookiesMap(), key, value;
while (reader.pos < end) {
let tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
message.createTime = reader.int64();
break;
}
case 2: {
message.updateTime = reader.int64();
break;
}
case 5: {
if (message.domainCookieMap === $util.emptyObject)
message.domainCookieMap = {};
let end2 = reader.uint32() + reader.pos;
key = "";
value = null;
while (reader.pos < end2) {
let tag2 = reader.uint32();
switch (tag2 >>> 3) {
case 1:
key = reader.string();
break;
case 2:
value = $root.DomainCookie.decode(reader, reader.uint32());
break;
default:
reader.skipType(tag2 & 7);
break;
}
}
message.domainCookieMap[key] = value;
break;
}
default:
reader.skipType(tag & 7);
break;
}
}
return message;
};
/**
* Decodes a CookiesMap message from the specified reader or buffer, length delimited.
* @function decodeDelimited
* @memberof CookiesMap
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @returns {CookiesMap} CookiesMap
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
CookiesMap.decodeDelimited = function decodeDelimited(reader) {
if (!(reader instanceof $Reader))
reader = new $Reader(reader);
return this.decode(reader, reader.uint32());
};
/**
* Verifies a CookiesMap message.
* @function verify
* @memberof CookiesMap
* @static
* @param {Object.<string,*>} message Plain object to verify
* @returns {string|null} `null` if valid, otherwise the reason why it is not
*/
CookiesMap.verify = function verify(message) {
if (typeof message !== "object" || message === null)
return "object expected";
if (message.createTime != null && message.hasOwnProperty("createTime"))
if (!$util.isInteger(message.createTime) && !(message.createTime && $util.isInteger(message.createTime.low) && $util.isInteger(message.createTime.high)))
return "createTime: integer|Long expected";
if (message.updateTime != null && message.hasOwnProperty("updateTime"))
if (!$util.isInteger(message.updateTime) && !(message.updateTime && $util.isInteger(message.updateTime.low) && $util.isInteger(message.updateTime.high)))
return "updateTime: integer|Long expected";
if (message.domainCookieMap != null && message.hasOwnProperty("domainCookieMap")) {
if (!$util.isObject(message.domainCookieMap))
return "domainCookieMap: object expected";
let key = Object.keys(message.domainCookieMap);
for (let i = 0; i < key.length; ++i) {
let error = $root.DomainCookie.verify(message.domainCookieMap[key[i]]);
if (error)
return "domainCookieMap." + error;
}
}
return null;
};
/**
* Creates a CookiesMap message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof CookiesMap
* @static
* @param {Object.<string,*>} object Plain object
* @returns {CookiesMap} CookiesMap
*/
CookiesMap.fromObject = function fromObject(object) {
if (object instanceof $root.CookiesMap)
return object;
let message = new $root.CookiesMap();
if (object.createTime != null)
if ($util.Long)
(message.createTime = $util.Long.fromValue(object.createTime)).unsigned = false;
else if (typeof object.createTime === "string")
message.createTime = parseInt(object.createTime, 10);
else if (typeof object.createTime === "number")
message.createTime = object.createTime;
else if (typeof object.createTime === "object")
message.createTime = new $util.LongBits(object.createTime.low >>> 0, object.createTime.high >>> 0).toNumber();
if (object.updateTime != null)
if ($util.Long)
(message.updateTime = $util.Long.fromValue(object.updateTime)).unsigned = false;
else if (typeof object.updateTime === "string")
message.updateTime = parseInt(object.updateTime, 10);
else if (typeof object.updateTime === "number")
message.updateTime = object.updateTime;
else if (typeof object.updateTime === "object")
message.updateTime = new $util.LongBits(object.updateTime.low >>> 0, object.updateTime.high >>> 0).toNumber();
if (object.domainCookieMap) {
if (typeof object.domainCookieMap !== "object")
throw TypeError(".CookiesMap.domainCookieMap: object expected");
message.domainCookieMap = {};
for (let keys = Object.keys(object.domainCookieMap), i = 0; i < keys.length; ++i) {
if (typeof object.domainCookieMap[keys[i]] !== "object")
throw TypeError(".CookiesMap.domainCookieMap: object expected");
message.domainCookieMap[keys[i]] = $root.DomainCookie.fromObject(object.domainCookieMap[keys[i]]);
}
}
return message;
};
/**
* Creates a plain object from a CookiesMap message. Also converts values to other types if specified.
* @function toObject
* @memberof CookiesMap
* @static
* @param {CookiesMap} message CookiesMap
* @param {$protobuf.IConversionOptions} [options] Conversion options
* @returns {Object.<string,*>} Plain object
*/
CookiesMap.toObject = function toObject(message, options) {
if (!options)
options = {};
let object = {};
if (options.objects || options.defaults)
object.domainCookieMap = {};
if (options.defaults) {
if ($util.Long) {
let long = new $util.Long(0, 0, false);
object.createTime = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
} else
object.createTime = options.longs === String ? "0" : 0;
if ($util.Long) {
let long = new $util.Long(0, 0, false);
object.updateTime = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
} else
object.updateTime = options.longs === String ? "0" : 0;
}
if (message.createTime != null && message.hasOwnProperty("createTime"))
if (typeof message.createTime === "number")
object.createTime = options.longs === String ? String(message.createTime) : message.createTime;
else
object.createTime = options.longs === String ? $util.Long.prototype.toString.call(message.createTime) : options.longs === Number ? new $util.LongBits(message.createTime.low >>> 0, message.createTime.high >>> 0).toNumber() : message.createTime;
if (message.updateTime != null && message.hasOwnProperty("updateTime"))
if (typeof message.updateTime === "number")
object.updateTime = options.longs === String ? String(message.updateTime) : message.updateTime;
else
object.updateTime = options.longs === String ? $util.Long.prototype.toString.call(message.updateTime) : options.longs === Number ? new $util.LongBits(message.updateTime.low >>> 0, message.updateTime.high >>> 0).toNumber() : message.updateTime;
let keys2;
if (message.domainCookieMap && (keys2 = Object.keys(message.domainCookieMap)).length) {
object.domainCookieMap = {};
for (let j = 0; j < keys2.length; ++j)
object.domainCookieMap[keys2[j]] = $root.DomainCookie.toObject(message.domainCookieMap[keys2[j]], options);
}
return object;
};
/**
* Converts this CookiesMap to JSON.
* @function toJSON
* @memberof CookiesMap
* @instance
* @returns {Object.<string,*>} JSON object
*/
CookiesMap.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
/**
* Gets the default type url for CookiesMap
* @function getTypeUrl
* @memberof CookiesMap
* @static
* @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns {string} The default type url
*/
CookiesMap.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
if (typeUrlPrefix === undefined) {
typeUrlPrefix = "type.googleapis.com";
}
return typeUrlPrefix + "/CookiesMap";
};
return CookiesMap;
})();
export { $root as default };
================================================
FILE: packages/protobuf/package.json
================================================
{
"name": "@sync-your-cookie/protobuf",
"version": "0.0.1",
"description": "chrome extension protobuf code",
"private": true,
"sideEffects": false,
"files": [
"dist/**"
],
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"scripts": {
"clean": "rimraf ./dist && rimraf .turbo",
"build": "tsup index.ts --format esm,cjs --dts --external react,chrome",
"dev": "tsc -w",
"copy:proto": "cp -r ./lib/protobuf/proto ./dist/lib/protobuf",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "pnpm lint --fix",
"prettier": "prettier . --write",
"type-check": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"proto": "pbjs -o ./lib/protobuf/proto/cookie.js -w es6 -t static-module ./proto/*.proto && pbts ./lib/protobuf/proto/cookie.js -o ./lib/protobuf/proto/cookie.d.ts"
},
"dependencies": {
"pako": "^2.1.0",
"protobufjs": "^7.3.2"
},
"devDependencies": {
"@sync-your-cookie/tsconfig": "workspace:*",
"@types/pako": "^2.0.3",
"protobufjs-cli": "^1.1.3",
"tsup": "8.0.2",
"tsx": "^4.19.1",
"vitest": "^1.6.0"
}
}
================================================
FILE: packages/protobuf/proto/cookie.proto
================================================
syntax = "proto3";
message Cookie {
string domain = 1;
string name = 2;
string storeId = 3;
string value = 4;
bool session = 5;
bool hostOnly = 6;
float expirationDate = 7;
string path = 8;
bool httpOnly = 9;
bool secure = 10;
string sameSite = 11;
}
message LocalStorageItem {
string key = 1;
string value = 2;
}
message DomainCookie {
int64 createTime = 1;
int64 updateTime = 2;
repeated Cookie cookies = 5;
repeated LocalStorageItem localStorageItems = 6;
string userAgent = 7;
}
message CookiesMap {
int64 createTime = 1;
int64 updateTime = 2;
map<string, DomainCookie> domainCookieMap = 5;
}
================================================
FILE: packages/protobuf/tsconfig.json
================================================
{
"extends": "@sync-your-cookie/tsconfig/utils",
"compilerOptions": {
"outDir": "dist",
"jsx": "react-jsx",
"checkJs": false,
"allowJs": false,
"baseUrl": ".",
"paths": {
"@lib/*": ["lib/*"]
},
"types": ["chrome"]
},
"exclude": ["code-test.ts"],
"include": ["index.ts", "lib"],
}
================================================
FILE: packages/protobuf/tsup.config.ts
================================================
import { defineConfig } from 'tsup';
export default defineConfig({
treeshake: true,
format: ['cjs', 'esm'],
dts: true,
external: ['chrome'],
});
================================================
FILE: packages/protobuf/utils/base64.ts
================================================
export function arrayBufferToBase64(arrayBuffer: ArrayBuffer) {
let base64 = '';
const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const bytes = new Uint8Array(arrayBuffer);
const byteLength = bytes.byteLength;
const byteRemainder = byteLength % 3;
const mainLength = byteLength - byteRemainder;
let a, b, c, d;
let chunk;
// Main loop deals with bytes in chunks of 3
for (let i = 0; i < mainLength; i = i + 3) {
// Combine the three bytes into a single integer
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
// Use bitmasks to extract 6-bit segments from the triplet
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
d = chunk & 63; // 63 = 2^6 - 1
// Convert the raw binary segments to the appropriate ASCII encoding
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
}
// Deal with the remaining bytes and padding
if (byteRemainder == 1) {
chunk = bytes[mainLength];
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
// Set the 4 least significant bits to zero
b = (chunk & 3) << 4; // 3 = 2^2 - 1
base64 += encodings[a] + encodings[b] + '==';
} else if (byteRemainder == 2) {
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
// Set the 2 least significant bits to zero
c = (chunk & 15) << 2; // 15 = 2^4 - 1
base64 += encodings[a] + encodings[b] + encodings[c] + '=';
}
return base64;
}
export function base64ToArrayBuffer(base64: string) {
const binaryString = atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
================================================
FILE: packages/protobuf/utils/compress.ts
================================================
async function concatUint8Arrays(uint8arrays: ArrayBuffer[]) {
const blob = new Blob(uint8arrays);
const buffer = await blob.arrayBuffer();
return new Uint8Array(buffer);
}
/**
* Compress a string into a Uint8Array.
* @param byteArray
* @param method
* @returns Promise<ArrayBuffer>
*/
export const compress = async (byteArray: Uint8Array, method: CompressionFormat = 'gzip'): Promise<Uint8Array> => {
const stream = new Blob([byteArray]).stream();
// const byteArray: Uint8Array = new TextEncoder().encode(string);
const compressedStream = stream.pipeThrough(new CompressionStream(method)) as unknown as ArrayBuffer[];
const chunks: ArrayBuffer[] = [];
for await (const chunk of compressedStream) {
chunks.push(chunk);
}
return await concatUint8Arrays(chunks);
};
/**
* Decompress bytes into a Uint8Array.
*
* @param {Uint8Array} compressedBytes
* @returns {Promise<Uint8Array>}
*/
export async function decompress(compressedBytes: Uint8Array) {
// Convert the bytes to a stream.
const stream = new Blob([compressedBytes]).stream();
// Create a decompressed stream.
const decompressedStream = stream.pipeThrough(new DecompressionStream('gzip')) as unknown as ArrayBuffer[];
// Read all the bytes from this stream.
const chunks = [];
for await (const chunk of decompressedStream) {
chunks.push(chunk);
}
const stringBytes = await concatUint8Arrays(chunks);
return stringBytes;
}
================================================
FILE: packages/protobuf/utils/encryption.test.ts
================================================
import { describe, it, expect } from 'vitest';
import {
encrypt,
decrypt,
isEncrypted,
encryptBase64,
decryptBase64,
isBase64Encrypted,
} from './encryption';
// Helper to create test data
function createTestData(size: number): Uint8Array {
const data = new Uint8Array(size);
for (let i = 0; i < size; i++) {
data[i] = i % 256;
}
return data;
}
// Helper to convert string to Uint8Array
function stringToUint8Array(str: string): Uint8Array {
return new TextEncoder().encode(str);
}
// Helper to convert Uint8Array to string
function uint8ArrayToString(arr: Uint8Array): string {
return new TextDecoder().decode(arr);
}
describe('Encryption Module', () => {
describe('encrypt and decrypt', () => {
it('should encrypt and decrypt small data correctly', async () => {
const originalData = stringToUint8Array('Hello, World!');
const password = 'test-password-123';
const encrypted = await encrypt(originalData, password);
const decrypted = await decrypt(encrypted, password);
expect(uint8ArrayToString(decrypted)).toBe('Hello, World!');
});
it('should encrypt and decrypt empty data', async () => {
const originalData = new Uint8Array(0);
const password = 'test-password';
const encrypted = await encrypt(originalData, password);
const decrypted = await decrypt(encrypted, password);
expect(decrypted.length).toBe(0);
});
it('should encrypt and decrypt large data (1MB)', async () => {
const originalData = createTestData(1024 * 1024); // 1MB
const password = 'strong-password-456';
const encrypted = await encrypt(originalData, password);
const decrypted = await decrypt(encrypted, password);
expect(decrypted).toEqual(originalData);
});
it('should encrypt and decrypt binary data', async () => {
const originalData = new Uint8Array([0, 1, 2, 255, 254, 253, 128, 127]);
const password = 'binary-test';
const encrypted = await encrypt(originalData, password);
const decrypted = await decrypt(encrypted, password);
expect(decrypted).toEqual(originalData);
});
it('should produce different ciphertext for same plaintext (random IV)', async () => {
const originalData = stringToUint8Array('Same message');
const password = 'same-password';
const encrypted1 = await encrypt(originalData, password);
const encrypted2 = await encrypt(originalData, password);
// Ciphertexts should be different due to random IV and salt
expect(encrypted1).not.toEqual(encrypted2);
// But both should decrypt to the same plaintext
const decrypted1 = await decrypt(encrypted1, password);
const decrypted2 = await decrypt(encrypted2, password);
expect(decrypted1).toEqual(decrypted2);
});
it('should fail decryption with wrong password', async () => {
const originalData = stringToUint8Array('Secret message');
const correctPassword = 'correct-password';
const wrongPassword = 'wrong-password';
const encrypted = await encrypt(originalData, correctPassword);
await expect(decrypt(encrypted, wrongPassword)).rejects.toThrow(
'Decryption failed: incorrect password or corrupted data',
);
});
it('should fail decryption with corrupted data', async () => {
const originalData = stringToUint8Array('Test data');
const password = 'test-password';
const encrypted = await encrypt(originalData, password);
// Corrupt the ciphertext (modify bytes after the header)
encrypted[encrypted.length - 1] ^= 0xff;
await expect(decrypt(encrypted, password)).rejects.toThrow();
});
it('should fail decryption with invalid magic bytes', async () => {
const fakeEncrypted = new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x01, ...new Array(33).fill(0)]);
await expect(decrypt(fakeEncrypted, 'any-password')).rejects.toThrow(
'Invalid encrypted data: magic bytes mismatch',
);
});
it('should fail decryption with unsupported version', async () => {
// Create data with correct magic bytes but wrong version
const fakeEncrypted = new Uint8Array([
0x53,
0x59,
0x43,
0x45, // SYCE magic bytes
0x99, // Invalid version
...new Array(33).fill(0),
]);
await expect(decrypt(fakeEncrypted, 'any-password')).rejects.toThrow('Unsupported encryption version: 153');
});
it('should handle unicode strings correctly', async () => {
const originalData = stringToUint8Array('Hello 世界 🌍 Привет');
const password = 'unicode-test-密码';
const encrypted = await encrypt(originalData, password);
const decrypted = await decrypt(encrypted, password);
expect(uint8ArrayToString(decrypted)).toBe('Hello 世界 🌍 Привет');
});
it('should handle special characters in password', async () => {
const originalData = stringToUint8Array('Test data');
const password = '!@#$%^&*()_+-=[]{}|;:,.<>?`~"\'\\';
const encrypted = await encrypt(originalData, password);
const decrypted = await decrypt(encrypted, password);
expect(uint8ArrayToString(decrypted)).toBe('Test data');
});
it('should handle very long passwords', async () => {
const originalData = stringToUint8Array('Test data');
const password = 'a'.repeat(10000);
const encrypted = await encrypt(originalData, password);
const decrypted = await decrypt(encrypted, password);
expect(uint8ArrayToString(decrypted)).toBe('Test data');
});
it('should handle empty password', async () => {
const originalData = stringToUint8Array('Test data');
const password = '';
const encrypted = await encrypt(originalData, password);
const decrypted = await decrypt(encrypted, password);
expect(uint8ArrayToString(decrypted)).toBe('Test data');
});
});
describe('isEncrypted', () => {
it('should return true for encrypted data', async () => {
const originalData = stringToUint8Array('Test');
const encrypted = await encrypt(originalData, 'password');
expect(isEncrypted(encrypted)).toBe(true);
});
it('should return false for non-encrypted data', () => {
const plainData = stringToUint8Array('Plain text data');
expect(isEncrypted(plainData)).toBe(false);
});
it('should return false for data shorter than magic bytes', () => {
const shortData = new Uint8Array([0x53, 0x59]); // Only 2 bytes
expect(isEncrypted(shortData)).toBe(false);
});
it('should return false for empty data', () => {
const emptyData = new Uint8Array(0);
expect(isEncrypted(emptyData)).toBe(false);
});
it('should return false for data with partial magic bytes match', () => {
const partialMatch = new Uint8Array([0x53, 0x59, 0x43, 0x00]); // SYCA instead of SYCE
expect(isEncrypted(partialMatch)).toBe(false);
});
});
describe('encryptBase64 and decryptBase64', () => {
it('should encrypt and decrypt base64 strings', async () => {
const originalBase64 = btoa('Hello, World!');
const password = 'test-password';
const encryptedBase64 = await encryptBase64(originalBase64, password);
const decryptedBase64 = await decryptBase64(encryptedBase64, password);
expect(decryptedBase64).toBe(originalBase64);
});
it('should handle complex base64 data', async () => {
// Create some binary data and convert to base64
const binaryData = new Uint8Array([0, 127, 128, 255, 1, 2, 3]);
const originalBase64 = btoa(String.fromCharCode(...binaryData));
const password = 'complex-test';
const encryptedBase64 = await encryptBase64(originalBase64, password);
const decryptedBase64 = await decryptBase64(encryptedBase64, password);
expect(decryptedBase64).toBe(originalBase64);
});
it('should fail with wrong password', async () => {
const originalBase64 = btoa('Secret data');
const encryptedBase64 = await encryptBase64(originalBase64, 'correct-password');
await expect(decryptBase64(encryptedBase64, 'wrong-password')).rejects.toThrow();
});
it('should handle empty base64 string', async () => {
const originalBase64 = btoa('');
const password = 'test';
const encryptedBase64 = await encryptBase64(originalBase64, password);
const decryptedBase64 = await decryptBase64(encryptedBase64, password);
expect(decryptedBase64).toBe(originalBase64);
});
});
describe('isBase64Encrypted', () => {
it('should return true for encrypted base64 data', async () => {
const originalBase64 = btoa('Test data');
const encryptedBase64 = await encryptBase64(originalBase64, 'password');
expect(isBase64Encrypted(encryptedBase64)).toBe(true);
});
it('should return false for plain base64 data', () => {
const plainBase64 = btoa('Plain text');
expect(isBase64Encrypted(plainBase64)).toBe(false);
});
it('should return false for invalid base64 string', () => {
const invalidBase64 = '!!!not-valid-base64!!!';
expect(isBase64Encrypted(invalidBase64)).toBe(false);
});
it('should return false for empty string', () => {
expect(isBase64Encrypted('')).toBe(false);
});
it('should return false for JSON-like base64', () => {
const jsonBase64 = btoa('{"key": "value"}');
expect(isBase64Encrypted(jsonBase64)).toBe(false);
});
});
describe('Security properties', () => {
it('encrypted data should be larger than original due to header', async () => {
const originalData = stringToUint8Array('Test');
const encrypted = await encrypt(originalData, 'password');
// Header size: 4 (magic) + 1 (version) + 16 (salt) + 12 (IV) = 33 bytes
// Plus authentication tag from GCM (16 bytes)
expect(encrypted.length).toBeGreaterThan(originalData.length + 33);
});
it('should have correct magic bytes at the start', async () => {
const originalData = stringToUint8Array('Test');
const encrypted = await encrypt(originalData, 'password');
// Check SYCE magic bytes
expect(encrypted[0]).toBe(0x53); // S
expect(encrypted[1]).toBe(0x59); // Y
expect(encrypted[2]).toBe(0x43); // C
expect(encrypted[3]).toBe(0x45); // E
});
it('should have version 1 in the header', async () => {
const originalData = stringToUint8Array('Test');
const encrypted = await encrypt(originalData, 'password');
expect(encrypted[4]).toBe(1); // Version
});
});
});
================================================
FILE: packages/protobuf/utils/encryption.ts
================================================
/**
* End-to-end encryption utilities using Web Crypto API.
* Uses AES-GCM for authenticated encryption and PBKDF2 for password-based key derivation.
*/
// Constants for encryption
const ALGORITHM = 'AES-GCM';
const KEY_LENGTH = 256;
const PBKDF2_ITERATIONS = 100000;
const SALT_LENGTH = 16;
const IV_LENGTH = 12;
// Magic bytes to identify encrypted data (ASCII: "SYCE" - Sync Your Cookie Encrypted)
const MAGIC_BYTES = new Uint8Array([0x53, 0x59, 0x43, 0x45]);
const VERSION = 1;
/**
* Derives a cryptographic key from a password using PBKDF2.
*/
async function deriveKey(password: string, salt: Uint8Array): Promise<CryptoKey> {
const encoder = new TextEncoder();
const passwordKey = await crypto.subtle.importKey('raw', encoder.encode(password), 'PBKDF2', false, [
'deriveKey',
]);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: PBKDF2_ITERATIONS,
hash: 'SHA-256',
},
passwordKey,
{
name: ALGORITHM,
length: KEY_LENGTH,
},
false,
['encrypt', 'decrypt'],
);
}
/**
* Encrypts data using AES-GCM with a password-derived key.
*
* Output format:
* [MAGIC_BYTES (4)] [VERSION (1)] [SALT (16)] [IV (12)] [CIPHERTEXT (...)]
*
* @param data - The data to encrypt (Uint8Array)
* @param password - The password to derive the encryption key from
* @returns Promise<Uint8Array> - The encrypted data with header
*/
export async function encrypt(data: Uint8Array, password: string): Promise<Uint8Array> {
// Generate random salt and IV
const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
// Derive key from password
const key = await deriveKey(password, salt);
// Encrypt the data
const ciphertext = await crypto.subtle.encrypt(
{
name: ALGORITHM,
iv,
},
key,
data,
);
// Combine: MAGIC + VERSION + SALT + IV + CIPHERTEXT
const result = new Uint8Array(MAGIC_BYTES.length + 1 + SALT_LENGTH + IV_LENGTH + ciphertext.byteLength);
let offset = 0;
result.set(MAGIC_BYTES, offset);
offset += MAGIC_BYTES.length;
result[offset] = VERSION;
offset += 1;
result.set(salt, offset);
offset += SALT_LENGTH;
result.set(iv, offset);
offset += IV_LENGTH;
result.set(new Uint8Array(ciphertext), offset);
return result;
}
/**
* Decrypts data that was encrypted with the encrypt() function.
*
* @param encryptedData - The encrypted data with header (Uint8Array)
* @param password - The password to derive the decryption key from
* @returns Promise<Uint8Array> - The decrypted data
* @throws Error if decryption fails or data is invalid
*/
export async function decrypt(encryptedData: Uint8Array, password: string): Promise<Uint8Array> {
let offset = 0;
// Verify magic bytes
const magic = encryptedData.slice(offset, offset + MAGIC_BYTES.length);
offset += MAGIC_BYTES.length;
if (!magic.every((byte, i) => byte === MAGIC_BYTES[i])) {
throw new Error('Invalid encrypted data: magic bytes mismatch');
}
// Check version
const version = encryptedData[offset];
offset += 1;
if (version !== VERSION) {
throw new Error(`Unsupported encryption version: ${version}`);
}
// Extract salt
const salt = encryptedData.slice(offset, offset + SALT_LENGTH);
offset += SALT_LENGTH;
// Extract IV
const iv = encryptedData.slice(offset, offset + IV_LENGTH);
offset += IV_LENGTH;
// Extract ciphertext
const ciphertext = encryptedData.slice(offset);
// Derive key from password
const key = await deriveKey(password, salt);
// Decrypt the data
try {
const decrypted = await crypto.subtle.decrypt(
{
name: ALGORITHM,
iv,
},
key,
ciphertext,
);
return new Uint8Array(decrypted);
} catch {
throw new Error('Decryption failed: incorrect password or corrupted data');
}
}
/**
* Checks if data appears to be encrypted (starts with magic bytes).
*
* @param data - The data to check (Uint8Array)
* @returns boolean - True if data appears to be encrypted
*/
export function isEncrypted(data: Uint8Array): boolean {
if (data.length < MAGIC_BYTES.length) {
return false;
}
return data.slice(0, MAGIC_BYTES.length).every((byte, i) => byte === MAGIC_BYTES[i]);
}
/**
* Encrypts a base64 string using the provided password.
* Returns a new base64 string containing the encrypted data.
*
* @param base64Data - The base64-encoded data to encrypt
* @param password - The password to use for encryption
* @returns Promise<string> - The encrypted data as a base64 string
*/
export async function encryptBase64(base64Data: string, password: string): Promise<string> {
const binaryString = atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
const encrypted = await encrypt(bytes, password);
return arrayBufferToBase64ForEncryption(encrypted);
}
/**
* Decrypts a base64 string that was encrypted with encryptBase64().
* Returns the original base64-encoded data.
*
* @param encryptedBase64 - The encrypted base64 string
* @param password - The password to use for decryption
* @returns Promise<string> - The decrypted data as a base64 string
*/
export async function decryptBase64(encryptedBase64: string, password: string): Promise<string> {
const binaryString = atob(encryptedBase64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
const decrypted = await decrypt(bytes, password);
return arrayBufferToBase64ForEncryption(decrypted);
}
/**
* Checks if a base64 string appears to be encrypted data.
*
* @param base64Data - The base64 string to check
* @returns boolean - True if the data appears to be encrypted
*/
export function isBase64Encrypted(base64Data: string): boolean {
try {
const binaryString = atob(base64Data);
const bytes = new Uint8Array(Math.min(binaryString.length, MAGIC_BYTES.length));
for (let i = 0; i < bytes.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return isEncrypted(bytes);
} catch {
return false;
}
}
// Helper function for base64 encoding (to avoid circular dependency)
function arrayBufferToBase64ForEncryption(arrayBuffer: ArrayBuffer | Uint8Array) {
let base64 = '';
const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const bytes = arrayBuffer instanceof Uint8Array ? arrayBuffer : new Uint8Array(arrayBuffer);
const byteLength = bytes.byteLength;
const byteRemainder = byteLength % 3;
const mainLength = byteLength - byteRemainder;
let a, b, c, d;
let chunk;
for (let i = 0; i < mainLength; i = i + 3) {
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
a = (chunk & 16515072) >> 18;
b = (chunk & 258048) >> 12;
c = (chunk & 4032) >> 6;
d = chunk & 63;
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
}
if (byteRemainder == 1) {
chunk = bytes[mainLength];
a = (chunk & 252) >> 2;
b = (chunk & 3) << 4;
base64 += encodings[a] + encodings[b] + '==';
} else if (byteRemainder == 2) {
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
a = (chunk & 64512) >> 10;
b = (chunk & 1008) >> 4;
c = (chunk & 15) << 2;
base64 += encodings[a] + encodings[b] + encodings[c] + '=';
}
return base64;
}
================================================
FILE: packages/protobuf/utils/index.ts
================================================
export * from './base64';
export * from './encryption';
================================================
FILE: packages/protobuf/vitest.config.ts
================================================
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['**/*.test.ts'],
},
});
================================================
FILE: packages/shared/README.md
================================================
# Shared Package
This package contains code shared with other packages.
To use the code in the package, you need to add the following to the package.json file.
```json
{
"dependencies": {
"@sync-your-cookie/shared": "workspace:*"
}
}
```
After building this package, real-time cache busting does not occur in the code of other packages that reference this package.
You need to rerun it from the root path with `pnpm dev`, etc. (This will be improved in the future.)
If the type does not require compilation, there is no problem, but if the implementation requiring compilation is changed, a problem may occur.
Therefore, it is recommended to extract and use it in each context if it is easier to manage by extracting overlapping or business logic from the code that changes frequently in this package.
================================================
FILE: packages/shared/index.ts
================================================
export * from './lib/cloudflare';
export * from './lib/cookie';
export * from './lib/hoc';
export * from './lib/hooks';
export * from './lib/Providers';
export * from './lib/message';
export * from './lib/utils';
export * from './lib/github';
================================================
FILE: packages/shared/lib/Providers/ThemeProvider.tsx
================================================
import { useStorageSuspense } from '@lib/hooks/useStorageSuspense';
import { themeStorage } from '@sync-your-cookie/storage/lib/themeStorage';
import { createContext, useEffect } from 'react';
type Theme = 'dark' | 'light' | 'system';
type ThemeProviderProps = {
children: React.ReactNode;
};
type ThemeProviderState = {
theme: Theme;
setTheme: (theme: Theme) => void;
};
const initialState: ThemeProviderState = {
theme: 'system',
setTheme: () => null,
};
export const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
const theme = useStorageSuspense(themeStorage);
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove('light', 'dark');
if (theme === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
root.classList.add(systemTheme);
return;
}
root.classList.add(theme);
}, [theme]);
const value = {
theme,
setTheme: (theme: Theme) => {
// localStorage.setItem(storageKey, theme);
themeStorage.set(theme);
// setTheme(theme);
},
};
return (
<ThemeProviderContext.Provider {...props} value={value}>
{children}
</ThemeProviderContext.Provider>
);
}
================================================
FILE: packages/shared/lib/Providers/hooks/index.ts
================================================
export * from './useTheme';
================================================
FILE: packages/shared/lib/Providers/hooks/useTheme.ts
================================================
import { ThemeProviderContext } from '../';
import { useContext, useEffect } from 'react';
export const useTheme = () => {
const context = useContext(ThemeProviderContext);
useEffect(() => {
const handler = (event: MediaQueryListEvent) => {
if (event.matches) {
context.setTheme('dark');
} else {
context.setTheme('light');
}
};
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', handler);
return () => {
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', handler);
};
}, []);
if (context === undefined) throw new Error('useTheme must be used within a ThemeProvider');
return context;
};
================================================
FILE: packages/shared/lib/Providers/index.tsx
================================================
export * from './hooks';
export * from './ThemeProvider';
================================================
FILE: packages/shared/lib/cloudflare/api.ts
================================================
import { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';
export interface WriteResponse {
success: boolean;
errors: {
code: number;
message: string;
}[];
}
/**
*
* @param value specify the value to write
* @param accountId cloudflare account id
* @param namespaceId cloudflare namespace id
* @param token api token
* @returns promise<res>
*/
export const writeCloudflareKV = async (value: string, accountId: string, namespaceId: string, token: string) => {
const storageKey = settingsStorage.getSnapshot()?.storageKey;
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${storageKey}`;
// const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`;
// const payload = [
// {
// key: DEFAULT_KEY,
// metadata: JSON.stringify({
// someMetadataKey: value,
// }),
// value: value,
// },
// ];
const options = {
method: 'PUT',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: value,
};
return fetch(url, options).then(res => res.json());
};
/**
*
* @param accountId cloudflare account id
* @param namespaceId cloudflare namespace id
* @param token api token
* @returns Promise<res>
*/
export const readCloudflareKV = async (accountId: string, namespaceId: string, token: string) => {
const storageKey = settingsStorage.getSnapshot()?.storageKey;
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${storageKey}`;
const options = {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
};
return fetch(url, options).then(async res => {
if (res.status === 404) {
return '';
}
if (res.status === 200) {
const text = await res.text();
return text.trim();
} else {
return Promise.reject(await res.json());
}
});
};
export const verifyCloudflareAccountToken = async (accountId: string, token: string) => {
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/tokens/verify`;
const options = {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
};
return fetch(url, options).then(async res => {
if (res.status === 200) {
return res.json();
} else {
return Promise.reject(await res.json());
}
});
};
export const verifyCloudflareToken = async (accountId: string, token: string) => {
const url = `https://api.cloudflare.com/client/v4/user/tokens/verify`;
const options = {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
};
return fetch(url, options).then(async res => {
if (res.status === 200) {
return res.json();
} else {
return verifyCloudflareAccountToken(accountId, token);
}
});
};
================================================
FILE: packages/shared/lib/cloudflare/enum.ts
================================================
export enum ErrorCode {
NotFoundRoute = 7003,
AuthenicationError = 10000,
NamespaceIdError = 10011,
}
================================================
FILE: packages/shared/lib/cloudflare/index.ts
================================================
export * from './api';
export * from './enum';
================================================
FILE: packages/shared/lib/cookie/index.ts
================================================
export * from './withCloudflare';
export * from './withStorage';
================================================
FILE: packages/shared/lib/cookie/withCloudflare.ts
================================================
import { accountStorage, type AccountInfo } from '@sync-your-cookie/storage/lib/accountStorage';
import { getActiveStorageItem, settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';
import { readCloudflareKV, writeCloudflareKV, WriteResponse } from '../cloudflare/api';
import { GithubApi } from '@lib/github';
import { MessageErrorCode } from '@lib/message';
import { RestEndpointMethodTypes } from '@octokit/rest';
import {
arrayBufferToBase64,
base64ToArrayBuffer,
decodeCookiesMap,
decryptBase64,
encodeCookiesMap,
encryptBase64,
ICookie,
ICookiesMap,
ILocalStorageItem,
isBase64Encrypted,
} from '@sync-your-cookie/protobuf';
export const check = (accountInfo?: AccountInfo) => {
const cloudflareAccountInfo = accountInfo || accountStorage.getSnapshot();
if (cloudflareAccountInfo?.selectedProvider === 'github') {
if (!cloudflareAccountInfo.githubAccessToken) {
return Promise.reject({
message: 'GitHub Access Token is empty',
code: MessageErrorCode.AccountCheck,
});
}
} else {
if (!cloudflareAccountInfo?.accountId || !cloudflareAccountInfo.namespaceId || !cloudflareAccountInfo.token) {
let message = 'Account ID is empty';
if (!cloudflareAccountInfo?.namespaceId) {
message = 'NamespaceId ID is empty';
} else if (!cloudflareAccountInfo.token) {
message = 'Token is empty';
}
return Promise.reject({
message,
code: MessageErrorCode.AccountCheck,
});
}
}
return cloudflareAccountInfo;
};
export const readCookiesMap = async (accountInfo: AccountInfo): Promise<ICookiesMap> => {
let content = '';
if (accountInfo.selectedProvider === 'github') {
const activeStorageItem = getActiveStorageItem();
if (activeStorageItem?.rawUrl) {
content = await GithubApi.instance.fetchRawContent(activeStorageItem.rawUrl);
}
} else {
await check(accountInfo);
content = await readCloudflareKV(accountInfo.accountId!, accountInfo.namespaceId!, accountInfo.token!);
}
if (content) {
try {
const settingsInfo = settingsStorage.getSnapshot();
const encryptionEnabled = settingsInfo?.encryptionEnabled;
const encryptionPassword = settingsInfo?.encryptionPassword;
// Check if content is encrypted and decrypt if needed
let processedContent = content;
const protobufEncoding = !content.startsWith('{');
if (protobufEncoding && isBase64Encrypted(content)) {
if (!encryptionEnabled || !encryptionPassword) {
return Promise.reject({
message: 'Failed to decrypt data. Please check your encryption password.',
code: MessageErrorCode.DecryptFailed,
});
}
try {
processedContent = await decryptBase64(content, encryptionPassword);
} catch (decryptError) {
console.error('Decryption failed:', decryptError);
// throw new Error('Failed to decrypt data. Please check your encryption password.');
return Promise.reject({
message: 'Failed to decrypt data. Please check your encryption password.',
code: MessageErrorCode.DecryptFailed,
});
}
}
if (protobufEncoding) {
const compressedBuffer = base64ToArrayBuffer(processedContent);
const deMsg = await decodeCookiesMap(compressedBuffer);
console.log('readCookiesMap->deMsg', deMsg);
return deMsg;
} else {
console.log('readCookiesMap->res', JSON.parse(processedContent));
return JSON.parse(processedContent);
}
} catch (error) {
console.log('Decode error', error, content);
// return {};
return Promise.reject({
message: `Decode error: ${error}, please check your save settings`,
code: MessageErrorCode.DecodeFailed,
});
}
} else {
return {};
}
};
export const writeCookiesMap = async (accountInfo: AccountInfo, cookiesMap: ICookiesMap = {}) => {
const settingsInfo = settingsStorage.getSnapshot();
const protobufEncoding = settingsInfo?.protobufEncoding;
const encryptionEnabled = settingsInfo?.encryptionEnabled;
const encryptionPassword = settingsInfo?.encryptionPassword;
let encodingStr = '';
if (protobufEncoding) {
const buffered = await encodeCookiesMap(cookiesMap);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
encodingStr = arrayBufferToBase64(buffered as any);
// Encrypt the data if encryption is enabled
if (encryptionEnabled && encryptionPassword) {
encodingStr = await encryptBase64(encodingStr, encryptionPassword);
console.log('writeCookiesMap-> data encrypted');
}
} else {
encodingStr = JSON.stringify(cookiesMap);
console.log('writeCookiesMap->', cookiesMap);
}
if (accountInfo.selectedProvider === 'github') {
const storageKeyGistId = settingsInfo?.storageKeyGistId;
const storageKey = settingsInfo?.storageKey;
return await GithubApi.instance.updateGist(storageKeyGistId!, storageKey!, encodingStr);
} else {
const res = await writeCloudflareKV(
encodingStr,
accountInfo.accountId!,
accountInfo.namespaceId!,
accountInfo.token!,
);
return res;
}
};
export const mergeAndWriteCookies = async (
accountInfo: AccountInfo,
domain: string,
cookies: ICookie[],
localStorageItems: ILocalStorageItem[] = [],
userAgent = '',
oldCookieMap: ICookiesMap = {},
): Promise<[WriteResponse | RestEndpointMethodTypes['gists']['update']['response'], ICookiesMap]> => {
await check(accountInfo);
const cookiesMap: ICookiesMap = {
updateTime: Date.now(),
createTime: oldCookieMap?.createTime || Date.now(),
domainCookieMap: {
...(oldCookieMap.domainCookieMap || {}),
[domain]: {
updateTime: Date.now(),
createTime: oldCookieMap.domainCookieMap?.[domain]?.createTime || Date.now(),
cookies: cookies,
localStorageItems: localStorageItems,
userAgent: userAgent || oldCookieMap.domainCookieMap?.[domain]?.userAgent || '',
},
},
};
const res = await writeCookiesMap(accountInfo, cookiesMap);
return [res, cookiesMap];
};
export const mergeAndWriteMultipleDomainCookies = async (
cloudflareAccountInfo: AccountInfo,
domainCookies: { domain: string; cookies: ICookie[]; localStorageItems: ILocalStorageItem[]; userAgent?: string }[],
oldCookieMap: ICookiesMap = {},
): Promise<[WriteResponse, ICookiesMap]> => {
await check(cloudflareAccountInfo);
const newDomainCookieMap = {
...(oldCookieMap.domainCookieMap || {}),
};
for (const { domain, cookies, localStorageItems, userAgent } of domainCookies) {
newDomainCookieMap[domain] = {
updateTime: Date.now(),
createTime: oldCookieMap.domainCookieMap?.[domain]?.createTime || Date.now(),
cookies: cookies,
localStorageItems: localStorageItems || [],
userAgent: userAgent || oldCookieMap.domainCookieMap?.[domain]?.userAgent || '',
};
}
const cookiesMap: ICookiesMap = {
updateTime: Date.now(),
createTime: oldCookieMap?.createTime || Date.now(),
domainCookieMap: newDomainCookieMap,
};
const res = await writeCookiesMap(cloudflareAccountInfo, cookiesMap);
return [res, cookiesMap];
};
export const removeAndWriteCookies = async (
cloudflareAccountInfo: AccountInfo,
domain: string,
oldCookieMap: ICookiesMap = {},
id?: string,
): Promise<[WriteResponse, ICookiesMap]> => {
await check(cloudflareAccountInfo);
const cookiesMap: ICookiesMap = {
updateTime: Date.now(),
createTime: oldCookieMap?.createTime || Date.now(),
domainCookieMap: {
...(oldCookieMap.domainCookieMap || {}),
},
};
if (cookiesMap.domainCookieMap) {
if (id !== undefined) {
if (cookiesMap.domainCookieMap[domain]?.cookies) {
const oldLength = cookiesMap.domainCookieMap[domain]?.cookies?.length || 0;
cookiesMap.domainCookieMap[domain].cookies =
cookiesMap.domainCookieMap[domain].cookies?.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(cookie: any) => `${cookie.domain}_${cookie.name}` !== id,
) || [];
const newLength = cookiesMap.domainCookieMap[domain]?.cookies?.length || 0;
if (oldLength === newLength) {
throw new Error(`${id}: cookie not found`);
}
}
} else {
delete cookiesMap.domainCookieMap[domain];
}
}
const res = await writeCookiesMap(cloudflareAccountInfo, cookiesMap);
return [res, cookiesMap];
};
export const editAndWriteCookies = async (
cloudflareAccountInfo: AccountInfo,
host: string,
oldCookieMap: ICookiesMap = {},
oldItem: ICookie,
newItem: ICookie,
): Promise<[WriteResponse, ICookiesMap]> => {
await check(cloudflareAccountInfo);
const cookiesMap: ICookiesMap = {
updateTime: Date.now(),
createTime: oldCookieMap?.createTime || Date.now(),
domainCookieMap: {
...(oldCookieMap.domainCookieMap || {}),
},
};
if (cookiesMap.domainCookieMap) {
const cookieLength = cookiesMap.domainCookieMap[host]?.cookies?.length || 0;
for (let i = 0; i < cookieLength; i++) {
const cookieItem = cookiesMap.domainCookieMap[host]?.cookies?.[i];
if (cookieItem?.name === oldItem.name && cookieItem?.domain === oldItem.domain) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(cookiesMap.domainCookieMap[host].cookies as any)[i] = {
...cookieItem,
...newItem,
};
break;
}
}
}
const res = await writeCookiesMap(cloudflareAccountInfo, cookiesMap);
return [res, cookiesMap];
};
================================================
FILE: packages/shared/lib/cookie/withStorage.ts
================================================
import { ICookie, ICookiesMap, ILocalStorageItem } from '@sync-your-cookie/protobuf';
import { Cookie, cookieStorage } from '@sync-your-cookie/storage/lib/cookieStorage';
import { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';
import { AccountInfo, accountStorage } from '@sync-your-cookie/storage/lib/accountStorage';
import { MessageType, sendMessage } from '@lib/message';
import { RestEndpointMethodTypes } from '@octokit/rest';
import { OctokitResponse } from '@octokit/types';
import { WriteResponse } from '../cloudflare';
import {
editAndWriteCookies,
mergeAndWriteCookies,
mergeAndWriteMultipleDomainCookies,
readCookiesMap,
removeAndWriteCookies,
} from './withCloudflare';
export const readCookiesMapWithStatus = async (cloudflareInfo: AccountInfo) => {
let cookieMap: Cookie | null = null;
const domainStatus = await domainStatusStorage.get();
if (domainStatus.pushing) {
cookieMap = await cookieStorage.getSnapshot();
}
if (cookieMap && Object.keys(cookieMap.domainCookieMap || {}).length > 0) {
return cookieMap;
}
return await readCookiesMap(cloudflareInfo);
};
export const pullCookies = async (isInit = false): Promise<Cookie> => {
const cloudflareInfo = await accountStorage.get();
if (isInit && (!cloudflareInfo.accountId || !cloudflareInfo.namespaceId || !cloudflareInfo.token)) {
return {};
}
try {
const domainStatus = await domainStatusStorage.get();
if (domainStatus.pulling) {
const cookieMap = await cookieStorage.getSnapshot();
if (cookieMap && Object.keys(cookieMap.domainCookieMap || {}).length > 0) {
return cookieMap;
}
}
await domainStatusStorage.update({
pulling: true,
});
const cookieMap = await readCookiesMapWithStatus(cloudflareInfo);
const res = await cookieStorage.update(cookieMap, isInit);
await domainStatusStorage.update({
pulling: false,
});
return res;
} catch (e) {
console.error('pullCookies fail', e);
await domainStatusStorage.update({
pulling: false,
});
return Promise.reject(e);
}
};
function extractPortRegex(host: string) {
const match = host.match(/:(\d+)$/);
return match ? match[1] : null;
}
export const pullAndSetCookies = async (activeTabUrl: string, host: string, isReload = true): Promise<Cookie> => {
const cookieMap = await pullCookies();
const cookieDetails = cookieMap?.domainCookieMap?.[host]?.cookies || [];
const localStorageItems = cookieMap?.domainCookieMap?.[host]?.localStorageItems || [];
if (cookieDetails.length === 0 && localStorageItems.length === 0) {
console.warn('no cookies to pull, push first please', host, cookieMap);
throw new Error('No cookies to pull, push first please');
} else {
const cookiesPromiseList: Promise<unknown>[] = [];
for (const cookie of cookieDetails) {
let removeWWWHost = host.replace('www.', '');
const port = extractPortRegex(removeWWWHost);
if (port) {
removeWWWHost = removeWWWHost.replace(':' + port, '');
}
if (cookie.domain?.includes(removeWWWHost)) {
let url = activeTabUrl;
if (cookie.domain) {
const urlObj = new URL(activeTabUrl);
const protocol = activeTabUrl ? urlObj.protocol : 'http:';
const itemHost = cookie.domain.startsWith('.') ? cookie.domain.slice(1) : cookie.domain;
url = `${protocol}//${itemHost}`;
}
const cookieDetail: chrome.cookies.SetDetails = {
domain: cookie.domain.startsWith('.') || !url ? cookie.domain : undefined,
name: cookie.name ?? undefined,
url: url,
storeId: cookie.storeId ?? undefined,
value: cookie.value ?? undefined,
expirationDate: cookie.expirationDate ?? undefined,
path: cookie.path ?? undefined,
httpOnly: cookie.httpOnly ?? undefined,
secure: cookie.secure ?? undefined,
sameSite: (cookie.sameSite ?? undefined) as chrome.cookies.SameSiteStatus,
};
const promise = new Promise((resolve, reject) => {
try {
chrome.cookies.set(cookieDetail, res => {
resolve(res);
});
} catch (error) {
console.error('cookie set error', cookieDetail, error);
reject(error);
}
});
cookiesPromiseList.push(promise);
}
}
// reload window after set cookies
// await new Promise(resolve => {
// setTimeout(resolve, 5000);
// });
await sendMessage(
{
type: MessageType.SetLocalStorage,
payload: {
domain: host,
value: localStorageItems,
},
},
true,
)
.then(res => {
console.log('set local storage', res);
})
.catch(err => {
console.error('set local storage error', err);
});
if (cookiesPromiseList.length === 0 && localStorageItems.length === 0) {
console.warn('no matched cookies and localStorageItems to pull, push first please', host, cookieMap);
throw new Error('No matched cookies and localStorageItems to pull, push first please');
}
await Promise.allSettled(cookiesPromiseList);
if (isReload) {
chrome.tabs.query({}, function (tabs) {
tabs.forEach(function (tab) {
// 使用字符串匹配
if (tab.url && tab.url.includes(host) && tab.id) {
console.log('tab', tab);
chrome.tabs.reload(tab.id);
}
});
});
}
}
return cookieMap;
};
export type GistUpdateResponse = RestEndpointMethodTypes['gists']['update']['response'];
export type PushCookiesResponse = WriteResponse | GistUpdateResponse;
const checkSuccessAndUpdate = async (
accountInfo: AccountInfo,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
res: WriteResponse | OctokitResponse<any>,
cookieMap: ICookiesMap,
) => {
if (accountInfo.selectedProvider === 'github') {
if ((res as unknown as GistUpdateResponse)?.status?.toString()?.startsWith('2')) {
await cookieStorage.update(cookieMap);
}
} else {
if ((res as WriteResponse).success) {
await cookieStorage.update(cookieMap);
}
}
};
export const pushCookies = async (
domain: string,
cookies: ICookie[],
localStorageItems: ILocalStorageItem[] = [],
userAgent = '',
): Promise<PushCookiesResponse> => {
const accountInfo = await accountStorage.get();
try {
const domainStatus = await domainStatusStorage.get();
if (domainStatus.pushing) return Promise.reject('the cookie is pushing');
await domainStatusStorage.update({
pushing: true,
});
const oldCookie = await readCookiesMapWithStatus(accountInfo);
const [res, cookieMap] = await mergeAndWriteCookies(
accountInfo,
domain,
cookies,
localStorageItems,
userAgent,
oldCookie,
);
console.log('res->pushCookies', res);
await checkSuccessAndUpdate(accountInfo, res, cookieMap);
await domainStatusStorage.update({
pushing: false,
});
return res;
} catch (e) {
console.error('pushCookies fail err', e);
await domainStatusStorage.update({
pushing: false,
});
return Promise.reject(e);
}
};
export const pushMultipleDomainCookies = async (
domainCookies: { domain: string; cookies: ICookie[]; localStorageItems: ILocalStorageItem[]; userAgent?: string }[],
): Promise<WriteResponse> => {
const accountInfo = await accountStorage.get();
try {
const domainStatus = await domainStatusStorage.get();
if (domainStatus.pushing) return Promise.reject('cookie is pushing');
await domainStatusStorage.update({
pushing: true,
});
const oldCookie = await readCookiesMapWithStatus(accountInfo);
const [res, cookieMap] = await mergeAndWriteMultipleDomainCookies(accountInfo, domainCookies, oldCookie);
await domainStatusStorage.update({
pushing: false,
});
await checkSuccessAndUpdate(accountInfo, res, cookieMap);
return res;
} catch (e) {
console.error('pushMultipleDomainCookies fail err', e);
await domainStatusStorage.update({
pushing: false,
});
return Promise.reject(e);
}
};
export const removeCookies = async (domain: string): Promise<WriteResponse> => {
const accountInfo = await accountStorage.get();
try {
const domainStatus = await domainStatusStorage.get();
if (domainStatus.pushing) return Promise.reject('the cookie is pushing');
await domainStatusStorage.update({
pushing: true,
});
// const oldCookie = await cookieStorage.get();
const oldCookie = await readCookiesMapWithStatus(accountInfo);
const [res, cookieMap] = await removeAndWriteCookies(accountInfo, domain, oldCookie);
await domainStatusStorage.update({
pushing: false,
});
await checkSuccessAndUpdate(accountInfo, res, cookieMap);
return res;
} catch (e) {
console.error('removeCookies fail err', e);
await domainStatusStorage.update({
pushing: false,
});
return Promise.reject(e);
}
};
export const removeCookieItem = async (domain: string, id: string): Promise<WriteResponse> => {
const accountInfo = await accountStorage.get();
try {
const domainStatus = await domainStatusStorage.get();
if (domainStatus.pushing) return Promise.reject('the cookie is pushing');
await domainStatusStorage.update({
pushing: true,
});
// const oldCookie = await cookieStorage.get();
const oldCookie = await readCookiesMapWithStatus(accountInfo);
const [res, cookieMap] = await removeAndWriteCookies(accountInfo, domain, oldCookie, id);
await domainStatusStorage.update({
pushing: false,
});
await checkSuccessAndUpdate(accountInfo, res, cookieMap);
return res;
} catch (e) {
console.error('removeCookieItem fail err', e);
await domainStatusStorage.update({
pushing: false,
});
return Promise.reject(e);
}
};
export const editCookieItem = async (domain: string, name: string): Promise<WriteResponse> => {
const accountInfo = await accountStorage.get();
try {
const domainStatus = await domainStatusStorage.get();
if (domainStatus.pushing) return Promise.reject('the cookie is pushing');
await domainStatusStorage.update({
pushing: true,
});
// const oldCookie = await cookieStorage.get();
const oldCookie = await readCookiesMapWithStatus(accountInfo);
const [res, cookieMap] = await removeAndWriteCookies(accountInfo, domain, oldCookie, name);
await domainStatusStorage.update({
pushing: false,
});
await checkSuccessAndUpdate(accountInfo, res, cookieMap);
return res;
} catch (e) {
console.error('removeCookieItem fail err', e);
await domainStatusStorage.update({
pushing: false,
});
return Promise.reject(e);
}
};
export class CookieOperator {
static async prepare() {
const cloudflareInfo = await accountStorage.get();
const domainStatus = await domainStatusStorage.get();
if (domainStatus.pushing) return Promise.reject('the cookie is pushing');
return { cloudflareInfo };
}
static async setPushing(open: boolean) {
await domainStatusStorage.update({
pushing: open,
});
}
static async editCookieItem(host: string, oldItem: ICookie, newItem: ICookie) {
try {
const { cloudflareInfo } = await this.prepare();
await this.setPushing(true);
const oldCookie = await readCookiesMapWithStatus(cloudflareInfo);
const [res, cookieMap] = await editAndWriteCookies(cloudflareInfo, host, oldCookie, oldItem, newItem);
await this.setPushing(false);
await checkSuccessAndUpdate(cloudflareInfo, res, cookieMap);
return res;
} catch (e) {
console.error('removeCookieItem fail err', e);
await this.setPushing(false);
return Promise.reject(e);
}
}
}
================================================
FILE: packages/shared/lib/github/api.ts
================================================
import { Octokit, RestEndpointMethodTypes } from '@octokit/rest';
import { arrayBufferToBase64, encodeCookiesMap, ICookiesMap } from '@sync-your-cookie/protobuf';
import { accountStorage } from '@sync-your-cookie/storage/lib/accountStorage';
import { IStorageItem, settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';
export class GithubApi {
private clientId: string;
private clientSecret: string;
private accessToken?: string | null = null;
private prefix = 'sync-your-cookie_';
static instance: GithubApi;
octokit!: Octokit;
private inited = false;
private initDefault = false;
constructor(clientId: string, clientSecret: string, initDefault: boolean = false) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.accessToken = accountStorage.getSnapshot()?.githubAccessToken;
this.initDefault = initDefault;
this.subscribe();
this.init();
}
public static getInstance(clientId: string, clientSecret: string, initDefault: boolean = false): GithubApi {
if (!GithubApi.instance) {
GithubApi.instance = new GithubApi(clientId, clientSecret, initDefault);
}
return GithubApi.instance;
}
async newOctokit() {
if (this.accessToken) {
this.octokit = new Octokit({ auth: this.accessToken });
// this.octokit.hook.before('request', options => {
// console.log('请求 URL:', options.url);
// console.log('请求头:', options.headers);
// });
}
}
async init() {
if (this.inited) {
return;
}
this.reload();
this.inited = true;
}
reload() {
this.newOctokit();
this.initStorageKeyList();
}
async getSyncGists() {
const res = await this.listGists();
const fullList = res.data;
const syncGist = fullList.find(gist => {
const files = gist.files;
const keys = Object.keys(files);
const hasSyncFile = keys.some(key => key.startsWith(this.prefix));
return hasSyncFile;
});
return syncGist;
}
async initStorageKeyList() {
if (!this.octokit) {
return;
}
let syncGist = await this.getSyncGists();
if (!syncGist && this.initDefault) {
console.log('No sync gists found, creating one...');
const content = await this.initContent();
await this.createGist('Sync Your Cookie Gist', `${this.prefix}Default`, content, false);
// syncGists.push(newGist.data);
syncGist = await this.getSyncGists();
}
if (syncGist) {
await this.setStorageKeyList(syncGist);
}
}
async initContent() {
const cookiesMap: ICookiesMap = {
updateTime: Date.now(),
createTime: Date.now(),
domainCookieMap: {},
};
let encodingStr = '';
const protobufEncoding = settingsStorage.getSnapshot()?.protobufEncoding;
if (protobufEncoding) {
const buffered = await encodeCookiesMap(cookiesMap);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
encodingStr = arrayBufferToBase64(buffered as any);
} else {
encodingStr = JSON.stringify(cookiesMap);
}
return encodingStr;
}
async setStorageKeyList(gist: RestEndpointMethodTypes['gists']['list']['response']['data'][number]) {
const files = gist.files;
const storageKeys: IStorageItem[] = [];
if (files) {
const currentStorageKey = settingsStorage.getSnapshot()?.storageKey;
let currentStorageExist = false;
for (const filename in files) {
const file = files[filename];
if (filename.startsWith(this.prefix)) {
const tempValue = filename.replace(this.prefix, '');
if (!currentStorageExist) {
if (currentStorageKey && tempValue === currentStorageKey) {
currentStorageExist = true;
}
}
storageKeys.push({
value: tempValue,
label: tempValue,
rawUrl: file.raw_url,
gistId: gist.id,
});
// storageKeys.push(file[0].replace(this.prefix, ''));
}
}
console.log('storageKeys', storageKeys, gist.id);
settingsStorage.update({
storageKeyList: storageKeys,
storageKey: currentStorageExist ? currentStorageKey : storageKeys[0]?.value,
storageKeyGistId: gist.id,
gistHtmlUrl: gist.html_url,
});
} else {
console.log('No files found in gist', gist.id);
}
// const keys = Object.keys(files);
// const storageKeys = keys.filter(key => key.startsWith(this.prefix)).map(key => key.replace(this.prefix, ''));
// console.log('storageKeys', storageKeys);
// return storageKeys;
}
subscribe() {
accountStorage.subscribe(async () => {
const accessToken = accountStorage.getSnapshot()?.githubAccessToken;
if (this.accessToken === accessToken || !accessToken) {
return;
}
this.accessToken = accessToken;
console.log('GithubApi accountStorage changed -> this.accessToken', this.accessToken);
this.inited = false;
this.init();
});
}
// 用 code 换取 access_token
async fetchAccessToken(code: string): Promise<string> {
const url = 'https://github.com/login/oauth/access_token';
const params = {
client_id: this.clientId,
client_secret: this.clientSecret,
code,
};
const headers = { Accept: 'application/json', 'Content-Type': 'application/json' };
const res = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(params),
});
const data = await res.json();
if (data.access_token) {
this.accessToken = data.access_token;
return this.accessToken || '';
}
throw new Error('获取 access_token 失败');
}
// 用 access_token 获取用户信息
async fetchUser() {
this.ensureToken();
// const res = await this.octokit.users.getById();
const res = await fetch('https://api.github.com/user', {
headers: { Authorization: `token ${this.accessToken}` },
});
return res.json() as Promise<RestEndpointMethodTypes['users']['getById']['response']['data']>;
}
async request(method: string, path: string, payload: Record<string, any> = {}) {
this.ensureToken();
const res = await this.octokit.request(`${method} ${path}`, {
...payload,
headers: {
// Authorization: `Bearer ${this.accessToken as string}`,
'X-GitHub-Api-Version': '2022-11-28',
},
});
if (res.status !== 200) {
return Promise.reject(res);
}
return res.data;
}
async get(path: string) {
return this.request('GET', path);
}
async post(path: string, payload: Record<string, any> = {}) {
return this.request('POST', path, payload);
}
async patch(path: string, payload: Record<string, any> = {}) {
return this.request('PATCH', path, payload);
}
// 获取 gist 列表
async listGists() {
const res = await this.octokit.gists.list();
return res;
}
// 创建 gist
async createGist(description: string, filename: string, content: string, publicGist = false) {
return this.octokit.gists.create({
description: description,
public: publicGist,
files: {
[filename]: {
content: content,
},
},
});
}
async getGist(gistId: string) {
return this.octokit.gists.get({ gist_id: gistId });
}
// 更新 gist
async updateGist(gistId: string, filename: string, content: string) {
// const { data: gist } = await this.octokit.gists.get({
// gist_id: gistId,
// });
// console.log('files', gist.files);
// const existFiles = gist.files || {};
const syncFileName = filename.startsWith(this.prefix) ? filename : this.prefix +
gitextract_67wvu_tp/ ├── .eslintignore ├── .eslintrc ├── .github/ │ ├── CODEOWNERS │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── auto_assign.yml │ ├── dependabot.yml │ ├── stale.yml │ └── workflows/ │ ├── auto-assign.yml │ ├── build-zip.yml │ ├── claude.yml │ └── greetings.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .vscode/ │ └── settings.json ├── LICENSE ├── README.md ├── README_ZH.md ├── chrome-extension/ │ ├── lib/ │ │ └── background/ │ │ ├── badge.ts │ │ ├── contextMenu.ts │ │ ├── index.ts │ │ ├── listen.ts │ │ └── subscribe.ts │ ├── manifest.js │ ├── package.json │ ├── public/ │ │ ├── _locales/ │ │ │ └── en/ │ │ │ └── messages.json │ │ └── content.css │ ├── tsconfig.json │ ├── utils/ │ │ └── plugins/ │ │ └── make-manifest-plugin.ts │ └── vite.config.ts ├── googlef759ff453695209f.html ├── how-to-use.md ├── package.json ├── packages/ │ ├── dev-utils/ │ │ ├── index.ts │ │ ├── lib/ │ │ │ ├── logger.ts │ │ │ └── manifest-parser/ │ │ │ ├── impl.ts │ │ │ ├── index.ts │ │ │ └── type.ts │ │ ├── package.json │ │ └── tsconfig.json │ ├── hmr/ │ │ ├── index.ts │ │ ├── lib/ │ │ │ ├── constant.ts │ │ │ ├── debounce.ts │ │ │ ├── initClient.ts │ │ │ ├── initReloadServer.ts │ │ │ ├── injections/ │ │ │ │ ├── refresh.ts │ │ │ │ └── reload.ts │ │ │ ├── interpreter/ │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ └── plugins/ │ │ │ ├── index.ts │ │ │ ├── make-entry-point-plugin.ts │ │ │ └── watch-rebuild-plugin.ts │ │ ├── package.json │ │ ├── rollup.config.mjs │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── protobuf/ │ │ ├── README.md │ │ ├── index.ts │ │ ├── lib/ │ │ │ └── protobuf/ │ │ │ ├── code.ts │ │ │ ├── index.ts │ │ │ └── proto/ │ │ │ ├── cookie.d.ts │ │ │ └── cookie.js │ │ ├── package.json │ │ ├── proto/ │ │ │ └── cookie.proto │ │ ├── tsconfig.json │ │ ├── tsup.config.ts │ │ ├── utils/ │ │ │ ├── base64.ts │ │ │ ├── compress.ts │ │ │ ├── encryption.test.ts │ │ │ ├── encryption.ts │ │ │ └── index.ts │ │ └── vitest.config.ts │ ├── shared/ │ │ ├── README.md │ │ ├── index.ts │ │ ├── lib/ │ │ │ ├── Providers/ │ │ │ │ ├── ThemeProvider.tsx │ │ │ │ ├── hooks/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── useTheme.ts │ │ │ │ └── index.tsx │ │ │ ├── cloudflare/ │ │ │ │ ├── api.ts │ │ │ │ ├── enum.ts │ │ │ │ └── index.ts │ │ │ ├── cookie/ │ │ │ │ ├── index.ts │ │ │ │ ├── withCloudflare.ts │ │ │ │ └── withStorage.ts │ │ │ ├── github/ │ │ │ │ ├── api.ts │ │ │ │ └── index.ts │ │ │ ├── hoc/ │ │ │ │ ├── index.ts │ │ │ │ ├── withErrorBoundary.tsx │ │ │ │ └── withSuspense.tsx │ │ │ ├── hooks/ │ │ │ │ ├── index.ts │ │ │ │ ├── useCookieAction.ts │ │ │ │ ├── useStorage.ts │ │ │ │ └── useStorageSuspense.tsx │ │ │ ├── message/ │ │ │ │ └── index.ts │ │ │ └── utils/ │ │ │ └── index.ts │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── tsup.config.ts │ ├── storage/ │ │ ├── index.ts │ │ ├── lib/ │ │ │ ├── accountStorage.ts │ │ │ ├── base.ts │ │ │ ├── cookieStorage.ts │ │ │ ├── domainConfigStorage.ts │ │ │ ├── domainStatusStorage.ts │ │ │ ├── index.ts │ │ │ ├── settingsStorage.ts │ │ │ └── themeStorage.ts │ │ ├── package.json │ │ └── tsconfig.json │ ├── tailwind-config/ │ │ ├── package.json │ │ └── tailwind.config.js │ ├── tsconfig/ │ │ ├── app.json │ │ ├── base.json │ │ ├── package.json │ │ └── utils.json │ ├── ui/ │ │ ├── components.json │ │ ├── globals.css │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── DateTable/ │ │ │ │ │ └── index.tsx │ │ │ │ ├── Image/ │ │ │ │ │ └── index.tsx │ │ │ │ ├── Spinner/ │ │ │ │ │ └── index.tsx │ │ │ │ ├── ThemeDropdown/ │ │ │ │ │ └── index.tsx │ │ │ │ ├── Tooltip/ │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.ts │ │ │ │ └── ui/ │ │ │ │ ├── alert-dialog.tsx │ │ │ │ ├── alert.tsx │ │ │ │ ├── avatar.tsx │ │ │ │ ├── button.tsx │ │ │ │ ├── card.tsx │ │ │ │ ├── dropdown-menu.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── input.tsx │ │ │ │ ├── label.tsx │ │ │ │ ├── popover.tsx │ │ │ │ ├── select.tsx │ │ │ │ ├── sonner.tsx │ │ │ │ ├── switch.tsx │ │ │ │ ├── table.tsx │ │ │ │ ├── toggle.tsx │ │ │ │ └── tooltip.tsx │ │ │ ├── index.ts │ │ │ └── libs/ │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ ├── tailwind.config.js │ │ ├── tsconfig.json │ │ └── tsup.config.ts │ └── zipper/ │ ├── index.mts │ ├── lib/ │ │ └── index.ts │ ├── package.json │ └── tsconfig.json ├── pages/ │ ├── content/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── listener.ts │ │ │ ├── localStorage.ts │ │ │ └── observer.ts │ │ ├── tsconfig.json │ │ └── vite.config.mts │ ├── options/ │ │ ├── index.html │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── src/ │ │ │ ├── Options.tsx │ │ │ ├── components/ │ │ │ │ ├── SettingsPopover.tsx │ │ │ │ └── StorageSelect.tsx │ │ │ ├── hooks/ │ │ │ │ └── useGithub.ts │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── tailwind.config.js │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── popup/ │ │ ├── index.html │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── src/ │ │ │ ├── Popup.tsx │ │ │ ├── components/ │ │ │ │ └── AutoSwtich/ │ │ │ │ └── index.tsx │ │ │ ├── hooks/ │ │ │ │ └── useDomainConfig.ts │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ └── utils/ │ │ │ └── index.ts │ │ ├── tailwind.config.js │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── sidepanel/ │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── src/ │ │ ├── SidePanel.tsx │ │ ├── components/ │ │ │ └── CookieTable/ │ │ │ ├── SearchInput.tsx │ │ │ ├── hooks/ │ │ │ │ ├── useAction.ts │ │ │ │ ├── useCookieItem.ts │ │ │ │ └── useSelected.tsx │ │ │ └── index.tsx │ │ ├── index.css │ │ ├── index.html │ │ └── index.tsx │ ├── tailwind.config.js │ ├── tsconfig.json │ └── vite.config.ts ├── pnpm-workspace.yaml ├── private-policy.md └── turbo.json
SYMBOL INDEX (210 symbols across 63 files)
FILE: chrome-extension/lib/background/badge.ts
function setBadge (line 1) | function setBadge(text: string, color: string = '#7246e4') {
function clearBadge (line 6) | function clearBadge() {
function setPullingBadge (line 10) | function setPullingBadge() {
function setPushingBadge (line 14) | function setPushingBadge() {
function setPushingAndPullingBadge (line 18) | function setPushingAndPullingBadge() {
FILE: chrome-extension/lib/background/listen.ts
type HandleCallback (line 23) | type HandleCallback = (response?: SendResponse) => void;
function handleMessage (line 120) | function handleMessage(
FILE: chrome-extension/utils/plugins/make-manifest-plugin.ts
function makeManifestPlugin (line 25) | function makeManifestPlugin(config: { outDir: string }): PluginOption {
FILE: packages/dev-utils/lib/logger.ts
type ColorType (line 1) | type ColorType = 'success' | 'info' | 'error' | 'warning' | keyof typeof...
type ValueOf (line 2) | type ValueOf<T> = T[keyof T];
function colorLog (line 4) | function colorLog(message: string, type: ColorType) {
constant COLORS (line 28) | const COLORS = {
FILE: packages/dev-utils/lib/manifest-parser/impl.ts
function convertToFirefoxCompatibleManifest (line 12) | function convertToFirefoxCompatibleManifest(manifest: Manifest) {
FILE: packages/dev-utils/lib/manifest-parser/type.ts
type Manifest (line 1) | type Manifest = chrome.runtime.ManifestV3;
type ManifestParserInterface (line 3) | interface ManifestParserInterface {
FILE: packages/hmr/lib/constant.ts
constant LOCAL_RELOAD_SOCKET_PORT (line 1) | const LOCAL_RELOAD_SOCKET_PORT = 8081;
constant LOCAL_RELOAD_SOCKET_URL (line 2) | const LOCAL_RELOAD_SOCKET_URL = `ws://localhost:${LOCAL_RELOAD_SOCKET_PO...
FILE: packages/hmr/lib/debounce.ts
function debounce (line 1) | function debounce<A extends unknown[]>(callback: (...args: A) => void, d...
FILE: packages/hmr/lib/initClient.ts
function initReloadClient (line 4) | function initReloadClient({ id, onUpdate }: { id: string; onUpdate: () =...
FILE: packages/hmr/lib/initReloadServer.ts
function initReloadServer (line 9) | function initReloadServer() {
function ping (line 44) | function ping() {
FILE: packages/hmr/lib/injections/refresh.ts
function addRefresh (line 3) | function addRefresh() {
FILE: packages/hmr/lib/injections/reload.ts
function addReload (line 3) | function addReload() {
FILE: packages/hmr/lib/interpreter/index.ts
class MessageInterpreter (line 3) | class MessageInterpreter {
method constructor (line 5) | private constructor() {}
method send (line 7) | static send(message: WebSocketMessage): SerializedMessage {
method receive (line 10) | static receive(serializedMessage: SerializedMessage): WebSocketMessage {
FILE: packages/hmr/lib/interpreter/types.ts
type UpdateRequestMessage (line 1) | type UpdateRequestMessage = {
type UpdateCompleteMessage (line 5) | type UpdateCompleteMessage = { type: 'done_update' };
type PingMessage (line 6) | type PingMessage = { type: 'ping' };
type BuildCompletionMessage (line 7) | type BuildCompletionMessage = { type: 'build_complete'; id: string };
type SerializedMessage (line 9) | type SerializedMessage = string;
type WebSocketMessage (line 11) | type WebSocketMessage = UpdateCompleteMessage | UpdateRequestMessage | B...
FILE: packages/hmr/lib/plugins/make-entry-point-plugin.ts
function makeEntryPointPlugin (line 9) | function makeEntryPointPlugin(): PluginOption {
function extractContentDir (line 61) | function extractContentDir(outputDir: string) {
FILE: packages/hmr/lib/plugins/watch-rebuild-plugin.ts
type PluginConfig (line 8) | type PluginConfig = {
function watchRebuildPlugin (line 19) | function watchRebuildPlugin(config: PluginConfig): PluginOption {
FILE: packages/protobuf/lib/protobuf/proto/cookie.d.ts
type ICookie (line 4) | interface ICookie {
class Cookie (line 41) | class Cookie implements ICookie {
type ILocalStorageItem (line 161) | interface ILocalStorageItem {
class LocalStorageItem (line 171) | class LocalStorageItem implements ILocalStorageItem {
type IDomainCookie (line 264) | interface IDomainCookie {
class DomainCookie (line 283) | class DomainCookie implements IDomainCookie {
type ICookiesMap (line 385) | interface ICookiesMap {
class CookiesMap (line 398) | class CookiesMap implements ICookiesMap {
FILE: packages/protobuf/lib/protobuf/proto/cookie.js
function Cookie (line 37) | function Cookie(properties) {
function LocalStorageItem (line 462) | function LocalStorageItem(properties) {
function DomainCookie (line 692) | function DomainCookie(properties) {
function CookiesMap (line 1058) | function CookiesMap(properties) {
FILE: packages/protobuf/utils/base64.ts
function arrayBufferToBase64 (line 1) | function arrayBufferToBase64(arrayBuffer: ArrayBuffer) {
function base64ToArrayBuffer (line 53) | function base64ToArrayBuffer(base64: string) {
FILE: packages/protobuf/utils/compress.ts
function concatUint8Arrays (line 1) | async function concatUint8Arrays(uint8arrays: ArrayBuffer[]) {
function decompress (line 30) | async function decompress(compressedBytes: Uint8Array) {
FILE: packages/protobuf/utils/encryption.test.ts
function createTestData (line 12) | function createTestData(size: number): Uint8Array {
function stringToUint8Array (line 21) | function stringToUint8Array(str: string): Uint8Array {
function uint8ArrayToString (line 26) | function uint8ArrayToString(arr: Uint8Array): string {
FILE: packages/protobuf/utils/encryption.ts
constant ALGORITHM (line 7) | const ALGORITHM = 'AES-GCM';
constant KEY_LENGTH (line 8) | const KEY_LENGTH = 256;
constant PBKDF2_ITERATIONS (line 9) | const PBKDF2_ITERATIONS = 100000;
constant SALT_LENGTH (line 10) | const SALT_LENGTH = 16;
constant IV_LENGTH (line 11) | const IV_LENGTH = 12;
constant MAGIC_BYTES (line 14) | const MAGIC_BYTES = new Uint8Array([0x53, 0x59, 0x43, 0x45]);
constant VERSION (line 15) | const VERSION = 1;
function deriveKey (line 20) | async function deriveKey(password: string, salt: Uint8Array): Promise<Cr...
function encrypt (line 53) | async function encrypt(data: Uint8Array, password: string): Promise<Uint...
function decrypt (line 100) | async function decrypt(encryptedData: Uint8Array, password: string): Pro...
function isEncrypted (line 156) | function isEncrypted(data: Uint8Array): boolean {
function encryptBase64 (line 171) | async function encryptBase64(base64Data: string, password: string): Prom...
function decryptBase64 (line 190) | async function decryptBase64(encryptedBase64: string, password: string):...
function isBase64Encrypted (line 207) | function isBase64Encrypted(base64Data: string): boolean {
function arrayBufferToBase64ForEncryption (line 221) | function arrayBufferToBase64ForEncryption(arrayBuffer: ArrayBuffer | Uin...
FILE: packages/shared/lib/Providers/ThemeProvider.tsx
type Theme (line 5) | type Theme = 'dark' | 'light' | 'system';
type ThemeProviderProps (line 7) | type ThemeProviderProps = {
type ThemeProviderState (line 11) | type ThemeProviderState = {
function ThemeProvider (line 23) | function ThemeProvider({ children, ...props }: ThemeProviderProps) {
FILE: packages/shared/lib/cloudflare/api.ts
type WriteResponse (line 3) | interface WriteResponse {
FILE: packages/shared/lib/cloudflare/enum.ts
type ErrorCode (line 1) | enum ErrorCode {
FILE: packages/shared/lib/cookie/withStorage.ts
function extractPortRegex (line 61) | function extractPortRegex(host: string) {
type GistUpdateResponse (line 156) | type GistUpdateResponse = RestEndpointMethodTypes['gists']['update']['re...
type PushCookiesResponse (line 158) | type PushCookiesResponse = WriteResponse | GistUpdateResponse;
class CookieOperator (line 315) | class CookieOperator {
method prepare (line 316) | static async prepare() {
method setPushing (line 323) | static async setPushing(open: boolean) {
method editCookieItem (line 329) | static async editCookieItem(host: string, oldItem: ICookie, newItem: I...
FILE: packages/shared/lib/github/api.ts
class GithubApi (line 6) | class GithubApi {
method constructor (line 21) | constructor(clientId: string, clientSecret: string, initDefault: boole...
method getInstance (line 30) | public static getInstance(clientId: string, clientSecret: string, init...
method newOctokit (line 37) | async newOctokit() {
method init (line 47) | async init() {
method reload (line 55) | reload() {
method getSyncGists (line 60) | async getSyncGists() {
method initStorageKeyList (line 72) | async initStorageKeyList() {
method initContent (line 90) | async initContent() {
method setStorageKeyList (line 108) | async setStorageKeyList(gist: RestEndpointMethodTypes['gists']['list']...
method subscribe (line 148) | subscribe() {
method fetchAccessToken (line 162) | async fetchAccessToken(code: string): Promise<string> {
method fetchUser (line 184) | async fetchUser() {
method request (line 194) | async request(method: string, path: string, payload: Record<string, an...
method get (line 209) | async get(path: string) {
method post (line 213) | async post(path: string, payload: Record<string, any> = {}) {
method patch (line 217) | async patch(path: string, payload: Record<string, any> = {}) {
method listGists (line 222) | async listGists() {
method createGist (line 228) | async createGist(description: string, filename: string, content: strin...
method getGist (line 240) | async getGist(gistId: string) {
method updateGist (line 245) | async updateGist(gistId: string, filename: string, content: string) {
method addGistFile (line 272) | async addGistFile(gistId: string, filename: string) {
method deleteGistFile (line 276) | async deleteGistFile(gistId: string, filename: string) {
method deleteGist (line 288) | async deleteGist(gistId: string) {
method ensureToken (line 292) | private ensureToken() {
method fetchRawContent (line 303) | async fetchRawContent(rawUrl: string) {
FILE: packages/shared/lib/hoc/withErrorBoundary.tsx
class ErrorBoundary (line 4) | class ErrorBoundary extends Component<
method getDerivedStateFromError (line 15) | static getDerivedStateFromError() {
method componentDidCatch (line 19) | componentDidCatch(error: Error, errorInfo: ErrorInfo) {
method render (line 23) | render() {
function withErrorBoundary (line 32) | function withErrorBoundary<T extends Record<string, unknown>>(
FILE: packages/shared/lib/hoc/withSuspense.tsx
function withSuspense (line 3) | function withSuspense<T extends Record<string, unknown>>(
FILE: packages/shared/lib/hooks/useStorage.ts
function useStorage (line 4) | function useStorage<
FILE: packages/shared/lib/hooks/useStorageSuspense.tsx
type WrappedPromise (line 4) | type WrappedPromise = ReturnType<typeof wrapPromise>;
function useStorageSuspense (line 8) | function useStorageSuspense<
function wrapPromise (line 23) | function wrapPromise<R>(promise: Promise<R>) {
FILE: packages/shared/lib/message/index.ts
type MessageType (line 6) | enum MessageType {
type MessageErrorCode (line 17) | enum MessageErrorCode {
type PushCookieMessagePayload (line 24) | type PushCookieMessagePayload = {
type DomainPayload (line 30) | type DomainPayload = {
type RemoveCookieMessagePayload (line 34) | type RemoveCookieMessagePayload = {
type RemoveCookieItemMessagePayload (line 38) | type RemoveCookieItemMessagePayload = {
type PullCookieMessagePayload (line 43) | type PullCookieMessagePayload = {
type EditCookieItemMessagePayload (line 49) | type EditCookieItemMessagePayload = {
type SetLocalStorageMessagePayload (line 55) | type SetLocalStorageMessagePayload = {
type MessageMap (line 61) | type MessageMap = {
type Message (line 98) | type Message<T extends MessageType = MessageType> = MessageMap[T];
type SendResponse (line 100) | type SendResponse = {
function sendMessage (line 107) | function sendMessage<T extends MessageType>(message: Message<T>, isTab =...
function pushCookieUsingMessage (line 157) | function pushCookieUsingMessage(payload: PushCookieMessagePayload) {
function removeCookieUsingMessage (line 164) | function removeCookieUsingMessage(payload: RemoveCookieMessagePayload) {
function pullCookieUsingMessage (line 171) | function pullCookieUsingMessage(payload: PullCookieMessagePayload) {
function removeCookieItemUsingMessage (line 178) | function removeCookieItemUsingMessage(payload: RemoveCookieItemMessagePa...
function editCookieItemUsingMessage (line 186) | function editCookieItemUsingMessage(payload: EditCookieItemMessagePayloa...
FILE: packages/shared/lib/utils/index.ts
function debounce (line 6) | function debounce<T = unknown>(func: (...args: T[]) => void, timeout = 3...
function checkResponseAndCallback (line 26) | function checkResponseAndCallback(
function addProtocol (line 70) | function addProtocol(uri: string) {
function extractDomainAndPort (line 74) | async function extractDomainAndPort(url: string, isRemoveWWW = true): Pr...
FILE: packages/storage/lib/accountStorage.ts
type AccountInfo (line 3) | interface AccountInfo {
type AccountInfoStorage (line 37) | type AccountInfoStorage = BaseStorage<AccountInfo> & {
FILE: packages/storage/lib/base.ts
type StorageType (line 5) | enum StorageType {
type SessionAccessLevel (line 32) | enum SessionAccessLevel {
type ValueOrUpdate (line 44) | type ValueOrUpdate<D> = D | ((prev: D) => Promise<D> | D);
type BaseStorage (line 46) | type BaseStorage<D> = {
type StorageConfig (line 53) | type StorageConfig<D = string> = {
function updateCache (line 91) | async function updateCache<D>(valueOrUpdate: ValueOrUpdate<D>, cache: D ...
function checkStoragePermission (line 124) | function checkStoragePermission(storageType: StorageType): void {
function createStorage (line 133) | function createStorage<D = string>(key: string, fallback: D, config?: St...
FILE: packages/storage/lib/cookieStorage.ts
type Cookie (line 4) | interface Cookie extends ICookiesMap {}
FILE: packages/storage/lib/domainConfigStorage.ts
type DomainItemConfig (line 3) | type DomainItemConfig = {
type DomainConfig (line 10) | interface DomainConfig {
FILE: packages/storage/lib/domainStatusStorage.ts
type DomainItemConfig (line 3) | type DomainItemConfig = {
type DomainConfig (line 8) | interface DomainConfig {
FILE: packages/storage/lib/settingsStorage.ts
type IStorageItem (line 2) | interface IStorageItem {
type ISettings (line 9) | interface ISettings {
type TSettingsStorage (line 49) | type TSettingsStorage = BaseStorage<ISettings> & {
FILE: packages/storage/lib/themeStorage.ts
type Theme (line 3) | type Theme = 'light' | 'dark' | 'system';
type ThemeStorage (line 5) | type ThemeStorage = BaseStorage<Theme> & {
FILE: packages/ui/src/components/DateTable/index.tsx
type DataTableProps (line 7) | interface DataTableProps<TData, TValue> {
function DataTable (line 14) | function DataTable<TData, TValue>({ columns, data }: DataTableProps<TDat...
FILE: packages/ui/src/components/Image/index.tsx
type ImageProps (line 15) | interface ImageProps {
FILE: packages/ui/src/components/Spinner/index.tsx
type SpinnerContentProps (line 34) | interface SpinnerContentProps extends VariantProps<typeof spinnerVariant...
function Spinner (line 39) | function Spinner({ size, show = true, children, className }: SpinnerCont...
FILE: packages/ui/src/components/ThemeDropdown/index.tsx
type ThemeDropdownProps (line 13) | interface ThemeDropdownProps {
function ThemeDropdown (line 17) | function ThemeDropdown(props: ThemeDropdownProps) {
FILE: packages/ui/src/components/Tooltip/index.tsx
type TooltipProps (line 3) | interface TooltipProps {
FILE: packages/ui/src/components/ui/button.tsx
type ButtonProps (line 33) | interface ButtonProps
FILE: packages/ui/src/components/ui/input.tsx
type InputProps (line 5) | interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
FILE: packages/ui/src/components/ui/sonner.tsx
type ToasterProps (line 4) | type ToasterProps = React.ComponentProps<typeof Sonner>
FILE: packages/ui/src/components/ui/toggle.tsx
function Toggle (line 31) | function Toggle({
FILE: packages/ui/src/libs/utils.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
FILE: packages/zipper/lib/index.ts
function toMB (line 7) | function toMB(bytes: number): number {
function ensureBuildDirectoryExists (line 12) | function ensureBuildDirectoryExists(buildDirectory: string): void {
function logPackageSize (line 19) | function logPackageSize(size: number, startTime: number): void {
function streamFileToZip (line 24) | function streamFileToZip(
FILE: pages/content/src/listener.ts
type Window (line 5) | interface Window {
class MessageListener (line 15) | class MessageListener {
method constructor (line 25) | constructor() {
method getInstance (line 36) | public static getInstance(): MessageListener {
method init (line 65) | private init(): void {
method on (line 120) | public on(event: string, callback: (...args: any[]) => void): void {
method emit (line 129) | async emit(event: string, data: any, sendResponse?: (...args: any[]) =...
FILE: pages/content/src/observer.ts
class Observer (line 5) | class Observer {
method constructor (line 8) | constructor() {
method subscribeGetLocalStorage (line 12) | subscribeGetLocalStorage(callback: (data: any, sendResponse: (data: Se...
method subscribeSetLocalStorage (line 16) | subscribeSetLocalStorage(callback: (data: any, sendResponse: (data: Se...
class eventHandler (line 24) | class eventHandler {
method constructor (line 25) | constructor() {
method init (line 29) | init() {
method handleGetLocalStorage (line 34) | handleGetLocalStorage() {
method handleSetLocalStorage (line 69) | handleSetLocalStorage() {
FILE: pages/options/src/components/SettingsPopover.tsx
type SettingsPopover (line 10) | interface SettingsPopover {
function SettingsPopover (line 14) | function SettingsPopover({ trigger }: SettingsPopover) {
FILE: pages/options/src/components/StorageSelect.tsx
type IOption (line 16) | interface IOption {
type StorageSelectProps (line 22) | interface StorageSelectProps extends React.ComponentProps<typeof Select> {
function StorageSelect (line 29) | function StorageSelect(props: StorageSelectProps) {
FILE: pages/options/src/index.tsx
function init (line 7) | function init() {
FILE: pages/popup/src/components/AutoSwtich/index.tsx
type AutoSwitchProps (line 3) | interface AutoSwitchProps {
function AutoSwitch (line 9) | function AutoSwitch(props: AutoSwitchProps) {
FILE: pages/popup/src/index.tsx
function init (line 7) | function init() {
FILE: pages/popup/src/utils/index.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
FILE: pages/sidepanel/src/components/CookieTable/SearchInput.tsx
type SearchInputProps (line 5) | interface SearchInputProps {
FILE: pages/sidepanel/src/components/CookieTable/hooks/useSelected.tsx
type CookieShowItem (line 26) | type CookieShowItem = {
type LocalStorageShowItem (line 41) | type LocalStorageShowItem = {
FILE: pages/sidepanel/src/components/CookieTable/index.tsx
type CookieItem (line 42) | type CookieItem = {
FILE: pages/sidepanel/src/index.tsx
function init (line 7) | function init() {
Condensed preview — 204 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (409K chars).
[
{
"path": ".eslintignore",
"chars": 37,
"preview": "dist\nnode_modules\ntailwind.config.js\n"
},
{
"path": ".eslintrc",
"chars": 919,
"preview": "{\n \"env\": {\n \"browser\": true,\n \"es6\": true,\n \"node\": true\n },\n \"extends\": [\n \"eslint:recommended\",\n \"p"
},
{
"path": ".github/CODEOWNERS",
"chars": 13,
"preview": "* @jackluson\n"
},
{
"path": ".github/FUNDING.yml",
"chars": 18,
"preview": "github: jackluson\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 699,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: jackluson\n\n---\n\n**Descri"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 611,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: jackluson\n\n--"
},
{
"path": ".github/auto_assign.yml",
"chars": 747,
"preview": "# Set to true to add reviewers to pull requests\naddReviewers: true\n\n# Set to true to add assignees to pull requests\naddA"
},
{
"path": ".github/dependabot.yml",
"chars": 502,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/stale.yml",
"chars": 898,
"preview": "# Number of days of inactivity before an Issue or Pull Request becomes stale\ndaysUntilStale: 90\n# Number of days of inac"
},
{
"path": ".github/workflows/auto-assign.yml",
"chars": 258,
"preview": "name: 'Auto Assign'\non:\n pull_request:\n types: [opened, ready_for_review]\n\njobs:\n add-reviews:\n runs-on: ubuntu-"
},
{
"path": ".github/workflows/build-zip.yml",
"chars": 645,
"preview": "name: Build And Upload Extension Zip Via Artifact\n\non:\n push:\n branches: [ main ]\n pull_request:\n\njobs:\n build:\n\n "
},
{
"path": ".github/workflows/claude.yml",
"chars": 1507,
"preview": "name: Claude Code Integration\n\non:\n # 当 Issue 或 PR 中有新评论时触发(支持 @claude 唤醒)\n issue_comment:\n types: [created]\n # 当代"
},
{
"path": ".github/workflows/greetings.yml",
"chars": 490,
"preview": "name: Greetings\n\non: [pull_request_target, issues]\n\njobs:\n greeting:\n runs-on: ubuntu-latest\n permissions:\n "
},
{
"path": ".gitignore",
"chars": 209,
"preview": "# dependencies\n**/node_modules\n\n# testing\n**/coverage\n\n# build\n**/dist\n**/dist-zip\n**/build\n\n# env\n**/.env.local\n**/.env"
},
{
"path": ".npmrc",
"chars": 44,
"preview": "public-hoist-pattern[]=@testing-library/dom\n"
},
{
"path": ".nvmrc",
"chars": 8,
"preview": "20.13.1\n"
},
{
"path": ".prettierignore",
"chars": 115,
"preview": "dist\nnode_modules\nproto\n.gitignore\n.github\n.eslintignore\n.husky\n.nvmrc\n.prettierignore\nLICENSE\n*.md\npnpm-lock.yaml\n"
},
{
"path": ".prettierrc",
"chars": 183,
"preview": "{\n \"trailingComma\": \"all\",\n \"semi\": true,\n \"singleQuote\": true,\n \"arrowParens\": \"avoid\",\n \"printWidth\": 120,\n \"bra"
},
{
"path": ".vscode/settings.json",
"chars": 314,
"preview": "{\n\t\"eslint.validate\": [\n\t\t\"javascript\",\n\t\t\"javascriptreact\",\n\t\t\"typescript\",\n\t\t\"typescriptreact\"\n\t],\n\t\"editor.codeAction"
},
{
"path": "LICENSE",
"chars": 1074,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2024 Jack Lu\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 3004,
"preview": "<div align=\"center\">\n<img src=\"chrome-extension/public/icon-128.png\" alt=\"logo\"/>\n<h1> Sync your cookie to Your Cloudfla"
},
{
"path": "README_ZH.md",
"chars": 1748,
"preview": "<div align=\"center\">\n<img src=\"chrome-extension/public/icon-128.png\" alt=\"logo\"/>\n<h1> Sync your cookie to Cloudflare or"
},
{
"path": "chrome-extension/lib/background/badge.ts",
"chars": 436,
"preview": "export function setBadge(text: string, color: string = '#7246e4') {\n chrome.action.setBadgeText({ text });\n chrome.act"
},
{
"path": "chrome-extension/lib/background/contextMenu.ts",
"chars": 648,
"preview": "let globalMenuId: number | string = '';\n\nexport const initContextMenu = () => {\n globalMenuId = chrome.contextMenus.cre"
},
{
"path": "chrome-extension/lib/background/index.ts",
"chars": 6814,
"preview": "// sort-imports-ignore\nimport 'webextension-polyfill';\n\nimport { initGithubApi, pullAndSetCookies, pullCookies, pushMult"
},
{
"path": "chrome-extension/lib/background/listen.ts",
"chars": 5715,
"preview": "import {\n check,\n checkResponseAndCallback,\n CookieOperator,\n extractDomainAndPort,\n ICookie,\n Message,\n MessageT"
},
{
"path": "chrome-extension/lib/background/subscribe.ts",
"chars": 1625,
"preview": "import { accountStorage } from '@sync-your-cookie/storage/lib/accountStorage';\nimport { domainStatusStorage } from '@syn"
},
{
"path": "chrome-extension/manifest.js",
"chars": 1873,
"preview": "import fs from 'node:fs';\n\nconst packageJson = JSON.parse(fs.readFileSync('../package.json', 'utf8'));\n\nconst isFirefox "
},
{
"path": "chrome-extension/package.json",
"chars": 1237,
"preview": "{\n \"name\": \"chrome-extension\",\n \"version\": \"1.4.2\",\n \"description\": \"chrome extension\",\n \"scripts\": {\n \"clean\": \""
},
{
"path": "chrome-extension/public/_locales/en/messages.json",
"chars": 229,
"preview": "{\n \"extensionDescription\": {\n \"description\": \"sync your cookie free\",\n \"message\": \"Sync your cookies to cloudlfar"
},
{
"path": "chrome-extension/public/content.css",
"chars": 0,
"preview": ""
},
{
"path": "chrome-extension/tsconfig.json",
"chars": 350,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/app.json\",\n \"compilerOptions\": {\n \"baseUrl\": \"./\",\n \"declaration\": tru"
},
{
"path": "chrome-extension/utils/plugins/make-manifest-plugin.ts",
"chars": 1664,
"preview": "import * as fs from 'fs';\nimport * as path from 'path';\nimport { ManifestParser, colorLog } from '@sync-your-cookie/dev-"
},
{
"path": "chrome-extension/vite.config.ts",
"chars": 1338,
"preview": "import libAssetsPlugin from '@laynezh/vite-plugin-lib-assets';\nimport { watchRebuildPlugin } from '@sync-your-cookie/hmr"
},
{
"path": "googlef759ff453695209f.html",
"chars": 53,
"preview": "google-site-verification: googlef759ff453695209f.html"
},
{
"path": "how-to-use.md",
"chars": 1447,
"preview": "\n## How to use\n`Sync-Your-Cookie` uses Cloudflare [KV](https://developers.cloudflare.com/kv/) to store cookie data. Here"
},
{
"path": "package.json",
"chars": 2073,
"preview": "{\n \"name\": \"sync-your-cookie\",\n \"version\": \"1.4.2\",\n \"description\": \"sync your cookie extension monorepo\",\n \"license"
},
{
"path": "packages/dev-utils/index.ts",
"chars": 69,
"preview": "export * from './lib/manifest-parser';\nexport * from './lib/logger';\n"
},
{
"path": "packages/dev-utils/lib/logger.ts",
"chars": 1109,
"preview": "type ColorType = 'success' | 'info' | 'error' | 'warning' | keyof typeof COLORS;\ntype ValueOf<T> = T[keyof T];\n\nexport f"
},
{
"path": "packages/dev-utils/lib/manifest-parser/impl.ts",
"chars": 850,
"preview": "import { ManifestParserInterface, Manifest } from './type';\n\nexport const ManifestParserImpl: ManifestParserInterface = "
},
{
"path": "packages/dev-utils/lib/manifest-parser/index.ts",
"chars": 95,
"preview": "import { ManifestParserImpl } from './impl';\nexport const ManifestParser = ManifestParserImpl;\n"
},
{
"path": "packages/dev-utils/lib/manifest-parser/type.ts",
"chars": 182,
"preview": "export type Manifest = chrome.runtime.ManifestV3;\n\nexport interface ManifestParserInterface {\n convertManifestToString:"
},
{
"path": "packages/dev-utils/package.json",
"chars": 640,
"preview": "{\n \"name\": \"@sync-your-cookie/dev-utils\",\n \"version\": \"0.0.1\",\n \"description\": \"chrome extension dev utils\",\n \"priva"
},
{
"path": "packages/dev-utils/tsconfig.json",
"chars": 160,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n \"compilerOptions\": {\n \"outDir\": \"dist\",\n \"types\": [\"chrome\"]\n"
},
{
"path": "packages/hmr/index.ts",
"chars": 31,
"preview": "export * from './lib/plugins';\n"
},
{
"path": "packages/hmr/lib/constant.ts",
"chars": 131,
"preview": "export const LOCAL_RELOAD_SOCKET_PORT = 8081;\nexport const LOCAL_RELOAD_SOCKET_URL = `ws://localhost:${LOCAL_RELOAD_SOCK"
},
{
"path": "packages/hmr/lib/debounce.ts",
"chars": 245,
"preview": "export function debounce<A extends unknown[]>(callback: (...args: A) => void, delay: number) {\n let timer: NodeJS.Timeo"
},
{
"path": "packages/hmr/lib/initClient.ts",
"chars": 1301,
"preview": "import { LOCAL_RELOAD_SOCKET_URL } from './constant';\nimport MessageInterpreter from './interpreter';\n\nexport default fu"
},
{
"path": "packages/hmr/lib/initReloadServer.ts",
"chars": 1470,
"preview": "#!/usr/bin/env node\n\nimport { WebSocket, WebSocketServer } from 'ws';\nimport { LOCAL_RELOAD_SOCKET_PORT, LOCAL_RELOAD_SO"
},
{
"path": "packages/hmr/lib/injections/refresh.ts",
"chars": 679,
"preview": "import initClient from '../initClient';\n\nfunction addRefresh() {\n let pendingReload = false;\n\n initClient({\n // esl"
},
{
"path": "packages/hmr/lib/injections/reload.ts",
"chars": 335,
"preview": "import initClient from '../initClient';\n\nfunction addReload() {\n const reload = () => {\n // eslint-disable-next-line"
},
{
"path": "packages/hmr/lib/interpreter/index.ts",
"chars": 431,
"preview": "import type { WebSocketMessage, SerializedMessage } from './types';\n\nexport default class MessageInterpreter {\n // esli"
},
{
"path": "packages/hmr/lib/interpreter/types.ts",
"chars": 387,
"preview": "type UpdateRequestMessage = {\n type: 'do_update';\n id: string;\n};\ntype UpdateCompleteMessage = { type: 'done_update' }"
},
{
"path": "packages/hmr/lib/plugins/index.ts",
"chars": 83,
"preview": "export * from './watch-rebuild-plugin';\nexport * from './make-entry-point-plugin';\n"
},
{
"path": "packages/hmr/lib/plugins/make-entry-point-plugin.ts",
"chars": 2255,
"preview": "import * as fs from 'fs';\nimport path from 'path';\nimport type { PluginOption } from 'vite';\n\n/**\n * make entry point fi"
},
{
"path": "packages/hmr/lib/plugins/watch-rebuild-plugin.ts",
"chars": 2194,
"preview": "import type { PluginOption } from 'vite';\nimport { WebSocket } from 'ws';\nimport MessageInterpreter from '../interpreter"
},
{
"path": "packages/hmr/package.json",
"chars": 989,
"preview": "{\n \"name\": \"@sync-your-cookie/hmr\",\n \"version\": \"0.0.1\",\n \"description\": \"chrome extension hot module reload or refre"
},
{
"path": "packages/hmr/rollup.config.mjs",
"chars": 519,
"preview": "import sucrase from '@rollup/plugin-sucrase';\n\nconst plugins = [\n sucrase({\n exclude: ['node_modules/**'],\n trans"
},
{
"path": "packages/hmr/tsconfig.build.json",
"chars": 173,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n \"compilerOptions\": {\n \"outDir\": \"dist\"\n },\n \"exclude\": [\"lib/i"
},
{
"path": "packages/hmr/tsconfig.json",
"chars": 156,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n \"compilerOptions\": {\n \"outDir\": \"dist\"\n },\n \"include\": [\"lib\","
},
{
"path": "packages/protobuf/README.md",
"chars": 815,
"preview": "# Shared Package\n\nThis package contains code shared with other packages.\nTo use the code in the package, you need to add"
},
{
"path": "packages/protobuf/index.ts",
"chars": 102,
"preview": "export * from './lib/protobuf';\n\nexport * from './utils';\n\nimport pako from 'pako';\n\nexport { pako };\n"
},
{
"path": "packages/protobuf/lib/protobuf/code.ts",
"chars": 1040,
"preview": "import pako from 'pako';\nimport { compress, decompress } from './../../utils/compress';\nimport type { ICookiesMap } from"
},
{
"path": "packages/protobuf/lib/protobuf/index.ts",
"chars": 24,
"preview": "export * from './code';\n"
},
{
"path": "packages/protobuf/lib/protobuf/proto/cookie.d.ts",
"chars": 16443,
"preview": "import * as $protobuf from \"protobufjs\";\nimport Long = require(\"long\");\n/** Properties of a Cookie. */\nexport interface "
},
{
"path": "packages/protobuf/lib/protobuf/proto/cookie.js",
"chars": 55220,
"preview": "/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, n"
},
{
"path": "packages/protobuf/package.json",
"chars": 1164,
"preview": "{\n \"name\": \"@sync-your-cookie/protobuf\",\n \"version\": \"0.0.1\",\n \"description\": \"chrome extension protobuf code\",\n \"pr"
},
{
"path": "packages/protobuf/proto/cookie.proto",
"chars": 643,
"preview": "syntax = \"proto3\";\n\nmessage Cookie {\n string domain = 1;\n string name = 2;\n string storeId = 3;\n string value = 4;\n "
},
{
"path": "packages/protobuf/tsconfig.json",
"chars": 328,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n \"compilerOptions\": {\n \"outDir\": \"dist\",\n \"jsx\": \"react-jsx\",\n"
},
{
"path": "packages/protobuf/tsup.config.ts",
"chars": 154,
"preview": "import { defineConfig } from 'tsup';\n\nexport default defineConfig({\n treeshake: true,\n format: ['cjs', 'esm'],\n dts: "
},
{
"path": "packages/protobuf/utils/base64.ts",
"chars": 1991,
"preview": "export function arrayBufferToBase64(arrayBuffer: ArrayBuffer) {\n let base64 = '';\n const encodings = 'ABCDEFGHIJKLMNOP"
},
{
"path": "packages/protobuf/utils/compress.ts",
"chars": 1441,
"preview": "async function concatUint8Arrays(uint8arrays: ArrayBuffer[]) {\n const blob = new Blob(uint8arrays);\n const buffer = aw"
},
{
"path": "packages/protobuf/utils/encryption.test.ts",
"chars": 10678,
"preview": "import { describe, it, expect } from 'vitest';\nimport {\n encrypt,\n decrypt,\n isEncrypted,\n encryptBase64,\n decryptB"
},
{
"path": "packages/protobuf/utils/encryption.ts",
"chars": 7544,
"preview": "/**\n * End-to-end encryption utilities using Web Crypto API.\n * Uses AES-GCM for authenticated encryption and PBKDF2 for"
},
{
"path": "packages/protobuf/utils/index.ts",
"chars": 56,
"preview": "export * from './base64';\nexport * from './encryption';\n"
},
{
"path": "packages/protobuf/vitest.config.ts",
"chars": 171,
"preview": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n test: {\n globals: true,\n environmen"
},
{
"path": "packages/shared/README.md",
"chars": 815,
"preview": "# Shared Package\n\nThis package contains code shared with other packages.\nTo use the code in the package, you need to add"
},
{
"path": "packages/shared/index.ts",
"chars": 246,
"preview": "export * from './lib/cloudflare';\n\nexport * from './lib/cookie';\nexport * from './lib/hoc';\nexport * from './lib/hooks';"
},
{
"path": "packages/shared/lib/Providers/ThemeProvider.tsx",
"chars": 1360,
"preview": "import { useStorageSuspense } from '@lib/hooks/useStorageSuspense';\nimport { themeStorage } from '@sync-your-cookie/stor"
},
{
"path": "packages/shared/lib/Providers/hooks/index.ts",
"chars": 28,
"preview": "export * from './useTheme';\n"
},
{
"path": "packages/shared/lib/Providers/hooks/useTheme.ts",
"chars": 717,
"preview": "import { ThemeProviderContext } from '../';\n\nimport { useContext, useEffect } from 'react';\n\nexport const useTheme = () "
},
{
"path": "packages/shared/lib/Providers/index.tsx",
"chars": 59,
"preview": "export * from './hooks';\nexport * from './ThemeProvider';\n\n"
},
{
"path": "packages/shared/lib/cloudflare/api.ts",
"chars": 3084,
"preview": "import { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';\n\nexport interface WriteResponse {\n suc"
},
{
"path": "packages/shared/lib/cloudflare/enum.ts",
"chars": 108,
"preview": "export enum ErrorCode {\n NotFoundRoute = 7003,\n AuthenicationError = 10000,\n NamespaceIdError = 10011,\n}\n"
},
{
"path": "packages/shared/lib/cloudflare/index.ts",
"chars": 48,
"preview": "export * from './api';\n\nexport * from './enum';\n"
},
{
"path": "packages/shared/lib/cookie/index.ts",
"chars": 66,
"preview": "export * from './withCloudflare';\nexport * from './withStorage';\n\n"
},
{
"path": "packages/shared/lib/cookie/withCloudflare.ts",
"chars": 9706,
"preview": "import { accountStorage, type AccountInfo } from '@sync-your-cookie/storage/lib/accountStorage';\nimport { getActiveStora"
},
{
"path": "packages/shared/lib/cookie/withStorage.ts",
"chars": 11931,
"preview": "import { ICookie, ICookiesMap, ILocalStorageItem } from '@sync-your-cookie/protobuf';\nimport { Cookie, cookieStorage } f"
},
{
"path": "packages/shared/lib/github/api.ts",
"chars": 9577,
"preview": "import { Octokit, RestEndpointMethodTypes } from '@octokit/rest';\nimport { arrayBufferToBase64, encodeCookiesMap, ICooki"
},
{
"path": "packages/shared/lib/github/index.ts",
"chars": 23,
"preview": "export * from './api';\n"
},
{
"path": "packages/shared/lib/hoc/index.ts",
"chars": 149,
"preview": "import { withSuspense } from './withSuspense';\nimport { withErrorBoundary } from './withErrorBoundary';\n\nexport { withSu"
},
{
"path": "packages/shared/lib/hoc/withErrorBoundary.tsx",
"chars": 890,
"preview": "import type { ComponentType, ErrorInfo, ReactElement } from 'react';\nimport { Component } from 'react';\n\nclass ErrorBoun"
},
{
"path": "packages/shared/lib/hoc/withSuspense.tsx",
"chars": 366,
"preview": "import { ComponentType, ReactElement, Suspense } from 'react';\n\nexport function withSuspense<T extends Record<string, un"
},
{
"path": "packages/shared/lib/hooks/index.ts",
"chars": 243,
"preview": "import { catchHandler, useCookieAction } from './useCookieAction';\nimport { useStorage } from './useStorage';\nimport { u"
},
{
"path": "packages/shared/lib/hooks/useCookieAction.ts",
"chars": 3682,
"preview": "import {\n MessageErrorCode,\n pullCookieUsingMessage,\n pushCookieUsingMessage,\n removeCookieUsingMessage,\n} from '@li"
},
{
"path": "packages/shared/lib/hooks/useStorage.ts",
"chars": 780,
"preview": "import { useSyncExternalStore } from 'react';\nimport { BaseStorage } from '@sync-your-cookie/storage';\n\nexport function "
},
{
"path": "packages/shared/lib/hooks/useStorageSuspense.tsx",
"chars": 1245,
"preview": "import { BaseStorage } from '@sync-your-cookie/storage';\nimport { useSyncExternalStore } from 'react';\n\ntype WrappedProm"
},
{
"path": "packages/shared/lib/message/index.ts",
"chars": 7581,
"preview": "import { pushCookies } from '@lib/cookie';\nimport { ICookie, ILocalStorageItem } from '@sync-your-cookie/protobuf';\nimpo"
},
{
"path": "packages/shared/lib/utils/index.ts",
"chars": 4559,
"preview": "import { ErrorCode, WriteResponse } from '@lib/cloudflare';\nimport { MessageErrorCode, SendResponse } from '@lib/message"
},
{
"path": "packages/shared/package.json",
"chars": 1186,
"preview": "{\n \"name\": \"@sync-your-cookie/shared\",\n \"version\": \"0.0.1\",\n \"description\": \"chrome extension shared code\",\n \"privat"
},
{
"path": "packages/shared/tsconfig.json",
"chars": 374,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n \"compilerOptions\": {\n \"outDir\": \"dist\",\n \"jsx\": \"react-jsx\",\n"
},
{
"path": "packages/shared/tsup.config.ts",
"chars": 174,
"preview": "import { defineConfig } from 'tsup';\n\nexport default defineConfig({\n treeshake: true,\n splitting: false,\n format: ['c"
},
{
"path": "packages/storage/index.ts",
"chars": 23,
"preview": "export * from './lib';\n"
},
{
"path": "packages/storage/lib/accountStorage.ts",
"chars": 1150,
"preview": "import { BaseStorage, createStorage, StorageType } from './base';\n\nexport interface AccountInfo {\n accountId?: string;\n"
},
{
"path": "packages/storage/lib/base.ts",
"chars": 8065,
"preview": "/**\n * Storage area type for persisting and exchanging data.\n * @see https://developer.chrome.com/docs/extensions/refere"
},
{
"path": "packages/storage/lib/cookieStorage.ts",
"chars": 2521,
"preview": "import { ICookie, ICookiesMap, ILocalStorageItem } from '@sync-your-cookie/protobuf';\nimport { BaseStorage, createStorag"
},
{
"path": "packages/storage/lib/domainConfigStorage.ts",
"chars": 2294,
"preview": "import { BaseStorage, createStorage, StorageType } from './base';\n\ntype DomainItemConfig = {\n autoPull?: boolean;\n aut"
},
{
"path": "packages/storage/lib/domainStatusStorage.ts",
"chars": 2873,
"preview": "import { BaseStorage, createStorage, StorageType } from './base';\n\ntype DomainItemConfig = {\n pulling?: boolean;\n push"
},
{
"path": "packages/storage/lib/index.ts",
"chars": 314,
"preview": "import { SessionAccessLevel, StorageType, createStorage, type BaseStorage } from './base';\n// export * from './accountSt"
},
{
"path": "packages/storage/lib/settingsStorage.ts",
"chars": 2806,
"preview": "import { BaseStorage, createStorage, StorageType } from './base';\nexport interface IStorageItem {\n value: string;\n lab"
},
{
"path": "packages/storage/lib/themeStorage.ts",
"chars": 824,
"preview": "import { BaseStorage, createStorage, StorageType } from './base';\n\ntype Theme = 'light' | 'dark' | 'system';\n\ntype Theme"
},
{
"path": "packages/storage/package.json",
"chars": 731,
"preview": "{\n \"name\": \"@sync-your-cookie/storage\",\n \"version\": \"0.0.1\",\n \"description\": \"chrome extension storage\",\n \"private\":"
},
{
"path": "packages/storage/tsconfig.json",
"chars": 252,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n \"compilerOptions\": {\n \"outDir\": \"dist\",\n \"jsx\": \"react-jsx\",\n"
},
{
"path": "packages/tailwind-config/package.json",
"chars": 191,
"preview": "{\n \"name\": \"@sync-your-cookie/tailwindcss-config\",\n \"version\": \"1.0.0\",\n \"description\": \"Tailwind CSS configuration f"
},
{
"path": "packages/tailwind-config/tailwind.config.js",
"chars": 187,
"preview": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n /** shared theme configuration */\n theme: {\n extend"
},
{
"path": "packages/tsconfig/app.json",
"chars": 122,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/tsconfig\",\n \"display\": \"Chrome Extension App\",\n \"extends\": \"./base.json\"\n"
},
{
"path": "packages/tsconfig/base.json",
"chars": 707,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/tsconfig\",\n \"display\": \"Base\",\n \"compilerOptions\": {\n \"outDir\": \"dist\""
},
{
"path": "packages/tsconfig/package.json",
"chars": 289,
"preview": "{\n \"name\": \"@sync-your-cookie/tsconfig\",\n \"version\": \"1.0.0\",\n \"description\": \"tsconfig for chrome extension\",\n \"pri"
},
{
"path": "packages/tsconfig/utils.json",
"chars": 418,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/tsconfig\",\n \"display\": \"Chrome Extension Utils\",\n \"extends\": \"./base.json"
},
{
"path": "packages/ui/components.json",
"chars": 343,
"preview": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"default\",\n \"rsc\": false,\n \"tsx\": true,\n \"tailwind\": {"
},
{
"path": "packages/ui/globals.css",
"chars": 1815,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --f"
},
{
"path": "packages/ui/package.json",
"chars": 1863,
"preview": "{\n \"name\": \"@sync-your-cookie/ui\",\n \"version\": \"0.0.1\",\n \"description\": \"chrome extension ui\",\n \"private\": true,\n \""
},
{
"path": "packages/ui/postcss.config.js",
"chars": 83,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n"
},
{
"path": "packages/ui/src/components/DateTable/index.tsx",
"chars": 1857,
"preview": "import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';\n\nimport type { ColumnDef } from '@ta"
},
{
"path": "packages/ui/src/components/Image/index.tsx",
"chars": 1088,
"preview": "import { FC } from 'react';\nimport { Avatar, AvatarFallback, AvatarImage } from '../ui';\nconst randomBgColor = [\n '#ec6"
},
{
"path": "packages/ui/src/components/Spinner/index.tsx",
"chars": 1131,
"preview": "import { cn } from '@libs/utils';\nimport { VariantProps, cva } from 'class-variance-authority';\nimport { Loader } from '"
},
{
"path": "packages/ui/src/components/ThemeDropdown/index.tsx",
"chars": 1271,
"preview": "import { Moon, Sun } from 'lucide-react';\n\nimport { Button } from '@/components/ui/button';\nimport {\n DropdownMenu,\n D"
},
{
"path": "packages/ui/src/components/Tooltip/index.tsx",
"chars": 672,
"preview": "import { Tooltip as STooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';\n\ninterfa"
},
{
"path": "packages/ui/src/components/index.ts",
"chars": 242,
"preview": "export * from './DateTable';\nexport * from './Image';\nexport * from './Spinner';\nexport * from './ThemeDropdown';\nexport"
},
{
"path": "packages/ui/src/components/ui/alert-dialog.tsx",
"chars": 4298,
"preview": "import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';\nimport * as React from 'react';\n\nimport { cn } fro"
},
{
"path": "packages/ui/src/components/ui/alert.tsx",
"chars": 1546,
"preview": "import { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn } from '"
},
{
"path": "packages/ui/src/components/ui/avatar.tsx",
"chars": 1366,
"preview": "import * as AvatarPrimitive from '@radix-ui/react-avatar';\nimport * as React from 'react';\n\nimport { cn } from '@libs/ut"
},
{
"path": "packages/ui/src/components/ui/button.tsx",
"chars": 1774,
"preview": "import { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport *"
},
{
"path": "packages/ui/src/components/ui/card.tsx",
"chars": 1786,
"preview": "import * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst Card = React.forwardRef<HTMLDivElement, React."
},
{
"path": "packages/ui/src/components/ui/dropdown-menu.tsx",
"chars": 7229,
"preview": "import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';\nimport { Check, ChevronRight, Circle } from 'luc"
},
{
"path": "packages/ui/src/components/ui/index.ts",
"chars": 405,
"preview": "export * from './alert';\nexport * from './button';\n\nexport * from './avatar';\nexport * from './card';\nexport * from './d"
},
{
"path": "packages/ui/src/components/ui/input.tsx",
"chars": 801,
"preview": "import * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nexport interface InputProps extends React.InputHTMLA"
},
{
"path": "packages/ui/src/components/ui/label.tsx",
"chars": 696,
"preview": "import * as LabelPrimitive from '@radix-ui/react-label';\nimport { cva, type VariantProps } from 'class-variance-authorit"
},
{
"path": "packages/ui/src/components/ui/popover.tsx",
"chars": 1239,
"preview": "import * as PopoverPrimitive from '@radix-ui/react-popover';\nimport * as React from 'react';\n\nimport { cn } from '@libs/"
},
{
"path": "packages/ui/src/components/ui/select.tsx",
"chars": 5725,
"preview": "import * as SelectPrimitive from '@radix-ui/react-select';\nimport { Check, ChevronDown, ChevronUp } from 'lucide-react';"
},
{
"path": "packages/ui/src/components/ui/sonner.tsx",
"chars": 880,
"preview": "import { useTheme } from \"next-themes\"\nimport { Toaster as Sonner } from \"sonner\"\n\ntype ToasterProps = React.ComponentPr"
},
{
"path": "packages/ui/src/components/ui/switch.tsx",
"chars": 1144,
"preview": "import * as SwitchPrimitives from '@radix-ui/react-switch';\nimport * as React from 'react';\n\nimport { cn } from '@libs/u"
},
{
"path": "packages/ui/src/components/ui/table.tsx",
"chars": 2694,
"preview": "import * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst Table = React.forwardRef<HTMLTableElement, Rea"
},
{
"path": "packages/ui/src/components/ui/toggle.tsx",
"chars": 1570,
"preview": "\"use client\"\n\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\"\nimport { cva, type VariantProps } from \"class-va"
},
{
"path": "packages/ui/src/components/ui/tooltip.tsx",
"chars": 1156,
"preview": "import * as TooltipPrimitive from '@radix-ui/react-tooltip';\nimport * as React from 'react';\n\nimport { cn } from '@libs/"
},
{
"path": "packages/ui/src/index.ts",
"chars": 61,
"preview": "export * from './components';\nexport * from './libs/index';\n\n"
},
{
"path": "packages/ui/src/libs/index.ts",
"chars": 25,
"preview": "export * from './utils';\n"
},
{
"path": "packages/ui/src/libs/utils.ts",
"chars": 169,
"preview": "import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: C"
},
{
"path": "packages/ui/tailwind.config.js",
"chars": 2295,
"preview": "const baseConfig = require('@sync-your-cookie/tailwindcss-config');\n\n/** @type {import('tailwindcss').Config} */\nmodule."
},
{
"path": "packages/ui/tsconfig.json",
"chars": 425,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n \"compilerOptions\": {\n \"outDir\": \"dist\",\n \"target\": \"ESNext\",\n"
},
{
"path": "packages/ui/tsup.config.ts",
"chars": 215,
"preview": "import { defineConfig } from 'tsup';\n\nexport default defineConfig({\n treeshake: true,\n splitting: true,\n entry: ['src"
},
{
"path": "packages/zipper/index.mts",
"chars": 840,
"preview": "import fs from 'node:fs';\nimport { resolve } from 'node:path';\nimport { zipBundle } from './lib/index.js';\nconst IS_FIRE"
},
{
"path": "packages/zipper/lib/index.ts",
"chars": 2980,
"preview": "import fg from 'fast-glob';\nimport { AsyncZipDeflate, Zip } from 'fflate';\nimport { createReadStream, createWriteStream,"
},
{
"path": "packages/zipper/package.json",
"chars": 849,
"preview": "{\n \"name\": \"@sync-your-cookie/zipper\",\n \"version\": \"0.8.0\",\n \"description\": \"chrome extension - zipper\",\n \"type\": \"m"
},
{
"path": "packages/zipper/tsconfig.json",
"chars": 204,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n \"compilerOptions\": {\n \"baseUrl\": \".\",\n \"outDir\": \"dist\",\n "
},
{
"path": "pages/content/package.json",
"chars": 956,
"preview": "{\n \"name\": \"@sync-your-cookie/content-script\",\n \"version\": \"0.3.3\",\n \"description\": \"chrome extension - content scrip"
},
{
"path": "pages/content/src/index.ts",
"chars": 47,
"preview": "import { init } from './localStorage';\ninit();\n"
},
{
"path": "pages/content/src/listener.ts",
"chars": 3959,
"preview": "import { Message, MessageType, SendResponse } from '@sync-your-cookie/shared';\nimport EventEmitter from 'eventemitter3';"
},
{
"path": "pages/content/src/localStorage.ts",
"chars": 156,
"preview": "import { eventHandlerInstance } from \"./observer\";\n\nexport const init = () => {\n eventHandlerInstance.init();\n console"
},
{
"path": "pages/content/src/observer.ts",
"chars": 3253,
"preview": "import { DomainPayload, MessageType, SendResponse, SetLocalStorageMessagePayload } from '@sync-your-cookie/shared';\nimpo"
},
{
"path": "pages/content/tsconfig.json",
"chars": 340,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n \"compilerOptions\": {\n \"outDir\": \"../dist/content\",\n \"jsx\": \"r"
},
{
"path": "pages/content/vite.config.mts",
"chars": 1000,
"preview": "import { watchRebuildPlugin } from '@sync-your-cookie/hmr';\nimport react from '@vitejs/plugin-react-swc';\nimport { resol"
},
{
"path": "pages/options/index.html",
"chars": 244,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <title>SyncYourCookie-Options</title>\n </hea"
},
{
"path": "pages/options/package.json",
"chars": 962,
"preview": "{\n \"name\": \"@sync-your-cookie/options\",\n \"version\": \"0.0.1\",\n \"description\": \"chrome extension options\",\n \"private\":"
},
{
"path": "pages/options/postcss.config.js",
"chars": 83,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n"
},
{
"path": "pages/options/src/Options.tsx",
"chars": 11729,
"preview": "import {\n useStorageSuspense,\n useTheme,\n verifyCloudflareToken,\n withErrorBoundary,\n withSuspense,\n} from '@sync-y"
},
{
"path": "pages/options/src/components/SettingsPopover.tsx",
"chars": 9094,
"preview": "import { GithubApi, pullCookies, useStorageSuspense } from '@sync-your-cookie/shared';\nimport { accountStorage } from '@"
},
{
"path": "pages/options/src/components/StorageSelect.tsx",
"chars": 4125,
"preview": "import {\n Button,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectPortal,\n SelectTrigger,\n SelectValue,\n} f"
},
{
"path": "pages/options/src/hooks/useGithub.ts",
"chars": 2141,
"preview": "import { clientId, GithubApi, initGithubApi, scope } from '@sync-your-cookie/shared';\nimport { accountStorage } from '@s"
},
{
"path": "pages/options/src/index.css",
"chars": 266,
"preview": "\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantar"
},
{
"path": "pages/options/src/index.tsx",
"chars": 496,
"preview": "import Options from '@src/Options';\nimport '@src/index.css';\nimport { ThemeProvider } from '@sync-your-cookie/shared';\ni"
},
{
"path": "pages/options/tailwind.config.js",
"chars": 149,
"preview": "const uiConfig = require('@sync-your-cookie/ui/tailwind.config');\n\n\n/** @type {import('tailwindcss').Config} */\nmodule.e"
},
{
"path": "pages/options/tsconfig.json",
"chars": 225,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/base\",\n \"compilerOptions\": {\n \"baseUrl\": \".\",\n \"paths\": {\n \"@src/"
},
{
"path": "pages/options/vite.config.ts",
"chars": 1032,
"preview": "import { watchRebuildPlugin } from '@sync-your-cookie/hmr';\nimport react from '@vitejs/plugin-react-swc';\nimport { resol"
},
{
"path": "pages/popup/index.html",
"chars": 227,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <title>Popup</title>\n </head>\n\n <body>\n "
},
{
"path": "pages/popup/package.json",
"chars": 1083,
"preview": "{\n \"name\": \"@sync-your-cookie/popup\",\n \"version\": \"0.0.1\",\n \"description\": \"chrome extension popup\",\n \"private\": tru"
},
{
"path": "pages/popup/postcss.config.js",
"chars": 112,
"preview": "module.exports = {\n plugins: {\n // 'postcss-import': {},\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n"
},
{
"path": "pages/popup/src/Popup.tsx",
"chars": 6495,
"preview": "import { extractDomainAndPort, useTheme, withErrorBoundary, withSuspense } from '@sync-your-cookie/shared';\n\nimport { Bu"
},
{
"path": "pages/popup/src/components/AutoSwtich/index.tsx",
"chars": 487,
"preview": "import { Label, Switch } from '@sync-your-cookie/ui';\n\ninterface AutoSwitchProps {\n value: boolean;\n onChange: (value:"
},
{
"path": "pages/popup/src/hooks/useDomainConfig.ts",
"chars": 332,
"preview": "import { useCookieAction } from '@sync-your-cookie/shared';\nimport { useState } from 'react';\nimport { toast } from 'son"
},
{
"path": "pages/popup/src/index.css",
"chars": 382,
"preview": "\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantar"
},
{
"path": "pages/popup/src/index.tsx",
"chars": 522,
"preview": "import Popup from '@src/Popup';\nimport '@src/index.css';\nimport { initGithubApi, ThemeProvider } from '@sync-your-cookie"
},
{
"path": "pages/popup/src/utils/index.ts",
"chars": 169,
"preview": "import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: C"
},
{
"path": "pages/popup/tailwind.config.js",
"chars": 148,
"preview": "const uiConfig = require('@sync-your-cookie/ui/tailwind.config');\n\n/** @type {import('tailwindcss').Config} */\nmodule.ex"
},
{
"path": "pages/popup/tsconfig.json",
"chars": 217,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/base\",\n \"compilerOptions\": {\n \"baseUrl\": \".\",\n \"paths\": {\n \"@src/"
},
{
"path": "pages/popup/vite.config.ts",
"chars": 852,
"preview": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react-swc';\nimport { resolve } from 'path';\nimpor"
},
{
"path": "pages/sidepanel/index.html",
"chars": 231,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <title>SidePanel</title>\n </head>\n\n <body>\n"
},
{
"path": "pages/sidepanel/package.json",
"chars": 1091,
"preview": "{\n \"name\": \"@sync-your-cookie/sidepanel\",\n \"version\": \"0.0.1\",\n \"description\": \"chrome extension sidepanel\",\n \"priva"
},
{
"path": "pages/sidepanel/postcss.config.js",
"chars": 83,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n"
},
{
"path": "pages/sidepanel/src/SidePanel.tsx",
"chars": 1067,
"preview": "import { useTheme, withErrorBoundary, withSuspense } from '@sync-your-cookie/shared';\nimport { Toaster } from '@sync-you"
},
{
"path": "pages/sidepanel/src/components/CookieTable/SearchInput.tsx",
"chars": 1144,
"preview": "import { Input } from '@sync-your-cookie/ui';\nimport { Search, X } from 'lucide-react';\nimport { FC, useState } from 're"
},
{
"path": "pages/sidepanel/src/components/CookieTable/hooks/useAction.ts",
"chars": 3523,
"preview": "import { useCookieAction } from '@sync-your-cookie/shared';\nimport type { Cookie } from '@sync-your-cookie/storage/lib/c"
},
{
"path": "pages/sidepanel/src/components/CookieTable/hooks/useCookieItem.ts",
"chars": 1527,
"preview": "import {\n catchHandler,\n editCookieItemUsingMessage,\n ICookie,\n removeCookieItemUsingMessage,\n} from '@sync-your-coo"
},
{
"path": "pages/sidepanel/src/components/CookieTable/hooks/useSelected.tsx",
"chars": 14268,
"preview": "import { useStorageSuspense } from '@sync-your-cookie/shared';\nimport { Cookie } from '@sync-your-cookie/storage/lib/coo"
},
{
"path": "pages/sidepanel/src/components/CookieTable/index.tsx",
"chars": 14768,
"preview": "/* eslint-disable react/no-unescaped-entities */\n/* eslint-disable jsx-a11y/click-events-have-key-events */\nimport { get"
},
{
"path": "pages/sidepanel/src/index.css",
"chars": 59,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
},
{
"path": "pages/sidepanel/src/index.html",
"chars": 225,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <title>Options</title>\n </head>\n\n <body>\n "
},
{
"path": "pages/sidepanel/src/index.tsx",
"chars": 534,
"preview": "import '@src/index.css';\nimport SidePanel from '@src/SidePanel';\nimport { initGithubApi, ThemeProvider } from '@sync-you"
},
{
"path": "pages/sidepanel/tailwind.config.js",
"chars": 148,
"preview": "const uiConfig = require('@sync-your-cookie/ui/tailwind.config');\n\n/** @type {import('tailwindcss').Config} */\nmodule.ex"
},
{
"path": "pages/sidepanel/tsconfig.json",
"chars": 217,
"preview": "{\n \"extends\": \"@sync-your-cookie/tsconfig/base\",\n \"compilerOptions\": {\n \"baseUrl\": \".\",\n \"paths\": {\n \"@src/"
}
]
// ... and 4 more files (download for full content)
About this extraction
This page contains the full source code of the jackluson/sync-your-cookie GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 204 files (370.7 KB), approximately 97.8k tokens, and a symbol index with 210 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.