[
  {
    "path": ".eslintignore",
    "content": "dist\nnode_modules\ntailwind.config.js\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true,\n    \"node\": true\n  },\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:react/recommended\",\n    \"plugin:@typescript-eslint/recommended\",\n    \"plugin:react-hooks/recommended\",\n    \"plugin:import/recommended\",\n    \"plugin:jsx-a11y/recommended\",\n    \"prettier\"\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"ecmaFeatures\": {\n      \"jsx\": true\n    },\n    \"ecmaVersion\": \"latest\",\n    \"sourceType\": \"module\"\n  },\n  \"plugins\": [\"react\", \"@typescript-eslint\", \"react-hooks\", \"import\", \"jsx-a11y\", \"prettier\"],\n  \"settings\": {\n    \"react\": {\n      \"version\": \"detect\"\n    }\n  },\n  \"rules\": {\n    \"prettier/prettier\": \"error\",\n    \"react/react-in-jsx-scope\": \"off\",\n    \"import/no-unresolved\": \"off\",\n    \"jsx-a11y/click-events-have-key-events\": \"warn\"\n  },\n  \"globals\": {\n    \"chrome\": \"readonly\"\n  },\n  \"ignorePatterns\": [\"watch.js\", \"dist/**\"]\n}\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @jackluson\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: jackluson\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: jackluson\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. Mac, Window, Linux]\n - Browser [e.g. chrome, firefox]\n - Node Version [e.g. 18.12.0]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: jackluson\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/auto_assign.yml",
    "content": "# Set to true to add reviewers to pull requests\naddReviewers: true\n\n# Set to true to add assignees to pull requests\naddAssignees: author\n\n# A list of reviewers to be added to pull requests (GitHub user name)\nreviewers:\n  - jackluson\n\n# A number of reviewers added to the pull request\n# Set 0 to add all the reviewers (default: 0)\nnumberOfReviewers: 0\n\n# A list of assignees, overrides reviewers if set\n# assignees:\n#   - assigneeA\n\n# A number of assignees to add to the pull request\n# Set to 0 to add all of the assignees.\n# Uses numberOfReviewers if unset.\n# numberOfAssignees: 2\n\n# A list of keywords to be skipped the process that add reviewers if pull requests include it\n# skipKeywords:\n#   - wip\n\nfilterLabels:\n  exclude:\n    - dependencies\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  - package-ecosystem: \"npm\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an Issue or Pull Request becomes stale\ndaysUntilStale: 90\n# Number of days of inactivity before a stale Issue or Pull Request is closed\ndaysUntilClose: 30\n# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable\nexemptLabels:\n  - pinned\n  - security\n# Label to use when marking as stale\nstaleLabel: stale\n# Comment to post when marking as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n# Comment to post when removing the stale label. Set to `false` to disable\nunmarkComment: false\n# Comment to post when closing a stale Issue or Pull Request. Set to `false` to disable\ncloseComment: true\n# Limit to only `issues` or `pulls`\nonly: issues\n"
  },
  {
    "path": ".github/workflows/auto-assign.yml",
    "content": "name: 'Auto Assign'\non:\n  pull_request:\n    types: [opened, ready_for_review]\n\njobs:\n  add-reviews:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: kentaro-m/auto-assign-action@v1.2.5\n        with:\n          configuration-path: '.github/auto_assign.yml'\n"
  },
  {
    "path": ".github/workflows/build-zip.yml",
    "content": "name: Build And Upload Extension Zip Via Artifact\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version-file: \".nvmrc\"\n\n      - uses: actions/cache@v3\n        with:\n          path: node_modules\n          key: ${{ runner.OS }}-build-${{ hashFiles('**/pnpm-lock.yaml') }}\n\n      - uses: pnpm/action-setup@v4\n\n      - run: pnpm install --frozen-lockfile\n\n      - run: pnpm build\n\n      - uses: actions/upload-artifact@v4\n        with:\n          path: dist/*\n"
  },
  {
    "path": ".github/workflows/claude.yml",
    "content": "name: Claude Code Integration\n\non:\n  # 当 Issue 或 PR 中有新评论时触发（支持 @claude 唤醒）\n  issue_comment:\n    types: [created]\n  # 当代码推送或创建拉取请求时自动触发功能\n  pull_request:\n    types: [opened, synchronize, reopened]\n\npermissions:\n  id-token: write   # 必须添加这个权限，才能生成 ID Token 并消除报错\n  contents: write   # 允许工作流读写代码（Claude Code 常用）\n  pull-requests: write # 允许在 PR 下回复评论\n  issues: write     # 如果你在 issue comment 里触发，需要这个\n\njobs:\n  claude-code-action:\n    runs-on: ubuntu-latest\n    # 限制仅在需要时运行，节省资源\n    if: >\n      github.event_name == 'pull_request' || \n      (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude'))\n    steps:\n      # 1. 检出代码\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0 # 获取完整历史记录以便 Claude 分析上下文\n\n      # 2. 调用 Claude Code 官方 Action\n      - name: Run Claude Code\n        uses: anthropics/claude-code-action@v1 # 请以实际官方最新版本号为准\n        with:\n          # 提供 GitHub token 以便 Claude 能回复评论和修改代码\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          claude_code_oauth_token: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}\n        env:\n          # 以下环境变量在 Github Secrets 中提前配置好\n          ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }} # 例如填写 OpenRouter 地址\n          ANTHROPIC_AUTH_TOKEN: ${{ secrets.ANTHROPIC_AUTH_TOKEN }} # 填写中转 Key\n          ANTHROPIC_API_KEY: \"\" # 必须为空\n          ANTHROPIC_DEFAULT_OPUS_MODEL: \"glm-5\"\n          ANTHROPIC_DEFAULT_HAIKU_MODEL: \"glm-5\"\n          ANTHROPIC_DEFAULT_SONNET_MODEL: \"glm-5\"\n"
  },
  {
    "path": ".github/workflows/greetings.yml",
    "content": "name: Greetings\n\non: [pull_request_target, issues]\n\njobs:\n  greeting:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n    steps:\n    - uses: actions/first-interaction@v1\n      with:\n        repo-token: ${{ secrets.GITHUB_TOKEN }}\n        issue-message: 'Thank you for your contribution. We will check and reply to you as soon as possible.'\n        pr-message: 'Thank you for your contribution. We will check and reply to you as soon as possible.'\n"
  },
  {
    "path": ".gitignore",
    "content": "# 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\n\n# etc\n.DS_Store\n.idea\n**/.turbo\n\n# compiled\napps/chrome-extension/public/manifest.json\n"
  },
  {
    "path": ".npmrc",
    "content": "public-hoist-pattern[]=@testing-library/dom\n"
  },
  {
    "path": ".nvmrc",
    "content": "20.13.1\n"
  },
  {
    "path": ".prettierignore",
    "content": "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",
    "content": "{\n  \"trailingComma\": \"all\",\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"arrowParens\": \"avoid\",\n  \"printWidth\": 120,\n  \"bracketSameLine\": true,\n  \"htmlWhitespaceSensitivity\": \"strict\"\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\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.codeActionsOnSave\": {\n\t\t\"source.formatDocument\": \"explicit\",\n\t\t\"source.fixAll.eslint\": \"explicit\",\n\t\t\"source.organizeImports\": \"explicit\"\n\t},\n\t\"files.insertFinalNewline\": true,\n\t\"deepscan.enable\": true\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2024 Jack Lu\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n<img src=\"chrome-extension/public/icon-128.png\" alt=\"logo\"/>\n<h1> Sync your cookie to Your Cloudflare or Github Gist</h1>\n\n![](https://img.shields.io/badge/React-61DAFB?style=flat-square&logo=react&logoColor=black)\n![](https://img.shields.io/badge/Typescript-3178C6?style=flat-square&logo=typescript&logoColor=white)\n![](https://badges.aleen42.com/src/vitejs.svg)\n![GitHub action badge](https://github.com/jackluson/sync-your-cookie/actions/workflows/build-zip.yml/badge.svg)\n<!-- <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\"/> -->\n\n</div>\n\n[English](./README.md) | [中文](./README_ZH.md)\n\n`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. \n\n### Install\nChrome: [Sync Your Cookie](https://chromewebstore.google.com/detail/sync-your-cookie/bcegpckmgklcpcapnbigfdadedcneopf)\n\nEdge: [Sync Your Cookie](https://microsoftedge.microsoft.com/addons/detail/sync-your-cookie/ohlcghldllgnmkegocpcphdbbphikgfm)\n\n\n### Features\n\n- Supports syncing cookies to Cloudflare or Github Gist (Include LocalStorage)\n- Supports configuring `Auto Merge` and `Auto Push` rules for different sites\n- Cookie data is transmitted via protobuf encoding\n- Provides a management panel to facilitate viewing, copying, and managing synchronized cookie data\n- Multi-account synchronization based on Storage-key\n\n\n### Project Screenshots\n\nAccount Settings Page\n\n<img width=\"600\" src=\"./screenshots/settings_v2.png\" alt=\"account settings\"/>\n\nCookie Sync Popup Page\n\n<img width=\"600\" src=\"./screenshots/sync.png\" alt=\"cookie sync popup\"/>\n\nCookie Manager Sidebar Panel\n\n<img width=\"600\" src=\"./screenshots/panel.png\" alt=\"cookie manager sidebar panel\"/>\n\nCookie Detail\n\n<img width=\"600\" src=\"./screenshots/panel_item.png\" alt=\"cookie manager sidebar panel\"/>\n\nLocalStorage Detail\n\n<img width=\"600\" src=\"./screenshots/panel_item_localStorage.png\" alt=\"cookie manager sidebar panel\"/>\n\nPushed Cookie on Github Gist\n\n<img width=\"600\" src=\"./screenshots/gist.png\" alt=\"Pushed Cookie on Github Gist\"/>\n\n\nPushed Cookie on Cloudflare\n\n<img width=\"600\" src=\"./screenshots/key_value.png\" alt=\"Pushed Cookie on Cloudflare\"/>\n\n\n### Usage\n\n[How to use](./how-to-use.md)\n\n### TODO\n\n- [x] Custom Save Configure\n- [x] Multi-account synchronization based on Storage-key\n- [x] Sync LocalStorage\n- [x] More Cloud Platform (First github gist)\n\n### Privacy Policy\n\nPlease refer to [Privacy Policy](./private-policy.md) for more information.\n\n### Support\n\nIf you find this project helpful, you can support the development by:\n- Starring the repository ⭐\n- [Sponsoring via Ko-fi](https://ko-fi.com/jacklu) 💖\n- Sponsor via Wechat\n  <div>\n    <img src=\"./screenshots/wechat_sponsor.jpg\" alt=\"微信支付\" style=\"width: 150px;\">\n  </div>\n- Sharing it with others 🚀\n"
  },
  {
    "path": "README_ZH.md",
    "content": "<div align=\"center\">\n<img src=\"chrome-extension/public/icon-128.png\" alt=\"logo\"/>\n<h1> Sync your cookie to Cloudflare or Github Gist</h1>\n</div>\n\n[English](./README.md) | [中文](./README_ZH.md)\n\n`Sync your cookie` 是一个 Chrome 扩展程序，它可以帮助您将 Cookie 同步到 Cloudflare。它是一个有用的工具，用于在不同设备之间共享 Cookie, 免去了登录流程的烦恼，此外也提供了cookie管理面板查看，管理已经过同步的 cookie。\n\n\n### 安装\nChrome: [Sync Your Cookie](https://chromewebstore.google.com/detail/sync-your-cookie/bcegpckmgklcpcapnbigfdadedcneopf)\n\nEdge: [Sync Your Cookie](https://microsoftedge.microsoft.com/addons/detail/sync-your-cookie/ohlcghldllgnmkegocpcphdbbphikgfm)\n\n\n### 功能\n- 支持同步 Cookie 到 Cloudflare 或者 github Gist (支持LocalStorage)\n- 支持为不同站点配置`Auto Merge`和`Auto Push`规则\n- Cookie数据经过 protobuf 编码传输\n- 提供了一个管理面板，方便查看、复制、管理已经同步的 Cookie 数据\n- 可配置多个Key，支持多账户同步\n\n### 项目截图\n\n账号设置页面\n\n<img width=\"600\" src=\"./screenshots/settings_v2.png\" alt=\"account settings\"/>\n\nCookie 同步页面\n\n<img width=\"600\" src=\"./screenshots/sync.png\" alt=\"cookie sync popup\"/>\n\nCookie 管理侧边栏面板\n\n<img width=\"600\" src=\"./screenshots/panel.png\" alt=\"cookie manager sidebar panel\"/>\n\nCookie 详情\n\n<img width=\"600\" src=\"./screenshots/panel_item.png\" alt=\"cookie manager sidebar panel\"/>\n\n\nGithub Gist上传的cookie\n\n<img width=\"600\" src=\"./screenshots/gist.png\" alt=\"Pushed Cookie on Github Gist\"/>\n\n\nCloudflare上传的cookie\n\n<img width=\"600\" src=\"./screenshots/key_value.png\" alt=\"Pushed Cookie on Cloudflare\"/>\n\n\n\n### 使用指引\n\n[How to use](./how-to-use.md)\n\n### Privacy Policy\n\nPlease refer to [Privacy Policy](./private-policy.md) for more information.\n\n### 赞赏\n\n如果你觉得这个项目对你有帮助，欢迎通过以下方式支持我：\n- 给项目点个 Star ⭐\n- [通过 Ko-fi 赞助](https://ko-fi.com/jacklu) 💖\n- 通过 微信支付 赞助\n  <div>\n    <img src=\"./screenshots/wechat_sponsor.jpg\" alt=\"微信支付\" style=\"width: 150px;\">\n  </div>\n- 分享给更多需要的人 🚀\n\n\n\n\n"
  },
  {
    "path": "chrome-extension/lib/background/badge.ts",
    "content": "export function setBadge(text: string, color: string = '#7246e4') {\n  chrome.action.setBadgeText({ text });\n  chrome.action.setBadgeBackgroundColor({ color });\n}\n\nexport function clearBadge() {\n  chrome.action.setBadgeText({ text: '' });\n}\n\nexport function setPullingBadge() {\n  setBadge('↓');\n}\n\nexport function setPushingBadge() {\n  setBadge('↑');\n}\n\nexport function setPushingAndPullingBadge() {\n  // badge('↓↑');\n  setBadge('⇅');\n}\n"
  },
  {
    "path": "chrome-extension/lib/background/contextMenu.ts",
    "content": "let globalMenuId: number | string = '';\n\nexport const initContextMenu = () => {\n  globalMenuId = chrome.contextMenus.create({\n    id: 'openSidePanel',\n    title: 'Open Cookie Manager',\n    contexts: ['all'],\n  });\n  chrome.contextMenus.onClicked.addListener((info, tab) => {\n    if (info.menuItemId === 'openSidePanel' && tab?.windowId) {\n      // This will open the panel in all the pages on the current window.\n      console.log('openSidePanel->tab', tab);\n      chrome.sidePanel.open({ windowId: tab.windowId });\n    }\n  });\n};\n\nexport const removeContextMenu = () => {\n  if (globalMenuId) {\n    chrome.contextMenus.remove(globalMenuId);\n  }\n};\n"
  },
  {
    "path": "chrome-extension/lib/background/index.ts",
    "content": "// sort-imports-ignore\nimport 'webextension-polyfill';\n\nimport { initGithubApi, pullAndSetCookies, pullCookies, pushMultipleDomainCookies } from '@sync-your-cookie/shared';\nimport { cookieStorage } from '@sync-your-cookie/storage/lib/cookieStorage';\nimport { domainConfigStorage } from '@sync-your-cookie/storage/lib/domainConfigStorage';\nimport { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';\nimport { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';\nimport { initContextMenu } from './contextMenu';\nimport { refreshListen } from './listen';\nimport { initSubscribe } from './subscribe';\n\nconst ping = () => {\n  chrome.tabs.query({ active: true, currentWindow: true }, async function (tabs) {\n    if (tabs.length === 0) {\n      // const allOpendTabs = await chrome.tabs.query({});\n      console.log('No active tab found, try alternative way');\n      // reject({ isOk: false, msg: 'No active tab found' } as SendResponse);\n      return;\n    }\n    chrome.tabs.sendMessage(tabs[0].id!, 'ping', function (result) {\n      console.log('result->', result);\n    });\n  });\n  // setTimeout(ping, 4000);\n};\n\nconst init = async () => {\n  try {\n    await refreshListen();\n    console.log('initListen finish');\n    await initSubscribe(); // await state reset finish\n    console.log('initSubscribe finish');\n    await pullCookies(true);\n    console.log('initPullCookies finish');\n    // ping();\n  } catch (error) {\n    console.log('init-->error', error);\n  }\n};\n\nchrome.runtime.onInstalled.addListener(async () => {\n  init();\n  console.log('onInstalled');\n  chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: false });\n  const settingsSnapShot = await settingsStorage.get();\n  if (settingsSnapShot?.contextMenu) {\n    initContextMenu();\n  }\n});\n\nlet delayTimer: NodeJS.Timeout | null = null;\nlet checkDelayTimer: NodeJS.Timeout | null = null;\nlet timeoutFlag = false;\nconst changedDomainSet = new Set<string>();\nchrome.cookies.onChanged.addListener(async changeInfo => {\n  const domainConfigSnapShot = await domainConfigStorage.getSnapshot();\n  const domain = changeInfo.cookie.domain;\n  const domainMap = domainConfigSnapShot?.domainMap || {};\n  const removeHeadDomain = domain.startsWith('.') ? domain.slice(1) : domain;\n  let flag = false;\n  for (const key in domainMap) {\n    if (key.endsWith(removeHeadDomain) && domainMap[key]?.autoPush) {\n      flag = true;\n      break;\n    }\n  }\n  if (!flag) return;\n  if (delayTimer && timeoutFlag) {\n    return;\n  }\n  delayTimer && clearTimeout(delayTimer);\n  changedDomainSet.add(removeHeadDomain);\n  delayTimer = setTimeout(async () => {\n    timeoutFlag = false;\n    if (checkDelayTimer) {\n      clearTimeout(checkDelayTimer);\n    }\n    const domainConfig = await domainConfigStorage.get();\n    // const pushDomainSet = new Set<string>();\n    const pushDomainHostMap = new Map<string, string[]>();\n    for (const domain of changedDomainSet) {\n      for (const key in domainConfig.domainMap) {\n        if (key.endsWith(domain) && domainConfig.domainMap[key]?.autoPush) {\n          // pushDomainSet.add(domain);\n          const existedHost = pushDomainHostMap.get(domain) || [];\n          pushDomainHostMap.set(domain, [key, ...existedHost]);\n        }\n      }\n    }\n\n    const uploadDomainCookies = [];\n    const cookieMap = await cookieStorage.getSnapshot();\n    const userAgent = navigator?.userAgent || '';\n    console.log('pushDomainHostMap', pushDomainHostMap);\n    for (const domain of pushDomainHostMap.keys()) {\n      const hosts = pushDomainHostMap.get(domain) || [];\n      // const [domain] = await extractDomainAndPort(host);\n\n      const cookies = await chrome.cookies.getAll({\n        domain: domain,\n      });\n      for (const host of hosts) {\n        uploadDomainCookies.push({\n          domain: host,\n          cookies,\n          localStorageItems: cookieMap?.domainCookieMap?.[host]?.localStorageItems || [],\n          userAgent,\n        });\n      }\n    }\n    if (uploadDomainCookies.length) {\n      await pushMultipleDomainCookies(uploadDomainCookies);\n      changedDomainSet.clear();\n    }\n  }, 10000);\n\n  if (!checkDelayTimer) {\n    checkDelayTimer = setTimeout(() => {\n      if (delayTimer) {\n        console.info('checkDelayTimer timeout');\n        timeoutFlag = true;\n        delayTimer = null;\n      }\n      checkDelayTimer = null;\n    }, 30000);\n  }\n});\n\nlet previousActiveTabList: chrome.tabs.Tab[] = [];\n\nchrome.tabs.onUpdated.addListener(async function (tabId, changeInfo, tab) {\n  // 1. current tab not exist in the tabMap\n  // read changeInfo data and do something with it (like read the url)\n  if (changeInfo.status === 'loading' && changeInfo.url) {\n    const domainConfig = await domainConfigStorage.get();\n    let pullDomain = '';\n    let needPull = false;\n    for (const key in domainConfig.domainMap) {\n      if (new URL(changeInfo.url).host.endsWith(key) && domainConfig.domainMap[key]?.autoPull) {\n        needPull = true;\n        pullDomain = key;\n        break;\n        // await pullCookies();\n      }\n    }\n    if (needPull) {\n      const allOpendTabs = await chrome.tabs.query({});\n      const otherExistedTabs = allOpendTabs.filter(itemTab => tab.id !== itemTab.id);\n      for (const itemTab of otherExistedTabs) {\n        if (itemTab.url && new URL(itemTab.url).host === new URL(changeInfo.url).host) {\n          needPull = false;\n          break;\n        }\n      }\n    }\n\n    if (needPull) {\n      for (const itemTab of previousActiveTabList) {\n        if (itemTab.url && new URL(itemTab.url).host === new URL(changeInfo.url).host) {\n          needPull = false;\n          break;\n        }\n      }\n    }\n    if (needPull) {\n      await pullAndSetCookies(changeInfo.url, pullDomain);\n    }\n    const allActiveTabs = await chrome.tabs.query({\n      active: true,\n    });\n    previousActiveTabList = allActiveTabs;\n  }\n});\n\n// let previousUrl = '';\n// chrome.webNavigation?.onBeforeNavigate.addListener(function (object) {\n//   chrome.tabs.get(object.tabId, function (tab) {\n//     previousUrl = tab.url || '';\n//     console.log('previousUrl', previousUrl);\n//   });\n// });\n\n// chrome.tabs.onRemoved.addListener(async function (tabId, removeInfo) {\n//   const allActiveTabs = await chrome.tabs.query({\n//     active: true,\n//   });\n//   previousActiveTabList = allActiveTabs;\n// });\n\nchrome.tabs.onActivated.addListener(async function () {\n  const allActiveTabs = await chrome.tabs.query({\n    active: true,\n  });\n  previousActiveTabList = allActiveTabs;\n  console.log('refreshListen', previousActiveTabList);\n  const domainStatus = await domainStatusStorage.get();\n  const settingsStorageInfo = await settingsStorage.get();\n  if (!domainStatus.pulling && !domainStatus.pushing && !settingsStorageInfo.localStorageGetting) {\n    refreshListen();\n  }\n});\n\ninitGithubApi(true);\n"
  },
  {
    "path": "chrome-extension/lib/background/listen.ts",
    "content": "import {\n  check,\n  checkResponseAndCallback,\n  CookieOperator,\n  extractDomainAndPort,\n  ICookie,\n  Message,\n  MessageType,\n  pullAndSetCookies,\n  PushCookieMessagePayload,\n  pushCookies,\n  removeCookieItem,\n  removeCookies,\n  sendGetLocalStorageMessage,\n  SendResponse,\n} from '@sync-your-cookie/shared';\nimport { cookieStorage } from '@sync-your-cookie/storage/lib/cookieStorage';\nimport { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';\n\nimport { domainConfigStorage } from '@sync-your-cookie/storage/lib/domainConfigStorage';\nimport { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';\n\ntype HandleCallback = (response?: SendResponse) => void;\n\nconst handlePush = async (payload: PushCookieMessagePayload, callback: HandleCallback) => {\n  const { sourceUrl, host, favIconUrl } = payload || {};\n  const userAgent = navigator?.userAgent || '';\n  try {\n    await check();\n    await domainConfigStorage.updateItem(host, {\n      sourceUrl: sourceUrl,\n      favIconUrl,\n    });\n    await domainStatusStorage.updateItem(host, {\n      pushing: true,\n    });\n    const [domain, port, hostname] = await extractDomainAndPort(host);\n    const condition = sourceUrl ? { url: sourceUrl } : { domain: domain };\n    const cookies = await chrome.cookies.getAll(condition);\n\n    let localStorageItems: NonNullable<Parameters<typeof pushCookies>[2]> = [];\n    const includeLocalStorage = settingsStorage.getSnapshot()?.includeLocalStorage;\n    if (includeLocalStorage) {\n      try {\n        const hostname = sourceUrl ? new URL(sourceUrl).hostname : host;\n        localStorageItems = await sendGetLocalStorageMessage(hostname);\n      } catch (error) {\n        console.error('sendGetLocalStorageMessage error', error);\n        const cookieMap = await cookieStorage.getSnapshot();\n        localStorageItems = cookieMap?.domainCookieMap?.[host]?.localStorageItems || [];\n      }\n    } else {\n      const cookieMap = await cookieStorage.getSnapshot();\n      localStorageItems = cookieMap?.domainCookieMap?.[host]?.localStorageItems || [];\n    }\n\n    if (cookies?.length || localStorageItems.length) {\n      const res = await pushCookies(host, cookies, localStorageItems, userAgent);\n      checkResponseAndCallback(res, 'push', callback);\n    } else {\n      callback({ isOk: false, msg: 'no cookies and  localStorageItems found', result: cookies });\n    }\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  } catch (err: any) {\n    checkResponseAndCallback(err, 'push', callback);\n  } finally {\n    await domainStatusStorage.togglePushingState(host, false);\n  }\n};\n\nconst handlePull = async (activeTabUrl: string, domain: string, isReload: boolean, callback: HandleCallback) => {\n  try {\n    await check();\n    await domainStatusStorage.togglePullingState(domain, true);\n    const cookieMap = await pullAndSetCookies(activeTabUrl, domain, isReload);\n    console.log('handlePull->cookieMap', cookieMap);\n    callback({ isOk: true, msg: 'Pull success', result: cookieMap });\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  } catch (err: any) {\n    checkResponseAndCallback(err, 'pull', callback);\n  } finally {\n    await domainStatusStorage.togglePullingState(domain, false);\n  }\n};\n\nconst handleRemove = async (domain: string, callback: HandleCallback) => {\n  try {\n    await check();\n    const res = await removeCookies(domain);\n    checkResponseAndCallback(res, 'remove', callback);\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  } catch (err: any) {\n    checkResponseAndCallback(err, 'remove', callback);\n\n    // callback({ isOk: false, msg: (err as Error).message || 'remove fail, please try again ', result: err });\n  }\n};\n\nconst handleRemoveItem = async (domain: string, id: string, callback: HandleCallback) => {\n  try {\n    await check();\n    const res = await removeCookieItem(domain, id);\n    checkResponseAndCallback(res, 'delete', callback);\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  } catch (err: any) {\n    checkResponseAndCallback(err, 'delete', callback);\n  }\n};\n\nconst handleEditItem = async (domain: string, oldItem: ICookie, newItem: ICookie, callback: HandleCallback) => {\n  try {\n    await check();\n    const res = await CookieOperator.editCookieItem(domain, oldItem, newItem);\n    checkResponseAndCallback(res, 'edit', callback);\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  } catch (err: any) {\n    checkResponseAndCallback(err, 'edit', callback);\n  }\n};\nfunction handleMessage(\n  message: Message,\n  sender: chrome.runtime.MessageSender,\n  callback: (response?: SendResponse) => void,\n) {\n  const type = message.type;\n  switch (type) {\n    case MessageType.PushCookie:\n      handlePush(message.payload, callback);\n      break;\n    case MessageType.PullCookie:\n      // eslint-disable-next-line no-case-declarations, @typescript-eslint/no-non-null-asserted-optional-chain\n      const activeTabUrl = message.payload.activeTabUrl || sender.tab?.url!;\n      handlePull(activeTabUrl!, message.payload.domain, message.payload.reload, callback);\n      break;\n    case MessageType.RemoveCookie:\n      handleRemove(message.payload.domain, callback);\n      break;\n    case MessageType.RemoveCookieItem:\n      handleRemoveItem(message.payload.domain, message.payload.id, callback);\n      break;\n    case MessageType.EditCookieItem:\n      handleEditItem(message.payload.domain, message.payload.oldItem, message.payload.newItem, callback);\n      break;\n    default:\n      break;\n  }\n  return true;\n}\nexport const refreshListen = async () => {\n  chrome.runtime.onMessage.removeListener(handleMessage);\n  chrome.runtime.onMessage.addListener(handleMessage);\n};\n"
  },
  {
    "path": "chrome-extension/lib/background/subscribe.ts",
    "content": "import { accountStorage } from '@sync-your-cookie/storage/lib/accountStorage';\nimport { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';\n\nimport { pullCookies } from '@sync-your-cookie/shared';\nimport { cookieStorage } from '@sync-your-cookie/storage/lib/cookieStorage';\nimport { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';\nimport { clearBadge, setPullingBadge, setPushingAndPullingBadge, setPushingBadge } from './badge';\nimport { initContextMenu, removeContextMenu } from './contextMenu';\n\nexport const initSubscribe = async () => {\n  await domainStatusStorage.resetState();\n  domainStatusStorage.subscribe(async () => {\n    const domainStatus = await domainStatusStorage.get();\n    if (domainStatus?.pulling && domainStatus.pushing) {\n      setPushingAndPullingBadge();\n    } else if (domainStatus?.pushing) {\n      setPushingBadge();\n    } else if (domainStatus?.pulling) {\n      setPullingBadge();\n    } else {\n      clearBadge();\n    }\n  });\n\n  accountStorage.subscribe(async () => {\n    await domainStatusStorage.resetState();\n    await cookieStorage.reset();\n    await pullCookies();\n    console.log('reset finished');\n  });\n\n  let previousContextMenu: boolean | undefined = undefined;\n\n  settingsStorage.subscribe(async () => {\n    const settingsSnapShot = await settingsStorage.getSnapshot();\n    if (previousContextMenu === settingsSnapShot?.contextMenu) {\n      return;\n    }\n    previousContextMenu = settingsSnapShot?.contextMenu;\n    if (settingsSnapShot?.contextMenu) {\n      initContextMenu();\n    } else {\n      removeContextMenu();\n    }\n  });\n};\n"
  },
  {
    "path": "chrome-extension/manifest.js",
    "content": "import fs from 'node:fs';\n\nconst packageJson = JSON.parse(fs.readFileSync('../package.json', 'utf8'));\n\nconst isFirefox = process.env.__FIREFOX__ === 'true';\n\nconst sidePanelConfig = {\n  side_panel: {\n    default_path: 'sidepanel/index.html',\n  },\n  permissions: !isFirefox ? ['sidePanel', 'contextMenus'] : [],\n};\n\n/**\n * After changing, please reload the extension at `chrome://extensions`\n * @type {chrome.runtime.ManifestV3}\n */\nconst manifest = Object.assign(\n  {\n    manifest_version: 3,\n    default_locale: 'en',\n    /**\n     * if you want to support multiple languages, you can use the following reference\n     * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Internationalization\n     */\n    name: 'Sync Your Cookie',\n    version: packageJson.version,\n    description: 'A browser extension for syncing cookies and localStorage to Cloudflare KV or GitHub Gist',\n    permissions: ['cookies', 'activeTab', 'tabs', 'storage', 'identity'].concat(sidePanelConfig.permissions),\n    host_permissions: ['<all_urls>'],\n    options_page: 'options/index.html',\n    background: {\n      service_worker: 'background.iife.js',\n      type: 'module',\n    },\n    action: {\n      default_popup: 'popup/index.html',\n      default_icon: 'icon-34.png',\n    },\n    // key: key,\n    // chrome_url_overrides: {\n    //   newtab: 'newtab/index.html',\n    // },\n    icons: {\n      128: 'icon-128.png',\n    },\n    content_scripts: [\n      {\n        matches: ['*://*/*'],\n        js: ['content/index.iife.js'],\n        run_at: 'document_start',\n      },\n    ],\n    // devtools_page: 'devtools/index.html',\n    web_accessible_resources: [\n      {\n        resources: ['*.js', '*.css', '*.svg', 'icon-128.png', 'icon-34.png'],\n        matches: ['*://*/*'],\n      },\n    ],\n  },\n  !isFirefox && { side_panel: { ...sidePanelConfig.side_panel } },\n);\n\nexport default manifest;\n"
  },
  {
    "path": "chrome-extension/package.json",
    "content": "{\n  \"name\": \"chrome-extension\",\n  \"version\": \"1.4.2\",\n  \"description\": \"chrome extension\",\n  \"scripts\": {\n    \"clean\": \"rimraf ../../dist && rimraf .turbo\",\n    \"build\": \"tsc --noEmit && vite build\",\n    \"build:firefox\": \"tsc --noEmit && cross-env __FIREFOX__=true vite build\",\n    \"build:watch\": \"cross-env __DEV__=true vite build -w --mode development\",\n    \"build:firefox:watch\": \"cross-env __DEV__=true __FIREFOX__=true vite build -w --mode development\",\n    \"dev\": \"pnpm build:watch\",\n    \"dev:firefox\": \"pnpm build:firefox:watch\",\n    \"test\": \"vitest run\",\n    \"lint\": \"eslint ./ --ext .ts,.js,.tsx,.jsx\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"@sync-your-cookie/shared\": \"workspace:*\",\n    \"@sync-your-cookie/storage\": \"workspace:*\",\n    \"p-timeout\": \"^6.1.4\",\n    \"webextension-polyfill\": \"^0.12.0\"\n  },\n  \"devDependencies\": {\n    \"@laynezh/vite-plugin-lib-assets\": \"^0.5.21\",\n    \"@sync-your-cookie/dev-utils\": \"workspace:*\",\n    \"@sync-your-cookie/hmr\": \"workspace:*\",\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\",\n    \"@types/ws\": \"^8.5.10\",\n    \"magic-string\": \"^0.30.12\",\n    \"ts-loader\": \"^9.5.1\"\n  }\n}\n"
  },
  {
    "path": "chrome-extension/public/_locales/en/messages.json",
    "content": "{\n  \"extensionDescription\": {\n    \"description\": \"sync your cookie free\",\n    \"message\": \"Sync your cookies to cloudlfare \"\n  },\n  \"extensionName\": {\n    \"description\": \"Sync your cookie\",\n    \"message\": \"Sync your cookie\"\n  }\n}\n"
  },
  {
    "path": "chrome-extension/public/content.css",
    "content": ""
  },
  {
    "path": "chrome-extension/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/app.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"types\": [\"vite/client\", \"node\", \"chrome\"],\n    \"paths\": {\n      \"@root/*\": [\"./*\"],\n      \"@lib/*\": [\"lib/*\"]\n    }\n  },\n  \"include\": [\"lib\", \"utils\", \"vite.config.ts\", \"node_modules/@types\"]\n}\n"
  },
  {
    "path": "chrome-extension/utils/plugins/make-manifest-plugin.ts",
    "content": "import * as fs from 'fs';\nimport * as path from 'path';\nimport { ManifestParser, colorLog } from '@sync-your-cookie/dev-utils';\nimport type { PluginOption } from 'vite';\nimport { pathToFileURL } from 'url';\nimport * as process from 'process';\n\nconst { resolve } = path;\n\nconst rootDir = resolve(__dirname, '..', '..');\nconst manifestFile = resolve(rootDir, 'manifest.js');\n\nconst getManifestWithCacheBurst = (): Promise<{ default: chrome.runtime.ManifestV3 }> => {\n  const withCacheBurst = (path: string) => `${path}?${Date.now().toString()}`;\n  /**\n   * In Windows, import() doesn't work without file:// protocol.\n   * So, we need to convert path to file:// protocol. (url.pathToFileURL)\n   */\n  if (process.platform === 'win32') {\n    return import(withCacheBurst(pathToFileURL(manifestFile).href));\n  }\n  return import(withCacheBurst(manifestFile));\n};\n\nexport default function makeManifestPlugin(config: { outDir: string }): PluginOption {\n  function makeManifest(manifest: chrome.runtime.ManifestV3, to: string) {\n    if (!fs.existsSync(to)) {\n      fs.mkdirSync(to);\n    }\n    const manifestPath = resolve(to, 'manifest.json');\n\n    const isFirefox = process.env.__FIREFOX__;\n    fs.writeFileSync(manifestPath, ManifestParser.convertManifestToString(manifest, isFirefox ? 'firefox' : 'chrome'));\n\n    colorLog(`Manifest file copy complete: ${manifestPath}`, 'success');\n  }\n\n  return {\n    name: 'make-manifest',\n    buildStart() {\n      this.addWatchFile(manifestFile);\n    },\n    async writeBundle() {\n      const outDir = config.outDir;\n      const manifest = await getManifestWithCacheBurst();\n      makeManifest(manifest.default, outDir);\n    },\n  };\n}\n"
  },
  {
    "path": "chrome-extension/vite.config.ts",
    "content": "import libAssetsPlugin from '@laynezh/vite-plugin-lib-assets';\nimport { watchRebuildPlugin } from '@sync-your-cookie/hmr';\nimport { resolve } from 'path';\nimport { defineConfig } from 'vite';\nimport makeManifestPlugin from './utils/plugins/make-manifest-plugin';\n\nconst rootDir = resolve(__dirname);\nconst libDir = resolve(rootDir, 'lib');\n\nconst isDev = process.env.__DEV__ === 'true';\nconst isProduction = !isDev;\n\nconst outDir = resolve(rootDir, '..', 'dist');\nexport default defineConfig({\n  resolve: {\n    alias: {\n      '@root': rootDir,\n      '@lib': libDir,\n      '@assets': resolve(libDir, 'assets'),\n    },\n  },\n  define: {\n    'process.env.NODE_ENV': isDev ? `\"development\"` : `\"production\"`,\n  },\n  plugins: [\n    libAssetsPlugin({\n      outputPath: outDir,\n    }),\n    makeManifestPlugin({ outDir }),\n    isDev && watchRebuildPlugin({ reload: true }),\n  ],\n  publicDir: resolve(rootDir, 'public'),\n  build: {\n    lib: {\n      formats: ['iife'],\n      entry: resolve(__dirname, 'lib/background/index.ts'),\n      name: 'BackgroundScript',\n      fileName: 'background',\n    },\n    outDir,\n    sourcemap: isDev,\n    minify: isProduction,\n    reportCompressedSize: isProduction,\n    modulePreload: true,\n    rollupOptions: {\n      external: ['chrome'],\n      output: {\n        inlineDynamicImports: true,\n      },\n    },\n  },\n});\n"
  },
  {
    "path": "googlef759ff453695209f.html",
    "content": "google-site-verification: googlef759ff453695209f.html"
  },
  {
    "path": "how-to-use.md",
    "content": "\n## How to use\n`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:\n\n## Create Namespace\n\n![create_namespace](./screenshots/kv//create_namepace.png)\n\nInput\n![created_namespace](./screenshots/kv/input_name.png)\n\nYour NamespaceId\n![namespaceId](./screenshots/kv/namespaceId.png)\n\n## Your AccountId\n\n![your_account_id](./screenshots/kv//account-id.png)\n\n## Create Token\n\n1. Enter Profile Page\n\n![token_page](./screenshots/kv//create_token.png)\n\n2. Custom Permission\n\n![setting-up](./screenshots/kv//custom_token.png)\n\n3. Select KV Read and Write Permission\n\n![select-permission](./screenshots/kv/setting-permission.png)\n\n4. Confirm Create\n\n![confirm-create](./screenshots/kv/finish_create_token.png)\n\n5. Copy Token\n\n![copy-token](./screenshots/kv/copy_token.png)\n\n6. Your Token List\n\n![your-token-list](./screenshots/kv/created_token_list.png)\n\n7. Paste Your Account Info And Save\n\n![paste-and-save](./screenshots/kv/paste.png)\n\n8. Push Your Cookie\n\n![push-cookie](./screenshots/kv/push_cookie.png)\n\n9. Check Your Cookie\n\nThe uploaded cookie is a protobuf-encoded string\n![check your cookie](./screenshots/kv/reload_page.png)\n\n\n\n## Reference\n\n- [create-token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/)\n- [account-owned-tokens](https://developers.cloudflare.com/fundamentals/api/get-started/account-owned-tokens/)\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"sync-your-cookie\",\n  \"version\": \"1.4.2\",\n  \"description\": \"sync your cookie extension monorepo\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/jackluson/sync-your-cookie.git\"\n  },\n  \"scripts\": {\n    \"clean\": \"rimraf dist && rimraf .turbo && turbo clean\",\n    \"build\": \"turbo build\",\n    \"build:firefox\": \"cross-env __FIREFOX__=true turbo build\",\n    \"dev-server\": \"pnpm -F hmr build && pnpm -F hmr dev-server\",\n    \"dev:apps\": \"cross-env __DEV__=true turbo run dev --filter=./pages/* --filter=chrome-extension --filter=@sync-your-cookie/hmr --concurrency 15\",\n    \"dev\": \"cross-env __DEV__=true turbo run dev --concurrency 25\",\n    \"dev:firefox\": \"cross-env __DEV__=true __FIREFOX__=true turbo dev --concurrency 20\",\n    \"zip\": \"pnpm build && pnpm -F zipper zip\",\n    \"test\": \"turbo test\",\n    \"type-check\": \"turbo type-check\",\n    \"lint\": \"turbo lint\",\n    \"lint:fix\": \"turbo lint:fix\",\n    \"prettier\": \"turbo prettier\"\n  },\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"react\": \"18.2.0\",\n    \"react-dom\": \"18.2.0\"\n  },\n  \"devDependencies\": {\n    \"@types/chrome\": \"^0.0.268\",\n    \"@types/node\": \"^20.12.11\",\n    \"@types/react\": \"^18.3.2\",\n    \"@types/react-dom\": \"^18.3.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^6.21.0\",\n    \"@typescript-eslint/parser\": \"^6.21.0\",\n    \"@vitejs/plugin-react-swc\": \"^3.6.0\",\n    \"autoprefixer\": \"^10.4.19\",\n    \"concurrently\": \"^8.2.2\",\n    \"cross-env\": \"^7.0.3\",\n    \"deepmerge\": \"^4.3.1\",\n    \"eslint\": \"8.56.0\",\n    \"eslint-config-airbnb-typescript\": \"17.1.0\",\n    \"eslint-config-prettier\": \"9.0.0\",\n    \"eslint-plugin-import\": \"2.29.1\",\n    \"eslint-plugin-jsx-a11y\": \"6.8.0\",\n    \"eslint-plugin-prettier\": \"5.1.3\",\n    \"eslint-plugin-react\": \"7.33.2\",\n    \"eslint-plugin-react-hooks\": \"4.6.2\",\n    \"postcss\": \"^8.4.38\",\n    \"prettier\": \"^3.2.5\",\n    \"rimraf\": \"^5.0.7\",\n    \"tailwindcss\": \"^3.4.3\",\n    \"tslib\": \"^2.6.2\",\n    \"turbo\": \"^2.0.3\",\n    \"typescript\": \"5.2.2\",\n    \"vite\": \"^5.2.11\"\n  },\n  \"packageManager\": \"pnpm@9.1.1\",\n  \"engines\": {\n    \"node\": \">=20.12.0\"\n  }\n}\n"
  },
  {
    "path": "packages/dev-utils/index.ts",
    "content": "export * from './lib/manifest-parser';\nexport * from './lib/logger';\n"
  },
  {
    "path": "packages/dev-utils/lib/logger.ts",
    "content": "type ColorType = 'success' | 'info' | 'error' | 'warning' | keyof typeof COLORS;\ntype ValueOf<T> = T[keyof T];\n\nexport function colorLog(message: string, type: ColorType) {\n  let color: ValueOf<typeof COLORS>;\n\n  switch (type) {\n    case 'success':\n      color = COLORS.FgGreen;\n      break;\n    case 'info':\n      color = COLORS.FgBlue;\n      break;\n    case 'error':\n      color = COLORS.FgRed;\n      break;\n    case 'warning':\n      color = COLORS.FgYellow;\n      break;\n    default:\n      color = COLORS[type];\n      break;\n  }\n\n  console.log(color, message);\n}\n\nconst COLORS = {\n  Reset: '\\x1b[0m',\n  Bright: '\\x1b[1m',\n  Dim: '\\x1b[2m',\n  Underscore: '\\x1b[4m',\n  Blink: '\\x1b[5m',\n  Reverse: '\\x1b[7m',\n  Hidden: '\\x1b[8m',\n  FgBlack: '\\x1b[30m',\n  FgRed: '\\x1b[31m',\n  FgGreen: '\\x1b[32m',\n  FgYellow: '\\x1b[33m',\n  FgBlue: '\\x1b[34m',\n  FgMagenta: '\\x1b[35m',\n  FgCyan: '\\x1b[36m',\n  FgWhite: '\\x1b[37m',\n  BgBlack: '\\x1b[40m',\n  BgRed: '\\x1b[41m',\n  BgGreen: '\\x1b[42m',\n  BgYellow: '\\x1b[43m',\n  BgBlue: '\\x1b[44m',\n  BgMagenta: '\\x1b[45m',\n  BgCyan: '\\x1b[46m',\n  BgWhite: '\\x1b[47m',\n} as const;\n"
  },
  {
    "path": "packages/dev-utils/lib/manifest-parser/impl.ts",
    "content": "import { ManifestParserInterface, Manifest } from './type';\n\nexport const ManifestParserImpl: ManifestParserInterface = {\n  convertManifestToString: (manifest, env) => {\n    if (env === 'firefox') {\n      manifest = convertToFirefoxCompatibleManifest(manifest);\n    }\n    return JSON.stringify(manifest, null, 2);\n  },\n};\n\nfunction convertToFirefoxCompatibleManifest(manifest: Manifest) {\n  const manifestCopy = {\n    ...manifest,\n  } as { [key: string]: unknown };\n\n  manifestCopy.background = {\n    scripts: [manifest.background?.service_worker],\n    type: 'module',\n  };\n  manifestCopy.options_ui = {\n    page: manifest.options_page,\n    browser_style: false,\n  };\n  manifestCopy.content_security_policy = {\n    extension_pages: \"script-src 'self'; object-src 'self'\",\n  };\n  delete manifestCopy.options_page;\n  return manifestCopy as Manifest;\n}\n"
  },
  {
    "path": "packages/dev-utils/lib/manifest-parser/index.ts",
    "content": "import { ManifestParserImpl } from './impl';\nexport const ManifestParser = ManifestParserImpl;\n"
  },
  {
    "path": "packages/dev-utils/lib/manifest-parser/type.ts",
    "content": "export type Manifest = chrome.runtime.ManifestV3;\n\nexport interface ManifestParserInterface {\n  convertManifestToString: (manifest: Manifest, env: 'chrome' | 'firefox') => string;\n}\n"
  },
  {
    "path": "packages/dev-utils/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/dev-utils\",\n  \"version\": \"0.0.1\",\n  \"description\": \"chrome extension dev utils\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"files\": [\n    \"dist/**\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"scripts\": {\n    \"clean\": \"rimraf ./dist && rimraf ./build && rimraf .turbo\",\n    \"build\": \"pnpm run clean && tsc\",\n    \"lint\": \"eslint . --ext .ts,.tsx\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {},\n  \"devDependencies\": {\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "packages/dev-utils/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"types\": [\"chrome\"]\n  },\n  \"include\": [\"index.ts\", \"lib\"]\n}\n"
  },
  {
    "path": "packages/hmr/index.ts",
    "content": "export * from './lib/plugins';\n"
  },
  {
    "path": "packages/hmr/lib/constant.ts",
    "content": "export const LOCAL_RELOAD_SOCKET_PORT = 8081;\nexport const LOCAL_RELOAD_SOCKET_URL = `ws://localhost:${LOCAL_RELOAD_SOCKET_PORT}`;\n"
  },
  {
    "path": "packages/hmr/lib/debounce.ts",
    "content": "export function debounce<A extends unknown[]>(callback: (...args: A) => void, delay: number) {\n  let timer: NodeJS.Timeout;\n  return function (...args: A) {\n    clearTimeout(timer);\n    timer = setTimeout(() => callback(...args), delay);\n  };\n}\n"
  },
  {
    "path": "packages/hmr/lib/initClient.ts",
    "content": "import { LOCAL_RELOAD_SOCKET_URL } from './constant';\nimport MessageInterpreter from './interpreter';\n\nexport default function initReloadClient({ id, onUpdate }: { id: string; onUpdate: () => void }) {\n  let ws: WebSocket | null = null;\n  try {\n    ws = new WebSocket(LOCAL_RELOAD_SOCKET_URL);\n    ws.onopen = () => {\n      ws?.addEventListener('message', event => {\n        const message = MessageInterpreter.receive(String(event.data));\n        if (message.type === 'ping') {\n          console.log('[HMR] Client OK');\n        }\n        if (message.type === 'do_update' && message.id === id) {\n          sendUpdateCompleteMessage();\n          onUpdate();\n          return;\n        }\n      });\n    };\n\n    ws.onclose = () => {\n      console.log(\n        `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.`,\n      );\n      setTimeout(() => {\n        initReloadClient({ onUpdate, id });\n      }, 1000);\n    };\n  } catch (e) {\n    setTimeout(() => {\n      initReloadClient({ onUpdate, id });\n    }, 1000);\n  }\n\n  function sendUpdateCompleteMessage() {\n    ws?.send(MessageInterpreter.send({ type: 'done_update' }));\n  }\n}\n"
  },
  {
    "path": "packages/hmr/lib/initReloadServer.ts",
    "content": "#!/usr/bin/env node\n\nimport { WebSocket, WebSocketServer } from 'ws';\nimport { LOCAL_RELOAD_SOCKET_PORT, LOCAL_RELOAD_SOCKET_URL } from './constant';\nimport MessageInterpreter from './interpreter';\n\nconst clientsThatNeedToUpdate: Set<WebSocket> = new Set();\n\nfunction initReloadServer() {\n  try {\n    const wss = new WebSocketServer({ port: LOCAL_RELOAD_SOCKET_PORT });\n\n    wss.on('listening', () => console.log(`[HMR] Server listening at ${LOCAL_RELOAD_SOCKET_URL}`));\n\n    wss.on('connection', ws => {\n      clientsThatNeedToUpdate.add(ws);\n\n      ws.addEventListener('close', () => clientsThatNeedToUpdate.delete(ws));\n      ws.addEventListener('message', event => {\n        if (typeof event.data !== 'string') return;\n\n        const message = MessageInterpreter.receive(event.data);\n\n        if (message.type === 'done_update') {\n          ws.close();\n        }\n        if (message.type === 'build_complete') {\n          clientsThatNeedToUpdate.forEach((ws: WebSocket) =>\n            ws.send(MessageInterpreter.send({ type: 'do_update', id: message.id })),\n          );\n        }\n      });\n    });\n\n    ping();\n  } catch {\n    console.error(`[HMR] Failed to start server at ${LOCAL_RELOAD_SOCKET_URL}`);\n    console.error('PLEASE MAKE SURE YOU ARE RUNNING `pnpm dev-server`');\n  }\n}\n\ninitReloadServer();\n\nfunction ping() {\n  clientsThatNeedToUpdate.forEach(ws => ws.send(MessageInterpreter.send({ type: 'ping' })));\n  setTimeout(() => {\n    ping();\n  }, 15_000);\n}\n"
  },
  {
    "path": "packages/hmr/lib/injections/refresh.ts",
    "content": "import initClient from '../initClient';\n\nfunction addRefresh() {\n  let pendingReload = false;\n\n  initClient({\n    // eslint-disable-next-line\n    // @ts-ignore\n    id: __HMR_ID,\n    onUpdate: () => {\n      // disable reload when tab is hidden\n      if (document.hidden) {\n        pendingReload = true;\n        return;\n      }\n      reload();\n    },\n  });\n\n  // reload\n  function reload(): void {\n    pendingReload = false;\n    window.location.reload();\n  }\n\n  // reload when tab is visible\n  function reloadWhenTabIsVisible(): void {\n    !document.hidden && pendingReload && reload();\n  }\n  document.addEventListener('visibilitychange', reloadWhenTabIsVisible);\n}\n\naddRefresh();\n"
  },
  {
    "path": "packages/hmr/lib/injections/reload.ts",
    "content": "import initClient from '../initClient';\n\nfunction addReload() {\n  const reload = () => {\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    chrome.runtime.reload();\n  };\n\n  initClient({\n    // eslint-disable-next-line\n    // @ts-ignore\n    id: __HMR_ID,\n    onUpdate: reload,\n  });\n}\n\naddReload();\n"
  },
  {
    "path": "packages/hmr/lib/interpreter/index.ts",
    "content": "import type { WebSocketMessage, SerializedMessage } from './types';\n\nexport default class MessageInterpreter {\n  // eslint-disable-next-line @typescript-eslint/no-empty-function\n  private constructor() {}\n\n  static send(message: WebSocketMessage): SerializedMessage {\n    return JSON.stringify(message);\n  }\n  static receive(serializedMessage: SerializedMessage): WebSocketMessage {\n    return JSON.parse(serializedMessage);\n  }\n}\n"
  },
  {
    "path": "packages/hmr/lib/interpreter/types.ts",
    "content": "type UpdateRequestMessage = {\n  type: 'do_update';\n  id: string;\n};\ntype UpdateCompleteMessage = { type: 'done_update' };\ntype PingMessage = { type: 'ping' };\ntype BuildCompletionMessage = { type: 'build_complete'; id: string };\n\nexport type SerializedMessage = string;\n\nexport type WebSocketMessage = UpdateCompleteMessage | UpdateRequestMessage | BuildCompletionMessage | PingMessage;\n"
  },
  {
    "path": "packages/hmr/lib/plugins/index.ts",
    "content": "export * from './watch-rebuild-plugin';\nexport * from './make-entry-point-plugin';\n"
  },
  {
    "path": "packages/hmr/lib/plugins/make-entry-point-plugin.ts",
    "content": "import * as fs from 'fs';\nimport path from 'path';\nimport type { PluginOption } from 'vite';\n\n/**\n * make entry point file for content script cache busting\n */\n\nexport function makeEntryPointPlugin(): PluginOption {\n  const cleanupTargets = new Set<string>();\n  const isFirefox = process.env.__FIREFOX__ === 'true';\n\n  return {\n    name: 'make-entry-point-plugin',\n    generateBundle(options, bundle) {\n      const outputDir = options.dir;\n      if (!outputDir) {\n        throw new Error('Output directory not found');\n      }\n      for (const module of Object.values(bundle)) {\n        const fileName = path.basename(module.fileName);\n        const newFileName = fileName.replace('.js', '_dev.js');\n        switch (module.type) {\n          case 'asset':\n            // map file\n            if (fileName.endsWith('.map')) {\n              cleanupTargets.add(path.resolve(outputDir, fileName));\n              const originalFileName = fileName.replace('.map', '');\n              const replacedSource = String(module.source).replaceAll(originalFileName, newFileName);\n              module.source = '';\n              fs.writeFileSync(path.resolve(outputDir, newFileName), replacedSource);\n              break;\n            }\n            break;\n          case 'chunk': {\n            fs.writeFileSync(path.resolve(outputDir, newFileName), module.code);\n            console.log('newFileName', newFileName);\n            if (isFirefox) {\n              const contentDirectory = extractContentDir(outputDir);\n              module.code = `import(browser.runtime.getURL(\"${contentDirectory}/${newFileName}\"));`;\n            } else {\n              module.code = `import('./${newFileName}');`;\n            }\n            break;\n          }\n        }\n      }\n    },\n    closeBundle() {\n      cleanupTargets.forEach(target => {\n        fs.unlinkSync(target);\n      });\n    },\n  };\n}\n\n/**\n * Extract content directory from output directory for Firefox\n * @param outputDir\n */\nfunction extractContentDir(outputDir: string) {\n  const parts = outputDir.split(path.sep);\n  const distIndex = parts.indexOf('dist');\n  if (distIndex !== -1 && distIndex < parts.length - 1) {\n    return parts.slice(distIndex + 1);\n  }\n  throw new Error('Output directory does not contain \"dist\"');\n}\n"
  },
  {
    "path": "packages/hmr/lib/plugins/watch-rebuild-plugin.ts",
    "content": "import type { PluginOption } from 'vite';\nimport { WebSocket } from 'ws';\nimport MessageInterpreter from '../interpreter';\nimport { LOCAL_RELOAD_SOCKET_URL } from '../constant';\nimport * as fs from 'fs';\nimport path from 'path';\n\ntype PluginConfig = {\n  onStart?: () => void;\n  reload?: boolean;\n  refresh?: boolean;\n};\n\nconst injectionsPath = path.resolve(__dirname, '..', '..', '..', 'build', 'injections');\n\nconst refreshCode = fs.readFileSync(path.resolve(injectionsPath, 'refresh.js'), 'utf-8');\nconst reloadCode = fs.readFileSync(path.resolve(injectionsPath, 'reload.js'), 'utf-8');\n\nexport function watchRebuildPlugin(config: PluginConfig): PluginOption {\n  let ws: WebSocket | null = null;\n  const id = Math.random().toString(36);\n  const { refresh, reload } = config;\n\n  const hmrCode = (refresh ? refreshCode : '') + (reload ? reloadCode : '');\n\n  function initializeWebSocket() {\n    if (!ws) {\n      ws = new WebSocket(LOCAL_RELOAD_SOCKET_URL);\n      ws.onopen = () => {\n        console.log(`[HMR] Connected to dev-server at ${LOCAL_RELOAD_SOCKET_URL}`);\n      };\n      ws.onerror = () => {\n        console.error(`[HMR] Failed to start server at ${LOCAL_RELOAD_SOCKET_URL}`);\n        console.error('PLEASE MAKE SURE YOU ARE RUNNING `pnpm dev-server`');\n        console.warn('Retrying in 5 seconds...');\n        ws = null;\n        setTimeout(() => initializeWebSocket(), 5_000);\n      };\n    }\n  }\n\n  return {\n    name: 'watch-rebuild',\n    writeBundle() {\n      config.onStart?.();\n      if (!ws) {\n        initializeWebSocket();\n        return;\n      }\n      /**\n       * When the build is complete, send a message to the reload server.\n       * The reload server will send a message to the client to reload or refresh the extension.\n       */\n      if (!ws) {\n        throw new Error('WebSocket is not initialized');\n      }\n      ws.send(MessageInterpreter.send({ type: 'build_complete', id }));\n    },\n    generateBundle(_options, bundle) {\n      for (const module of Object.values(bundle)) {\n        if (module.type === 'chunk') {\n          module.code = `(function() {let __HMR_ID = \"${id}\";\\n` + hmrCode + '\\n' + '})();' + '\\n' + module.code;\n        }\n      }\n    },\n  };\n}\n"
  },
  {
    "path": "packages/hmr/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/hmr\",\n  \"version\": \"0.0.1\",\n  \"description\": \"chrome extension hot module reload or refresh\",\n  \"private\": true,\n  \"sideEffects\": true,\n  \"files\": [\n    \"dist/**\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"scripts\": {\n    \"clean\": \"rimraf ./dist && rimraf ./build && rimraf .turbo\",\n    \"build:tsc\": \"tsc -b tsconfig.build.json\",\n    \"build:rollup\": \"rollup --config rollup.config.mjs\",\n    \"build\": \"pnpm run build:tsc && pnpm run build:rollup\",\n    \"dev\": \"node dist/lib/initReloadServer.js\",\n    \"lint\": \"eslint . --ext .ts,.tsx\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {\n    \"ws\": \"8.17.0\"\n  },\n  \"devDependencies\": {\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\",\n    \"@rollup/plugin-sucrase\": \"^5.0.2\",\n    \"@types/ws\": \"^8.5.10\",\n    \"esm\": \"^3.2.25\",\n    \"rollup\": \"^4.17.2\",\n    \"ts-node\": \"^10.9.2\"\n  }\n}\n"
  },
  {
    "path": "packages/hmr/rollup.config.mjs",
    "content": "import sucrase from '@rollup/plugin-sucrase';\n\nconst plugins = [\n  sucrase({\n    exclude: ['node_modules/**'],\n    transforms: ['typescript'],\n  }),\n];\n\n/**\n * @type {import(\"rollup\").RollupOptions[]}\n */\nexport default [\n  {\n    plugins,\n    input: 'lib/injections/reload.ts',\n    output: {\n      format: 'iife',\n      file: 'build/injections/reload.js',\n    },\n  },\n  {\n    plugins,\n    input: 'lib/injections/refresh.ts',\n    output: {\n      format: 'iife',\n      file: 'build/injections/refresh.js',\n    },\n  },\n];\n"
  },
  {
    "path": "packages/hmr/tsconfig.build.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\"\n  },\n  \"exclude\": [\"lib/injections/**/*\"],\n  \"include\": [\"lib\", \"index.ts\"]\n}\n"
  },
  {
    "path": "packages/hmr/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\"\n  },\n  \"include\": [\"lib\", \"index.ts\", \"rollup.config.mjs\"]\n}\n"
  },
  {
    "path": "packages/protobuf/README.md",
    "content": "# Shared Package\n\nThis package contains code shared with other packages.\nTo use the code in the package, you need to add the following to the package.json file.\n\n```json\n{\n  \"dependencies\": {\n    \"@sync-your-cookie/shared\": \"workspace:*\"\n  }\n}\n```\n\nAfter building this package, real-time cache busting does not occur in the code of other packages that reference this package.\nYou need to rerun it from the root path with `pnpm dev`, etc. (This will be improved in the future.)\n\nIf the type does not require compilation, there is no problem, but if the implementation requiring compilation is changed, a problem may occur.\n\nTherefore, 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.\n"
  },
  {
    "path": "packages/protobuf/index.ts",
    "content": "export * from './lib/protobuf';\n\nexport * from './utils';\n\nimport pako from 'pako';\n\nexport { pako };\n"
  },
  {
    "path": "packages/protobuf/lib/protobuf/code.ts",
    "content": "import pako from 'pako';\nimport { compress, decompress } from './../../utils/compress';\nimport type { ICookiesMap } from './proto/cookie';\nimport { CookiesMap } from './proto/cookie';\n\nexport const encodeCookiesMap = async (\n  cookiesMap: ICookiesMap = {},\n  isCompress: boolean = true,\n): Promise<Uint8Array> => {\n  // verify 只会校验数据的类型是否合法，并不会校验是否缺少或增加了数据项。\n  const invalid = CookiesMap.verify(cookiesMap);\n  if (invalid) {\n    throw Error(invalid);\n  }\n\n  const message = CookiesMap.create(cookiesMap);\n  const buffer = CookiesMap.encode(message).finish();\n  if (isCompress) {\n    const compressedBuf = pako.deflate(buffer);\n    return await compress(compressedBuf);\n  }\n  return buffer;\n};\n\nexport const decodeCookiesMap = async (buffer: Uint8Array, isDeCompress: boolean = true) => {\n  let buf = buffer;\n  if (isDeCompress) {\n    buf = await decompress(buf);\n    buf = pako.inflate(buf);\n  }\n  const message = CookiesMap.decode(buf);\n  return message;\n};\n\nexport type { ICookie, ICookiesMap, ILocalStorageItem } from './proto/cookie';\n\n"
  },
  {
    "path": "packages/protobuf/lib/protobuf/index.ts",
    "content": "export * from './code';\n"
  },
  {
    "path": "packages/protobuf/lib/protobuf/proto/cookie.d.ts",
    "content": "import * as $protobuf from \"protobufjs\";\nimport Long = require(\"long\");\n/** Properties of a Cookie. */\nexport interface ICookie {\n\n    /** Cookie domain */\n    domain?: (string|null);\n\n    /** Cookie name */\n    name?: (string|null);\n\n    /** Cookie storeId */\n    storeId?: (string|null);\n\n    /** Cookie value */\n    value?: (string|null);\n\n    /** Cookie session */\n    session?: (boolean|null);\n\n    /** Cookie hostOnly */\n    hostOnly?: (boolean|null);\n\n    /** Cookie expirationDate */\n    expirationDate?: (number|null);\n\n    /** Cookie path */\n    path?: (string|null);\n\n    /** Cookie httpOnly */\n    httpOnly?: (boolean|null);\n\n    /** Cookie secure */\n    secure?: (boolean|null);\n\n    /** Cookie sameSite */\n    sameSite?: (string|null);\n}\n\n/** Represents a Cookie. */\nexport class Cookie implements ICookie {\n\n    /**\n     * Constructs a new Cookie.\n     * @param [properties] Properties to set\n     */\n    constructor(properties?: ICookie);\n\n    /** Cookie domain. */\n    public domain: string;\n\n    /** Cookie name. */\n    public name: string;\n\n    /** Cookie storeId. */\n    public storeId: string;\n\n    /** Cookie value. */\n    public value: string;\n\n    /** Cookie session. */\n    public session: boolean;\n\n    /** Cookie hostOnly. */\n    public hostOnly: boolean;\n\n    /** Cookie expirationDate. */\n    public expirationDate: number;\n\n    /** Cookie path. */\n    public path: string;\n\n    /** Cookie httpOnly. */\n    public httpOnly: boolean;\n\n    /** Cookie secure. */\n    public secure: boolean;\n\n    /** Cookie sameSite. */\n    public sameSite: string;\n\n    /**\n     * Creates a new Cookie instance using the specified properties.\n     * @param [properties] Properties to set\n     * @returns Cookie instance\n     */\n    public static create(properties?: ICookie): Cookie;\n\n    /**\n     * Encodes the specified Cookie message. Does not implicitly {@link Cookie.verify|verify} messages.\n     * @param message Cookie message or plain object to encode\n     * @param [writer] Writer to encode to\n     * @returns Writer\n     */\n    public static encode(message: ICookie, writer?: $protobuf.Writer): $protobuf.Writer;\n\n    /**\n     * Encodes the specified Cookie message, length delimited. Does not implicitly {@link Cookie.verify|verify} messages.\n     * @param message Cookie message or plain object to encode\n     * @param [writer] Writer to encode to\n     * @returns Writer\n     */\n    public static encodeDelimited(message: ICookie, writer?: $protobuf.Writer): $protobuf.Writer;\n\n    /**\n     * Decodes a Cookie message from the specified reader or buffer.\n     * @param reader Reader or buffer to decode from\n     * @param [length] Message length if known beforehand\n     * @returns Cookie\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): Cookie;\n\n    /**\n     * Decodes a Cookie message from the specified reader or buffer, length delimited.\n     * @param reader Reader or buffer to decode from\n     * @returns Cookie\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): Cookie;\n\n    /**\n     * Verifies a Cookie message.\n     * @param message Plain object to verify\n     * @returns `null` if valid, otherwise the reason why it is not\n     */\n    public static verify(message: { [k: string]: any }): (string|null);\n\n    /**\n     * Creates a Cookie message from a plain object. Also converts values to their respective internal types.\n     * @param object Plain object\n     * @returns Cookie\n     */\n    public static fromObject(object: { [k: string]: any }): Cookie;\n\n    /**\n     * Creates a plain object from a Cookie message. Also converts values to other types if specified.\n     * @param message Cookie\n     * @param [options] Conversion options\n     * @returns Plain object\n     */\n    public static toObject(message: Cookie, options?: $protobuf.IConversionOptions): { [k: string]: any };\n\n    /**\n     * Converts this Cookie to JSON.\n     * @returns JSON object\n     */\n    public toJSON(): { [k: string]: any };\n\n    /**\n     * Gets the default type url for Cookie\n     * @param [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n     * @returns The default type url\n     */\n    public static getTypeUrl(typeUrlPrefix?: string): string;\n}\n\n/** Properties of a LocalStorageItem. */\nexport interface ILocalStorageItem {\n\n    /** LocalStorageItem key */\n    key?: (string|null);\n\n    /** LocalStorageItem value */\n    value?: (string|null);\n}\n\n/** Represents a LocalStorageItem. */\nexport class LocalStorageItem implements ILocalStorageItem {\n\n    /**\n     * Constructs a new LocalStorageItem.\n     * @param [properties] Properties to set\n     */\n    constructor(properties?: ILocalStorageItem);\n\n    /** LocalStorageItem key. */\n    public key: string;\n\n    /** LocalStorageItem value. */\n    public value: string;\n\n    /**\n     * Creates a new LocalStorageItem instance using the specified properties.\n     * @param [properties] Properties to set\n     * @returns LocalStorageItem instance\n     */\n    public static create(properties?: ILocalStorageItem): LocalStorageItem;\n\n    /**\n     * Encodes the specified LocalStorageItem message. Does not implicitly {@link LocalStorageItem.verify|verify} messages.\n     * @param message LocalStorageItem message or plain object to encode\n     * @param [writer] Writer to encode to\n     * @returns Writer\n     */\n    public static encode(message: ILocalStorageItem, writer?: $protobuf.Writer): $protobuf.Writer;\n\n    /**\n     * Encodes the specified LocalStorageItem message, length delimited. Does not implicitly {@link LocalStorageItem.verify|verify} messages.\n     * @param message LocalStorageItem message or plain object to encode\n     * @param [writer] Writer to encode to\n     * @returns Writer\n     */\n    public static encodeDelimited(message: ILocalStorageItem, writer?: $protobuf.Writer): $protobuf.Writer;\n\n    /**\n     * Decodes a LocalStorageItem message from the specified reader or buffer.\n     * @param reader Reader or buffer to decode from\n     * @param [length] Message length if known beforehand\n     * @returns LocalStorageItem\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): LocalStorageItem;\n\n    /**\n     * Decodes a LocalStorageItem message from the specified reader or buffer, length delimited.\n     * @param reader Reader or buffer to decode from\n     * @returns LocalStorageItem\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): LocalStorageItem;\n\n    /**\n     * Verifies a LocalStorageItem message.\n     * @param message Plain object to verify\n     * @returns `null` if valid, otherwise the reason why it is not\n     */\n    public static verify(message: { [k: string]: any }): (string|null);\n\n    /**\n     * Creates a LocalStorageItem message from a plain object. Also converts values to their respective internal types.\n     * @param object Plain object\n     * @returns LocalStorageItem\n     */\n    public static fromObject(object: { [k: string]: any }): LocalStorageItem;\n\n    /**\n     * Creates a plain object from a LocalStorageItem message. Also converts values to other types if specified.\n     * @param message LocalStorageItem\n     * @param [options] Conversion options\n     * @returns Plain object\n     */\n    public static toObject(message: LocalStorageItem, options?: $protobuf.IConversionOptions): { [k: string]: any };\n\n    /**\n     * Converts this LocalStorageItem to JSON.\n     * @returns JSON object\n     */\n    public toJSON(): { [k: string]: any };\n\n    /**\n     * Gets the default type url for LocalStorageItem\n     * @param [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n     * @returns The default type url\n     */\n    public static getTypeUrl(typeUrlPrefix?: string): string;\n}\n\n/** Properties of a DomainCookie. */\nexport interface IDomainCookie {\n\n    /** DomainCookie createTime */\n    createTime?: (number|Long|null);\n\n    /** DomainCookie updateTime */\n    updateTime?: (number|Long|null);\n\n    /** DomainCookie cookies */\n    cookies?: (ICookie[]|null);\n\n    /** DomainCookie localStorageItems */\n    localStorageItems?: (ILocalStorageItem[]|null);\n\n    /** DomainCookie userAgent */\n    userAgent?: (string|null);\n}\n\n/** Represents a DomainCookie. */\nexport class DomainCookie implements IDomainCookie {\n\n    /**\n     * Constructs a new DomainCookie.\n     * @param [properties] Properties to set\n     */\n    constructor(properties?: IDomainCookie);\n\n    /** DomainCookie createTime. */\n    public createTime: (number|Long);\n\n    /** DomainCookie updateTime. */\n    public updateTime: (number|Long);\n\n    /** DomainCookie cookies. */\n    public cookies: ICookie[];\n\n    /** DomainCookie localStorageItems. */\n    public localStorageItems: ILocalStorageItem[];\n\n    /** DomainCookie userAgent. */\n    public userAgent: string;\n\n    /**\n     * Creates a new DomainCookie instance using the specified properties.\n     * @param [properties] Properties to set\n     * @returns DomainCookie instance\n     */\n    public static create(properties?: IDomainCookie): DomainCookie;\n\n    /**\n     * Encodes the specified DomainCookie message. Does not implicitly {@link DomainCookie.verify|verify} messages.\n     * @param message DomainCookie message or plain object to encode\n     * @param [writer] Writer to encode to\n     * @returns Writer\n     */\n    public static encode(message: IDomainCookie, writer?: $protobuf.Writer): $protobuf.Writer;\n\n    /**\n     * Encodes the specified DomainCookie message, length delimited. Does not implicitly {@link DomainCookie.verify|verify} messages.\n     * @param message DomainCookie message or plain object to encode\n     * @param [writer] Writer to encode to\n     * @returns Writer\n     */\n    public static encodeDelimited(message: IDomainCookie, writer?: $protobuf.Writer): $protobuf.Writer;\n\n    /**\n     * Decodes a DomainCookie message from the specified reader or buffer.\n     * @param reader Reader or buffer to decode from\n     * @param [length] Message length if known beforehand\n     * @returns DomainCookie\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): DomainCookie;\n\n    /**\n     * Decodes a DomainCookie message from the specified reader or buffer, length delimited.\n     * @param reader Reader or buffer to decode from\n     * @returns DomainCookie\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): DomainCookie;\n\n    /**\n     * Verifies a DomainCookie message.\n     * @param message Plain object to verify\n     * @returns `null` if valid, otherwise the reason why it is not\n     */\n    public static verify(message: { [k: string]: any }): (string|null);\n\n    /**\n     * Creates a DomainCookie message from a plain object. Also converts values to their respective internal types.\n     * @param object Plain object\n     * @returns DomainCookie\n     */\n    public static fromObject(object: { [k: string]: any }): DomainCookie;\n\n    /**\n     * Creates a plain object from a DomainCookie message. Also converts values to other types if specified.\n     * @param message DomainCookie\n     * @param [options] Conversion options\n     * @returns Plain object\n     */\n    public static toObject(message: DomainCookie, options?: $protobuf.IConversionOptions): { [k: string]: any };\n\n    /**\n     * Converts this DomainCookie to JSON.\n     * @returns JSON object\n     */\n    public toJSON(): { [k: string]: any };\n\n    /**\n     * Gets the default type url for DomainCookie\n     * @param [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n     * @returns The default type url\n     */\n    public static getTypeUrl(typeUrlPrefix?: string): string;\n}\n\n/** Properties of a CookiesMap. */\nexport interface ICookiesMap {\n\n    /** CookiesMap createTime */\n    createTime?: (number|Long|null);\n\n    /** CookiesMap updateTime */\n    updateTime?: (number|Long|null);\n\n    /** CookiesMap domainCookieMap */\n    domainCookieMap?: ({ [k: string]: IDomainCookie }|null);\n}\n\n/** Represents a CookiesMap. */\nexport class CookiesMap implements ICookiesMap {\n\n    /**\n     * Constructs a new CookiesMap.\n     * @param [properties] Properties to set\n     */\n    constructor(properties?: ICookiesMap);\n\n    /** CookiesMap createTime. */\n    public createTime: (number|Long);\n\n    /** CookiesMap updateTime. */\n    public updateTime: (number|Long);\n\n    /** CookiesMap domainCookieMap. */\n    public domainCookieMap: { [k: string]: IDomainCookie };\n\n    /**\n     * Creates a new CookiesMap instance using the specified properties.\n     * @param [properties] Properties to set\n     * @returns CookiesMap instance\n     */\n    public static create(properties?: ICookiesMap): CookiesMap;\n\n    /**\n     * Encodes the specified CookiesMap message. Does not implicitly {@link CookiesMap.verify|verify} messages.\n     * @param message CookiesMap message or plain object to encode\n     * @param [writer] Writer to encode to\n     * @returns Writer\n     */\n    public static encode(message: ICookiesMap, writer?: $protobuf.Writer): $protobuf.Writer;\n\n    /**\n     * Encodes the specified CookiesMap message, length delimited. Does not implicitly {@link CookiesMap.verify|verify} messages.\n     * @param message CookiesMap message or plain object to encode\n     * @param [writer] Writer to encode to\n     * @returns Writer\n     */\n    public static encodeDelimited(message: ICookiesMap, writer?: $protobuf.Writer): $protobuf.Writer;\n\n    /**\n     * Decodes a CookiesMap message from the specified reader or buffer.\n     * @param reader Reader or buffer to decode from\n     * @param [length] Message length if known beforehand\n     * @returns CookiesMap\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): CookiesMap;\n\n    /**\n     * Decodes a CookiesMap message from the specified reader or buffer, length delimited.\n     * @param reader Reader or buffer to decode from\n     * @returns CookiesMap\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): CookiesMap;\n\n    /**\n     * Verifies a CookiesMap message.\n     * @param message Plain object to verify\n     * @returns `null` if valid, otherwise the reason why it is not\n     */\n    public static verify(message: { [k: string]: any }): (string|null);\n\n    /**\n     * Creates a CookiesMap message from a plain object. Also converts values to their respective internal types.\n     * @param object Plain object\n     * @returns CookiesMap\n     */\n    public static fromObject(object: { [k: string]: any }): CookiesMap;\n\n    /**\n     * Creates a plain object from a CookiesMap message. Also converts values to other types if specified.\n     * @param message CookiesMap\n     * @param [options] Conversion options\n     * @returns Plain object\n     */\n    public static toObject(message: CookiesMap, options?: $protobuf.IConversionOptions): { [k: string]: any };\n\n    /**\n     * Converts this CookiesMap to JSON.\n     * @returns JSON object\n     */\n    public toJSON(): { [k: string]: any };\n\n    /**\n     * Gets the default type url for CookiesMap\n     * @param [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n     * @returns The default type url\n     */\n    public static getTypeUrl(typeUrlPrefix?: string): string;\n}\n"
  },
  {
    "path": "packages/protobuf/lib/protobuf/proto/cookie.js",
    "content": "/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/\nimport * as $protobuf from \"protobufjs/minimal\";\n\n// Common aliases\nconst $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;\n\n// Exported root namespace\nconst $root = $protobuf.roots[\"default\"] || ($protobuf.roots[\"default\"] = {});\n\nexport const Cookie = $root.Cookie = (() => {\n\n    /**\n     * Properties of a Cookie.\n     * @exports ICookie\n     * @interface ICookie\n     * @property {string|null} [domain] Cookie domain\n     * @property {string|null} [name] Cookie name\n     * @property {string|null} [storeId] Cookie storeId\n     * @property {string|null} [value] Cookie value\n     * @property {boolean|null} [session] Cookie session\n     * @property {boolean|null} [hostOnly] Cookie hostOnly\n     * @property {number|null} [expirationDate] Cookie expirationDate\n     * @property {string|null} [path] Cookie path\n     * @property {boolean|null} [httpOnly] Cookie httpOnly\n     * @property {boolean|null} [secure] Cookie secure\n     * @property {string|null} [sameSite] Cookie sameSite\n     */\n\n    /**\n     * Constructs a new Cookie.\n     * @exports Cookie\n     * @classdesc Represents a Cookie.\n     * @implements ICookie\n     * @constructor\n     * @param {ICookie=} [properties] Properties to set\n     */\n    function Cookie(properties) {\n        if (properties)\n            for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)\n                if (properties[keys[i]] != null)\n                    this[keys[i]] = properties[keys[i]];\n    }\n\n    /**\n     * Cookie domain.\n     * @member {string} domain\n     * @memberof Cookie\n     * @instance\n     */\n    Cookie.prototype.domain = \"\";\n\n    /**\n     * Cookie name.\n     * @member {string} name\n     * @memberof Cookie\n     * @instance\n     */\n    Cookie.prototype.name = \"\";\n\n    /**\n     * Cookie storeId.\n     * @member {string} storeId\n     * @memberof Cookie\n     * @instance\n     */\n    Cookie.prototype.storeId = \"\";\n\n    /**\n     * Cookie value.\n     * @member {string} value\n     * @memberof Cookie\n     * @instance\n     */\n    Cookie.prototype.value = \"\";\n\n    /**\n     * Cookie session.\n     * @member {boolean} session\n     * @memberof Cookie\n     * @instance\n     */\n    Cookie.prototype.session = false;\n\n    /**\n     * Cookie hostOnly.\n     * @member {boolean} hostOnly\n     * @memberof Cookie\n     * @instance\n     */\n    Cookie.prototype.hostOnly = false;\n\n    /**\n     * Cookie expirationDate.\n     * @member {number} expirationDate\n     * @memberof Cookie\n     * @instance\n     */\n    Cookie.prototype.expirationDate = 0;\n\n    /**\n     * Cookie path.\n     * @member {string} path\n     * @memberof Cookie\n     * @instance\n     */\n    Cookie.prototype.path = \"\";\n\n    /**\n     * Cookie httpOnly.\n     * @member {boolean} httpOnly\n     * @memberof Cookie\n     * @instance\n     */\n    Cookie.prototype.httpOnly = false;\n\n    /**\n     * Cookie secure.\n     * @member {boolean} secure\n     * @memberof Cookie\n     * @instance\n     */\n    Cookie.prototype.secure = false;\n\n    /**\n     * Cookie sameSite.\n     * @member {string} sameSite\n     * @memberof Cookie\n     * @instance\n     */\n    Cookie.prototype.sameSite = \"\";\n\n    /**\n     * Creates a new Cookie instance using the specified properties.\n     * @function create\n     * @memberof Cookie\n     * @static\n     * @param {ICookie=} [properties] Properties to set\n     * @returns {Cookie} Cookie instance\n     */\n    Cookie.create = function create(properties) {\n        return new Cookie(properties);\n    };\n\n    /**\n     * Encodes the specified Cookie message. Does not implicitly {@link Cookie.verify|verify} messages.\n     * @function encode\n     * @memberof Cookie\n     * @static\n     * @param {ICookie} message Cookie message or plain object to encode\n     * @param {$protobuf.Writer} [writer] Writer to encode to\n     * @returns {$protobuf.Writer} Writer\n     */\n    Cookie.encode = function encode(message, writer) {\n        if (!writer)\n            writer = $Writer.create();\n        if (message.domain != null && Object.hasOwnProperty.call(message, \"domain\"))\n            writer.uint32(/* id 1, wireType 2 =*/10).string(message.domain);\n        if (message.name != null && Object.hasOwnProperty.call(message, \"name\"))\n            writer.uint32(/* id 2, wireType 2 =*/18).string(message.name);\n        if (message.storeId != null && Object.hasOwnProperty.call(message, \"storeId\"))\n            writer.uint32(/* id 3, wireType 2 =*/26).string(message.storeId);\n        if (message.value != null && Object.hasOwnProperty.call(message, \"value\"))\n            writer.uint32(/* id 4, wireType 2 =*/34).string(message.value);\n        if (message.session != null && Object.hasOwnProperty.call(message, \"session\"))\n            writer.uint32(/* id 5, wireType 0 =*/40).bool(message.session);\n        if (message.hostOnly != null && Object.hasOwnProperty.call(message, \"hostOnly\"))\n            writer.uint32(/* id 6, wireType 0 =*/48).bool(message.hostOnly);\n        if (message.expirationDate != null && Object.hasOwnProperty.call(message, \"expirationDate\"))\n            writer.uint32(/* id 7, wireType 5 =*/61).float(message.expirationDate);\n        if (message.path != null && Object.hasOwnProperty.call(message, \"path\"))\n            writer.uint32(/* id 8, wireType 2 =*/66).string(message.path);\n        if (message.httpOnly != null && Object.hasOwnProperty.call(message, \"httpOnly\"))\n            writer.uint32(/* id 9, wireType 0 =*/72).bool(message.httpOnly);\n        if (message.secure != null && Object.hasOwnProperty.call(message, \"secure\"))\n            writer.uint32(/* id 10, wireType 0 =*/80).bool(message.secure);\n        if (message.sameSite != null && Object.hasOwnProperty.call(message, \"sameSite\"))\n            writer.uint32(/* id 11, wireType 2 =*/90).string(message.sameSite);\n        return writer;\n    };\n\n    /**\n     * Encodes the specified Cookie message, length delimited. Does not implicitly {@link Cookie.verify|verify} messages.\n     * @function encodeDelimited\n     * @memberof Cookie\n     * @static\n     * @param {ICookie} message Cookie message or plain object to encode\n     * @param {$protobuf.Writer} [writer] Writer to encode to\n     * @returns {$protobuf.Writer} Writer\n     */\n    Cookie.encodeDelimited = function encodeDelimited(message, writer) {\n        return this.encode(message, writer).ldelim();\n    };\n\n    /**\n     * Decodes a Cookie message from the specified reader or buffer.\n     * @function decode\n     * @memberof Cookie\n     * @static\n     * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n     * @param {number} [length] Message length if known beforehand\n     * @returns {Cookie} Cookie\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    Cookie.decode = function decode(reader, length) {\n        if (!(reader instanceof $Reader))\n            reader = $Reader.create(reader);\n        let end = length === undefined ? reader.len : reader.pos + length, message = new $root.Cookie();\n        while (reader.pos < end) {\n            let tag = reader.uint32();\n            switch (tag >>> 3) {\n            case 1: {\n                    message.domain = reader.string();\n                    break;\n                }\n            case 2: {\n                    message.name = reader.string();\n                    break;\n                }\n            case 3: {\n                    message.storeId = reader.string();\n                    break;\n                }\n            case 4: {\n                    message.value = reader.string();\n                    break;\n                }\n            case 5: {\n                    message.session = reader.bool();\n                    break;\n                }\n            case 6: {\n                    message.hostOnly = reader.bool();\n                    break;\n                }\n            case 7: {\n                    message.expirationDate = reader.float();\n                    break;\n                }\n            case 8: {\n                    message.path = reader.string();\n                    break;\n                }\n            case 9: {\n                    message.httpOnly = reader.bool();\n                    break;\n                }\n            case 10: {\n                    message.secure = reader.bool();\n                    break;\n                }\n            case 11: {\n                    message.sameSite = reader.string();\n                    break;\n                }\n            default:\n                reader.skipType(tag & 7);\n                break;\n            }\n        }\n        return message;\n    };\n\n    /**\n     * Decodes a Cookie message from the specified reader or buffer, length delimited.\n     * @function decodeDelimited\n     * @memberof Cookie\n     * @static\n     * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n     * @returns {Cookie} Cookie\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    Cookie.decodeDelimited = function decodeDelimited(reader) {\n        if (!(reader instanceof $Reader))\n            reader = new $Reader(reader);\n        return this.decode(reader, reader.uint32());\n    };\n\n    /**\n     * Verifies a Cookie message.\n     * @function verify\n     * @memberof Cookie\n     * @static\n     * @param {Object.<string,*>} message Plain object to verify\n     * @returns {string|null} `null` if valid, otherwise the reason why it is not\n     */\n    Cookie.verify = function verify(message) {\n        if (typeof message !== \"object\" || message === null)\n            return \"object expected\";\n        if (message.domain != null && message.hasOwnProperty(\"domain\"))\n            if (!$util.isString(message.domain))\n                return \"domain: string expected\";\n        if (message.name != null && message.hasOwnProperty(\"name\"))\n            if (!$util.isString(message.name))\n                return \"name: string expected\";\n        if (message.storeId != null && message.hasOwnProperty(\"storeId\"))\n            if (!$util.isString(message.storeId))\n                return \"storeId: string expected\";\n        if (message.value != null && message.hasOwnProperty(\"value\"))\n            if (!$util.isString(message.value))\n                return \"value: string expected\";\n        if (message.session != null && message.hasOwnProperty(\"session\"))\n            if (typeof message.session !== \"boolean\")\n                return \"session: boolean expected\";\n        if (message.hostOnly != null && message.hasOwnProperty(\"hostOnly\"))\n            if (typeof message.hostOnly !== \"boolean\")\n                return \"hostOnly: boolean expected\";\n        if (message.expirationDate != null && message.hasOwnProperty(\"expirationDate\"))\n            if (typeof message.expirationDate !== \"number\")\n                return \"expirationDate: number expected\";\n        if (message.path != null && message.hasOwnProperty(\"path\"))\n            if (!$util.isString(message.path))\n                return \"path: string expected\";\n        if (message.httpOnly != null && message.hasOwnProperty(\"httpOnly\"))\n            if (typeof message.httpOnly !== \"boolean\")\n                return \"httpOnly: boolean expected\";\n        if (message.secure != null && message.hasOwnProperty(\"secure\"))\n            if (typeof message.secure !== \"boolean\")\n                return \"secure: boolean expected\";\n        if (message.sameSite != null && message.hasOwnProperty(\"sameSite\"))\n            if (!$util.isString(message.sameSite))\n                return \"sameSite: string expected\";\n        return null;\n    };\n\n    /**\n     * Creates a Cookie message from a plain object. Also converts values to their respective internal types.\n     * @function fromObject\n     * @memberof Cookie\n     * @static\n     * @param {Object.<string,*>} object Plain object\n     * @returns {Cookie} Cookie\n     */\n    Cookie.fromObject = function fromObject(object) {\n        if (object instanceof $root.Cookie)\n            return object;\n        let message = new $root.Cookie();\n        if (object.domain != null)\n            message.domain = String(object.domain);\n        if (object.name != null)\n            message.name = String(object.name);\n        if (object.storeId != null)\n            message.storeId = String(object.storeId);\n        if (object.value != null)\n            message.value = String(object.value);\n        if (object.session != null)\n            message.session = Boolean(object.session);\n        if (object.hostOnly != null)\n            message.hostOnly = Boolean(object.hostOnly);\n        if (object.expirationDate != null)\n            message.expirationDate = Number(object.expirationDate);\n        if (object.path != null)\n            message.path = String(object.path);\n        if (object.httpOnly != null)\n            message.httpOnly = Boolean(object.httpOnly);\n        if (object.secure != null)\n            message.secure = Boolean(object.secure);\n        if (object.sameSite != null)\n            message.sameSite = String(object.sameSite);\n        return message;\n    };\n\n    /**\n     * Creates a plain object from a Cookie message. Also converts values to other types if specified.\n     * @function toObject\n     * @memberof Cookie\n     * @static\n     * @param {Cookie} message Cookie\n     * @param {$protobuf.IConversionOptions} [options] Conversion options\n     * @returns {Object.<string,*>} Plain object\n     */\n    Cookie.toObject = function toObject(message, options) {\n        if (!options)\n            options = {};\n        let object = {};\n        if (options.defaults) {\n            object.domain = \"\";\n            object.name = \"\";\n            object.storeId = \"\";\n            object.value = \"\";\n            object.session = false;\n            object.hostOnly = false;\n            object.expirationDate = 0;\n            object.path = \"\";\n            object.httpOnly = false;\n            object.secure = false;\n            object.sameSite = \"\";\n        }\n        if (message.domain != null && message.hasOwnProperty(\"domain\"))\n            object.domain = message.domain;\n        if (message.name != null && message.hasOwnProperty(\"name\"))\n            object.name = message.name;\n        if (message.storeId != null && message.hasOwnProperty(\"storeId\"))\n            object.storeId = message.storeId;\n        if (message.value != null && message.hasOwnProperty(\"value\"))\n            object.value = message.value;\n        if (message.session != null && message.hasOwnProperty(\"session\"))\n            object.session = message.session;\n        if (message.hostOnly != null && message.hasOwnProperty(\"hostOnly\"))\n            object.hostOnly = message.hostOnly;\n        if (message.expirationDate != null && message.hasOwnProperty(\"expirationDate\"))\n            object.expirationDate = options.json && !isFinite(message.expirationDate) ? String(message.expirationDate) : message.expirationDate;\n        if (message.path != null && message.hasOwnProperty(\"path\"))\n            object.path = message.path;\n        if (message.httpOnly != null && message.hasOwnProperty(\"httpOnly\"))\n            object.httpOnly = message.httpOnly;\n        if (message.secure != null && message.hasOwnProperty(\"secure\"))\n            object.secure = message.secure;\n        if (message.sameSite != null && message.hasOwnProperty(\"sameSite\"))\n            object.sameSite = message.sameSite;\n        return object;\n    };\n\n    /**\n     * Converts this Cookie to JSON.\n     * @function toJSON\n     * @memberof Cookie\n     * @instance\n     * @returns {Object.<string,*>} JSON object\n     */\n    Cookie.prototype.toJSON = function toJSON() {\n        return this.constructor.toObject(this, $protobuf.util.toJSONOptions);\n    };\n\n    /**\n     * Gets the default type url for Cookie\n     * @function getTypeUrl\n     * @memberof Cookie\n     * @static\n     * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n     * @returns {string} The default type url\n     */\n    Cookie.getTypeUrl = function getTypeUrl(typeUrlPrefix) {\n        if (typeUrlPrefix === undefined) {\n            typeUrlPrefix = \"type.googleapis.com\";\n        }\n        return typeUrlPrefix + \"/Cookie\";\n    };\n\n    return Cookie;\n})();\n\nexport const LocalStorageItem = $root.LocalStorageItem = (() => {\n\n    /**\n     * Properties of a LocalStorageItem.\n     * @exports ILocalStorageItem\n     * @interface ILocalStorageItem\n     * @property {string|null} [key] LocalStorageItem key\n     * @property {string|null} [value] LocalStorageItem value\n     */\n\n    /**\n     * Constructs a new LocalStorageItem.\n     * @exports LocalStorageItem\n     * @classdesc Represents a LocalStorageItem.\n     * @implements ILocalStorageItem\n     * @constructor\n     * @param {ILocalStorageItem=} [properties] Properties to set\n     */\n    function LocalStorageItem(properties) {\n        if (properties)\n            for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)\n                if (properties[keys[i]] != null)\n                    this[keys[i]] = properties[keys[i]];\n    }\n\n    /**\n     * LocalStorageItem key.\n     * @member {string} key\n     * @memberof LocalStorageItem\n     * @instance\n     */\n    LocalStorageItem.prototype.key = \"\";\n\n    /**\n     * LocalStorageItem value.\n     * @member {string} value\n     * @memberof LocalStorageItem\n     * @instance\n     */\n    LocalStorageItem.prototype.value = \"\";\n\n    /**\n     * Creates a new LocalStorageItem instance using the specified properties.\n     * @function create\n     * @memberof LocalStorageItem\n     * @static\n     * @param {ILocalStorageItem=} [properties] Properties to set\n     * @returns {LocalStorageItem} LocalStorageItem instance\n     */\n    LocalStorageItem.create = function create(properties) {\n        return new LocalStorageItem(properties);\n    };\n\n    /**\n     * Encodes the specified LocalStorageItem message. Does not implicitly {@link LocalStorageItem.verify|verify} messages.\n     * @function encode\n     * @memberof LocalStorageItem\n     * @static\n     * @param {ILocalStorageItem} message LocalStorageItem message or plain object to encode\n     * @param {$protobuf.Writer} [writer] Writer to encode to\n     * @returns {$protobuf.Writer} Writer\n     */\n    LocalStorageItem.encode = function encode(message, writer) {\n        if (!writer)\n            writer = $Writer.create();\n        if (message.key != null && Object.hasOwnProperty.call(message, \"key\"))\n            writer.uint32(/* id 1, wireType 2 =*/10).string(message.key);\n        if (message.value != null && Object.hasOwnProperty.call(message, \"value\"))\n            writer.uint32(/* id 2, wireType 2 =*/18).string(message.value);\n        return writer;\n    };\n\n    /**\n     * Encodes the specified LocalStorageItem message, length delimited. Does not implicitly {@link LocalStorageItem.verify|verify} messages.\n     * @function encodeDelimited\n     * @memberof LocalStorageItem\n     * @static\n     * @param {ILocalStorageItem} message LocalStorageItem message or plain object to encode\n     * @param {$protobuf.Writer} [writer] Writer to encode to\n     * @returns {$protobuf.Writer} Writer\n     */\n    LocalStorageItem.encodeDelimited = function encodeDelimited(message, writer) {\n        return this.encode(message, writer).ldelim();\n    };\n\n    /**\n     * Decodes a LocalStorageItem message from the specified reader or buffer.\n     * @function decode\n     * @memberof LocalStorageItem\n     * @static\n     * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n     * @param {number} [length] Message length if known beforehand\n     * @returns {LocalStorageItem} LocalStorageItem\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    LocalStorageItem.decode = function decode(reader, length) {\n        if (!(reader instanceof $Reader))\n            reader = $Reader.create(reader);\n        let end = length === undefined ? reader.len : reader.pos + length, message = new $root.LocalStorageItem();\n        while (reader.pos < end) {\n            let tag = reader.uint32();\n            switch (tag >>> 3) {\n            case 1: {\n                    message.key = reader.string();\n                    break;\n                }\n            case 2: {\n                    message.value = reader.string();\n                    break;\n                }\n            default:\n                reader.skipType(tag & 7);\n                break;\n            }\n        }\n        return message;\n    };\n\n    /**\n     * Decodes a LocalStorageItem message from the specified reader or buffer, length delimited.\n     * @function decodeDelimited\n     * @memberof LocalStorageItem\n     * @static\n     * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n     * @returns {LocalStorageItem} LocalStorageItem\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    LocalStorageItem.decodeDelimited = function decodeDelimited(reader) {\n        if (!(reader instanceof $Reader))\n            reader = new $Reader(reader);\n        return this.decode(reader, reader.uint32());\n    };\n\n    /**\n     * Verifies a LocalStorageItem message.\n     * @function verify\n     * @memberof LocalStorageItem\n     * @static\n     * @param {Object.<string,*>} message Plain object to verify\n     * @returns {string|null} `null` if valid, otherwise the reason why it is not\n     */\n    LocalStorageItem.verify = function verify(message) {\n        if (typeof message !== \"object\" || message === null)\n            return \"object expected\";\n        if (message.key != null && message.hasOwnProperty(\"key\"))\n            if (!$util.isString(message.key))\n                return \"key: string expected\";\n        if (message.value != null && message.hasOwnProperty(\"value\"))\n            if (!$util.isString(message.value))\n                return \"value: string expected\";\n        return null;\n    };\n\n    /**\n     * Creates a LocalStorageItem message from a plain object. Also converts values to their respective internal types.\n     * @function fromObject\n     * @memberof LocalStorageItem\n     * @static\n     * @param {Object.<string,*>} object Plain object\n     * @returns {LocalStorageItem} LocalStorageItem\n     */\n    LocalStorageItem.fromObject = function fromObject(object) {\n        if (object instanceof $root.LocalStorageItem)\n            return object;\n        let message = new $root.LocalStorageItem();\n        if (object.key != null)\n            message.key = String(object.key);\n        if (object.value != null)\n            message.value = String(object.value);\n        return message;\n    };\n\n    /**\n     * Creates a plain object from a LocalStorageItem message. Also converts values to other types if specified.\n     * @function toObject\n     * @memberof LocalStorageItem\n     * @static\n     * @param {LocalStorageItem} message LocalStorageItem\n     * @param {$protobuf.IConversionOptions} [options] Conversion options\n     * @returns {Object.<string,*>} Plain object\n     */\n    LocalStorageItem.toObject = function toObject(message, options) {\n        if (!options)\n            options = {};\n        let object = {};\n        if (options.defaults) {\n            object.key = \"\";\n            object.value = \"\";\n        }\n        if (message.key != null && message.hasOwnProperty(\"key\"))\n            object.key = message.key;\n        if (message.value != null && message.hasOwnProperty(\"value\"))\n            object.value = message.value;\n        return object;\n    };\n\n    /**\n     * Converts this LocalStorageItem to JSON.\n     * @function toJSON\n     * @memberof LocalStorageItem\n     * @instance\n     * @returns {Object.<string,*>} JSON object\n     */\n    LocalStorageItem.prototype.toJSON = function toJSON() {\n        return this.constructor.toObject(this, $protobuf.util.toJSONOptions);\n    };\n\n    /**\n     * Gets the default type url for LocalStorageItem\n     * @function getTypeUrl\n     * @memberof LocalStorageItem\n     * @static\n     * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n     * @returns {string} The default type url\n     */\n    LocalStorageItem.getTypeUrl = function getTypeUrl(typeUrlPrefix) {\n        if (typeUrlPrefix === undefined) {\n            typeUrlPrefix = \"type.googleapis.com\";\n        }\n        return typeUrlPrefix + \"/LocalStorageItem\";\n    };\n\n    return LocalStorageItem;\n})();\n\nexport const DomainCookie = $root.DomainCookie = (() => {\n\n    /**\n     * Properties of a DomainCookie.\n     * @exports IDomainCookie\n     * @interface IDomainCookie\n     * @property {number|Long|null} [createTime] DomainCookie createTime\n     * @property {number|Long|null} [updateTime] DomainCookie updateTime\n     * @property {Array.<ICookie>|null} [cookies] DomainCookie cookies\n     * @property {Array.<ILocalStorageItem>|null} [localStorageItems] DomainCookie localStorageItems\n     * @property {string|null} [userAgent] DomainCookie userAgent\n     */\n\n    /**\n     * Constructs a new DomainCookie.\n     * @exports DomainCookie\n     * @classdesc Represents a DomainCookie.\n     * @implements IDomainCookie\n     * @constructor\n     * @param {IDomainCookie=} [properties] Properties to set\n     */\n    function DomainCookie(properties) {\n        this.cookies = [];\n        this.localStorageItems = [];\n        if (properties)\n            for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)\n                if (properties[keys[i]] != null)\n                    this[keys[i]] = properties[keys[i]];\n    }\n\n    /**\n     * DomainCookie createTime.\n     * @member {number|Long} createTime\n     * @memberof DomainCookie\n     * @instance\n     */\n    DomainCookie.prototype.createTime = $util.Long ? $util.Long.fromBits(0,0,false) : 0;\n\n    /**\n     * DomainCookie updateTime.\n     * @member {number|Long} updateTime\n     * @memberof DomainCookie\n     * @instance\n     */\n    DomainCookie.prototype.updateTime = $util.Long ? $util.Long.fromBits(0,0,false) : 0;\n\n    /**\n     * DomainCookie cookies.\n     * @member {Array.<ICookie>} cookies\n     * @memberof DomainCookie\n     * @instance\n     */\n    DomainCookie.prototype.cookies = $util.emptyArray;\n\n    /**\n     * DomainCookie localStorageItems.\n     * @member {Array.<ILocalStorageItem>} localStorageItems\n     * @memberof DomainCookie\n     * @instance\n     */\n    DomainCookie.prototype.localStorageItems = $util.emptyArray;\n\n    /**\n     * DomainCookie userAgent.\n     * @member {string} userAgent\n     * @memberof DomainCookie\n     * @instance\n     */\n    DomainCookie.prototype.userAgent = \"\";\n\n    /**\n     * Creates a new DomainCookie instance using the specified properties.\n     * @function create\n     * @memberof DomainCookie\n     * @static\n     * @param {IDomainCookie=} [properties] Properties to set\n     * @returns {DomainCookie} DomainCookie instance\n     */\n    DomainCookie.create = function create(properties) {\n        return new DomainCookie(properties);\n    };\n\n    /**\n     * Encodes the specified DomainCookie message. Does not implicitly {@link DomainCookie.verify|verify} messages.\n     * @function encode\n     * @memberof DomainCookie\n     * @static\n     * @param {IDomainCookie} message DomainCookie message or plain object to encode\n     * @param {$protobuf.Writer} [writer] Writer to encode to\n     * @returns {$protobuf.Writer} Writer\n     */\n    DomainCookie.encode = function encode(message, writer) {\n        if (!writer)\n            writer = $Writer.create();\n        if (message.createTime != null && Object.hasOwnProperty.call(message, \"createTime\"))\n            writer.uint32(/* id 1, wireType 0 =*/8).int64(message.createTime);\n        if (message.updateTime != null && Object.hasOwnProperty.call(message, \"updateTime\"))\n            writer.uint32(/* id 2, wireType 0 =*/16).int64(message.updateTime);\n        if (message.cookies != null && message.cookies.length)\n            for (let i = 0; i < message.cookies.length; ++i)\n                $root.Cookie.encode(message.cookies[i], writer.uint32(/* id 5, wireType 2 =*/42).fork()).ldelim();\n        if (message.localStorageItems != null && message.localStorageItems.length)\n            for (let i = 0; i < message.localStorageItems.length; ++i)\n                $root.LocalStorageItem.encode(message.localStorageItems[i], writer.uint32(/* id 6, wireType 2 =*/50).fork()).ldelim();\n        if (message.userAgent != null && Object.hasOwnProperty.call(message, \"userAgent\"))\n            writer.uint32(/* id 7, wireType 2 =*/58).string(message.userAgent);\n        return writer;\n    };\n\n    /**\n     * Encodes the specified DomainCookie message, length delimited. Does not implicitly {@link DomainCookie.verify|verify} messages.\n     * @function encodeDelimited\n     * @memberof DomainCookie\n     * @static\n     * @param {IDomainCookie} message DomainCookie message or plain object to encode\n     * @param {$protobuf.Writer} [writer] Writer to encode to\n     * @returns {$protobuf.Writer} Writer\n     */\n    DomainCookie.encodeDelimited = function encodeDelimited(message, writer) {\n        return this.encode(message, writer).ldelim();\n    };\n\n    /**\n     * Decodes a DomainCookie message from the specified reader or buffer.\n     * @function decode\n     * @memberof DomainCookie\n     * @static\n     * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n     * @param {number} [length] Message length if known beforehand\n     * @returns {DomainCookie} DomainCookie\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    DomainCookie.decode = function decode(reader, length) {\n        if (!(reader instanceof $Reader))\n            reader = $Reader.create(reader);\n        let end = length === undefined ? reader.len : reader.pos + length, message = new $root.DomainCookie();\n        while (reader.pos < end) {\n            let tag = reader.uint32();\n            switch (tag >>> 3) {\n            case 1: {\n                    message.createTime = reader.int64();\n                    break;\n                }\n            case 2: {\n                    message.updateTime = reader.int64();\n                    break;\n                }\n            case 5: {\n                    if (!(message.cookies && message.cookies.length))\n                        message.cookies = [];\n                    message.cookies.push($root.Cookie.decode(reader, reader.uint32()));\n                    break;\n                }\n            case 6: {\n                    if (!(message.localStorageItems && message.localStorageItems.length))\n                        message.localStorageItems = [];\n                    message.localStorageItems.push($root.LocalStorageItem.decode(reader, reader.uint32()));\n                    break;\n                }\n            case 7: {\n                    message.userAgent = reader.string();\n                    break;\n                }\n            default:\n                reader.skipType(tag & 7);\n                break;\n            }\n        }\n        return message;\n    };\n\n    /**\n     * Decodes a DomainCookie message from the specified reader or buffer, length delimited.\n     * @function decodeDelimited\n     * @memberof DomainCookie\n     * @static\n     * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n     * @returns {DomainCookie} DomainCookie\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    DomainCookie.decodeDelimited = function decodeDelimited(reader) {\n        if (!(reader instanceof $Reader))\n            reader = new $Reader(reader);\n        return this.decode(reader, reader.uint32());\n    };\n\n    /**\n     * Verifies a DomainCookie message.\n     * @function verify\n     * @memberof DomainCookie\n     * @static\n     * @param {Object.<string,*>} message Plain object to verify\n     * @returns {string|null} `null` if valid, otherwise the reason why it is not\n     */\n    DomainCookie.verify = function verify(message) {\n        if (typeof message !== \"object\" || message === null)\n            return \"object expected\";\n        if (message.createTime != null && message.hasOwnProperty(\"createTime\"))\n            if (!$util.isInteger(message.createTime) && !(message.createTime && $util.isInteger(message.createTime.low) && $util.isInteger(message.createTime.high)))\n                return \"createTime: integer|Long expected\";\n        if (message.updateTime != null && message.hasOwnProperty(\"updateTime\"))\n            if (!$util.isInteger(message.updateTime) && !(message.updateTime && $util.isInteger(message.updateTime.low) && $util.isInteger(message.updateTime.high)))\n                return \"updateTime: integer|Long expected\";\n        if (message.cookies != null && message.hasOwnProperty(\"cookies\")) {\n            if (!Array.isArray(message.cookies))\n                return \"cookies: array expected\";\n            for (let i = 0; i < message.cookies.length; ++i) {\n                let error = $root.Cookie.verify(message.cookies[i]);\n                if (error)\n                    return \"cookies.\" + error;\n            }\n        }\n        if (message.localStorageItems != null && message.hasOwnProperty(\"localStorageItems\")) {\n            if (!Array.isArray(message.localStorageItems))\n                return \"localStorageItems: array expected\";\n            for (let i = 0; i < message.localStorageItems.length; ++i) {\n                let error = $root.LocalStorageItem.verify(message.localStorageItems[i]);\n                if (error)\n                    return \"localStorageItems.\" + error;\n            }\n        }\n        if (message.userAgent != null && message.hasOwnProperty(\"userAgent\"))\n            if (!$util.isString(message.userAgent))\n                return \"userAgent: string expected\";\n        return null;\n    };\n\n    /**\n     * Creates a DomainCookie message from a plain object. Also converts values to their respective internal types.\n     * @function fromObject\n     * @memberof DomainCookie\n     * @static\n     * @param {Object.<string,*>} object Plain object\n     * @returns {DomainCookie} DomainCookie\n     */\n    DomainCookie.fromObject = function fromObject(object) {\n        if (object instanceof $root.DomainCookie)\n            return object;\n        let message = new $root.DomainCookie();\n        if (object.createTime != null)\n            if ($util.Long)\n                (message.createTime = $util.Long.fromValue(object.createTime)).unsigned = false;\n            else if (typeof object.createTime === \"string\")\n                message.createTime = parseInt(object.createTime, 10);\n            else if (typeof object.createTime === \"number\")\n                message.createTime = object.createTime;\n            else if (typeof object.createTime === \"object\")\n                message.createTime = new $util.LongBits(object.createTime.low >>> 0, object.createTime.high >>> 0).toNumber();\n        if (object.updateTime != null)\n            if ($util.Long)\n                (message.updateTime = $util.Long.fromValue(object.updateTime)).unsigned = false;\n            else if (typeof object.updateTime === \"string\")\n                message.updateTime = parseInt(object.updateTime, 10);\n            else if (typeof object.updateTime === \"number\")\n                message.updateTime = object.updateTime;\n            else if (typeof object.updateTime === \"object\")\n                message.updateTime = new $util.LongBits(object.updateTime.low >>> 0, object.updateTime.high >>> 0).toNumber();\n        if (object.cookies) {\n            if (!Array.isArray(object.cookies))\n                throw TypeError(\".DomainCookie.cookies: array expected\");\n            message.cookies = [];\n            for (let i = 0; i < object.cookies.length; ++i) {\n                if (typeof object.cookies[i] !== \"object\")\n                    throw TypeError(\".DomainCookie.cookies: object expected\");\n                message.cookies[i] = $root.Cookie.fromObject(object.cookies[i]);\n            }\n        }\n        if (object.localStorageItems) {\n            if (!Array.isArray(object.localStorageItems))\n                throw TypeError(\".DomainCookie.localStorageItems: array expected\");\n            message.localStorageItems = [];\n            for (let i = 0; i < object.localStorageItems.length; ++i) {\n                if (typeof object.localStorageItems[i] !== \"object\")\n                    throw TypeError(\".DomainCookie.localStorageItems: object expected\");\n                message.localStorageItems[i] = $root.LocalStorageItem.fromObject(object.localStorageItems[i]);\n            }\n        }\n        if (object.userAgent != null)\n            message.userAgent = String(object.userAgent);\n        return message;\n    };\n\n    /**\n     * Creates a plain object from a DomainCookie message. Also converts values to other types if specified.\n     * @function toObject\n     * @memberof DomainCookie\n     * @static\n     * @param {DomainCookie} message DomainCookie\n     * @param {$protobuf.IConversionOptions} [options] Conversion options\n     * @returns {Object.<string,*>} Plain object\n     */\n    DomainCookie.toObject = function toObject(message, options) {\n        if (!options)\n            options = {};\n        let object = {};\n        if (options.arrays || options.defaults) {\n            object.cookies = [];\n            object.localStorageItems = [];\n        }\n        if (options.defaults) {\n            if ($util.Long) {\n                let long = new $util.Long(0, 0, false);\n                object.createTime = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;\n            } else\n                object.createTime = options.longs === String ? \"0\" : 0;\n            if ($util.Long) {\n                let long = new $util.Long(0, 0, false);\n                object.updateTime = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;\n            } else\n                object.updateTime = options.longs === String ? \"0\" : 0;\n            object.userAgent = \"\";\n        }\n        if (message.createTime != null && message.hasOwnProperty(\"createTime\"))\n            if (typeof message.createTime === \"number\")\n                object.createTime = options.longs === String ? String(message.createTime) : message.createTime;\n            else\n                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;\n        if (message.updateTime != null && message.hasOwnProperty(\"updateTime\"))\n            if (typeof message.updateTime === \"number\")\n                object.updateTime = options.longs === String ? String(message.updateTime) : message.updateTime;\n            else\n                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;\n        if (message.cookies && message.cookies.length) {\n            object.cookies = [];\n            for (let j = 0; j < message.cookies.length; ++j)\n                object.cookies[j] = $root.Cookie.toObject(message.cookies[j], options);\n        }\n        if (message.localStorageItems && message.localStorageItems.length) {\n            object.localStorageItems = [];\n            for (let j = 0; j < message.localStorageItems.length; ++j)\n                object.localStorageItems[j] = $root.LocalStorageItem.toObject(message.localStorageItems[j], options);\n        }\n        if (message.userAgent != null && message.hasOwnProperty(\"userAgent\"))\n            object.userAgent = message.userAgent;\n        return object;\n    };\n\n    /**\n     * Converts this DomainCookie to JSON.\n     * @function toJSON\n     * @memberof DomainCookie\n     * @instance\n     * @returns {Object.<string,*>} JSON object\n     */\n    DomainCookie.prototype.toJSON = function toJSON() {\n        return this.constructor.toObject(this, $protobuf.util.toJSONOptions);\n    };\n\n    /**\n     * Gets the default type url for DomainCookie\n     * @function getTypeUrl\n     * @memberof DomainCookie\n     * @static\n     * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n     * @returns {string} The default type url\n     */\n    DomainCookie.getTypeUrl = function getTypeUrl(typeUrlPrefix) {\n        if (typeUrlPrefix === undefined) {\n            typeUrlPrefix = \"type.googleapis.com\";\n        }\n        return typeUrlPrefix + \"/DomainCookie\";\n    };\n\n    return DomainCookie;\n})();\n\nexport const CookiesMap = $root.CookiesMap = (() => {\n\n    /**\n     * Properties of a CookiesMap.\n     * @exports ICookiesMap\n     * @interface ICookiesMap\n     * @property {number|Long|null} [createTime] CookiesMap createTime\n     * @property {number|Long|null} [updateTime] CookiesMap updateTime\n     * @property {Object.<string,IDomainCookie>|null} [domainCookieMap] CookiesMap domainCookieMap\n     */\n\n    /**\n     * Constructs a new CookiesMap.\n     * @exports CookiesMap\n     * @classdesc Represents a CookiesMap.\n     * @implements ICookiesMap\n     * @constructor\n     * @param {ICookiesMap=} [properties] Properties to set\n     */\n    function CookiesMap(properties) {\n        this.domainCookieMap = {};\n        if (properties)\n            for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)\n                if (properties[keys[i]] != null)\n                    this[keys[i]] = properties[keys[i]];\n    }\n\n    /**\n     * CookiesMap createTime.\n     * @member {number|Long} createTime\n     * @memberof CookiesMap\n     * @instance\n     */\n    CookiesMap.prototype.createTime = $util.Long ? $util.Long.fromBits(0,0,false) : 0;\n\n    /**\n     * CookiesMap updateTime.\n     * @member {number|Long} updateTime\n     * @memberof CookiesMap\n     * @instance\n     */\n    CookiesMap.prototype.updateTime = $util.Long ? $util.Long.fromBits(0,0,false) : 0;\n\n    /**\n     * CookiesMap domainCookieMap.\n     * @member {Object.<string,IDomainCookie>} domainCookieMap\n     * @memberof CookiesMap\n     * @instance\n     */\n    CookiesMap.prototype.domainCookieMap = $util.emptyObject;\n\n    /**\n     * Creates a new CookiesMap instance using the specified properties.\n     * @function create\n     * @memberof CookiesMap\n     * @static\n     * @param {ICookiesMap=} [properties] Properties to set\n     * @returns {CookiesMap} CookiesMap instance\n     */\n    CookiesMap.create = function create(properties) {\n        return new CookiesMap(properties);\n    };\n\n    /**\n     * Encodes the specified CookiesMap message. Does not implicitly {@link CookiesMap.verify|verify} messages.\n     * @function encode\n     * @memberof CookiesMap\n     * @static\n     * @param {ICookiesMap} message CookiesMap message or plain object to encode\n     * @param {$protobuf.Writer} [writer] Writer to encode to\n     * @returns {$protobuf.Writer} Writer\n     */\n    CookiesMap.encode = function encode(message, writer) {\n        if (!writer)\n            writer = $Writer.create();\n        if (message.createTime != null && Object.hasOwnProperty.call(message, \"createTime\"))\n            writer.uint32(/* id 1, wireType 0 =*/8).int64(message.createTime);\n        if (message.updateTime != null && Object.hasOwnProperty.call(message, \"updateTime\"))\n            writer.uint32(/* id 2, wireType 0 =*/16).int64(message.updateTime);\n        if (message.domainCookieMap != null && Object.hasOwnProperty.call(message, \"domainCookieMap\"))\n            for (let keys = Object.keys(message.domainCookieMap), i = 0; i < keys.length; ++i) {\n                writer.uint32(/* id 5, wireType 2 =*/42).fork().uint32(/* id 1, wireType 2 =*/10).string(keys[i]);\n                $root.DomainCookie.encode(message.domainCookieMap[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim();\n            }\n        return writer;\n    };\n\n    /**\n     * Encodes the specified CookiesMap message, length delimited. Does not implicitly {@link CookiesMap.verify|verify} messages.\n     * @function encodeDelimited\n     * @memberof CookiesMap\n     * @static\n     * @param {ICookiesMap} message CookiesMap message or plain object to encode\n     * @param {$protobuf.Writer} [writer] Writer to encode to\n     * @returns {$protobuf.Writer} Writer\n     */\n    CookiesMap.encodeDelimited = function encodeDelimited(message, writer) {\n        return this.encode(message, writer).ldelim();\n    };\n\n    /**\n     * Decodes a CookiesMap message from the specified reader or buffer.\n     * @function decode\n     * @memberof CookiesMap\n     * @static\n     * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n     * @param {number} [length] Message length if known beforehand\n     * @returns {CookiesMap} CookiesMap\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    CookiesMap.decode = function decode(reader, length) {\n        if (!(reader instanceof $Reader))\n            reader = $Reader.create(reader);\n        let end = length === undefined ? reader.len : reader.pos + length, message = new $root.CookiesMap(), key, value;\n        while (reader.pos < end) {\n            let tag = reader.uint32();\n            switch (tag >>> 3) {\n            case 1: {\n                    message.createTime = reader.int64();\n                    break;\n                }\n            case 2: {\n                    message.updateTime = reader.int64();\n                    break;\n                }\n            case 5: {\n                    if (message.domainCookieMap === $util.emptyObject)\n                        message.domainCookieMap = {};\n                    let end2 = reader.uint32() + reader.pos;\n                    key = \"\";\n                    value = null;\n                    while (reader.pos < end2) {\n                        let tag2 = reader.uint32();\n                        switch (tag2 >>> 3) {\n                        case 1:\n                            key = reader.string();\n                            break;\n                        case 2:\n                            value = $root.DomainCookie.decode(reader, reader.uint32());\n                            break;\n                        default:\n                            reader.skipType(tag2 & 7);\n                            break;\n                        }\n                    }\n                    message.domainCookieMap[key] = value;\n                    break;\n                }\n            default:\n                reader.skipType(tag & 7);\n                break;\n            }\n        }\n        return message;\n    };\n\n    /**\n     * Decodes a CookiesMap message from the specified reader or buffer, length delimited.\n     * @function decodeDelimited\n     * @memberof CookiesMap\n     * @static\n     * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n     * @returns {CookiesMap} CookiesMap\n     * @throws {Error} If the payload is not a reader or valid buffer\n     * @throws {$protobuf.util.ProtocolError} If required fields are missing\n     */\n    CookiesMap.decodeDelimited = function decodeDelimited(reader) {\n        if (!(reader instanceof $Reader))\n            reader = new $Reader(reader);\n        return this.decode(reader, reader.uint32());\n    };\n\n    /**\n     * Verifies a CookiesMap message.\n     * @function verify\n     * @memberof CookiesMap\n     * @static\n     * @param {Object.<string,*>} message Plain object to verify\n     * @returns {string|null} `null` if valid, otherwise the reason why it is not\n     */\n    CookiesMap.verify = function verify(message) {\n        if (typeof message !== \"object\" || message === null)\n            return \"object expected\";\n        if (message.createTime != null && message.hasOwnProperty(\"createTime\"))\n            if (!$util.isInteger(message.createTime) && !(message.createTime && $util.isInteger(message.createTime.low) && $util.isInteger(message.createTime.high)))\n                return \"createTime: integer|Long expected\";\n        if (message.updateTime != null && message.hasOwnProperty(\"updateTime\"))\n            if (!$util.isInteger(message.updateTime) && !(message.updateTime && $util.isInteger(message.updateTime.low) && $util.isInteger(message.updateTime.high)))\n                return \"updateTime: integer|Long expected\";\n        if (message.domainCookieMap != null && message.hasOwnProperty(\"domainCookieMap\")) {\n            if (!$util.isObject(message.domainCookieMap))\n                return \"domainCookieMap: object expected\";\n            let key = Object.keys(message.domainCookieMap);\n            for (let i = 0; i < key.length; ++i) {\n                let error = $root.DomainCookie.verify(message.domainCookieMap[key[i]]);\n                if (error)\n                    return \"domainCookieMap.\" + error;\n            }\n        }\n        return null;\n    };\n\n    /**\n     * Creates a CookiesMap message from a plain object. Also converts values to their respective internal types.\n     * @function fromObject\n     * @memberof CookiesMap\n     * @static\n     * @param {Object.<string,*>} object Plain object\n     * @returns {CookiesMap} CookiesMap\n     */\n    CookiesMap.fromObject = function fromObject(object) {\n        if (object instanceof $root.CookiesMap)\n            return object;\n        let message = new $root.CookiesMap();\n        if (object.createTime != null)\n            if ($util.Long)\n                (message.createTime = $util.Long.fromValue(object.createTime)).unsigned = false;\n            else if (typeof object.createTime === \"string\")\n                message.createTime = parseInt(object.createTime, 10);\n            else if (typeof object.createTime === \"number\")\n                message.createTime = object.createTime;\n            else if (typeof object.createTime === \"object\")\n                message.createTime = new $util.LongBits(object.createTime.low >>> 0, object.createTime.high >>> 0).toNumber();\n        if (object.updateTime != null)\n            if ($util.Long)\n                (message.updateTime = $util.Long.fromValue(object.updateTime)).unsigned = false;\n            else if (typeof object.updateTime === \"string\")\n                message.updateTime = parseInt(object.updateTime, 10);\n            else if (typeof object.updateTime === \"number\")\n                message.updateTime = object.updateTime;\n            else if (typeof object.updateTime === \"object\")\n                message.updateTime = new $util.LongBits(object.updateTime.low >>> 0, object.updateTime.high >>> 0).toNumber();\n        if (object.domainCookieMap) {\n            if (typeof object.domainCookieMap !== \"object\")\n                throw TypeError(\".CookiesMap.domainCookieMap: object expected\");\n            message.domainCookieMap = {};\n            for (let keys = Object.keys(object.domainCookieMap), i = 0; i < keys.length; ++i) {\n                if (typeof object.domainCookieMap[keys[i]] !== \"object\")\n                    throw TypeError(\".CookiesMap.domainCookieMap: object expected\");\n                message.domainCookieMap[keys[i]] = $root.DomainCookie.fromObject(object.domainCookieMap[keys[i]]);\n            }\n        }\n        return message;\n    };\n\n    /**\n     * Creates a plain object from a CookiesMap message. Also converts values to other types if specified.\n     * @function toObject\n     * @memberof CookiesMap\n     * @static\n     * @param {CookiesMap} message CookiesMap\n     * @param {$protobuf.IConversionOptions} [options] Conversion options\n     * @returns {Object.<string,*>} Plain object\n     */\n    CookiesMap.toObject = function toObject(message, options) {\n        if (!options)\n            options = {};\n        let object = {};\n        if (options.objects || options.defaults)\n            object.domainCookieMap = {};\n        if (options.defaults) {\n            if ($util.Long) {\n                let long = new $util.Long(0, 0, false);\n                object.createTime = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;\n            } else\n                object.createTime = options.longs === String ? \"0\" : 0;\n            if ($util.Long) {\n                let long = new $util.Long(0, 0, false);\n                object.updateTime = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;\n            } else\n                object.updateTime = options.longs === String ? \"0\" : 0;\n        }\n        if (message.createTime != null && message.hasOwnProperty(\"createTime\"))\n            if (typeof message.createTime === \"number\")\n                object.createTime = options.longs === String ? String(message.createTime) : message.createTime;\n            else\n                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;\n        if (message.updateTime != null && message.hasOwnProperty(\"updateTime\"))\n            if (typeof message.updateTime === \"number\")\n                object.updateTime = options.longs === String ? String(message.updateTime) : message.updateTime;\n            else\n                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;\n        let keys2;\n        if (message.domainCookieMap && (keys2 = Object.keys(message.domainCookieMap)).length) {\n            object.domainCookieMap = {};\n            for (let j = 0; j < keys2.length; ++j)\n                object.domainCookieMap[keys2[j]] = $root.DomainCookie.toObject(message.domainCookieMap[keys2[j]], options);\n        }\n        return object;\n    };\n\n    /**\n     * Converts this CookiesMap to JSON.\n     * @function toJSON\n     * @memberof CookiesMap\n     * @instance\n     * @returns {Object.<string,*>} JSON object\n     */\n    CookiesMap.prototype.toJSON = function toJSON() {\n        return this.constructor.toObject(this, $protobuf.util.toJSONOptions);\n    };\n\n    /**\n     * Gets the default type url for CookiesMap\n     * @function getTypeUrl\n     * @memberof CookiesMap\n     * @static\n     * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n     * @returns {string} The default type url\n     */\n    CookiesMap.getTypeUrl = function getTypeUrl(typeUrlPrefix) {\n        if (typeUrlPrefix === undefined) {\n            typeUrlPrefix = \"type.googleapis.com\";\n        }\n        return typeUrlPrefix + \"/CookiesMap\";\n    };\n\n    return CookiesMap;\n})();\n\nexport { $root as default };\n"
  },
  {
    "path": "packages/protobuf/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/protobuf\",\n  \"version\": \"0.0.1\",\n  \"description\": \"chrome extension protobuf code\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"files\": [\n    \"dist/**\"\n  ],\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"scripts\": {\n    \"clean\": \"rimraf ./dist && rimraf .turbo\",\n    \"build\": \"tsup index.ts --format esm,cjs --dts --external react,chrome\",\n    \"dev\": \"tsc -w\",\n    \"copy:proto\": \"cp -r ./lib/protobuf/proto ./dist/lib/protobuf\",\n    \"lint\": \"eslint . --ext .ts,.tsx\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write\",\n    \"type-check\": \"tsc --noEmit\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest\",\n    \"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\"\n  },\n  \"dependencies\": {\n    \"pako\": \"^2.1.0\",\n    \"protobufjs\": \"^7.3.2\"\n  },\n  \"devDependencies\": {\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\",\n    \"@types/pako\": \"^2.0.3\",\n    \"protobufjs-cli\": \"^1.1.3\",\n    \"tsup\": \"8.0.2\",\n    \"tsx\": \"^4.19.1\",\n    \"vitest\": \"^1.6.0\"\n  }\n}\n"
  },
  {
    "path": "packages/protobuf/proto/cookie.proto",
    "content": "syntax = \"proto3\";\n\nmessage Cookie {\n  string domain = 1;\n  string name = 2;\n  string storeId = 3;\n  string value = 4;\n  bool session = 5;\n  bool hostOnly = 6;\n  float expirationDate = 7;\n  string path = 8;\n  bool httpOnly = 9;\n  bool secure = 10;\n  string sameSite = 11;\n}\n\nmessage LocalStorageItem {\n  string key = 1;\n  string value = 2;\n}\n\nmessage DomainCookie {\n  int64 createTime = 1;\n  int64 updateTime = 2;\n  repeated Cookie cookies = 5;\n  repeated LocalStorageItem localStorageItems = 6;\n  string userAgent = 7;\n}\n\nmessage CookiesMap {\n  int64 createTime = 1;\n  int64 updateTime = 2;\n  map<string, DomainCookie> domainCookieMap = 5;\n}\n"
  },
  {
    "path": "packages/protobuf/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"jsx\": \"react-jsx\",\n    \"checkJs\": false,\n    \"allowJs\": false,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@lib/*\": [\"lib/*\"]\n    },\n    \"types\": [\"chrome\"]\n  },\n  \"exclude\": [\"code-test.ts\"],\n  \"include\": [\"index.ts\", \"lib\"],\n}\n"
  },
  {
    "path": "packages/protobuf/tsup.config.ts",
    "content": "import { defineConfig } from 'tsup';\n\nexport default defineConfig({\n  treeshake: true,\n  format: ['cjs', 'esm'],\n  dts: true,\n  external: ['chrome'],\n});\n"
  },
  {
    "path": "packages/protobuf/utils/base64.ts",
    "content": "export function arrayBufferToBase64(arrayBuffer: ArrayBuffer) {\n  let base64 = '';\n  const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\n  const bytes = new Uint8Array(arrayBuffer);\n  const byteLength = bytes.byteLength;\n  const byteRemainder = byteLength % 3;\n  const mainLength = byteLength - byteRemainder;\n\n  let a, b, c, d;\n  let chunk;\n\n  // Main loop deals with bytes in chunks of 3\n  for (let i = 0; i < mainLength; i = i + 3) {\n    // Combine the three bytes into a single integer\n    chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];\n\n    // Use bitmasks to extract 6-bit segments from the triplet\n    a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18\n    b = (chunk & 258048) >> 12; // 258048   = (2^6 - 1) << 12\n    c = (chunk & 4032) >> 6; // 4032     = (2^6 - 1) << 6\n    d = chunk & 63; // 63       = 2^6 - 1\n\n    // Convert the raw binary segments to the appropriate ASCII encoding\n    base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];\n  }\n\n  // Deal with the remaining bytes and padding\n  if (byteRemainder == 1) {\n    chunk = bytes[mainLength];\n\n    a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2\n\n    // Set the 4 least significant bits to zero\n    b = (chunk & 3) << 4; // 3   = 2^2 - 1\n\n    base64 += encodings[a] + encodings[b] + '==';\n  } else if (byteRemainder == 2) {\n    chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];\n\n    a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10\n    b = (chunk & 1008) >> 4; // 1008  = (2^6 - 1) << 4\n\n    // Set the 2 least significant bits to zero\n    c = (chunk & 15) << 2; // 15    = 2^4 - 1\n\n    base64 += encodings[a] + encodings[b] + encodings[c] + '=';\n  }\n\n  return base64;\n}\n\nexport function base64ToArrayBuffer(base64: string) {\n  const binaryString = atob(base64);\n  const bytes = new Uint8Array(binaryString.length);\n  for (let i = 0; i < binaryString.length; i++) {\n    bytes[i] = binaryString.charCodeAt(i);\n  }\n  return bytes;\n}\n"
  },
  {
    "path": "packages/protobuf/utils/compress.ts",
    "content": "async function concatUint8Arrays(uint8arrays: ArrayBuffer[]) {\n  const blob = new Blob(uint8arrays);\n  const buffer = await blob.arrayBuffer();\n  return new Uint8Array(buffer);\n}\n\n/**\n * Compress a string into a Uint8Array.\n * @param byteArray\n * @param method\n * @returns Promise<ArrayBuffer>\n */\nexport const compress = async (byteArray: Uint8Array, method: CompressionFormat = 'gzip'): Promise<Uint8Array> => {\n  const stream = new Blob([byteArray]).stream();\n  // const byteArray: Uint8Array = new TextEncoder().encode(string);\n  const compressedStream = stream.pipeThrough(new CompressionStream(method)) as unknown as ArrayBuffer[];\n  const chunks: ArrayBuffer[] = [];\n  for await (const chunk of compressedStream) {\n    chunks.push(chunk);\n  }\n  return await concatUint8Arrays(chunks);\n};\n\n/**\n * Decompress bytes into a Uint8Array.\n *\n * @param {Uint8Array} compressedBytes\n * @returns {Promise<Uint8Array>}\n */\nexport async function decompress(compressedBytes: Uint8Array) {\n  // Convert the bytes to a stream.\n  const stream = new Blob([compressedBytes]).stream();\n\n  // Create a decompressed stream.\n  const decompressedStream = stream.pipeThrough(new DecompressionStream('gzip')) as unknown as ArrayBuffer[];\n\n  // Read all the bytes from this stream.\n  const chunks = [];\n  for await (const chunk of decompressedStream) {\n    chunks.push(chunk);\n  }\n  const stringBytes = await concatUint8Arrays(chunks);\n  return stringBytes;\n}\n"
  },
  {
    "path": "packages/protobuf/utils/encryption.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport {\n  encrypt,\n  decrypt,\n  isEncrypted,\n  encryptBase64,\n  decryptBase64,\n  isBase64Encrypted,\n} from './encryption';\n\n// Helper to create test data\nfunction createTestData(size: number): Uint8Array {\n  const data = new Uint8Array(size);\n  for (let i = 0; i < size; i++) {\n    data[i] = i % 256;\n  }\n  return data;\n}\n\n// Helper to convert string to Uint8Array\nfunction stringToUint8Array(str: string): Uint8Array {\n  return new TextEncoder().encode(str);\n}\n\n// Helper to convert Uint8Array to string\nfunction uint8ArrayToString(arr: Uint8Array): string {\n  return new TextDecoder().decode(arr);\n}\n\ndescribe('Encryption Module', () => {\n  describe('encrypt and decrypt', () => {\n    it('should encrypt and decrypt small data correctly', async () => {\n      const originalData = stringToUint8Array('Hello, World!');\n      const password = 'test-password-123';\n\n      const encrypted = await encrypt(originalData, password);\n      const decrypted = await decrypt(encrypted, password);\n\n      expect(uint8ArrayToString(decrypted)).toBe('Hello, World!');\n    });\n\n    it('should encrypt and decrypt empty data', async () => {\n      const originalData = new Uint8Array(0);\n      const password = 'test-password';\n\n      const encrypted = await encrypt(originalData, password);\n      const decrypted = await decrypt(encrypted, password);\n\n      expect(decrypted.length).toBe(0);\n    });\n\n    it('should encrypt and decrypt large data (1MB)', async () => {\n      const originalData = createTestData(1024 * 1024); // 1MB\n      const password = 'strong-password-456';\n\n      const encrypted = await encrypt(originalData, password);\n      const decrypted = await decrypt(encrypted, password);\n\n      expect(decrypted).toEqual(originalData);\n    });\n\n    it('should encrypt and decrypt binary data', async () => {\n      const originalData = new Uint8Array([0, 1, 2, 255, 254, 253, 128, 127]);\n      const password = 'binary-test';\n\n      const encrypted = await encrypt(originalData, password);\n      const decrypted = await decrypt(encrypted, password);\n\n      expect(decrypted).toEqual(originalData);\n    });\n\n    it('should produce different ciphertext for same plaintext (random IV)', async () => {\n      const originalData = stringToUint8Array('Same message');\n      const password = 'same-password';\n\n      const encrypted1 = await encrypt(originalData, password);\n      const encrypted2 = await encrypt(originalData, password);\n\n      // Ciphertexts should be different due to random IV and salt\n      expect(encrypted1).not.toEqual(encrypted2);\n\n      // But both should decrypt to the same plaintext\n      const decrypted1 = await decrypt(encrypted1, password);\n      const decrypted2 = await decrypt(encrypted2, password);\n\n      expect(decrypted1).toEqual(decrypted2);\n    });\n\n    it('should fail decryption with wrong password', async () => {\n      const originalData = stringToUint8Array('Secret message');\n      const correctPassword = 'correct-password';\n      const wrongPassword = 'wrong-password';\n\n      const encrypted = await encrypt(originalData, correctPassword);\n\n      await expect(decrypt(encrypted, wrongPassword)).rejects.toThrow(\n        'Decryption failed: incorrect password or corrupted data',\n      );\n    });\n\n    it('should fail decryption with corrupted data', async () => {\n      const originalData = stringToUint8Array('Test data');\n      const password = 'test-password';\n\n      const encrypted = await encrypt(originalData, password);\n\n      // Corrupt the ciphertext (modify bytes after the header)\n      encrypted[encrypted.length - 1] ^= 0xff;\n\n      await expect(decrypt(encrypted, password)).rejects.toThrow();\n    });\n\n    it('should fail decryption with invalid magic bytes', async () => {\n      const fakeEncrypted = new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x01, ...new Array(33).fill(0)]);\n\n      await expect(decrypt(fakeEncrypted, 'any-password')).rejects.toThrow(\n        'Invalid encrypted data: magic bytes mismatch',\n      );\n    });\n\n    it('should fail decryption with unsupported version', async () => {\n      // Create data with correct magic bytes but wrong version\n      const fakeEncrypted = new Uint8Array([\n        0x53,\n        0x59,\n        0x43,\n        0x45, // SYCE magic bytes\n        0x99, // Invalid version\n        ...new Array(33).fill(0),\n      ]);\n\n      await expect(decrypt(fakeEncrypted, 'any-password')).rejects.toThrow('Unsupported encryption version: 153');\n    });\n\n    it('should handle unicode strings correctly', async () => {\n      const originalData = stringToUint8Array('Hello 世界 🌍 Привет');\n      const password = 'unicode-test-密码';\n\n      const encrypted = await encrypt(originalData, password);\n      const decrypted = await decrypt(encrypted, password);\n\n      expect(uint8ArrayToString(decrypted)).toBe('Hello 世界 🌍 Привет');\n    });\n\n    it('should handle special characters in password', async () => {\n      const originalData = stringToUint8Array('Test data');\n      const password = '!@#$%^&*()_+-=[]{}|;:,.<>?`~\"\\'\\\\';\n\n      const encrypted = await encrypt(originalData, password);\n      const decrypted = await decrypt(encrypted, password);\n\n      expect(uint8ArrayToString(decrypted)).toBe('Test data');\n    });\n\n    it('should handle very long passwords', async () => {\n      const originalData = stringToUint8Array('Test data');\n      const password = 'a'.repeat(10000);\n\n      const encrypted = await encrypt(originalData, password);\n      const decrypted = await decrypt(encrypted, password);\n\n      expect(uint8ArrayToString(decrypted)).toBe('Test data');\n    });\n\n    it('should handle empty password', async () => {\n      const originalData = stringToUint8Array('Test data');\n      const password = '';\n\n      const encrypted = await encrypt(originalData, password);\n      const decrypted = await decrypt(encrypted, password);\n\n      expect(uint8ArrayToString(decrypted)).toBe('Test data');\n    });\n  });\n\n  describe('isEncrypted', () => {\n    it('should return true for encrypted data', async () => {\n      const originalData = stringToUint8Array('Test');\n      const encrypted = await encrypt(originalData, 'password');\n\n      expect(isEncrypted(encrypted)).toBe(true);\n    });\n\n    it('should return false for non-encrypted data', () => {\n      const plainData = stringToUint8Array('Plain text data');\n\n      expect(isEncrypted(plainData)).toBe(false);\n    });\n\n    it('should return false for data shorter than magic bytes', () => {\n      const shortData = new Uint8Array([0x53, 0x59]); // Only 2 bytes\n\n      expect(isEncrypted(shortData)).toBe(false);\n    });\n\n    it('should return false for empty data', () => {\n      const emptyData = new Uint8Array(0);\n\n      expect(isEncrypted(emptyData)).toBe(false);\n    });\n\n    it('should return false for data with partial magic bytes match', () => {\n      const partialMatch = new Uint8Array([0x53, 0x59, 0x43, 0x00]); // SYCA instead of SYCE\n\n      expect(isEncrypted(partialMatch)).toBe(false);\n    });\n  });\n\n  describe('encryptBase64 and decryptBase64', () => {\n    it('should encrypt and decrypt base64 strings', async () => {\n      const originalBase64 = btoa('Hello, World!');\n      const password = 'test-password';\n\n      const encryptedBase64 = await encryptBase64(originalBase64, password);\n      const decryptedBase64 = await decryptBase64(encryptedBase64, password);\n\n      expect(decryptedBase64).toBe(originalBase64);\n    });\n\n    it('should handle complex base64 data', async () => {\n      // Create some binary data and convert to base64\n      const binaryData = new Uint8Array([0, 127, 128, 255, 1, 2, 3]);\n      const originalBase64 = btoa(String.fromCharCode(...binaryData));\n      const password = 'complex-test';\n\n      const encryptedBase64 = await encryptBase64(originalBase64, password);\n      const decryptedBase64 = await decryptBase64(encryptedBase64, password);\n\n      expect(decryptedBase64).toBe(originalBase64);\n    });\n\n    it('should fail with wrong password', async () => {\n      const originalBase64 = btoa('Secret data');\n\n      const encryptedBase64 = await encryptBase64(originalBase64, 'correct-password');\n\n      await expect(decryptBase64(encryptedBase64, 'wrong-password')).rejects.toThrow();\n    });\n\n    it('should handle empty base64 string', async () => {\n      const originalBase64 = btoa('');\n      const password = 'test';\n\n      const encryptedBase64 = await encryptBase64(originalBase64, password);\n      const decryptedBase64 = await decryptBase64(encryptedBase64, password);\n\n      expect(decryptedBase64).toBe(originalBase64);\n    });\n  });\n\n  describe('isBase64Encrypted', () => {\n    it('should return true for encrypted base64 data', async () => {\n      const originalBase64 = btoa('Test data');\n      const encryptedBase64 = await encryptBase64(originalBase64, 'password');\n\n      expect(isBase64Encrypted(encryptedBase64)).toBe(true);\n    });\n\n    it('should return false for plain base64 data', () => {\n      const plainBase64 = btoa('Plain text');\n\n      expect(isBase64Encrypted(plainBase64)).toBe(false);\n    });\n\n    it('should return false for invalid base64 string', () => {\n      const invalidBase64 = '!!!not-valid-base64!!!';\n\n      expect(isBase64Encrypted(invalidBase64)).toBe(false);\n    });\n\n    it('should return false for empty string', () => {\n      expect(isBase64Encrypted('')).toBe(false);\n    });\n\n    it('should return false for JSON-like base64', () => {\n      const jsonBase64 = btoa('{\"key\": \"value\"}');\n\n      expect(isBase64Encrypted(jsonBase64)).toBe(false);\n    });\n  });\n\n  describe('Security properties', () => {\n    it('encrypted data should be larger than original due to header', async () => {\n      const originalData = stringToUint8Array('Test');\n      const encrypted = await encrypt(originalData, 'password');\n\n      // Header size: 4 (magic) + 1 (version) + 16 (salt) + 12 (IV) = 33 bytes\n      // Plus authentication tag from GCM (16 bytes)\n      expect(encrypted.length).toBeGreaterThan(originalData.length + 33);\n    });\n\n    it('should have correct magic bytes at the start', async () => {\n      const originalData = stringToUint8Array('Test');\n      const encrypted = await encrypt(originalData, 'password');\n\n      // Check SYCE magic bytes\n      expect(encrypted[0]).toBe(0x53); // S\n      expect(encrypted[1]).toBe(0x59); // Y\n      expect(encrypted[2]).toBe(0x43); // C\n      expect(encrypted[3]).toBe(0x45); // E\n    });\n\n    it('should have version 1 in the header', async () => {\n      const originalData = stringToUint8Array('Test');\n      const encrypted = await encrypt(originalData, 'password');\n\n      expect(encrypted[4]).toBe(1); // Version\n    });\n  });\n});\n"
  },
  {
    "path": "packages/protobuf/utils/encryption.ts",
    "content": "/**\n * End-to-end encryption utilities using Web Crypto API.\n * Uses AES-GCM for authenticated encryption and PBKDF2 for password-based key derivation.\n */\n\n// Constants for encryption\nconst ALGORITHM = 'AES-GCM';\nconst KEY_LENGTH = 256;\nconst PBKDF2_ITERATIONS = 100000;\nconst SALT_LENGTH = 16;\nconst IV_LENGTH = 12;\n\n// Magic bytes to identify encrypted data (ASCII: \"SYCE\" - Sync Your Cookie Encrypted)\nconst MAGIC_BYTES = new Uint8Array([0x53, 0x59, 0x43, 0x45]);\nconst VERSION = 1;\n\n/**\n * Derives a cryptographic key from a password using PBKDF2.\n */\nasync function deriveKey(password: string, salt: Uint8Array): Promise<CryptoKey> {\n  const encoder = new TextEncoder();\n  const passwordKey = await crypto.subtle.importKey('raw', encoder.encode(password), 'PBKDF2', false, [\n    'deriveKey',\n  ]);\n\n  return crypto.subtle.deriveKey(\n    {\n      name: 'PBKDF2',\n      salt,\n      iterations: PBKDF2_ITERATIONS,\n      hash: 'SHA-256',\n    },\n    passwordKey,\n    {\n      name: ALGORITHM,\n      length: KEY_LENGTH,\n    },\n    false,\n    ['encrypt', 'decrypt'],\n  );\n}\n\n/**\n * Encrypts data using AES-GCM with a password-derived key.\n *\n * Output format:\n * [MAGIC_BYTES (4)] [VERSION (1)] [SALT (16)] [IV (12)] [CIPHERTEXT (...)]\n *\n * @param data - The data to encrypt (Uint8Array)\n * @param password - The password to derive the encryption key from\n * @returns Promise<Uint8Array> - The encrypted data with header\n */\nexport async function encrypt(data: Uint8Array, password: string): Promise<Uint8Array> {\n  // Generate random salt and IV\n  const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));\n  const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));\n\n  // Derive key from password\n  const key = await deriveKey(password, salt);\n\n  // Encrypt the data\n  const ciphertext = await crypto.subtle.encrypt(\n    {\n      name: ALGORITHM,\n      iv,\n    },\n    key,\n    data,\n  );\n\n  // Combine: MAGIC + VERSION + SALT + IV + CIPHERTEXT\n  const result = new Uint8Array(MAGIC_BYTES.length + 1 + SALT_LENGTH + IV_LENGTH + ciphertext.byteLength);\n  let offset = 0;\n\n  result.set(MAGIC_BYTES, offset);\n  offset += MAGIC_BYTES.length;\n\n  result[offset] = VERSION;\n  offset += 1;\n\n  result.set(salt, offset);\n  offset += SALT_LENGTH;\n\n  result.set(iv, offset);\n  offset += IV_LENGTH;\n\n  result.set(new Uint8Array(ciphertext), offset);\n\n  return result;\n}\n\n/**\n * Decrypts data that was encrypted with the encrypt() function.\n *\n * @param encryptedData - The encrypted data with header (Uint8Array)\n * @param password - The password to derive the decryption key from\n * @returns Promise<Uint8Array> - The decrypted data\n * @throws Error if decryption fails or data is invalid\n */\nexport async function decrypt(encryptedData: Uint8Array, password: string): Promise<Uint8Array> {\n  let offset = 0;\n\n  // Verify magic bytes\n  const magic = encryptedData.slice(offset, offset + MAGIC_BYTES.length);\n  offset += MAGIC_BYTES.length;\n\n  if (!magic.every((byte, i) => byte === MAGIC_BYTES[i])) {\n    throw new Error('Invalid encrypted data: magic bytes mismatch');\n  }\n\n  // Check version\n  const version = encryptedData[offset];\n  offset += 1;\n\n  if (version !== VERSION) {\n    throw new Error(`Unsupported encryption version: ${version}`);\n  }\n\n  // Extract salt\n  const salt = encryptedData.slice(offset, offset + SALT_LENGTH);\n  offset += SALT_LENGTH;\n\n  // Extract IV\n  const iv = encryptedData.slice(offset, offset + IV_LENGTH);\n  offset += IV_LENGTH;\n\n  // Extract ciphertext\n  const ciphertext = encryptedData.slice(offset);\n\n  // Derive key from password\n  const key = await deriveKey(password, salt);\n\n  // Decrypt the data\n  try {\n    const decrypted = await crypto.subtle.decrypt(\n      {\n        name: ALGORITHM,\n        iv,\n      },\n      key,\n      ciphertext,\n    );\n\n    return new Uint8Array(decrypted);\n  } catch {\n    throw new Error('Decryption failed: incorrect password or corrupted data');\n  }\n}\n\n/**\n * Checks if data appears to be encrypted (starts with magic bytes).\n *\n * @param data - The data to check (Uint8Array)\n * @returns boolean - True if data appears to be encrypted\n */\nexport function isEncrypted(data: Uint8Array): boolean {\n  if (data.length < MAGIC_BYTES.length) {\n    return false;\n  }\n  return data.slice(0, MAGIC_BYTES.length).every((byte, i) => byte === MAGIC_BYTES[i]);\n}\n\n/**\n * Encrypts a base64 string using the provided password.\n * Returns a new base64 string containing the encrypted data.\n *\n * @param base64Data - The base64-encoded data to encrypt\n * @param password - The password to use for encryption\n * @returns Promise<string> - The encrypted data as a base64 string\n */\nexport async function encryptBase64(base64Data: string, password: string): Promise<string> {\n  const binaryString = atob(base64Data);\n  const bytes = new Uint8Array(binaryString.length);\n  for (let i = 0; i < binaryString.length; i++) {\n    bytes[i] = binaryString.charCodeAt(i);\n  }\n\n  const encrypted = await encrypt(bytes, password);\n  return arrayBufferToBase64ForEncryption(encrypted);\n}\n\n/**\n * Decrypts a base64 string that was encrypted with encryptBase64().\n * Returns the original base64-encoded data.\n *\n * @param encryptedBase64 - The encrypted base64 string\n * @param password - The password to use for decryption\n * @returns Promise<string> - The decrypted data as a base64 string\n */\nexport async function decryptBase64(encryptedBase64: string, password: string): Promise<string> {\n  const binaryString = atob(encryptedBase64);\n  const bytes = new Uint8Array(binaryString.length);\n  for (let i = 0; i < binaryString.length; i++) {\n    bytes[i] = binaryString.charCodeAt(i);\n  }\n\n  const decrypted = await decrypt(bytes, password);\n  return arrayBufferToBase64ForEncryption(decrypted);\n}\n\n/**\n * Checks if a base64 string appears to be encrypted data.\n *\n * @param base64Data - The base64 string to check\n * @returns boolean - True if the data appears to be encrypted\n */\nexport function isBase64Encrypted(base64Data: string): boolean {\n  try {\n    const binaryString = atob(base64Data);\n    const bytes = new Uint8Array(Math.min(binaryString.length, MAGIC_BYTES.length));\n    for (let i = 0; i < bytes.length; i++) {\n      bytes[i] = binaryString.charCodeAt(i);\n    }\n    return isEncrypted(bytes);\n  } catch {\n    return false;\n  }\n}\n\n// Helper function for base64 encoding (to avoid circular dependency)\nfunction arrayBufferToBase64ForEncryption(arrayBuffer: ArrayBuffer | Uint8Array) {\n  let base64 = '';\n  const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\n  const bytes = arrayBuffer instanceof Uint8Array ? arrayBuffer : new Uint8Array(arrayBuffer);\n  const byteLength = bytes.byteLength;\n  const byteRemainder = byteLength % 3;\n  const mainLength = byteLength - byteRemainder;\n\n  let a, b, c, d;\n  let chunk;\n\n  for (let i = 0; i < mainLength; i = i + 3) {\n    chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];\n    a = (chunk & 16515072) >> 18;\n    b = (chunk & 258048) >> 12;\n    c = (chunk & 4032) >> 6;\n    d = chunk & 63;\n    base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];\n  }\n\n  if (byteRemainder == 1) {\n    chunk = bytes[mainLength];\n    a = (chunk & 252) >> 2;\n    b = (chunk & 3) << 4;\n    base64 += encodings[a] + encodings[b] + '==';\n  } else if (byteRemainder == 2) {\n    chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];\n    a = (chunk & 64512) >> 10;\n    b = (chunk & 1008) >> 4;\n    c = (chunk & 15) << 2;\n    base64 += encodings[a] + encodings[b] + encodings[c] + '=';\n  }\n\n  return base64;\n}\n"
  },
  {
    "path": "packages/protobuf/utils/index.ts",
    "content": "export * from './base64';\nexport * from './encryption';\n"
  },
  {
    "path": "packages/protobuf/vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    environment: 'node',\n    include: ['**/*.test.ts'],\n  },\n});\n"
  },
  {
    "path": "packages/shared/README.md",
    "content": "# Shared Package\n\nThis package contains code shared with other packages.\nTo use the code in the package, you need to add the following to the package.json file.\n\n```json\n{\n  \"dependencies\": {\n    \"@sync-your-cookie/shared\": \"workspace:*\"\n  }\n}\n```\n\nAfter building this package, real-time cache busting does not occur in the code of other packages that reference this package.\nYou need to rerun it from the root path with `pnpm dev`, etc. (This will be improved in the future.)\n\nIf the type does not require compilation, there is no problem, but if the implementation requiring compilation is changed, a problem may occur.\n\nTherefore, 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.\n"
  },
  {
    "path": "packages/shared/index.ts",
    "content": "export * from './lib/cloudflare';\n\nexport * from './lib/cookie';\nexport * from './lib/hoc';\nexport * from './lib/hooks';\nexport * from './lib/Providers';\n\nexport * from './lib/message';\nexport * from './lib/utils';\n\nexport * from './lib/github';\n"
  },
  {
    "path": "packages/shared/lib/Providers/ThemeProvider.tsx",
    "content": "import { useStorageSuspense } from '@lib/hooks/useStorageSuspense';\nimport { themeStorage } from '@sync-your-cookie/storage/lib/themeStorage';\nimport { createContext, useEffect } from 'react';\n\ntype Theme = 'dark' | 'light' | 'system';\n\ntype ThemeProviderProps = {\n  children: React.ReactNode;\n};\n\ntype ThemeProviderState = {\n  theme: Theme;\n  setTheme: (theme: Theme) => void;\n};\n\nconst initialState: ThemeProviderState = {\n  theme: 'system',\n  setTheme: () => null,\n};\n\nexport const ThemeProviderContext = createContext<ThemeProviderState>(initialState);\n\nexport function ThemeProvider({ children, ...props }: ThemeProviderProps) {\n  const theme = useStorageSuspense(themeStorage);\n\n  useEffect(() => {\n    const root = window.document.documentElement;\n\n    root.classList.remove('light', 'dark');\n\n    if (theme === 'system') {\n      const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n      root.classList.add(systemTheme);\n      return;\n    }\n\n    root.classList.add(theme);\n  }, [theme]);\n\n  const value = {\n    theme,\n    setTheme: (theme: Theme) => {\n      // localStorage.setItem(storageKey, theme);\n      themeStorage.set(theme);\n      //   setTheme(theme);\n    },\n  };\n\n  return (\n    <ThemeProviderContext.Provider {...props} value={value}>\n      {children}\n    </ThemeProviderContext.Provider>\n  );\n}\n"
  },
  {
    "path": "packages/shared/lib/Providers/hooks/index.ts",
    "content": "export * from './useTheme';\n"
  },
  {
    "path": "packages/shared/lib/Providers/hooks/useTheme.ts",
    "content": "import { ThemeProviderContext } from '../';\n\nimport { useContext, useEffect } from 'react';\n\nexport const useTheme = () => {\n  const context = useContext(ThemeProviderContext);\n  useEffect(() => {\n    const handler = (event: MediaQueryListEvent) => {\n      if (event.matches) {\n        context.setTheme('dark');\n      } else {\n        context.setTheme('light');\n      }\n    };\n    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', handler);\n    return () => {\n      window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', handler);\n    };\n  }, []);\n\n  if (context === undefined) throw new Error('useTheme must be used within a ThemeProvider');\n\n  return context;\n};\n"
  },
  {
    "path": "packages/shared/lib/Providers/index.tsx",
    "content": "export * from './hooks';\nexport * from './ThemeProvider';\n\n"
  },
  {
    "path": "packages/shared/lib/cloudflare/api.ts",
    "content": "import { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';\n\nexport interface WriteResponse {\n  success: boolean;\n  errors: {\n    code: number;\n    message: string;\n  }[];\n}\n\n/**\n *\n * @param value specify the value to write\n * @param accountId cloudflare account id\n * @param namespaceId cloudflare namespace id\n * @param token api token\n * @returns promise<res>\n */\nexport const writeCloudflareKV = async (value: string, accountId: string, namespaceId: string, token: string) => {\n  const storageKey = settingsStorage.getSnapshot()?.storageKey;\n  const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${storageKey}`;\n  // const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`;\n  // const payload = [\n  //   {\n  //     key: DEFAULT_KEY,\n  //     metadata: JSON.stringify({\n  //       someMetadataKey: value,\n  //     }),\n  //     value: value,\n  //   },\n  // ];\n  const options = {\n    method: 'PUT',\n    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },\n    body: value,\n  };\n  return fetch(url, options).then(res => res.json());\n};\n\n/**\n *\n * @param accountId cloudflare account id\n * @param namespaceId cloudflare namespace id\n * @param token api token\n * @returns Promise<res>\n */\nexport const readCloudflareKV = async (accountId: string, namespaceId: string, token: string) => {\n  const storageKey = settingsStorage.getSnapshot()?.storageKey;\n  const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${storageKey}`;\n  const options = {\n    method: 'GET',\n    headers: {\n      Authorization: `Bearer ${token}`,\n      'Content-Type': 'application/json',\n    },\n  };\n  return fetch(url, options).then(async res => {\n    if (res.status === 404) {\n      return '';\n    }\n    if (res.status === 200) {\n      const text = await res.text();\n      return text.trim();\n    } else {\n      return Promise.reject(await res.json());\n    }\n  });\n};\n\nexport const verifyCloudflareAccountToken = async (accountId: string, token: string) => {\n  const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/tokens/verify`;\n  const options = {\n    method: 'GET',\n    headers: {\n      Authorization: `Bearer ${token}`,\n      'Content-Type': 'application/json',\n    },\n  };\n  return fetch(url, options).then(async res => {\n    if (res.status === 200) {\n      return res.json();\n    } else {\n      return Promise.reject(await res.json());\n    }\n  });\n};\n\nexport const verifyCloudflareToken = async (accountId: string, token: string) => {\n  const url = `https://api.cloudflare.com/client/v4/user/tokens/verify`;\n  const options = {\n    method: 'GET',\n    headers: {\n      Authorization: `Bearer ${token}`,\n      'Content-Type': 'application/json',\n    },\n  };\n  return fetch(url, options).then(async res => {\n    if (res.status === 200) {\n      return res.json();\n    } else {\n      return verifyCloudflareAccountToken(accountId, token);\n    }\n  });\n};\n"
  },
  {
    "path": "packages/shared/lib/cloudflare/enum.ts",
    "content": "export enum ErrorCode {\n  NotFoundRoute = 7003,\n  AuthenicationError = 10000,\n  NamespaceIdError = 10011,\n}\n"
  },
  {
    "path": "packages/shared/lib/cloudflare/index.ts",
    "content": "export * from './api';\n\nexport * from './enum';\n"
  },
  {
    "path": "packages/shared/lib/cookie/index.ts",
    "content": "export * from './withCloudflare';\nexport * from './withStorage';\n\n"
  },
  {
    "path": "packages/shared/lib/cookie/withCloudflare.ts",
    "content": "import { accountStorage, type AccountInfo } from '@sync-your-cookie/storage/lib/accountStorage';\nimport { getActiveStorageItem, settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';\n\nimport { readCloudflareKV, writeCloudflareKV, WriteResponse } from '../cloudflare/api';\n\nimport { GithubApi } from '@lib/github';\nimport { MessageErrorCode } from '@lib/message';\nimport { RestEndpointMethodTypes } from '@octokit/rest';\nimport {\n  arrayBufferToBase64,\n  base64ToArrayBuffer,\n  decodeCookiesMap,\n  decryptBase64,\n  encodeCookiesMap,\n  encryptBase64,\n  ICookie,\n  ICookiesMap,\n  ILocalStorageItem,\n  isBase64Encrypted,\n} from '@sync-your-cookie/protobuf';\n\nexport const check = (accountInfo?: AccountInfo) => {\n  const cloudflareAccountInfo = accountInfo || accountStorage.getSnapshot();\n  if (cloudflareAccountInfo?.selectedProvider === 'github') {\n    if (!cloudflareAccountInfo.githubAccessToken) {\n      return Promise.reject({\n        message: 'GitHub Access Token is empty',\n        code: MessageErrorCode.AccountCheck,\n      });\n    }\n  } else {\n    if (!cloudflareAccountInfo?.accountId || !cloudflareAccountInfo.namespaceId || !cloudflareAccountInfo.token) {\n      let message = 'Account ID is empty';\n      if (!cloudflareAccountInfo?.namespaceId) {\n        message = 'NamespaceId ID is empty';\n      } else if (!cloudflareAccountInfo.token) {\n        message = 'Token is empty';\n      }\n\n      return Promise.reject({\n        message,\n        code: MessageErrorCode.AccountCheck,\n      });\n    }\n  }\n  return cloudflareAccountInfo;\n};\n\nexport const readCookiesMap = async (accountInfo: AccountInfo): Promise<ICookiesMap> => {\n  let content = '';\n  if (accountInfo.selectedProvider === 'github') {\n    const activeStorageItem = getActiveStorageItem();\n    if (activeStorageItem?.rawUrl) {\n      content = await GithubApi.instance.fetchRawContent(activeStorageItem.rawUrl);\n    }\n  } else {\n    await check(accountInfo);\n    content = await readCloudflareKV(accountInfo.accountId!, accountInfo.namespaceId!, accountInfo.token!);\n  }\n\n  if (content) {\n    try {\n      const settingsInfo = settingsStorage.getSnapshot();\n      const encryptionEnabled = settingsInfo?.encryptionEnabled;\n      const encryptionPassword = settingsInfo?.encryptionPassword;\n\n      // Check if content is encrypted and decrypt if needed\n      let processedContent = content;\n      const protobufEncoding = !content.startsWith('{');\n\n      if (protobufEncoding && isBase64Encrypted(content)) {\n        if (!encryptionEnabled || !encryptionPassword) {\n          return Promise.reject({\n            message: 'Failed to decrypt data. Please check your encryption password.',\n            code: MessageErrorCode.DecryptFailed,\n          });\n        }\n        try {\n          processedContent = await decryptBase64(content, encryptionPassword);\n        } catch (decryptError) {\n          console.error('Decryption failed:', decryptError);\n          // throw new Error('Failed to decrypt data. Please check your encryption password.');\n          return Promise.reject({\n            message: 'Failed to decrypt data. Please check your encryption password.',\n            code: MessageErrorCode.DecryptFailed,\n          });\n        }\n      }\n\n      if (protobufEncoding) {\n        const compressedBuffer = base64ToArrayBuffer(processedContent);\n        const deMsg = await decodeCookiesMap(compressedBuffer);\n        console.log('readCookiesMap->deMsg', deMsg);\n        return deMsg;\n      } else {\n        console.log('readCookiesMap->res', JSON.parse(processedContent));\n        return JSON.parse(processedContent);\n      }\n    } catch (error) {\n      console.log('Decode error', error, content);\n      // return {};\n      return Promise.reject({\n        message: `Decode error: ${error}, please check your save settings`,\n        code: MessageErrorCode.DecodeFailed,\n      });\n    }\n  } else {\n    return {};\n  }\n};\n\nexport const writeCookiesMap = async (accountInfo: AccountInfo, cookiesMap: ICookiesMap = {}) => {\n  const settingsInfo = settingsStorage.getSnapshot();\n  const protobufEncoding = settingsInfo?.protobufEncoding;\n  const encryptionEnabled = settingsInfo?.encryptionEnabled;\n  const encryptionPassword = settingsInfo?.encryptionPassword;\n\n  let encodingStr = '';\n  if (protobufEncoding) {\n    const buffered = await encodeCookiesMap(cookiesMap);\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    encodingStr = arrayBufferToBase64(buffered as any);\n\n    // Encrypt the data if encryption is enabled\n    if (encryptionEnabled && encryptionPassword) {\n      encodingStr = await encryptBase64(encodingStr, encryptionPassword);\n      console.log('writeCookiesMap-> data encrypted');\n    }\n  } else {\n    encodingStr = JSON.stringify(cookiesMap);\n    console.log('writeCookiesMap->', cookiesMap);\n  }\n  if (accountInfo.selectedProvider === 'github') {\n    const storageKeyGistId = settingsInfo?.storageKeyGistId;\n    const storageKey = settingsInfo?.storageKey;\n    return await GithubApi.instance.updateGist(storageKeyGistId!, storageKey!, encodingStr);\n  } else {\n    const res = await writeCloudflareKV(\n      encodingStr,\n      accountInfo.accountId!,\n      accountInfo.namespaceId!,\n      accountInfo.token!,\n    );\n    return res;\n  }\n};\n\nexport const mergeAndWriteCookies = async (\n  accountInfo: AccountInfo,\n  domain: string,\n  cookies: ICookie[],\n  localStorageItems: ILocalStorageItem[] = [],\n  userAgent = '',\n  oldCookieMap: ICookiesMap = {},\n): Promise<[WriteResponse | RestEndpointMethodTypes['gists']['update']['response'], ICookiesMap]> => {\n  await check(accountInfo);\n  const cookiesMap: ICookiesMap = {\n    updateTime: Date.now(),\n    createTime: oldCookieMap?.createTime || Date.now(),\n    domainCookieMap: {\n      ...(oldCookieMap.domainCookieMap || {}),\n      [domain]: {\n        updateTime: Date.now(),\n        createTime: oldCookieMap.domainCookieMap?.[domain]?.createTime || Date.now(),\n        cookies: cookies,\n        localStorageItems: localStorageItems,\n        userAgent: userAgent || oldCookieMap.domainCookieMap?.[domain]?.userAgent || '',\n      },\n    },\n  };\n\n  const res = await writeCookiesMap(accountInfo, cookiesMap);\n  return [res, cookiesMap];\n};\n\nexport const mergeAndWriteMultipleDomainCookies = async (\n  cloudflareAccountInfo: AccountInfo,\n  domainCookies: { domain: string; cookies: ICookie[]; localStorageItems: ILocalStorageItem[]; userAgent?: string }[],\n  oldCookieMap: ICookiesMap = {},\n): Promise<[WriteResponse, ICookiesMap]> => {\n  await check(cloudflareAccountInfo);\n\n  const newDomainCookieMap = {\n    ...(oldCookieMap.domainCookieMap || {}),\n  };\n  for (const { domain, cookies, localStorageItems, userAgent } of domainCookies) {\n    newDomainCookieMap[domain] = {\n      updateTime: Date.now(),\n      createTime: oldCookieMap.domainCookieMap?.[domain]?.createTime || Date.now(),\n      cookies: cookies,\n      localStorageItems: localStorageItems || [],\n      userAgent: userAgent || oldCookieMap.domainCookieMap?.[domain]?.userAgent || '',\n    };\n  }\n  const cookiesMap: ICookiesMap = {\n    updateTime: Date.now(),\n    createTime: oldCookieMap?.createTime || Date.now(),\n    domainCookieMap: newDomainCookieMap,\n  };\n\n  const res = await writeCookiesMap(cloudflareAccountInfo, cookiesMap);\n  return [res, cookiesMap];\n};\n\nexport const removeAndWriteCookies = async (\n  cloudflareAccountInfo: AccountInfo,\n  domain: string,\n  oldCookieMap: ICookiesMap = {},\n  id?: string,\n): Promise<[WriteResponse, ICookiesMap]> => {\n  await check(cloudflareAccountInfo);\n  const cookiesMap: ICookiesMap = {\n    updateTime: Date.now(),\n    createTime: oldCookieMap?.createTime || Date.now(),\n    domainCookieMap: {\n      ...(oldCookieMap.domainCookieMap || {}),\n    },\n  };\n  if (cookiesMap.domainCookieMap) {\n    if (id !== undefined) {\n      if (cookiesMap.domainCookieMap[domain]?.cookies) {\n        const oldLength = cookiesMap.domainCookieMap[domain]?.cookies?.length || 0;\n        cookiesMap.domainCookieMap[domain].cookies =\n          cookiesMap.domainCookieMap[domain].cookies?.filter(\n            // eslint-disable-next-line @typescript-eslint/no-explicit-any\n            (cookie: any) => `${cookie.domain}_${cookie.name}` !== id,\n          ) || [];\n        const newLength = cookiesMap.domainCookieMap[domain]?.cookies?.length || 0;\n        if (oldLength === newLength) {\n          throw new Error(`${id}: cookie not found`);\n        }\n      }\n    } else {\n      delete cookiesMap.domainCookieMap[domain];\n    }\n  }\n\n  const res = await writeCookiesMap(cloudflareAccountInfo, cookiesMap);\n  return [res, cookiesMap];\n};\n\nexport const editAndWriteCookies = async (\n  cloudflareAccountInfo: AccountInfo,\n  host: string,\n  oldCookieMap: ICookiesMap = {},\n  oldItem: ICookie,\n  newItem: ICookie,\n): Promise<[WriteResponse, ICookiesMap]> => {\n  await check(cloudflareAccountInfo);\n  const cookiesMap: ICookiesMap = {\n    updateTime: Date.now(),\n    createTime: oldCookieMap?.createTime || Date.now(),\n    domainCookieMap: {\n      ...(oldCookieMap.domainCookieMap || {}),\n    },\n  };\n  if (cookiesMap.domainCookieMap) {\n    const cookieLength = cookiesMap.domainCookieMap[host]?.cookies?.length || 0;\n    for (let i = 0; i < cookieLength; i++) {\n      const cookieItem = cookiesMap.domainCookieMap[host]?.cookies?.[i];\n      if (cookieItem?.name === oldItem.name && cookieItem?.domain === oldItem.domain) {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        (cookiesMap.domainCookieMap[host].cookies as any)[i] = {\n          ...cookieItem,\n          ...newItem,\n        };\n        break;\n      }\n    }\n  }\n\n  const res = await writeCookiesMap(cloudflareAccountInfo, cookiesMap);\n  return [res, cookiesMap];\n};\n"
  },
  {
    "path": "packages/shared/lib/cookie/withStorage.ts",
    "content": "import { ICookie, ICookiesMap, ILocalStorageItem } from '@sync-your-cookie/protobuf';\nimport { Cookie, cookieStorage } from '@sync-your-cookie/storage/lib/cookieStorage';\nimport { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';\n\nimport { AccountInfo, accountStorage } from '@sync-your-cookie/storage/lib/accountStorage';\n\nimport { MessageType, sendMessage } from '@lib/message';\nimport { RestEndpointMethodTypes } from '@octokit/rest';\nimport { OctokitResponse } from '@octokit/types';\nimport { WriteResponse } from '../cloudflare';\nimport {\n  editAndWriteCookies,\n  mergeAndWriteCookies,\n  mergeAndWriteMultipleDomainCookies,\n  readCookiesMap,\n  removeAndWriteCookies,\n} from './withCloudflare';\n\nexport const readCookiesMapWithStatus = async (cloudflareInfo: AccountInfo) => {\n  let cookieMap: Cookie | null = null;\n  const domainStatus = await domainStatusStorage.get();\n  if (domainStatus.pushing) {\n    cookieMap = await cookieStorage.getSnapshot();\n  }\n  if (cookieMap && Object.keys(cookieMap.domainCookieMap || {}).length > 0) {\n    return cookieMap;\n  }\n  return await readCookiesMap(cloudflareInfo);\n};\n\nexport const pullCookies = async (isInit = false): Promise<Cookie> => {\n  const cloudflareInfo = await accountStorage.get();\n  if (isInit && (!cloudflareInfo.accountId || !cloudflareInfo.namespaceId || !cloudflareInfo.token)) {\n    return {};\n  }\n  try {\n    const domainStatus = await domainStatusStorage.get();\n    if (domainStatus.pulling) {\n      const cookieMap = await cookieStorage.getSnapshot();\n      if (cookieMap && Object.keys(cookieMap.domainCookieMap || {}).length > 0) {\n        return cookieMap;\n      }\n    }\n    await domainStatusStorage.update({\n      pulling: true,\n    });\n    const cookieMap = await readCookiesMapWithStatus(cloudflareInfo);\n    const res = await cookieStorage.update(cookieMap, isInit);\n    await domainStatusStorage.update({\n      pulling: false,\n    });\n    return res;\n  } catch (e) {\n    console.error('pullCookies fail', e);\n    await domainStatusStorage.update({\n      pulling: false,\n    });\n    return Promise.reject(e);\n  }\n};\nfunction extractPortRegex(host: string) {\n  const match = host.match(/:(\\d+)$/);\n  return match ? match[1] : null;\n}\nexport const pullAndSetCookies = async (activeTabUrl: string, host: string, isReload = true): Promise<Cookie> => {\n  const cookieMap = await pullCookies();\n  const cookieDetails = cookieMap?.domainCookieMap?.[host]?.cookies || [];\n  const localStorageItems = cookieMap?.domainCookieMap?.[host]?.localStorageItems || [];\n  if (cookieDetails.length === 0 && localStorageItems.length === 0) {\n    console.warn('no cookies to pull, push first please', host, cookieMap);\n    throw new Error('No cookies to pull, push first please');\n  } else {\n    const cookiesPromiseList: Promise<unknown>[] = [];\n    for (const cookie of cookieDetails) {\n      let removeWWWHost = host.replace('www.', '');\n      const port = extractPortRegex(removeWWWHost);\n      if (port) {\n        removeWWWHost = removeWWWHost.replace(':' + port, '');\n      }\n\n      if (cookie.domain?.includes(removeWWWHost)) {\n        let url = activeTabUrl;\n        if (cookie.domain) {\n          const urlObj = new URL(activeTabUrl);\n          const protocol = activeTabUrl ? urlObj.protocol : 'http:';\n          const itemHost = cookie.domain.startsWith('.') ? cookie.domain.slice(1) : cookie.domain;\n          url = `${protocol}//${itemHost}`;\n        }\n        const cookieDetail: chrome.cookies.SetDetails = {\n          domain: cookie.domain.startsWith('.') || !url ? cookie.domain : undefined,\n          name: cookie.name ?? undefined,\n          url: url,\n          storeId: cookie.storeId ?? undefined,\n          value: cookie.value ?? undefined,\n          expirationDate: cookie.expirationDate ?? undefined,\n          path: cookie.path ?? undefined,\n          httpOnly: cookie.httpOnly ?? undefined,\n          secure: cookie.secure ?? undefined,\n          sameSite: (cookie.sameSite ?? undefined) as chrome.cookies.SameSiteStatus,\n        };\n        const promise = new Promise((resolve, reject) => {\n          try {\n            chrome.cookies.set(cookieDetail, res => {\n              resolve(res);\n            });\n          } catch (error) {\n            console.error('cookie set error', cookieDetail, error);\n            reject(error);\n          }\n        });\n        cookiesPromiseList.push(promise);\n      }\n    }\n\n    // reload window after set cookies\n    // await new Promise(resolve => {\n    //   setTimeout(resolve, 5000);\n    // });\n    await sendMessage(\n      {\n        type: MessageType.SetLocalStorage,\n        payload: {\n          domain: host,\n          value: localStorageItems,\n        },\n      },\n      true,\n    )\n      .then(res => {\n        console.log('set local storage', res);\n      })\n      .catch(err => {\n        console.error('set local storage error', err);\n      });\n\n    if (cookiesPromiseList.length === 0 && localStorageItems.length === 0) {\n      console.warn('no matched cookies and localStorageItems to pull, push first please', host, cookieMap);\n      throw new Error('No matched cookies and localStorageItems to pull, push first please');\n    }\n    await Promise.allSettled(cookiesPromiseList);\n    if (isReload) {\n      chrome.tabs.query({}, function (tabs) {\n        tabs.forEach(function (tab) {\n          // 使用字符串匹配\n          if (tab.url && tab.url.includes(host) && tab.id) {\n            console.log('tab', tab);\n            chrome.tabs.reload(tab.id);\n          }\n        });\n      });\n    }\n  }\n  return cookieMap;\n};\n\nexport type GistUpdateResponse = RestEndpointMethodTypes['gists']['update']['response'];\n\nexport type PushCookiesResponse = WriteResponse | GistUpdateResponse;\n\nconst checkSuccessAndUpdate = async (\n  accountInfo: AccountInfo,\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  res: WriteResponse | OctokitResponse<any>,\n  cookieMap: ICookiesMap,\n) => {\n  if (accountInfo.selectedProvider === 'github') {\n    if ((res as unknown as GistUpdateResponse)?.status?.toString()?.startsWith('2')) {\n      await cookieStorage.update(cookieMap);\n    }\n  } else {\n    if ((res as WriteResponse).success) {\n      await cookieStorage.update(cookieMap);\n    }\n  }\n};\n\nexport const pushCookies = async (\n  domain: string,\n  cookies: ICookie[],\n  localStorageItems: ILocalStorageItem[] = [],\n  userAgent = '',\n): Promise<PushCookiesResponse> => {\n  const accountInfo = await accountStorage.get();\n  try {\n    const domainStatus = await domainStatusStorage.get();\n    if (domainStatus.pushing) return Promise.reject('the cookie is pushing');\n    await domainStatusStorage.update({\n      pushing: true,\n    });\n    const oldCookie = await readCookiesMapWithStatus(accountInfo);\n    const [res, cookieMap] = await mergeAndWriteCookies(\n      accountInfo,\n      domain,\n      cookies,\n      localStorageItems,\n      userAgent,\n      oldCookie,\n    );\n    console.log('res->pushCookies', res);\n    await checkSuccessAndUpdate(accountInfo, res, cookieMap);\n    await domainStatusStorage.update({\n      pushing: false,\n    });\n    return res;\n  } catch (e) {\n    console.error('pushCookies fail err', e);\n    await domainStatusStorage.update({\n      pushing: false,\n    });\n    return Promise.reject(e);\n  }\n};\n\nexport const pushMultipleDomainCookies = async (\n  domainCookies: { domain: string; cookies: ICookie[]; localStorageItems: ILocalStorageItem[]; userAgent?: string }[],\n): Promise<WriteResponse> => {\n  const accountInfo = await accountStorage.get();\n  try {\n    const domainStatus = await domainStatusStorage.get();\n    if (domainStatus.pushing) return Promise.reject('cookie is pushing');\n    await domainStatusStorage.update({\n      pushing: true,\n    });\n    const oldCookie = await readCookiesMapWithStatus(accountInfo);\n    const [res, cookieMap] = await mergeAndWriteMultipleDomainCookies(accountInfo, domainCookies, oldCookie);\n    await domainStatusStorage.update({\n      pushing: false,\n    });\n    await checkSuccessAndUpdate(accountInfo, res, cookieMap);\n    return res;\n  } catch (e) {\n    console.error('pushMultipleDomainCookies fail err', e);\n    await domainStatusStorage.update({\n      pushing: false,\n    });\n    return Promise.reject(e);\n  }\n};\n\nexport const removeCookies = async (domain: string): Promise<WriteResponse> => {\n  const accountInfo = await accountStorage.get();\n  try {\n    const domainStatus = await domainStatusStorage.get();\n    if (domainStatus.pushing) return Promise.reject('the cookie is pushing');\n    await domainStatusStorage.update({\n      pushing: true,\n    });\n    // const oldCookie = await cookieStorage.get();\n    const oldCookie = await readCookiesMapWithStatus(accountInfo);\n    const [res, cookieMap] = await removeAndWriteCookies(accountInfo, domain, oldCookie);\n    await domainStatusStorage.update({\n      pushing: false,\n    });\n    await checkSuccessAndUpdate(accountInfo, res, cookieMap);\n    return res;\n  } catch (e) {\n    console.error('removeCookies fail err', e);\n    await domainStatusStorage.update({\n      pushing: false,\n    });\n    return Promise.reject(e);\n  }\n};\n\nexport const removeCookieItem = async (domain: string, id: string): Promise<WriteResponse> => {\n  const accountInfo = await accountStorage.get();\n  try {\n    const domainStatus = await domainStatusStorage.get();\n    if (domainStatus.pushing) return Promise.reject('the cookie is pushing');\n    await domainStatusStorage.update({\n      pushing: true,\n    });\n    // const oldCookie = await cookieStorage.get();\n    const oldCookie = await readCookiesMapWithStatus(accountInfo);\n    const [res, cookieMap] = await removeAndWriteCookies(accountInfo, domain, oldCookie, id);\n    await domainStatusStorage.update({\n      pushing: false,\n    });\n    await checkSuccessAndUpdate(accountInfo, res, cookieMap);\n    return res;\n  } catch (e) {\n    console.error('removeCookieItem fail err', e);\n    await domainStatusStorage.update({\n      pushing: false,\n    });\n    return Promise.reject(e);\n  }\n};\n\nexport const editCookieItem = async (domain: string, name: string): Promise<WriteResponse> => {\n  const accountInfo = await accountStorage.get();\n  try {\n    const domainStatus = await domainStatusStorage.get();\n    if (domainStatus.pushing) return Promise.reject('the cookie is pushing');\n    await domainStatusStorage.update({\n      pushing: true,\n    });\n    // const oldCookie = await cookieStorage.get();\n    const oldCookie = await readCookiesMapWithStatus(accountInfo);\n    const [res, cookieMap] = await removeAndWriteCookies(accountInfo, domain, oldCookie, name);\n    await domainStatusStorage.update({\n      pushing: false,\n    });\n    await checkSuccessAndUpdate(accountInfo, res, cookieMap);\n    return res;\n  } catch (e) {\n    console.error('removeCookieItem fail err', e);\n    await domainStatusStorage.update({\n      pushing: false,\n    });\n    return Promise.reject(e);\n  }\n};\n\nexport class CookieOperator {\n  static async prepare() {\n    const cloudflareInfo = await accountStorage.get();\n    const domainStatus = await domainStatusStorage.get();\n    if (domainStatus.pushing) return Promise.reject('the cookie is pushing');\n    return { cloudflareInfo };\n  }\n\n  static async setPushing(open: boolean) {\n    await domainStatusStorage.update({\n      pushing: open,\n    });\n  }\n\n  static async editCookieItem(host: string, oldItem: ICookie, newItem: ICookie) {\n    try {\n      const { cloudflareInfo } = await this.prepare();\n      await this.setPushing(true);\n      const oldCookie = await readCookiesMapWithStatus(cloudflareInfo);\n      const [res, cookieMap] = await editAndWriteCookies(cloudflareInfo, host, oldCookie, oldItem, newItem);\n      await this.setPushing(false);\n\n      await checkSuccessAndUpdate(cloudflareInfo, res, cookieMap);\n\n      return res;\n    } catch (e) {\n      console.error('removeCookieItem fail err', e);\n      await this.setPushing(false);\n      return Promise.reject(e);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/shared/lib/github/api.ts",
    "content": "import { Octokit, RestEndpointMethodTypes } from '@octokit/rest';\nimport { arrayBufferToBase64, encodeCookiesMap, ICookiesMap } from '@sync-your-cookie/protobuf';\nimport { accountStorage } from '@sync-your-cookie/storage/lib/accountStorage';\nimport { IStorageItem, settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';\n\nexport class GithubApi {\n  private clientId: string;\n  private clientSecret: string;\n  private accessToken?: string | null = null;\n\n  private prefix = 'sync-your-cookie_';\n\n  static instance: GithubApi;\n\n  octokit!: Octokit;\n\n  private inited = false;\n\n  private initDefault = false;\n\n  constructor(clientId: string, clientSecret: string, initDefault: boolean = false) {\n    this.clientId = clientId;\n    this.clientSecret = clientSecret;\n    this.accessToken = accountStorage.getSnapshot()?.githubAccessToken;\n    this.initDefault = initDefault;\n    this.subscribe();\n    this.init();\n  }\n\n  public static getInstance(clientId: string, clientSecret: string, initDefault: boolean = false): GithubApi {\n    if (!GithubApi.instance) {\n      GithubApi.instance = new GithubApi(clientId, clientSecret, initDefault);\n    }\n    return GithubApi.instance;\n  }\n\n  async newOctokit() {\n    if (this.accessToken) {\n      this.octokit = new Octokit({ auth: this.accessToken });\n      // this.octokit.hook.before('request', options => {\n      //   console.log('请求 URL:', options.url);\n      //   console.log('请求头:', options.headers);\n      // });\n    }\n  }\n\n  async init() {\n    if (this.inited) {\n      return;\n    }\n    this.reload();\n    this.inited = true;\n  }\n\n  reload() {\n    this.newOctokit();\n    this.initStorageKeyList();\n  }\n\n  async getSyncGists() {\n    const res = await this.listGists();\n    const fullList = res.data;\n    const syncGist = fullList.find(gist => {\n      const files = gist.files;\n      const keys = Object.keys(files);\n      const hasSyncFile = keys.some(key => key.startsWith(this.prefix));\n      return hasSyncFile;\n    });\n    return syncGist;\n  }\n\n  async initStorageKeyList() {\n    if (!this.octokit) {\n      return;\n    }\n    let syncGist = await this.getSyncGists();\n\n    if (!syncGist && this.initDefault) {\n      console.log('No sync gists found, creating one...');\n      const content = await this.initContent();\n      await this.createGist('Sync Your Cookie Gist', `${this.prefix}Default`, content, false);\n      // syncGists.push(newGist.data);\n      syncGist = await this.getSyncGists();\n    }\n    if (syncGist) {\n      await this.setStorageKeyList(syncGist);\n    }\n  }\n\n  async initContent() {\n    const cookiesMap: ICookiesMap = {\n      updateTime: Date.now(),\n      createTime: Date.now(),\n      domainCookieMap: {},\n    };\n    let encodingStr = '';\n    const protobufEncoding = settingsStorage.getSnapshot()?.protobufEncoding;\n    if (protobufEncoding) {\n      const buffered = await encodeCookiesMap(cookiesMap);\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      encodingStr = arrayBufferToBase64(buffered as any);\n    } else {\n      encodingStr = JSON.stringify(cookiesMap);\n    }\n    return encodingStr;\n  }\n\n  async setStorageKeyList(gist: RestEndpointMethodTypes['gists']['list']['response']['data'][number]) {\n    const files = gist.files;\n    const storageKeys: IStorageItem[] = [];\n    if (files) {\n      const currentStorageKey = settingsStorage.getSnapshot()?.storageKey;\n      let currentStorageExist = false;\n      for (const filename in files) {\n        const file = files[filename];\n        if (filename.startsWith(this.prefix)) {\n          const tempValue = filename.replace(this.prefix, '');\n          if (!currentStorageExist) {\n            if (currentStorageKey && tempValue === currentStorageKey) {\n              currentStorageExist = true;\n            }\n          }\n          storageKeys.push({\n            value: tempValue,\n            label: tempValue,\n            rawUrl: file.raw_url,\n            gistId: gist.id,\n          });\n          // storageKeys.push(file[0].replace(this.prefix, ''));\n        }\n      }\n      console.log('storageKeys', storageKeys, gist.id);\n      settingsStorage.update({\n        storageKeyList: storageKeys,\n        storageKey: currentStorageExist ? currentStorageKey : storageKeys[0]?.value,\n        storageKeyGistId: gist.id,\n        gistHtmlUrl: gist.html_url,\n      });\n    } else {\n      console.log('No files found in gist', gist.id);\n    }\n    // const keys = Object.keys(files);\n    // const storageKeys = keys.filter(key => key.startsWith(this.prefix)).map(key => key.replace(this.prefix, ''));\n    // console.log('storageKeys', storageKeys);\n    // return storageKeys;\n  }\n\n  subscribe() {\n    accountStorage.subscribe(async () => {\n      const accessToken = accountStorage.getSnapshot()?.githubAccessToken;\n      if (this.accessToken === accessToken || !accessToken) {\n        return;\n      }\n      this.accessToken = accessToken;\n      console.log('GithubApi accountStorage changed -> this.accessToken', this.accessToken);\n      this.inited = false;\n      this.init();\n    });\n  }\n\n  // 用 code 换取 access_token\n  async fetchAccessToken(code: string): Promise<string> {\n    const url = 'https://github.com/login/oauth/access_token';\n    const params = {\n      client_id: this.clientId,\n      client_secret: this.clientSecret,\n      code,\n    };\n    const headers = { Accept: 'application/json', 'Content-Type': 'application/json' };\n    const res = await fetch(url, {\n      method: 'POST',\n      headers,\n      body: JSON.stringify(params),\n    });\n    const data = await res.json();\n    if (data.access_token) {\n      this.accessToken = data.access_token;\n      return this.accessToken || '';\n    }\n    throw new Error('获取 access_token 失败');\n  }\n\n  // 用 access_token 获取用户信息\n  async fetchUser() {\n    this.ensureToken();\n    // const res = await this.octokit.users.getById();\n\n    const res = await fetch('https://api.github.com/user', {\n      headers: { Authorization: `token ${this.accessToken}` },\n    });\n    return res.json() as Promise<RestEndpointMethodTypes['users']['getById']['response']['data']>;\n  }\n\n  async request(method: string, path: string, payload: Record<string, any> = {}) {\n    this.ensureToken();\n    const res = await this.octokit.request(`${method} ${path}`, {\n      ...payload,\n      headers: {\n        // Authorization: `Bearer ${this.accessToken as string}`,\n        'X-GitHub-Api-Version': '2022-11-28',\n      },\n    });\n    if (res.status !== 200) {\n      return Promise.reject(res);\n    }\n    return res.data;\n  }\n\n  async get(path: string) {\n    return this.request('GET', path);\n  }\n\n  async post(path: string, payload: Record<string, any> = {}) {\n    return this.request('POST', path, payload);\n  }\n\n  async patch(path: string, payload: Record<string, any> = {}) {\n    return this.request('PATCH', path, payload);\n  }\n\n  // 获取 gist 列表\n  async listGists() {\n    const res = await this.octokit.gists.list();\n    return res;\n  }\n\n  // 创建 gist\n  async createGist(description: string, filename: string, content: string, publicGist = false) {\n    return this.octokit.gists.create({\n      description: description,\n      public: publicGist,\n      files: {\n        [filename]: {\n          content: content,\n        },\n      },\n    });\n  }\n\n  async getGist(gistId: string) {\n    return this.octokit.gists.get({ gist_id: gistId });\n  }\n\n  // 更新 gist\n  async updateGist(gistId: string, filename: string, content: string) {\n    // const { data: gist } = await this.octokit.gists.get({\n    //   gist_id: gistId,\n    // });\n    // console.log('files', gist.files);\n    // const existFiles = gist.files || {};\n    const syncFileName = filename.startsWith(this.prefix) ? filename : this.prefix + filename;\n    const res = await this.octokit.gists.update({\n      gist_id: gistId,\n      files: {\n        [syncFileName]: {\n          content: content,\n        },\n      },\n    });\n    // update settingsStorage based result\n    this.setStorageKeyList(res.data as unknown as RestEndpointMethodTypes['gists']['list']['response']['data'][number]);\n    return res;\n    // return this.patch(`/gists/${gistId}`, {\n    //   files: {\n    //     [filename]: {\n    //       content: content,\n    //     },\n    //   },\n    // });\n  }\n\n  async addGistFile(gistId: string, filename: string) {\n    return this.updateGist(gistId, filename, await this.initContent());\n  }\n\n  async deleteGistFile(gistId: string, filename: string) {\n    const syncFileName = filename.startsWith(this.prefix) ? filename : this.prefix + filename;\n    return this.octokit.gists.update({\n      gist_id: gistId,\n      files: {\n        [syncFileName]: null,\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      } as any,\n    });\n  }\n\n  // 删除 gist\n  async deleteGist(gistId: string) {\n    return this.octokit.gists.delete({ gist_id: gistId });\n  }\n\n  private ensureToken() {\n    if (!this.accessToken) {\n      this.accessToken = accountStorage.getSnapshot()?.githubAccessToken;\n      console.log('this.accessToken', this.accessToken);\n      this.newOctokit();\n    }\n    if (!this.accessToken) {\n      throw new Error('请先获取 access_token');\n    }\n  }\n\n  async fetchRawContent(rawUrl: string) {\n    const content = await fetch(rawUrl).then(res => res.text());\n    return content;\n  }\n}\n\nexport const scope = 'gist';\nexport const clientId = 'Ov23liyhOkJsj8FzPlm0';\nconst clientSecret = '';\n\n// export const githubApi = new GithubApi(clientId, clientSecret);\n\nexport const initGithubApi = async (initDefault = false) => {\n  console.log('initGithubApi finish');\n  GithubApi.getInstance(clientId, clientSecret, initDefault);\n};\n"
  },
  {
    "path": "packages/shared/lib/github/index.ts",
    "content": "export * from './api';\n"
  },
  {
    "path": "packages/shared/lib/hoc/index.ts",
    "content": "import { withSuspense } from './withSuspense';\nimport { withErrorBoundary } from './withErrorBoundary';\n\nexport { withSuspense, withErrorBoundary };\n"
  },
  {
    "path": "packages/shared/lib/hoc/withErrorBoundary.tsx",
    "content": "import type { ComponentType, ErrorInfo, ReactElement } from 'react';\nimport { Component } from 'react';\n\nclass ErrorBoundary extends Component<\n  {\n    children: ReactElement;\n    fallback: ReactElement;\n  },\n  {\n    hasError: boolean;\n  }\n> {\n  state = { hasError: false };\n\n  static getDerivedStateFromError() {\n    return { hasError: true };\n  }\n\n  componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n    console.error(error, errorInfo);\n  }\n\n  render() {\n    if (this.state.hasError) {\n      return this.props.fallback;\n    }\n\n    return this.props.children;\n  }\n}\n\nexport function withErrorBoundary<T extends Record<string, unknown>>(\n  Component: ComponentType<T>,\n  ErrorComponent: ReactElement,\n) {\n  return function WithErrorBoundary(props: T) {\n    return (\n      <ErrorBoundary fallback={ErrorComponent}>\n        <Component {...props} />\n      </ErrorBoundary>\n    );\n  };\n}\n"
  },
  {
    "path": "packages/shared/lib/hoc/withSuspense.tsx",
    "content": "import { ComponentType, ReactElement, Suspense } from 'react';\n\nexport function withSuspense<T extends Record<string, unknown>>(\n  Component: ComponentType<T>,\n  SuspenseComponent: ReactElement,\n) {\n  return function WithSuspense(props: T) {\n    return (\n      <Suspense fallback={SuspenseComponent}>\n        <Component {...props} />\n      </Suspense>\n    );\n  };\n}\n"
  },
  {
    "path": "packages/shared/lib/hooks/index.ts",
    "content": "import { catchHandler, useCookieAction } from './useCookieAction';\nimport { useStorage } from './useStorage';\nimport { useStorageSuspense } from './useStorageSuspense';\nexport { catchHandler, useCookieAction, useStorage, useStorageSuspense };\n"
  },
  {
    "path": "packages/shared/lib/hooks/useCookieAction.ts",
    "content": "import {\n  MessageErrorCode,\n  pullCookieUsingMessage,\n  pushCookieUsingMessage,\n  removeCookieUsingMessage,\n} from '@lib/message';\nimport { domainConfigStorage } from '@sync-your-cookie/storage/lib/domainConfigStorage';\nimport { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';\n\nimport { toast as Toast } from 'sonner';\nimport { useStorageSuspense } from './index';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const catchHandler = (err: any, scene: 'push' | 'pull' | 'remove' | 'delete' | 'edit', toast: typeof Toast) => {\n  const defaultMsg = `${scene} fail`;\n  const code = err?.code;\n  const settingErrors = [\n    MessageErrorCode.AccountCheck,\n    MessageErrorCode.CloudflareNotFoundRoute,\n    MessageErrorCode.DecodeFailed,\n    MessageErrorCode.DecryptFailed,\n  ];\n  if (settingErrors.includes(code)) {\n    toast.error(err?.msg || err?.result?.message || defaultMsg, {\n      action: {\n        label: 'go to settings',\n        onClick: () => {\n          chrome.runtime.openOptionsPage();\n        },\n      },\n    });\n  } else {\n    toast.error(err?.msg || defaultMsg);\n  }\n  console.log('err', err);\n};\n\nexport const useCookieAction = (host: string, toast: typeof Toast) => {\n  const domainStatus = useStorageSuspense(domainStatusStorage);\n  const domainConfig = useStorageSuspense(domainConfigStorage);\n\n  const handlePush = async (selectedHost = host, sourceUrl?: string, favIconUrl?: string) => {\n    return pushCookieUsingMessage({\n      host: selectedHost,\n      sourceUrl,\n      favIconUrl,\n    })\n      .then(res => {\n        if (res.isOk) {\n          toast.success('Pushed success');\n        } else {\n          toast.error(res.msg || 'Pushed fail');\n        }\n        console.log('res', res);\n      })\n      .catch(err => {\n        catchHandler(err, 'push', toast);\n      });\n  };\n\n  const handlePull = async (activeTabUrl: string, selectedDomain = host, reload = true) => {\n    return pullCookieUsingMessage({\n      activeTabUrl: activeTabUrl,\n      domain: selectedDomain,\n      reload,\n    })\n      .then(res => {\n        console.log('res', res);\n        if (res.isOk) {\n          toast.success('Pull success');\n        } else {\n          toast.error(res.msg || 'Pull fail');\n        }\n      })\n      .catch(err => {\n        catchHandler(err, 'pull', toast);\n      });\n  };\n\n  const handleRemove = async (selectedDomain = host) => {\n    return removeCookieUsingMessage({\n      domain: selectedDomain,\n    })\n      .then(async res => {\n        console.log('res', res);\n        if (res.isOk) {\n          toast.success(res.msg || 'success');\n          await domainConfigStorage.removeItem(host);\n        } else {\n          toast.error(res.msg || 'Removed fail');\n        }\n        console.log('res', res);\n      })\n      .catch(err => {\n        catchHandler(err, 'remove', toast);\n      });\n  };\n\n  return {\n    // domainConfig: domainConfig as typeof domainConfig,\n    pulling: domainStatus.pulling,\n    pushing: domainStatus.pushing,\n    domainItemConfig: domainConfig.domainMap[host] || {},\n    domainItemStatus: domainStatus.domainMap[host] || {},\n    getDomainItemConfig: (selectedDomain: string) => {\n      return domainConfig.domainMap[selectedDomain] || {};\n    },\n    getDomainItemStatus: (selectedDomain: string) => {\n      return domainStatus.domainMap[selectedDomain] || {};\n    },\n    toggleAutoPullState: domainConfigStorage.toggleAutoPullState,\n    toggleAutoPushState: domainConfigStorage.toggleAutoPushState,\n    togglePullingState: domainStatusStorage.togglePullingState,\n    togglePushingState: domainStatusStorage.togglePushingState,\n    handlePush,\n    handlePull,\n    handleRemove,\n  };\n};\n"
  },
  {
    "path": "packages/shared/lib/hooks/useStorage.ts",
    "content": "import { useSyncExternalStore } from 'react';\nimport { BaseStorage } from '@sync-your-cookie/storage';\n\nexport function useStorage<\n  Storage extends BaseStorage<Data>,\n  Data = Storage extends BaseStorage<infer Data> ? Data : unknown,\n>(storage: Storage) {\n  const _data = useSyncExternalStore<Data | null>(storage.subscribe, storage.getSnapshot);\n\n  // eslint-disable-next-line\n  // @ts-ignore\n  if (!storageMap.has(storage)) {\n    // eslint-disable-next-line\n    // @ts-ignore\n    storageMap.set(storage, wrapPromise(storage.get()));\n  }\n  if (_data !== null) {\n    // eslint-disable-next-line\n    // @ts-ignore\n    storageMap.set(storage, { read: () => _data });\n  }\n  // eslint-disable-next-line\n  // @ts-ignore\n  return _data ?? (storageMap.get(storage)!.read() as Data);\n}\n"
  },
  {
    "path": "packages/shared/lib/hooks/useStorageSuspense.tsx",
    "content": "import { BaseStorage } from '@sync-your-cookie/storage';\nimport { useSyncExternalStore } from 'react';\n\ntype WrappedPromise = ReturnType<typeof wrapPromise>;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst storageMap: Map<BaseStorage<any>, WrappedPromise> = new Map();\n\nexport function useStorageSuspense<\n  Storage extends BaseStorage<Data>,\n  Data = Storage extends BaseStorage<infer Data> ? Data : unknown,\n>(storage: Storage) {\n  const _data = useSyncExternalStore<Data | null>(storage.subscribe, storage.getSnapshot);\n  if (!storageMap.has(storage)) {\n    storageMap.set(storage, wrapPromise(storage.get()));\n  }\n  if (_data !== null) {\n    storageMap.set(storage, { read: () => _data });\n  }\n\n  return _data ?? (storageMap.get(storage)!.read() as Data);\n}\n\nfunction wrapPromise<R>(promise: Promise<R>) {\n  let status = 'pending';\n  let result: R;\n  const suspender = promise.then(\n    r => {\n      status = 'success';\n      result = r;\n    },\n    e => {\n      status = 'error';\n      result = e;\n    },\n  );\n\n  return {\n    read() {\n      switch (status) {\n        case 'pending':\n          throw suspender;\n        case 'error':\n          throw result;\n        default:\n          return result;\n      }\n    },\n  };\n}\n"
  },
  {
    "path": "packages/shared/lib/message/index.ts",
    "content": "import { pushCookies } from '@lib/cookie';\nimport { ICookie, ILocalStorageItem } from '@sync-your-cookie/protobuf';\nimport { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';\nimport pTimeout from 'p-timeout';\nexport type { ICookie };\nexport enum MessageType {\n  PushCookie = 'PushCookie',\n  PullCookie = 'PullCookie',\n  RemoveCookie = 'RemoveCookie',\n  RemoveCookieItem = 'RemoveCookieItem',\n  EditCookieItem = 'EditCookieItem',\n  // LocalStorage\n  GetLocalStorage = 'GetLocalStorage',\n  SetLocalStorage = 'SetLocalStorage',\n}\n\nexport enum MessageErrorCode {\n  AccountCheck = 'AccountCheck',\n  CloudflareNotFoundRoute = 'CloudflareNotFoundRoute',\n  DecryptFailed = 'DecryptFailed',\n  DecodeFailed = 'DecodeFailed',\n}\n\nexport type PushCookieMessagePayload = {\n  host: string;\n  sourceUrl?: string;\n  favIconUrl?: string;\n};\n\nexport type DomainPayload = {\n  domain: string;\n};\n\nexport type RemoveCookieMessagePayload = {\n  domain: string;\n};\n\nexport type RemoveCookieItemMessagePayload = {\n  domain: string;\n  id: string;\n};\n\nexport type PullCookieMessagePayload = {\n  domain: string;\n  activeTabUrl: string;\n  reload: boolean;\n};\n\nexport type EditCookieItemMessagePayload = {\n  domain: string;\n  oldItem: ICookie;\n  newItem: ICookie;\n};\n\nexport type SetLocalStorageMessagePayload = {\n  domain: string;\n  value: ILocalStorageItem[];\n  onlyKey?: string;\n};\n\nexport type MessageMap = {\n  [MessageType.PushCookie]: {\n    type: MessageType.PushCookie;\n    payload: PushCookieMessagePayload;\n  };\n  [MessageType.RemoveCookie]: {\n    type: MessageType.RemoveCookie;\n    payload: RemoveCookieMessagePayload;\n  };\n  [MessageType.PullCookie]: {\n    type: MessageType.PullCookie;\n    payload: PullCookieMessagePayload;\n  };\n  [MessageType.RemoveCookieItem]: {\n    type: MessageType.RemoveCookieItem;\n    payload: RemoveCookieItemMessagePayload;\n  };\n  [MessageType.EditCookieItem]: {\n    type: MessageType.EditCookieItem;\n    payload: EditCookieItemMessagePayload;\n  };\n  // LocalStorage\n  [MessageType.GetLocalStorage]: {\n    type: MessageType.GetLocalStorage;\n    payload: DomainPayload;\n  };\n  [MessageType.SetLocalStorage]: {\n    type: MessageType.SetLocalStorage;\n    payload: SetLocalStorageMessagePayload;\n  };\n};\n\n// export type Message<T extends MessageType = MessageType> = {\n//   type: T;\n//   payload: MessagePayloadMap[T];\n// };\n\nexport type Message<T extends MessageType = MessageType> = MessageMap[T];\n\nexport type SendResponse = {\n  isOk: boolean;\n  msg: string;\n  result?: unknown;\n  code?: MessageErrorCode;\n};\n\nexport function sendMessage<T extends MessageType>(message: Message<T>, isTab = false, useTimeout: boolean = false) {\n  console.log('message', message);\n  const send = (resolve: (value: SendResponse | PromiseLike<SendResponse>) => void, reject: (reason?: any) => void) => {\n    chrome.runtime.sendMessage(message, function (result: SendResponse) {\n      console.log('sendMessage->message', message);\n      if (result?.isOk) {\n        resolve(result);\n      } else {\n        reject(result as SendResponse);\n      }\n    });\n  };\n  const fn = () => {\n    if (isTab) {\n      return new Promise<SendResponse>((resolve, reject) => {\n        chrome.tabs.query({ active: true, currentWindow: true }, async function (tabs) {\n          if (tabs.length === 0) {\n            // const allOpendTabs = await chrome.tabs.query({});\n\n            console.log('No active tab found, try alternative way');\n            // reject({ isOk: false, msg: 'No active tab found' } as SendResponse);\n            send(resolve, reject);\n            return;\n          }\n          chrome.tabs.sendMessage(tabs[0].id!, message, function (result) {\n            console.log('isTab', isTab, 'result->', result);\n            if (result?.isOk) {\n              resolve(result);\n            } else {\n              reject(result as SendResponse);\n            }\n          });\n        });\n      });\n    }\n    return new Promise<SendResponse>((resolve, reject) => {\n      send(resolve, reject);\n    });\n  };\n  if (useTimeout) {\n    return pTimeout(fn(), {\n      milliseconds: 5000,\n      fallback: () => {\n        return { isOk: false, msg: 'Timeout' } as SendResponse;\n      },\n    });\n  }\n  return fn();\n}\n\nexport function pushCookieUsingMessage(payload: PushCookieMessagePayload) {\n  return sendMessage<MessageType.PushCookie>({\n    payload,\n    type: MessageType.PushCookie,\n  });\n}\n\nexport function removeCookieUsingMessage(payload: RemoveCookieMessagePayload) {\n  return sendMessage<MessageType.RemoveCookie>({\n    payload,\n    type: MessageType.RemoveCookie,\n  });\n}\n\nexport function pullCookieUsingMessage(payload: PullCookieMessagePayload) {\n  return sendMessage<MessageType.PullCookie>({\n    payload,\n    type: MessageType.PullCookie,\n  });\n}\n\nexport function removeCookieItemUsingMessage(payload: RemoveCookieItemMessagePayload) {\n  const sendType = MessageType.RemoveCookieItem;\n  return sendMessage<typeof sendType>({\n    payload,\n    type: sendType,\n  });\n}\n\nexport function editCookieItemUsingMessage(payload: EditCookieItemMessagePayload) {\n  const sendType = MessageType.EditCookieItem;\n  return sendMessage<typeof sendType>({\n    payload,\n    type: sendType,\n  });\n}\n\nexport const getTabsByHost = async (host: string) => {\n  return new Promise<chrome.tabs.Tab[]>((resolve, reject) => {\n    try {\n      chrome.tabs.query({}, function (tabs) {\n        const matchedTabs = tabs.filter(tab => tab.url && tab.id && tab.url.includes(host));\n        resolve(matchedTabs);\n      });\n    } catch (error) {\n      reject(error);\n    }\n  });\n};\n\nexport const sendGetLocalStorageMessage = async (host: string, isTry = true) => {\n  return new Promise<NonNullable<Parameters<typeof pushCookies>[2]>>(async (resolve, reject) => {\n    const myResolve = (args: any) => {\n      settingsStorage.update({\n        localStorageGetting: false,\n      });\n      resolve(args);\n    };\n    const myReject = (err: any) => {\n      settingsStorage.update({\n        localStorageGetting: false,\n      });\n      reject(err);\n    };\n    settingsStorage.update({\n      localStorageGetting: true,\n    });\n    await sendMessage(\n      {\n        type: MessageType.GetLocalStorage,\n        payload: {\n          domain: host,\n        },\n      },\n      true,\n    )\n      .then(res => {\n        if (res.isOk) {\n          const localStorageItems = (res.result as any[]) || [];\n          myResolve(localStorageItems);\n        } else {\n          throw new Error(res.msg || 'getLocalStorage fail');\n        }\n      })\n      .catch(async (err: any) => {\n        if (isTry == false) {\n          myReject(err);\n          return;\n        }\n\n        console.error('getLocalStorage and try reload fetch again', err);\n        const matchedTabs = await getTabsByHost(host);\n        if (matchedTabs.length === 0) {\n          await chrome.tabs.create({\n            url: 'https://' + host,\n          });\n          // window.open(host, '_blank');\n        } else {\n          const activeTab = matchedTabs.find(tab => tab.active);\n          if (activeTab) {\n            chrome.tabs.reload(activeTab.id!);\n          } else {\n            matchedTabs.forEach(function (tab) {\n              chrome.tabs.reload(tab.id!);\n            });\n          }\n        }\n        setTimeout(async () => {\n          try {\n            const localStorageItems = await sendGetLocalStorageMessage(host, false);\n            myResolve(localStorageItems);\n          } catch (error) {\n            console.log('error', error);\n            myReject(error);\n          }\n        }, 500);\n      })\n      .catch(err => {\n        myReject(err);\n      });\n  });\n};\n"
  },
  {
    "path": "packages/shared/lib/utils/index.ts",
    "content": "import { ErrorCode, WriteResponse } from '@lib/cloudflare';\nimport { MessageErrorCode, SendResponse } from '@lib/message';\nimport { accountStorage } from '@sync-your-cookie/storage/lib/accountStorage';\nimport { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';\n\nexport function debounce<T = unknown>(func: (...args: T[]) => void, timeout = 300) {\n  let timer: number | null | NodeJS.Timeout = null;\n  return (...args: T[]) => {\n    timer && clearTimeout(timer);\n    timer = setTimeout(() => {\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n      // @ts-ignore\n      func.apply(this, args);\n    }, timeout);\n  };\n}\n\nconst successSceneMap = {\n  push: 'Pushed',\n  pull: 'Pulled',\n  remove: 'Removed',\n  delete: 'Deleted',\n  edit: 'Edited',\n};\n\nexport function checkResponseAndCallback(\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  res: WriteResponse | Error | any,\n  scene: 'push' | 'pull' | 'remove' | 'delete' | 'edit',\n  callback: (response?: SendResponse) => void,\n) {\n  const accountInfo = accountStorage.getSnapshot();\n  if (accountInfo?.selectedProvider === 'github') {\n    const statusCode = res?.status;\n    if (statusCode === 200 || statusCode === 201 || statusCode === 204) {\n      callback({ isOk: true, msg: `${successSceneMap[scene]} success` });\n    } else {\n      const defaultErrMsg =\n        res?.message?.toLowerCase().includes?.(scene) || ((statusCode || res.code) && res?.message)\n          ? res?.message\n          : `${scene} fail (status:${statusCode || res.code}), please try again.`;\n      callback({ isOk: false, code: res?.code, msg: defaultErrMsg, result: res });\n    }\n  } else {\n    if ((res as WriteResponse)?.success) {\n      callback({ isOk: true, msg: `${successSceneMap[scene]} success` });\n    } else {\n      const cloudFlareErrors = [ErrorCode.NotFoundRoute, ErrorCode.NamespaceIdError, ErrorCode.AuthenicationError];\n      const isAccountError = res?.errors?.length && cloudFlareErrors.includes(res.errors[0].code);\n      if (isAccountError) {\n        callback({\n          isOk: false,\n          msg:\n            res.errors[0].code === ErrorCode.NamespaceIdError\n              ? 'cloudflare namespace Id info is incorrect.'\n              : 'cloudflare account info is incorrect.',\n          code: MessageErrorCode.CloudflareNotFoundRoute,\n          result: res,\n        });\n      } else {\n        const defaultErrMsg =\n          res?.message?.toLowerCase().includes?.(scene) || (res?.code && res?.message)\n            ? res?.message\n            : `${scene} fail, please try again.`;\n        callback({ isOk: false, code: res?.code, msg: defaultErrMsg, result: res });\n      }\n    }\n  }\n}\nfunction addProtocol(uri: string) {\n  return uri.startsWith('http') ? uri : `http://${uri}`;\n}\n\nexport async function extractDomainAndPort(url: string, isRemoveWWW = true): Promise<[string, string, string]> {\n  let urlObj: URL;\n  try {\n    const maybeValidUrl = addProtocol(url);\n    urlObj = new URL(maybeValidUrl);\n  } catch (error) {\n    return [url, '', url];\n  }\n  let hostname = urlObj.hostname;\n  const port = urlObj.port;\n  hostname = hostname.replace('http://', '').replace('https://', '');\n  if (isRemoveWWW) {\n    hostname = hostname.replace('www.', '');\n  }\n  // match ip address\n  if (/^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(hostname)) {\n    return [hostname, port, hostname];\n  }\n  if (hostname.split('.').length <= 2) {\n    return [hostname, port, hostname];\n  }\n  const includeLocalStorage = settingsStorage.getSnapshot()?.includeLocalStorage;\n  if (includeLocalStorage) {\n    return [hostname, port, hostname];\n  }\n\n  return new Promise(resolve => {\n    try {\n      chrome.cookies.getAll(\n        {\n          url,\n        },\n        async cookies => {\n          console.log('cookies', cookies);\n          if (cookies) {\n            const hasHostCookie = cookies.find(item => item.domain.includes(hostname));\n            if (hasHostCookie) {\n              resolve([hostname, port, hostname]);\n            } else {\n              const domain = cookies[0].domain;\n              if (domain.startsWith('.')) {\n                resolve([domain.slice(1), port, hostname]);\n              } else {\n                resolve([domain, port, hostname]);\n              }\n            }\n          } else {\n            // const match = hostname.match(/([^.]+\\.[^.]+)$/);\n            resolve([hostname, port, hostname]);\n          }\n        },\n      );\n    } catch (error) {\n      console.error('error', error);\n      resolve([hostname, port, hostname]);\n    }\n  });\n}\n"
  },
  {
    "path": "packages/shared/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/shared\",\n  \"version\": \"0.0.1\",\n  \"description\": \"chrome extension shared code\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"files\": [\n    \"dist/**\"\n  ],\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"scripts\": {\n    \"clean\": \"rimraf ./dist && rimraf .turbo\",\n    \"build\": \"tsup index.ts --format esm,cjs --dts --external react,chrome\",\n    \"dev\": \"tsc -w\",\n    \"lint\": \"eslint . --ext .ts,.tsx\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write\",\n    \"type-check\": \"tsc --noEmit\",\n    \"proto\": \"pbjs -o ./lib/protobuf/proto/cookie.js -w es6 -t static-module ./lib/protobuf/proto/*.proto && pbts ./lib/protobuf/proto/cookie.js -o ./lib/protobuf/proto/cookie.d.ts\"\n  },\n  \"dependencies\": {\n    \"@octokit/rest\": \"^22.0.0\",\n    \"@octokit/types\": \"^15.0.1\",\n    \"p-timeout\": \"^6.1.4\",\n    \"pako\": \"^2.1.0\",\n    \"protobufjs\": \"^7.3.2\"\n  },\n  \"devDependencies\": {\n    \"@sync-your-cookie/protobuf\": \"workspace:*\",\n    \"@sync-your-cookie/storage\": \"workspace:*\",\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\",\n    \"@types/pako\": \"^2.0.3\",\n    \"sonner\": \"^1.5.0\",\n    \"tsup\": \"8.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/shared/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"jsx\": \"react-jsx\",\n    \"checkJs\": false,\n    \"allowJs\": false,\n    \"baseUrl\": \".\",\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"paths\": {\n      \"@lib/*\": [\"lib/*\"]\n    },\n    \"types\": [\"chrome\", \"node\", \"@octokit/types\"]\n  },\n  \"include\": [\"index.ts\", \"lib\"],\n}\n"
  },
  {
    "path": "packages/shared/tsup.config.ts",
    "content": "import { defineConfig } from 'tsup';\n\nexport default defineConfig({\n  treeshake: true,\n  splitting: false,\n  format: ['cjs', 'esm'],\n  dts: true,\n  external: ['chrome'],\n});\n"
  },
  {
    "path": "packages/storage/index.ts",
    "content": "export * from './lib';\n"
  },
  {
    "path": "packages/storage/lib/accountStorage.ts",
    "content": "import { BaseStorage, createStorage, StorageType } from './base';\n\nexport interface AccountInfo {\n  accountId?: string;\n  namespaceId?: string;\n  token?: string;\n  selectedProvider?: 'cloudflare' | 'github';\n  githubAccessToken?: string;\n  avatarUrl?: string;\n  name?: string | null;\n  bio?: string | null;\n  email?: string | null;\n}\nconst key = 'cloudflare-account-storage-key';\nconst cacheStorageMap = new Map();\n\nconst initStorage = (): BaseStorage<AccountInfo> => {\n  if (cacheStorageMap.has(key)) {\n    return cacheStorageMap.get(key);\n  }\n  const storage = createStorage<AccountInfo>(\n    key,\n    {\n      selectedProvider: 'cloudflare',\n    },\n    {\n      storageType: StorageType.Sync,\n      liveUpdate: true,\n    },\n  );\n  cacheStorageMap.set(key, storage);\n  return storage;\n};\n\nconst storage = initStorage();\n\ntype AccountInfoStorage = BaseStorage<AccountInfo> & {\n  update: (updateInfo: AccountInfo) => Promise<void>;\n};\n\nexport const accountStorage: AccountInfoStorage = {\n  ...storage,\n  update: async (updateInfo: AccountInfo) => {\n    await storage.set(currentInfo => {\n      return { ...currentInfo, ...updateInfo };\n    });\n  },\n};\n"
  },
  {
    "path": "packages/storage/lib/base.ts",
    "content": "/**\n * Storage area type for persisting and exchanging data.\n * @see https://developer.chrome.com/docs/extensions/reference/storage/#overview\n */\nexport enum StorageType {\n  /**\n   * Persist data locally against browser restarts. Will be deleted by uninstalling the extension.\n   * @default\n   */\n  Local = 'local',\n  /**\n   * Uploads data to the users account in the cloud and syncs to the users browsers on other devices. Limits apply.\n   */\n  Sync = 'sync',\n  /**\n   * Requires an [enterprise policy](https://www.chromium.org/administrators/configuring-policy-for-extensions) with a\n   * json schema for company wide config.\n   */\n  Managed = 'managed',\n  /**\n   * Only persist data until the browser is closed. Recommended for service workers which can shutdown anytime and\n   * therefore need to restore their state. Set {@link SessionAccessLevel} for permitting content scripts access.\n   * @implements Chromes [Session Storage](https://developer.chrome.com/docs/extensions/reference/storage/#property-session)\n   */\n  Session = 'session',\n}\n\n/**\n * Global access level requirement for the {@link StorageType.Session} Storage Area.\n * @implements Chromes [Session Access Level](https://developer.chrome.com/docs/extensions/reference/storage/#method-StorageArea-setAccessLevel)\n */\nexport enum SessionAccessLevel {\n  /**\n   * Storage can only be accessed by Extension pages (not Content scripts).\n   * @default\n   */\n  ExtensionPagesOnly = 'TRUSTED_CONTEXTS',\n  /**\n   * Storage can be accessed by both Extension pages and Content scripts.\n   */\n  ExtensionPagesAndContentScripts = 'TRUSTED_AND_UNTRUSTED_CONTEXTS',\n}\n\ntype ValueOrUpdate<D> = D | ((prev: D) => Promise<D> | D);\n\nexport type BaseStorage<D> = {\n  get: () => Promise<D>;\n  set: (value: ValueOrUpdate<D>) => Promise<void>;\n  getSnapshot: () => D | null;\n  subscribe: (listener: () => void) => () => void;\n};\n\ntype StorageConfig<D = string> = {\n  /**\n   * Assign the {@link StorageType} to use.\n   * @default Local\n   */\n  storageType?: StorageType;\n  /**\n   * Only for {@link StorageType.Session}: Grant Content scripts access to storage area?\n   * @default false\n   */\n  sessionAccessForContentScripts?: boolean;\n  /**\n   * Keeps state live in sync between all instances of the extension. Like between popup, side panel and content scripts.\n   * To allow chrome background scripts to stay in sync as well, use {@link StorageType.Session} storage area with\n   * {@link StorageConfig.sessionAccessForContentScripts} potentially also set to true.\n   * @see https://stackoverflow.com/a/75637138/2763239\n   * @default false\n   */\n  liveUpdate?: boolean;\n  /**\n   * An optional props for converting values from storage and into it.\n   * @default undefined\n   */\n  serialization?: {\n    /**\n     * convert non-native values to string to be saved in storage\n     */\n    serialize: (value: D) => string;\n    /**\n     * convert string value from storage to non-native values\n     */\n    deserialize: (text: string) => D;\n  };\n};\n\n/**\n * Sets or updates an arbitrary cache with a new value or the result of an update function.\n */\nasync function updateCache<D>(valueOrUpdate: ValueOrUpdate<D>, cache: D | null): Promise<D> {\n  // Type guard to check if our value or update is a function\n  function isFunction<D>(value: ValueOrUpdate<D>): value is (prev: D) => D | Promise<D> {\n    return typeof value === 'function';\n  }\n\n  // Type guard to check in case of a function, if its a Promise\n  function returnsPromise<D>(func: (prev: D) => D | Promise<D>): func is (prev: D) => Promise<D> {\n    // Use ReturnType to infer the return type of the function and check if it's a Promise\n    return (func as (prev: D) => Promise<D>) instanceof Promise;\n  }\n\n  if (isFunction(valueOrUpdate)) {\n    // Check if the function returns a Promise\n    if (returnsPromise(valueOrUpdate)) {\n      return await valueOrUpdate(cache as D);\n    } else {\n      return valueOrUpdate(cache as D);\n    }\n  } else {\n    return valueOrUpdate;\n  }\n}\n\n/**\n * If one session storage needs access from content scripts, we need to enable it globally.\n * @default false\n */\nlet globalSessionAccessLevelFlag: StorageConfig['sessionAccessForContentScripts'] = false;\n\n/**\n * Checks if the storage permission is granted in the manifest.json.\n */\nfunction checkStoragePermission(storageType: StorageType): void {\n  if (chrome.storage[storageType] === undefined) {\n    throw new Error(`Check your storage permission in manifest.json: ${storageType} is not defined`);\n  }\n}\n\n/**\n * Creates a storage area for persisting and exchanging data.\n */\nexport function createStorage<D = string>(key: string, fallback: D, config?: StorageConfig<D>): BaseStorage<D> {\n  let cache: D | null = null;\n  let initedCache = false;\n  let listeners: Array<() => void> = [];\n  const storageType = config?.storageType ?? StorageType.Local;\n  const liveUpdate = config?.liveUpdate ?? false;\n  const serialize = config?.serialization?.serialize ?? ((v: D) => v);\n  const deserialize = config?.serialization?.deserialize ?? (v => v as D);\n\n  // Set global session storage access level for StoryType.Session, only when not already done but needed.\n  if (\n    globalSessionAccessLevelFlag === false &&\n    storageType === StorageType.Session &&\n    config?.sessionAccessForContentScripts === true\n  ) {\n    checkStoragePermission(storageType);\n    chrome.storage[storageType]\n      .setAccessLevel({\n        accessLevel: SessionAccessLevel.ExtensionPagesAndContentScripts,\n      })\n      .catch(error => {\n        console.warn(error);\n        console.warn('Please call setAccessLevel into different context, like a background script.');\n      });\n    globalSessionAccessLevelFlag = true;\n  }\n\n  // Register life cycle methods\n  const _getDataFromStorage = async (): Promise<D> => {\n    checkStoragePermission(storageType);\n    const value = await chrome.storage[storageType].get([key]);\n    return deserialize(value[key]) ?? fallback;\n  };\n\n  const _emitChange = () => {\n    listeners.forEach(listener => listener());\n  };\n\n  let setUpdatePromise: Promise<D> | null = null;\n  let setStoragePromise: Promise<void> | null = null;\n\n  const set = async (valueOrUpdate: ValueOrUpdate<D>) => {\n    if (initedCache === false) {\n      cache = await _getDataFromStorage();\n    }\n    await Promise.allSettled([setUpdatePromise, setStoragePromise]);\n    setUpdatePromise = updateCache(valueOrUpdate, cache);\n    cache = await setUpdatePromise.then(val => {\n      setUpdatePromise = null;\n      return val;\n    });\n    // if (!liveUpdate) {\n    // }\n    _emitChange();\n    if (cache) {\n      //FIXME: 存在 set 执行完之后，onChanged 尚没有执行，，如果在 onchange 中再次改变 cache 值，在连续段时间内操作多次 set 操作，最终结果会不符合预期\n      setStoragePromise = chrome.storage[storageType].set({ [key]: serialize(cache) });\n      await setStoragePromise.then(async () => {\n        setStoragePromise = null;\n        return await new Promise(resolve => {\n          setTimeout(() => {\n            resolve(undefined);\n          }, 200);\n        });\n      });\n    }\n  };\n\n  const subscribe = (listener: () => void) => {\n    listeners = [...listeners, listener];\n    return () => {\n      listeners = listeners.filter(l => l !== listener);\n    };\n  };\n\n  const getSnapshot = () => {\n    return cache;\n  };\n\n  _getDataFromStorage().then(data => {\n    cache = data;\n    initedCache = true;\n    _emitChange();\n  });\n\n  // Listener for live updates from the browser\n  async function _updateFromStorageOnChanged(changes: { [key: string]: chrome.storage.StorageChange }) {\n    // Check if the key we are listening for is in the changes object\n    if (changes[key] === undefined) return;\n\n    const valueOrUpdate: ValueOrUpdate<D> = deserialize(changes[key].newValue);\n\n    if (cache === valueOrUpdate) return;\n\n    cache = await updateCache(valueOrUpdate, cache);\n\n    _emitChange();\n  }\n\n  // Register listener for live updates for our storage area\n  if (liveUpdate) {\n    chrome.storage[storageType].onChanged.addListener(_updateFromStorageOnChanged);\n  }\n\n  return {\n    get: _getDataFromStorage,\n    set,\n    getSnapshot,\n    subscribe,\n  };\n}\n"
  },
  {
    "path": "packages/storage/lib/cookieStorage.ts",
    "content": "import { ICookie, ICookiesMap, ILocalStorageItem } from '@sync-your-cookie/protobuf';\nimport { BaseStorage, createStorage, StorageType } from './base';\n\nexport interface Cookie extends ICookiesMap {}\n\nconst cacheStorageMap = new Map();\nconst key = 'cookie-storage-key';\n\nconst initStorage = (): BaseStorage<Cookie> => {\n  if (cacheStorageMap.has(key)) {\n    return cacheStorageMap.get(key);\n  }\n  const storage: BaseStorage<Cookie> = createStorage<Cookie>(\n    key,\n    {},\n    {\n      storageType: StorageType.Local,\n      liveUpdate: true,\n    },\n  );\n  cacheStorageMap.set(key, storage);\n  return storage;\n};\n\nconst storage = initStorage();\n\nexport const cookieStorage = {\n  ...storage,\n  reset: async () => {\n    await storage.set(() => {\n      return {};\n    });\n  },\n  updateItem: async (domain: string, updateCookies: ICookie[], items: ILocalStorageItem[] =[]) => {\n    let newVal: Cookie = {};\n    await storage.set(currentInfo => {\n      const domainCookieMap = currentInfo.domainCookieMap || {};\n      currentInfo.createTime = currentInfo.createTime || Date.now();\n      currentInfo.updateTime = Date.now();\n      domainCookieMap[domain] = {\n        ...domainCookieMap[domain],\n        cookies: updateCookies,\n        localStorageItems: items\n      };\n      newVal = { ...currentInfo, domainCookieMap };\n      return newVal;\n    });\n    return newVal;\n  },\n  update: async (updateInfo: Cookie, isInit = false) => {\n    let newVal: Cookie = {};\n    await storage.set(currentInfo => {\n      newVal = isInit ? updateInfo : { ...currentInfo, ...updateInfo };\n      return newVal;\n    });\n    return newVal;\n  },\n  removeItem: async (domain: string) => {\n    let newVal: Cookie = {};\n    await storage.set(currentInfo => {\n      const domainCookieMap = currentInfo.domainCookieMap || {};\n      delete domainCookieMap[domain];\n      newVal = { ...currentInfo, domainCookieMap };\n      return newVal;\n    });\n    return newVal;\n  },\n\n  removeDomainItem: async (domain: string, name: string) => {\n    let newVal: Cookie = {};\n    await storage.set(currentInfo => {\n      const domainCookieMap = currentInfo.domainCookieMap || {};\n      const domainCookies = domainCookieMap[domain] || {};\n      const cookies = domainCookies.cookies || [];\n      const newCookies = cookies.filter(cookie => cookie.name !== name);\n      domainCookieMap[domain] = {\n        ...domainCookies,\n        cookies: newCookies,\n      };\n      newVal = { ...currentInfo, domainCookieMap };\n      return newVal;\n    });\n    return newVal;\n  },\n};\n"
  },
  {
    "path": "packages/storage/lib/domainConfigStorage.ts",
    "content": "import { BaseStorage, createStorage, StorageType } from './base';\n\ntype DomainItemConfig = {\n  autoPull?: boolean;\n  autoPush?: boolean;\n  favIconUrl?: string;\n  sourceUrl?: string;\n};\n\ninterface DomainConfig {\n  domainMap: {\n    [host: string]: DomainItemConfig;\n  };\n}\nconst key = 'domainConfig-storage-key';\n\nconst cacheStorageMap = new Map();\nconst initStorage = (): BaseStorage<DomainConfig> => {\n  if (cacheStorageMap.has(key)) {\n    return cacheStorageMap.get(key);\n  }\n  const storage: BaseStorage<DomainConfig> = createStorage<DomainConfig>(\n    key,\n    {\n      domainMap: {},\n    },\n    {\n      storageType: StorageType.Local,\n      liveUpdate: true,\n      // onLoad: onLoad,\n    },\n  );\n  cacheStorageMap.set(key, storage);\n  return storage;\n};\n\nconst storage = initStorage();\n\nexport const domainConfigStorage = {\n  ...storage,\n  updateItem: async (host: string, updateConf: DomainItemConfig) => {\n    return await storage.set(currentInfo => {\n      const domainMap = currentInfo?.domainMap || {};\n      domainMap[host] = {\n        ...domainMap[host],\n        ...updateConf,\n      };\n      return { ...(currentInfo || {}), domainMap };\n    });\n  },\n  update: async (updateInfo: Partial<DomainConfig>) => {\n    return await storage.set(currentInfo => {\n      return { ...currentInfo, ...updateInfo };\n    });\n  },\n  removeItem: async (domain: string) => {\n    await storage.set(currentInfo => {\n      const domainCookieMap = currentInfo.domainMap || {};\n      delete domainCookieMap[domain];\n      return { ...currentInfo, domainCookieMap };\n    });\n  },\n\n  toggleAutoPullState: async (domain: string, checked?: boolean) => {\n    return await storage.set(currentInfo => {\n      const domainMap = currentInfo?.domainMap || {};\n      domainMap[domain] = {\n        ...domainMap[domain],\n        autoPull: checked ?? !domainMap[domain]?.autoPull,\n      };\n      return { ...(currentInfo || {}), domainMap };\n    });\n  },\n\n  toggleAutoPushState: async (domain: string, checked?: boolean) => {\n    return await storage.set(currentInfo => {\n      const domainMap = currentInfo?.domainMap || {};\n      domainMap[domain] = {\n        ...domainMap[domain],\n        autoPush: checked ?? !domainMap[domain]?.autoPush,\n      };\n      return { ...(currentInfo || {}), domainMap };\n    });\n  },\n};\n"
  },
  {
    "path": "packages/storage/lib/domainStatusStorage.ts",
    "content": "import { BaseStorage, createStorage, StorageType } from './base';\n\ntype DomainItemConfig = {\n  pulling?: boolean;\n  pushing?: boolean;\n};\n\ninterface DomainConfig {\n  pulling: boolean;\n  pushing: boolean;\n  domainMap: {\n    [host: string]: DomainItemConfig;\n  };\n}\nconst key = 'domainStatus-storage-key';\n\nconst cacheStorageMap = new Map();\nconst initStorage = (): BaseStorage<DomainConfig> => {\n  if (cacheStorageMap.has(key)) {\n    return cacheStorageMap.get(key);\n  }\n  const storage: BaseStorage<DomainConfig> = createStorage<DomainConfig>(\n    key,\n    {\n      pulling: false,\n      pushing: false,\n      domainMap: {},\n    },\n    {\n      storageType: StorageType.Session,\n      liveUpdate: true,\n      // onLoad: onLoad,\n    },\n  );\n  cacheStorageMap.set(key, storage);\n  return storage;\n};\n\nconst storage = initStorage();\n\nexport const domainStatusStorage = {\n  ...storage,\n  resetState: async () => {\n    return await storage.set(currentInfo => {\n      const domainMap = currentInfo?.domainMap || {};\n      for (const domain in domainMap) {\n        if (domain) {\n          domainMap[domain] = {\n            ...domainMap[domain],\n            pulling: false,\n            pushing: false,\n          };\n        } else {\n          delete domainMap[domain];\n        }\n      }\n      const resetInfo = {\n        pulling: false,\n        pushing: false,\n        domainMap: domainMap,\n      };\n      return resetInfo;\n    });\n  },\n  updateItem: async (host: string, updateConf: DomainItemConfig) => {\n    return await storage.set(currentInfo => {\n      const domainMap = currentInfo?.domainMap || {};\n      domainMap[host] = {\n        ...domainMap[host],\n        ...updateConf,\n      };\n      return { ...(currentInfo || {}), domainMap };\n    });\n  },\n  update: async (updateInfo: Partial<DomainConfig>) => {\n    return await storage.set(currentInfo => {\n      return { ...currentInfo, ...updateInfo };\n    });\n  },\n  removeItem: async (domain: string) => {\n    await storage.set(currentInfo => {\n      const domainCookieMap = currentInfo.domainMap || {};\n      delete domainCookieMap[domain];\n      return { ...currentInfo, domainCookieMap };\n    });\n  },\n\n  togglePullingState: async (domain: string, checked?: boolean) => {\n    return await storage.set(currentInfo => {\n      const domainMap = currentInfo?.domainMap || {};\n      domainMap[domain] = {\n        ...domainMap[domain],\n        pulling: checked ?? !domainMap[domain]?.pulling,\n      };\n      return { ...(currentInfo || {}), domainMap };\n    });\n  },\n\n  togglePushingState: async (domain: string, checked?: boolean) => {\n    return await storage.set(currentInfo => {\n      const domainMap = currentInfo?.domainMap || {};\n      domainMap[domain] = {\n        ...domainMap[domain],\n        pushing: checked ?? !domainMap[domain]?.pushing,\n      };\n      return { ...(currentInfo || {}), domainMap };\n    });\n  },\n};\n"
  },
  {
    "path": "packages/storage/lib/index.ts",
    "content": "import { SessionAccessLevel, StorageType, createStorage, type BaseStorage } from './base';\n// export * from './accountStorage';\n// export * from './cookieStorage';\n// export * from './domainConfigStorage';\n// export * from './themeStorage';\n\nexport { BaseStorage, SessionAccessLevel, StorageType, createStorage };\n"
  },
  {
    "path": "packages/storage/lib/settingsStorage.ts",
    "content": "import { BaseStorage, createStorage, StorageType } from './base';\nexport interface IStorageItem {\n  value: string;\n  label: string;\n  rawUrl?: string;\n  [key: string]: unknown;\n}\n\nexport interface ISettings {\n  storageKeyList: IStorageItem[];\n  storageKey?: string;\n  storageKeyGistId?: string;\n  gistHtmlUrl?: string;\n  protobufEncoding?: boolean;\n  includeLocalStorage?: boolean;\n  localStorageGetting?: boolean;\n  contextMenu?: boolean;\n  encryptionEnabled?: boolean;\n  encryptionPassword?: string;\n}\nconst key = 'settings-storage-key';\nconst cacheStorageMap = new Map();\nexport const defaultKey = 'sync-your-cookie';\n\nconst initStorage = (): BaseStorage<ISettings> => {\n  if (cacheStorageMap.has(key)) {\n    return cacheStorageMap.get(key);\n  }\n  const storage = createStorage<ISettings>(\n    key,\n    {\n      storageKeyList: [{ value: defaultKey, label: defaultKey }],\n      storageKey: defaultKey,\n      protobufEncoding: false,\n      includeLocalStorage: true,\n      contextMenu: false,\n    },\n    {\n      storageType: StorageType.Sync,\n      liveUpdate: true,\n    },\n  );\n  cacheStorageMap.set(key, storage);\n  return storage;\n};\n\nconst storage = initStorage();\n\ntype TSettingsStorage = BaseStorage<ISettings> & {\n  update: (updateInfo: Partial<ISettings>) => Promise<void>;\n  addStorageKey: (key: string) => Promise<void>;\n  removeStorageKey: (key: string) => Promise<void>;\n  // getStorageKeyList: () => Promise<string[]>;\n};\n\nexport const settingsStorage: TSettingsStorage = {\n  ...storage,\n  update: async (updateInfo: Partial<ISettings>) => {\n    await storage.set(currentInfo => {\n      return { ...currentInfo, ...updateInfo };\n    });\n  },\n\n  addStorageKey: async (key: string) => {\n    await storage.set(currentInfo => {\n      const exists = currentInfo.storageKeyList.find(item => item.value === key);\n      if (exists) {\n        return currentInfo;\n      }\n      return {\n        ...currentInfo,\n        storageKeyList: [...currentInfo.storageKeyList, { value: key, label: key }],\n      };\n    });\n  },\n\n  removeStorageKey: async (key: string) => {\n    await storage.set(currentInfo => {\n      const exists = currentInfo.storageKeyList.find(item => item.value === key);\n      if (!exists) {\n        return currentInfo;\n      }\n      return {\n        ...currentInfo,\n        storageKeyList: currentInfo.storageKeyList.filter(item => item.value !== key),\n      };\n    });\n  },\n};\n\nexport const getActiveStorageItem = (): IStorageItem | undefined => {\n  const snapshot = settingsStorage.getSnapshot();\n  const storageKey = snapshot?.storageKey;\n  return snapshot?.storageKeyList.find(item => item.value === storageKey);\n};\n\nexport const initStorageKey = () => {\n  settingsStorage.update({\n    storageKeyList: [{ value: defaultKey, label: defaultKey }],\n    storageKey: defaultKey,\n  });\n};\n"
  },
  {
    "path": "packages/storage/lib/themeStorage.ts",
    "content": "import { BaseStorage, createStorage, StorageType } from './base';\n\ntype Theme = 'light' | 'dark' | 'system';\n\ntype ThemeStorage = BaseStorage<Theme> & {\n  toggle: () => Promise<void>;\n};\nconst cacheStorageMap = new Map();\nconst key = 'theme-storage-key';\n\nconst initStorage = (): BaseStorage<Theme> => {\n  if (cacheStorageMap.has(key)) {\n    console.log('key', key);\n    return cacheStorageMap.get(key);\n  }\n  const storage = createStorage<Theme>(key, 'light', {\n    storageType: StorageType.Local,\n    liveUpdate: true,\n  });\n  cacheStorageMap.set(key, storage);\n  return storage;\n};\n\nconst storage = initStorage();\n\nexport const themeStorage: ThemeStorage = {\n  ...storage,\n  toggle: async () => {\n    await storage.set((currentTheme: string) => {\n      return currentTheme === 'light' ? 'dark' : 'light';\n    });\n  },\n};\n"
  },
  {
    "path": "packages/storage/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/storage\",\n  \"version\": \"0.0.1\",\n  \"description\": \"chrome extension storage\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"files\": [\n    \"dist/**\"\n  ],\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"scripts\": {\n    \"clean\": \"rimraf ./dist && rimraf .turbo\",\n    \"build\": \"tsup index.ts --format esm,cjs --dts\",\n    \"dev\": \"tsc -w\",\n    \"lint\": \"eslint . --ext .ts,.tsx\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {},\n  \"devDependencies\": {\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\",\n    \"@sync-your-cookie/protobuf\": \"workspace:*\",\n    \"tsup\": \"8.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/storage/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"jsx\": \"react-jsx\",\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@lib/*\": [\"lib/*\"]\n    },\n    \"types\": [\"chrome\"]\n  },\n  \"include\": [\"index.ts\", \"lib\"]\n}\n"
  },
  {
    "path": "packages/tailwind-config/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/tailwindcss-config\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Tailwind CSS configuration for boilerplate\",\n  \"main\": \"./tailwind.config.js\",\n  \"private\": true\n}\n"
  },
  {
    "path": "packages/tailwind-config/tailwind.config.js",
    "content": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  /** shared theme configuration */\n  theme: {\n    extend: {},\n  },\n  /** shared plugins configuration */\n  plugins: [],\n};\n"
  },
  {
    "path": "packages/tsconfig/app.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Chrome Extension App\",\n  \"extends\": \"./base.json\"\n}\n"
  },
  {
    "path": "packages/tsconfig/base.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Base\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"allowJs\": true,\n    \"noEmit\": true,\n    \"downlevelIteration\": true,\n    \"isolatedModules\": true,\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"moduleResolution\": \"node\",\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"resolveJsonModule\": true,\n    \"noImplicitReturns\": true,\n    \"jsx\": \"react-jsx\",\n    \"lib\": [\n      \"DOM\",\n      \"ESNext\"\n    ],\n    \"plugins\": [\n      {\n        \"transform\": \"typescript-transform-paths\"\n      },\n    ]\n  }\n}\n\n"
  },
  {
    "path": "packages/tsconfig/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/tsconfig\",\n  \"version\": \"1.0.0\",\n  \"description\": \"tsconfig for chrome extension\",\n  \"private\": true,\n  \"scripts\": {\n    \"prepare\": \"ts-patch install -s\"\n  },\n  \"devDependencies\": {\n    \"ts-patch\": \"^3.2.1\",\n    \"typescript-transform-paths\": \"^3.4.10\"\n  }\n}\n"
  },
  {
    "path": "packages/tsconfig/utils.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Chrome Extension Utils\",\n  \"extends\": \"./base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": false,\n    \"declaration\": true,\n    \"module\": \"CommonJS\",\n    \"moduleResolution\": \"node\",\n    \"declarationMap\": true,\n    \"target\": \"ES6\",\n    \"types\": [\"node\"],\n    \"plugins\": [\n      {\n        \"transform\": \"typescript-transform-paths\"\n      },\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/ui/components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": false,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"tailwind.config.js\",\n    \"css\": \"./globals.css\",\n    \"baseColor\": \"slate\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"src/components\",\n    \"utils\": \"@libs/utils\"\n  }\n}\n"
  },
  {
    "path": "packages/ui/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --foreground: 224 71.4% 4.1%;\n    --card: 0 0% 100%;\n    --card-foreground: 224 71.4% 4.1%;\n    --popover: 0 0% 100%;\n    --popover-foreground: 224 71.4% 4.1%;\n    --primary: 262.1 83.3% 57.8%;\n    --primary-foreground: 210 20% 98%;\n    --secondary: 220 14.3% 95.9%;\n    --secondary-foreground: 220.9 39.3% 11%;\n    --muted: 220 14.3% 95.9%;\n    --muted-foreground: 220 8.9% 46.1%;\n    --accent: 220 14.3% 95.9%;\n    --accent-foreground: 220.9 39.3% 11%;\n    --destructive: 0 84.2% 60.2%;\n    --destructive-foreground: 210 20% 98%;\n    --border: 220 13% 91%;\n    --input: 220 13% 91%;\n    --ring: 262.1 83.3% 57.8%;\n    --radius: 0.5rem;\n    --chart-1: 12 76% 61%;\n    --chart-2: 173 58% 39%;\n    --chart-3: 197 37% 24%;\n    --chart-4: 43 74% 66%;\n    --chart-5: 27 87% 67%;\n  }\n\n  .dark {\n    --background: 224 71.4% 4.1%;\n    --foreground: 210 20% 98%;\n    --card: 224 71.4% 4.1%;\n    --card-foreground: 210 20% 98%;\n    --popover: 224 71.4% 4.1%;\n    --popover-foreground: 210 20% 98%;\n    --primary: 263.4 70% 50.4%;\n    --primary-foreground: 210 20% 98%;\n    --secondary: 215 27.9% 16.9%;\n    --secondary-foreground: 210 20% 98%;\n    --muted: 215 27.9% 16.9%;\n    --muted-foreground: 217.9 10.6% 64.9%;\n    --accent: 215 27.9% 16.9%;\n    --accent-foreground: 210 20% 98%;\n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 210 20% 98%;\n    --border: 215 27.9% 16.9%;\n    --input: 215 27.9% 16.9%;\n    --ring: 263.4 70% 50.4%;\n    --chart-1: 220 70% 50%;\n    --chart-2: 160 60% 45%;\n    --chart-3: 30 80% 55%;\n    --chart-4: 280 65% 60%;\n    --chart-5: 340 75% 55%;\n  }\n}\n\n\n@layer base {\n  * {\n    @apply border-border;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n"
  },
  {
    "path": "packages/ui/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/ui\",\n  \"version\": \"0.0.1\",\n  \"description\": \"chrome extension ui\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"type\": \"module\",\n  \"files\": [\n    \"dist/**\",\n    \"dist/**/*.css\"\n  ],\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.js\",\n      \"require\": \"./dist/index.js\"\n    },\n    \"./css\": \"./globals.css\",\n    \"./tailwind.config\": \"./tailwind.config.js\"\n  },\n  \"scripts\": {\n    \"clean\": \"rimraf ./dist && rimraf .turbo\",\n    \"dev:tsup\": \"tsup src/index.ts --format esm,cjs --dts --external react,chrome --watch\",\n    \"build:tsup\": \"tsup src/index.ts --format esm,cjs --dts --external react,chrome --watch\",\n    \"dev\": \"tsc -w\",\n    \"build\": \"tsc\",\n    \"lint\": \"eslint . --ext .ts,.tsx\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {\n    \"@radix-ui/react-alert-dialog\": \"^1.1.2\",\n    \"@radix-ui/react-avatar\": \"^1.1.1\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.1.2\",\n    \"@radix-ui/react-label\": \"^2.1.0\",\n    \"@radix-ui/react-popover\": \"^1.1.2\",\n    \"@radix-ui/react-select\": \"^2.2.5\",\n    \"@radix-ui/react-slot\": \"^1.1.0\",\n    \"@radix-ui/react-switch\": \"^1.1.1\",\n    \"@radix-ui/react-toggle\": \"^1.1.9\",\n    \"@radix-ui/react-tooltip\": \"^1.1.3\",\n    \"@tanstack/react-table\": \"^8.20.5\",\n    \"class-variance-authority\": \"^0.7.0\",\n    \"clsx\": \"^2.1.1\",\n    \"lucide-react\": \"^0.394.0\",\n    \"next-themes\": \"^0.3.0\",\n    \"sonner\": \"^1.5.0\",\n    \"tailwind-merge\": \"^2.3.0\",\n    \"tailwindcss-animate\": \"^1.0.7\"\n  },\n  \"devDependencies\": {\n    \"@sync-your-cookie/tailwindcss-config\": \"workspace:*\",\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\",\n    \"tsup\": \"8.0.2\",\n    \"typescript-transform-paths\": \"^3.4.10\"\n  }\n}\n"
  },
  {
    "path": "packages/ui/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "packages/ui/src/components/DateTable/index.tsx",
    "content": "import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';\n\nimport type { ColumnDef } from '@tanstack/react-table';\n\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';\n\ninterface DataTableProps<TData, TValue> {\n  columns: ColumnDef<TData, TValue>[];\n  data: TData[];\n}\n// export * from '@tanstack/react-table';\nexport { ColumnDef };\n\nexport function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {\n  const table = useReactTable({\n    data,\n    columns,\n    getCoreRowModel: getCoreRowModel(),\n  });\n\n  return (\n    <div className=\"rounded-md border\">\n      <Table>\n        <TableHeader>\n          {table.getHeaderGroups().map(headerGroup => (\n            <TableRow key={headerGroup.id}>\n              {headerGroup.headers.map(header => {\n                return (\n                  <TableHead key={header.id}>\n                    {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}\n                  </TableHead>\n                );\n              })}\n            </TableRow>\n          ))}\n        </TableHeader>\n        <TableBody>\n          {table.getRowModel().rows?.length ? (\n            table.getRowModel().rows.map(row => (\n              <TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>\n                {row.getVisibleCells().map(cell => (\n                  <TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>\n                ))}\n              </TableRow>\n            ))\n          ) : (\n            <TableRow>\n              <TableCell colSpan={columns.length} className=\"h-24 text-center\">\n                No results.\n              </TableCell>\n            </TableRow>\n          )}\n        </TableBody>\n      </Table>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/ui/src/components/Image/index.tsx",
    "content": "import { FC } from 'react';\nimport { Avatar, AvatarFallback, AvatarImage } from '../ui';\nconst randomBgColor = [\n  '#ec6a5e',\n  '#f5bd4f',\n  '#61c455',\n  '#3a82f7',\n  '#7246e4',\n  '#bef653',\n  '#e97a35',\n  '#4c9f54',\n  '#3266e3',\n];\n\ninterface ImageProps {\n  src: string;\n  value?: string;\n  index?: number;\n}\n\nexport const Image: FC<ImageProps> = ({ index, src, value }) => {\n  const randomIndex = typeof index === 'number' ? index % randomBgColor.length : 0;\n  return (\n    <div className=\"justify-center flex items-center \" draggable={false}>\n      <Avatar draggable={false} className=\" h-5 w-5 inline-block mr-2 rounded-full\">\n        <AvatarImage draggable={false} src={src} />\n        {value && typeof randomIndex === 'number' && (\n          <AvatarFallback\n            delayMs={500}\n            draggable={false}\n            style={{\n              backgroundColor: randomBgColor[randomIndex],\n            }}\n            className=\" text-white text-sm \">\n            {value?.slice(0, 1).toLocaleUpperCase()}\n          </AvatarFallback>\n        )}\n      </Avatar>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/ui/src/components/Spinner/index.tsx",
    "content": "import { cn } from '@libs/utils';\nimport { VariantProps, cva } from 'class-variance-authority';\nimport { Loader } from 'lucide-react';\nimport React from 'react';\n\nconst spinnerVariants = cva(\n  'absolute bg-white/60 w-full h-full top-0 bottom-0 left-0 right-0 flex-col items-center justify-center',\n  {\n    variants: {\n      show: {\n        true: 'flex',\n        false: 'hidden',\n      },\n    },\n    defaultVariants: {\n      show: true,\n    },\n  },\n);\n\nconst loaderVariants = cva('animate-spin text-primary', {\n  variants: {\n    size: {\n      small: 'size-6',\n      medium: 'size-8',\n      large: 'size-12',\n    },\n  },\n  defaultVariants: {\n    size: 'medium',\n  },\n});\n\ninterface SpinnerContentProps extends VariantProps<typeof spinnerVariants>, VariantProps<typeof loaderVariants> {\n  className?: string;\n  children?: React.ReactNode;\n}\n\nexport function Spinner({ size, show = true, children, className }: SpinnerContentProps) {\n  return (\n    <>\n      {children}\n      <div className={spinnerVariants({ show })}>\n        {show ? <Loader className={cn(loaderVariants({ size }), className)} /> : null}\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/ui/src/components/ThemeDropdown/index.tsx",
    "content": "import { Moon, Sun } from 'lucide-react';\n\nimport { Button } from '@/components/ui/button';\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\n\n// import { useTheme } from \"@/components/theme-provider\"\n\ninterface ThemeDropdownProps {\n  setTheme: (theme: 'dark' | 'light' | 'system') => void;\n}\n\nexport function ThemeDropdown(props: ThemeDropdownProps) {\n  const { setTheme } = props;\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button variant=\"outline\" size=\"icon\">\n          <Sun className=\"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\" />\n          <Moon className=\"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\" />\n          <span className=\"sr-only\">Toggle theme</span>\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuItem onClick={() => setTheme('light')}>Light</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme('dark')}>Dark</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme('system')}>System</DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "packages/ui/src/components/Tooltip/index.tsx",
    "content": "import { Tooltip as STooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';\n\ninterface TooltipProps {\n  children: React.ReactNode;\n  title?: string | React.ReactNode;\n  align?: 'start' | 'center' | 'end' | undefined;\n  alignOffset?: number;\n}\n\nconst Tooltip = (props: TooltipProps) => {\n  const { children, title, alignOffset, align } = props;\n  return (\n    <TooltipProvider>\n      <STooltip>\n        <TooltipTrigger>{children}</TooltipTrigger>\n        <TooltipContent alignOffset={alignOffset} align={align}>\n          <p>{title}</p>\n        </TooltipContent>\n      </STooltip>\n    </TooltipProvider>\n  );\n};\n\nexport default Tooltip;\n"
  },
  {
    "path": "packages/ui/src/components/index.ts",
    "content": "export * from './DateTable';\nexport * from './Image';\nexport * from './Spinner';\nexport * from './ThemeDropdown';\nexport * from './ui';\n\nimport { default as SyncTooltip } from './Tooltip';\n// import  from './Tooltip';\nexport { SyncTooltip };\n"
  },
  {
    "path": "packages/ui/src/components/ui/alert-dialog.tsx",
    "content": "import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';\nimport * as React from 'react';\n\nimport { cn } from '@libs/utils';\nimport { buttonVariants } from 'src/components/ui/button';\n\nconst AlertDialog = AlertDialogPrimitive.Root;\n\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger;\n\nconst AlertDialogPortal = AlertDialogPrimitive.Portal;\n\nconst AlertDialogOverlay = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Overlay\n    className={cn(\n      'fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n      className,\n    )}\n    {...props}\n    ref={ref}\n  />\n));\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;\n\nconst AlertDialogContent = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPortal>\n    <AlertDialogOverlay />\n    <AlertDialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',\n        className,\n      )}\n      {...props}\n    />\n  </AlertDialogPortal>\n));\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;\n\nconst AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n  <div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />\n);\nAlertDialogHeader.displayName = 'AlertDialogHeader';\n\nconst AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n  <div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />\n);\nAlertDialogFooter.displayName = 'AlertDialogFooter';\n\nconst AlertDialogTitle = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Title ref={ref} className={cn('text-lg font-semibold', className)} {...props} />\n));\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;\n\nconst AlertDialogDescription = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Description ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />\n));\nAlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;\n\nconst AlertDialogAction = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Action>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />\n));\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;\n\nconst AlertDialogCancel = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Cancel\n    ref={ref}\n    className={cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className)}\n    {...props}\n  />\n));\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;\n\nexport {\n  AlertDialog, AlertDialogAction,\n  AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger\n};\n\n"
  },
  {
    "path": "packages/ui/src/components/ui/alert.tsx",
    "content": "import { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst alertVariants = cva(\n  'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',\n  {\n    variants: {\n      variant: {\n        default: 'bg-background text-foreground',\n        destructive: 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  },\n);\n\nconst Alert = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>\n>(({ className, variant, ...props }, ref) => (\n  <div ref={ref} role=\"alert\" className={cn(alertVariants({ variant }), className)} {...props} />\n));\nAlert.displayName = 'Alert';\n\nconst AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(\n  ({ className, ...props }, ref) => (\n    <h5 ref={ref} className={cn('mb-1 font-medium leading-none tracking-tight', className)} {...props} />\n  ),\n);\nAlertTitle.displayName = 'AlertTitle';\n\nconst AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(\n  ({ className, ...props }, ref) => (\n    <div ref={ref} className={cn('text-sm [&_p]:leading-relaxed', className)} {...props} />\n  ),\n);\nAlertDescription.displayName = 'AlertDescription';\n\nexport { Alert, AlertDescription, AlertTitle };\n"
  },
  {
    "path": "packages/ui/src/components/ui/avatar.tsx",
    "content": "import * as AvatarPrimitive from '@radix-ui/react-avatar';\nimport * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst Avatar = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Root\n    ref={ref}\n    className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}\n    {...props}\n  />\n));\nAvatar.displayName = AvatarPrimitive.Root.displayName;\n\nconst AvatarImage = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Image>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Image ref={ref} className={cn('aspect-square h-full w-full', className)} {...props} />\n));\nAvatarImage.displayName = AvatarPrimitive.Image.displayName;\n\nconst AvatarFallback = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Fallback>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Fallback\n    ref={ref}\n    className={cn('flex h-full w-full items-center justify-center rounded-full bg-muted', className)}\n    {...props}\n  />\n));\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;\n\nexport { Avatar, AvatarFallback, AvatarImage };\n\n"
  },
  {
    "path": "packages/ui/src/components/ui/button.tsx",
    "content": "import { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst buttonVariants = cva(\n  'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n        outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',\n        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n        ghost: 'hover:bg-accent hover:text-accent-foreground',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default: 'h-10 px-4 py-2',\n        sm: 'h-9 rounded-md px-3',\n        lg: 'h-11 rounded-md px-8',\n        icon: 'h-10 w-10',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n);\n\nexport interface ButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : 'button';\n    return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;\n  },\n);\nButton.displayName = 'Button';\n\nexport { Button, buttonVariants };\n"
  },
  {
    "path": "packages/ui/src/components/ui/card.tsx",
    "content": "import * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (\n  <div ref={ref} className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)} {...props} />\n));\nCard.displayName = 'Card';\n\nconst CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => (\n    <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />\n  ),\n);\nCardHeader.displayName = 'CardHeader';\n\nconst CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(\n  ({ className, ...props }, ref) => (\n    <h3 ref={ref} className={cn('text-2xl font-semibold leading-none tracking-tight', className)} {...props} />\n  ),\n);\nCardTitle.displayName = 'CardTitle';\n\nconst CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(\n  ({ className, ...props }, ref) => (\n    <p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />\n  ),\n);\nCardDescription.displayName = 'CardDescription';\n\nconst CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />,\n);\nCardContent.displayName = 'CardContent';\n\nconst CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => (\n    <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />\n  ),\n);\nCardFooter.displayName = 'CardFooter';\n\nexport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };\n\n"
  },
  {
    "path": "packages/ui/src/components/ui/dropdown-menu.tsx",
    "content": "import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';\nimport { Check, ChevronRight, Circle } from 'lucide-react';\nimport * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst DropdownMenu = DropdownMenuPrimitive.Root;\n\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\n\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group;\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal;\n\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub;\n\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n    inset?: boolean;\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',\n      inset && 'pl-8',\n      className,\n    )}\n    {...props}>\n    {children}\n    <ChevronRight className=\"ml-auto h-4 w-4\" />\n  </DropdownMenuPrimitive.SubTrigger>\n));\nDropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;\n\nconst DropdownMenuSubContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n      className,\n    )}\n    {...props}\n  />\n));\nDropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;\n\nconst DropdownMenuContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n  <DropdownMenuPrimitive.Portal>\n    <DropdownMenuPrimitive.Content\n      ref={ref}\n      sideOffset={sideOffset}\n      className={cn(\n        'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n        className,\n      )}\n      {...props}\n    />\n  </DropdownMenuPrimitive.Portal>\n));\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;\n\nconst DropdownMenuItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n    inset?: boolean;\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Item\n    ref={ref}\n    className={cn(\n      'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n      inset && 'pl-8',\n      className,\n    )}\n    {...props}\n  />\n));\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <DropdownMenuPrimitive.CheckboxItem\n    ref={ref}\n    className={cn(\n      'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n      className,\n    )}\n    checked={checked}\n    {...props}>\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.CheckboxItem>\n));\nDropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;\n\nconst DropdownMenuRadioItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.RadioItem\n    ref={ref}\n    className={cn(\n      'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n      className,\n    )}\n    {...props}>\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <Circle className=\"h-2 w-2 fill-current\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.RadioItem>\n));\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;\n\nconst DropdownMenuLabel = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n    inset?: boolean;\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Label\n    ref={ref}\n    className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}\n    {...props}\n  />\n));\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;\n\nconst DropdownMenuSeparator = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />\n));\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;\n\nconst DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {\n  return <span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />;\n};\nDropdownMenuShortcut.displayName = 'DropdownMenuShortcut';\n\nexport {\n  DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator,\n  DropdownMenuShortcut, DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger, DropdownMenuTrigger\n};\n\n"
  },
  {
    "path": "packages/ui/src/components/ui/index.ts",
    "content": "export * from './alert';\nexport * from './button';\n\nexport * from './avatar';\nexport * from './card';\nexport * from './dropdown-menu';\nexport * from './input';\nexport * from './label';\nexport * from './sonner';\nexport * from './switch';\n// export * from './table';\nexport * from './alert-dialog';\nexport * from './popover';\nexport * from './tooltip';\n\nexport * from './select';\nexport * from './toggle';\n\n"
  },
  {
    "path": "packages/ui/src/components/ui/input.tsx",
    "content": "import * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nexport interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {\n  return (\n    <input\n      type={type}\n      className={cn(\n        'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',\n        className,\n      )}\n      ref={ref}\n      {...props}\n    />\n  );\n});\nInput.displayName = 'Input';\n\nexport { Input };\n"
  },
  {
    "path": "packages/ui/src/components/ui/label.tsx",
    "content": "import * as LabelPrimitive from '@radix-ui/react-label';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst labelVariants = cva('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70');\n\nconst Label = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n  <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />\n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label };\n"
  },
  {
    "path": "packages/ui/src/components/ui/popover.tsx",
    "content": "import * as PopoverPrimitive from '@radix-ui/react-popover';\nimport * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst Popover = PopoverPrimitive.Root;\n\nconst PopoverTrigger = PopoverPrimitive.Trigger;\n\nconst PopoverContent = React.forwardRef<\n  React.ElementRef<typeof PopoverPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (\n  <PopoverPrimitive.Portal>\n    <PopoverPrimitive.Content\n      ref={ref}\n      align={align}\n      sideOffset={sideOffset}\n      className={cn(\n        'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n        className,\n      )}\n      {...props}\n    />\n  </PopoverPrimitive.Portal>\n));\nPopoverContent.displayName = PopoverPrimitive.Content.displayName;\n\nexport { Popover, PopoverContent, PopoverTrigger };\n"
  },
  {
    "path": "packages/ui/src/components/ui/select.tsx",
    "content": "import * as SelectPrimitive from '@radix-ui/react-select';\nimport { Check, ChevronDown, ChevronUp } from 'lucide-react';\nimport * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst Select = SelectPrimitive.Root;\nconst SelectPortal = SelectPrimitive.Portal;\n\nconst SelectGroup = SelectPrimitive.Group;\n\nconst SelectValue = SelectPrimitive.Value;\n\nconst SelectTrigger = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',\n      className,\n    )}\n    {...props}>\n    {children}\n    <SelectPrimitive.Icon asChild>\n      <ChevronDown className=\"h-4 w-4 opacity-50\" />\n    </SelectPrimitive.Icon>\n  </SelectPrimitive.Trigger>\n));\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName;\n\nconst SelectScrollUpButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollUpButton\n    ref={ref}\n    className={cn('flex cursor-default items-center justify-center py-1', className)}\n    {...props}>\n    <ChevronUp className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollUpButton>\n));\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;\n\nconst SelectScrollDownButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollDownButton\n    ref={ref}\n    className={cn('flex cursor-default items-center justify-center py-1', className)}\n    {...props}>\n    <ChevronDown className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollDownButton>\n));\nSelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;\n\nconst SelectContent = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = 'popper', ...props }, ref) => (\n  <SelectPrimitive.Portal>\n    <SelectPrimitive.Content\n      ref={ref}\n      className={cn(\n        'relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]',\n        position === 'popper' &&\n          'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n        className,\n      )}\n      position={position}\n      {...props}>\n      <SelectScrollUpButton />\n      <SelectPrimitive.Viewport\n        className={cn(\n          'p-1',\n          position === 'popper' &&\n            'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',\n        )}>\n        {children}\n      </SelectPrimitive.Viewport>\n      <SelectScrollDownButton />\n    </SelectPrimitive.Content>\n  </SelectPrimitive.Portal>\n));\nSelectContent.displayName = SelectPrimitive.Content.displayName;\n\nconst SelectLabel = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Label ref={ref} className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)} {...props} />\n));\nSelectLabel.displayName = SelectPrimitive.Label.displayName;\n\nconst SelectItem = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Item\n    ref={ref}\n    className={cn(\n      'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n      className,\n    )}\n    {...props}>\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <SelectPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </SelectPrimitive.ItemIndicator>\n    </span>\n\n    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n  </SelectPrimitive.Item>\n));\nSelectItem.displayName = SelectPrimitive.Item.displayName;\n\nconst SelectSeparator = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />\n));\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName;\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectPortal,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue\n};\n\n"
  },
  {
    "path": "packages/ui/src/components/ui/sonner.tsx",
    "content": "import { useTheme } from \"next-themes\"\nimport { Toaster as Sonner } from \"sonner\"\n\ntype ToasterProps = React.ComponentProps<typeof Sonner>\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = \"system\" } = useTheme()\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps[\"theme\"]}\n      className=\"toaster group\"\n      toastOptions={{\n        classNames: {\n          toast:\n            \"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg\",\n          description: \"group-[.toast]:text-muted-foreground\",\n          actionButton:\n            \"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground\",\n          cancelButton:\n            \"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground\",\n        },\n      }}\n      {...props}\n    />\n  )\n}\n\nexport { Toaster }\n"
  },
  {
    "path": "packages/ui/src/components/ui/switch.tsx",
    "content": "import * as SwitchPrimitives from '@radix-ui/react-switch';\nimport * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst Switch = React.forwardRef<\n  React.ElementRef<typeof SwitchPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n  <SwitchPrimitives.Root\n    className={cn(\n      'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',\n      className,\n    )}\n    {...props}\n    ref={ref}>\n    <SwitchPrimitives.Thumb\n      className={cn(\n        'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',\n      )}\n    />\n  </SwitchPrimitives.Root>\n));\nSwitch.displayName = SwitchPrimitives.Root.displayName;\n\nexport { Switch };\n"
  },
  {
    "path": "packages/ui/src/components/ui/table.tsx",
    "content": "import * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(\n  ({ className, ...props }, ref) => (\n    <div className=\"relative w-full overflow-auto\">\n      <table ref={ref} className={cn('w-full caption-bottom text-sm', className)} {...props} />\n    </div>\n  ),\n);\nTable.displayName = 'Table';\n\nconst TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n  ({ className, ...props }, ref) => <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />,\n);\nTableHeader.displayName = 'TableHeader';\n\nconst TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n  ({ className, ...props }, ref) => (\n    <tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />\n  ),\n);\nTableBody.displayName = 'TableBody';\n\nconst TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n  ({ className, ...props }, ref) => (\n    <tfoot ref={ref} className={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)} {...props} />\n  ),\n);\nTableFooter.displayName = 'TableFooter';\n\nconst TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(\n  ({ className, ...props }, ref) => (\n    <tr\n      ref={ref}\n      className={cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', className)}\n      {...props}\n    />\n  ),\n);\nTableRow.displayName = 'TableRow';\n\nconst TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(\n  ({ className, ...props }, ref) => (\n    <th\n      ref={ref}\n      className={cn(\n        'h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',\n        className,\n      )}\n      {...props}\n    />\n  ),\n);\nTableHead.displayName = 'TableHead';\n\nconst TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(\n  ({ className, ...props }, ref) => (\n    <td ref={ref} className={cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', className)} {...props} />\n  ),\n);\nTableCell.displayName = 'TableCell';\n\nconst TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(\n  ({ className, ...props }, ref) => (\n    <caption ref={ref} className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />\n  ),\n);\nTableCaption.displayName = 'TableCaption';\n\nexport { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };\n"
  },
  {
    "path": "packages/ui/src/components/ui/toggle.tsx",
    "content": "\"use client\"\n\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport * as React from \"react\"\n\nimport { cn } from \"@libs/utils\"\n\nconst toggleVariants = cva(\n  \"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        outline:\n          \"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground\",\n      },\n      size: {\n        default: \"h-9 px-2 min-w-9\",\n        sm: \"h-8 px-1.5 min-w-8\",\n        lg: \"h-10 px-2.5 min-w-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Toggle({\n  className,\n  variant,\n  size,\n  ...props\n}: React.ComponentProps<typeof TogglePrimitive.Root> &\n  VariantProps<typeof toggleVariants>) {\n  return (\n    <TogglePrimitive.Root\n      data-slot=\"toggle\"\n      className={cn(toggleVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Toggle, toggleVariants }\n"
  },
  {
    "path": "packages/ui/src/components/ui/tooltip.tsx",
    "content": "import * as TooltipPrimitive from '@radix-ui/react-tooltip';\nimport * as React from 'react';\n\nimport { cn } from '@libs/utils';\n\nconst TooltipProvider = TooltipPrimitive.Provider;\n\nconst Tooltip = TooltipPrimitive.Root;\n\nconst TooltipTrigger = TooltipPrimitive.Trigger;\n\nconst TooltipContent = React.forwardRef<\n  React.ElementRef<typeof TooltipPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n  <TooltipPrimitive.Content\n    ref={ref}\n    sideOffset={sideOffset}\n    className={cn(\n      'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n      className,\n    )}\n    {...props}\n  />\n));\nTooltipContent.displayName = TooltipPrimitive.Content.displayName;\n\nexport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };\n\n"
  },
  {
    "path": "packages/ui/src/index.ts",
    "content": "export * from './components';\nexport * from './libs/index';\n\n"
  },
  {
    "path": "packages/ui/src/libs/index.ts",
    "content": "export * from './utils';\n"
  },
  {
    "path": "packages/ui/src/libs/utils.ts",
    "content": "import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
  },
  {
    "path": "packages/ui/tailwind.config.js",
    "content": "const baseConfig = require('@sync-your-cookie/tailwindcss-config');\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  ...baseConfig,\n  darkMode: [\"class\"],\n  content: [\n    './pages/**/*.{ts,tsx}',\n    './components/**/*.{ts,tsx}',\n    './app/**/*.{ts,tsx}',\n    './src/**/*.{ts,tsx}',\n    `node_modules/@sync-your-cookie/ui/**/*.{js,ts,jsx,tsx,mdx}`\n  ],\n  prefix: \"\",\n  theme: {\n    container: {\n      center: true,\n      padding: \"2rem\",\n      screens: {\n        \"2xl\": \"1400px\",\n      },\n    },\n    extend: {\n      colors: {\n        border: \"hsl(var(--border))\",\n        input: \"hsl(var(--input))\",\n        ring: \"hsl(var(--ring))\",\n        background: \"hsl(var(--background))\",\n        foreground: \"hsl(var(--foreground))\",\n        primary: {\n          DEFAULT: \"hsl(var(--primary))\",\n          foreground: \"hsl(var(--primary-foreground))\",\n        },\n        secondary: {\n          DEFAULT: \"hsl(var(--secondary))\",\n          foreground: \"hsl(var(--secondary-foreground))\",\n        },\n        destructive: {\n          DEFAULT: \"hsl(var(--destructive))\",\n          foreground: \"hsl(var(--destructive-foreground))\",\n        },\n        muted: {\n          DEFAULT: \"hsl(var(--muted))\",\n          foreground: \"hsl(var(--muted-foreground))\",\n        },\n        accent: {\n          DEFAULT: \"hsl(var(--accent))\",\n          foreground: \"hsl(var(--accent-foreground))\",\n        },\n        popover: {\n          DEFAULT: \"hsl(var(--popover))\",\n          foreground: \"hsl(var(--popover-foreground))\",\n        },\n        card: {\n          DEFAULT: \"hsl(var(--card))\",\n          foreground: \"hsl(var(--card-foreground))\",\n        },\n      },\n      borderRadius: {\n        lg: \"var(--radius)\",\n        md: \"calc(var(--radius) - 2px)\",\n        sm: \"calc(var(--radius) - 4px)\",\n      },\n      keyframes: {\n        \"accordion-down\": {\n          from: { height: \"0\" },\n          to: { height: \"var(--radix-accordion-content-height)\" },\n        },\n        \"accordion-up\": {\n          from: { height: \"var(--radix-accordion-content-height)\" },\n          to: { height: \"0\" },\n        },\n      },\n      animation: {\n        \"accordion-down\": \"accordion-down 0.2s ease-out\",\n        \"accordion-up\": \"accordion-up 0.2s ease-out\",\n      },\n    },\n  },\n  plugins: [require(\"tailwindcss-animate\")],\n}\n"
  },
  {
    "path": "packages/ui/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"jsx\": \"react-jsx\",\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"src/*\": [\"./src/*\"],\n      \"@components/*\": [\"./src/components/*\"],\n      \"@/components/*\": [\"./src/components/*\"],\n      \"@libs/*\": [\"./src/libs/*\"]\n    },\n    \"types\": [\"chrome\"],\n  },\n  \"include\": [\"src/**/*\"]\n}\n"
  },
  {
    "path": "packages/ui/tsup.config.ts",
    "content": "import { defineConfig } from 'tsup';\n\nexport default defineConfig({\n  treeshake: true,\n  splitting: true,\n  entry: ['src/index.ts'],\n  format: ['cjs', 'esm'],\n  dts: true,\n  clean: true,\n  external: ['chrome'],\n});\n"
  },
  {
    "path": "packages/zipper/index.mts",
    "content": "import fs from 'node:fs';\nimport { resolve } from 'node:path';\nimport { zipBundle } from './lib/index.js';\nconst IS_FIREFOX = process.env.BROWSER === 'firefox';\nconst packageJson = JSON.parse(fs.readFileSync('../../package.json', 'utf8'));\n\nconst YYYY_MM_DD = new Date().toISOString().slice(0, 10).replace(/-/g, '');\nconst HH_mm_ss = new Date().toISOString().slice(11, 19).replace(/:/g, '');\n\nconst fileName = `extension-${packageJson.version}-${YYYY_MM_DD}-${HH_mm_ss}`;\n\nawait zipBundle({\n  distDirectory: resolve(import.meta.dirname, '..', '..', '..', 'dist'),\n  buildDirectory: resolve(import.meta.dirname, '..', '..', '..', 'dist-zip'),\n  archiveName: IS_FIREFOX ? `${fileName}.xpi` : `${fileName}.zip`,\n}).catch(error => {\n  console.error('Error zipping the bundle:', error);\n  process.exit(1);\n});\n\nconsole.log('Zipping completed');\n"
  },
  {
    "path": "packages/zipper/lib/index.ts",
    "content": "import fg from 'fast-glob';\nimport { AsyncZipDeflate, Zip } from 'fflate';\nimport { createReadStream, createWriteStream, existsSync, mkdirSync } from 'node:fs';\nimport { posix, resolve } from 'node:path';\n\n// Converts bytes to megabytes\nfunction toMB(bytes: number): number {\n  return bytes / 1024 / 1024;\n}\n\n// Creates the build directory if it doesn't exist\nfunction ensureBuildDirectoryExists(buildDirectory: string): void {\n  if (!existsSync(buildDirectory)) {\n    mkdirSync(buildDirectory, { recursive: true });\n  }\n}\n\n// Logs the package size and duration\nfunction logPackageSize(size: number, startTime: number): void {\n  console.log(`Zip Package size: ${toMB(size).toFixed(2)} MB in ${Date.now() - startTime}ms`);\n}\n\n// Handles file streaming and zipping\nfunction streamFileToZip(\n  absPath: string,\n  relPath: string,\n  zip: Zip,\n  onAbort: () => void,\n  onError: (error: Error) => void,\n): void {\n  const data = new AsyncZipDeflate(relPath, { level: 9 });\n  zip.add(data);\n\n  createReadStream(absPath)\n    .on('data', (chunk: string | Buffer) =>\n      typeof chunk === 'string' ? data.push(Buffer.from(chunk), false) : data.push(chunk, false),\n    )\n    .on('end', () => data.push(new Uint8Array(0), true))\n    .on('error', error => {\n      onAbort();\n      onError(error);\n    });\n}\n\n// Zips the bundle\nexport const zipBundle = async (\n  {\n    distDirectory,\n    buildDirectory,\n    archiveName,\n  }: {\n    distDirectory: string;\n    buildDirectory: string;\n    archiveName: string;\n  },\n  withMaps = false,\n): Promise<void> => {\n  ensureBuildDirectoryExists(buildDirectory);\n\n  const zipFilePath = resolve(buildDirectory, archiveName);\n  const output = createWriteStream(zipFilePath);\n\n  const fileList = await fg(\n    [\n      '**/*', // Pick all nested files\n      ...(!withMaps ? ['!**/(*.js.map|*.css.map)'] : []), // Exclude source maps conditionally\n    ],\n    {\n      cwd: distDirectory,\n      onlyFiles: true,\n    },\n  );\n\n  return new Promise<void>((pResolve, pReject) => {\n    let aborted = false;\n    let totalSize = 0;\n    const timer = Date.now();\n    const zip = new Zip((err, data, final) => {\n      if (err) {\n        pReject(err);\n      } else {\n        totalSize += data.length;\n        output.write(data);\n        if (final) {\n          logPackageSize(totalSize, timer);\n          output.end();\n          pResolve();\n        }\n      }\n    });\n\n    // Handle file read streams\n    for (const file of fileList) {\n      if (aborted) return;\n\n      const absPath = resolve(distDirectory, file);\n      const absPosixPath = posix.resolve(distDirectory, file);\n      const relPosixPath = posix.relative(distDirectory, absPosixPath);\n\n      console.log(`Adding file: ${relPosixPath}`);\n      streamFileToZip(\n        absPath,\n        relPosixPath,\n        zip,\n        () => {\n          aborted = true;\n          zip.terminate();\n        },\n        error => pReject(`Error reading file ${absPath}: ${error.message}`),\n      );\n    }\n\n    zip.end();\n  });\n};\n"
  },
  {
    "path": "packages/zipper/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/zipper\",\n  \"version\": \"0.8.0\",\n  \"description\": \"chrome extension - zipper\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"files\": [\n    \"dist/**\"\n  ],\n  \"types\": \"index.mts\",\n  \"main\": \"dist/index.mjs\",\n  \"scripts\": {\n    \"clean:bundle\": \"rimraf dist\",\n    \"clean:node_modules\": \"pnpx rimraf node_modules\",\n    \"clean:turbo\": \"rimraf .turbo\",\n    \"clean\": \"pnpm clean:bundle && pnpm clean:node_modules && pnpm clean:turbo\",\n    \"zip\": \"npm run ready && node dist/index.mjs\",\n    \"lint\": \"eslint .\",\n    \"ready\": \"tsc -b\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write --ignore-path ../../.prettierignore\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"devDependencies\": {\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\",\n    \"fflate\": \"^0.8.2\",\n    \"fast-glob\": \"^3.3.3\"\n  }\n}\n"
  },
  {
    "path": "packages/zipper/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"outDir\": \"dist\",\n    \"module\": \"esnext\",\n    \"target\": \"esnext\"\n  },\n  \"include\": [\"index.mts\", \"lib\"]\n}\n"
  },
  {
    "path": "pages/content/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/content-script\",\n  \"version\": \"0.3.3\",\n  \"description\": \"chrome extension - content script\",\n  \"private\": true,\n  \"sideEffects\": true,\n  \"files\": [\n    \"dist/**\"\n  ],\n  \"scripts\": {\n    \"clean:node_modules\": \"pnpx rimraf node_modules\",\n    \"clean:turbo\": \"rimraf .turbo\",\n    \"clean\": \"pnpm clean:turbo && pnpm clean:node_modules\",\n    \"build\": \"vite build\",\n    \"build:watch\": \"cross-env __DEV__=true vite build -w --mode development\",\n    \"dev\": \"pnpm build:watch\",\n    \"lint\": \"eslint . --ext .ts,.tsx\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write --ignore-path ../../.prettierignore\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {\n    \"@sync-your-cookie/shared\": \"workspace:*\",\n    \"@sync-your-cookie/storage\": \"workspace:*\",\n    \"eventemitter3\": \"^5.0.1\"\n  },\n  \"devDependencies\": {\n    \"@sync-your-cookie/hmr\": \"workspace:*\",\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "pages/content/src/index.ts",
    "content": "import { init } from './localStorage';\ninit();\n"
  },
  {
    "path": "pages/content/src/listener.ts",
    "content": "import { Message, MessageType, SendResponse } from '@sync-your-cookie/shared';\nimport EventEmitter from 'eventemitter3';\n\ndeclare global {\n  interface Window {\n    $messageListener: MessageListener;\n  }\n}\n\n\n/**\n * 消息监听器类\n * 基于发布订阅模式，集中管理消息处理\n */\nexport class MessageListener {\n  // 声明一个事件发射器\n  private emitter: EventEmitter;\n  // 声明一个单例实例\n  private static instance: MessageListener;\n  // 声明一个调试器开关\n  public debuggerOpen = true;\n\n  private timer: number | null = null;\n\n  constructor() {\n    // 初始化事件发射器\n    this.emitter = new EventEmitter();\n    // 初始化\n    this.init();\n  }\n\n  /**\n   * 获取单例实例\n   * @returns {MessageListener} 单例实例\n   */\n  public static getInstance(): MessageListener {\n    // 如果单例实例不存在，则创建一个新的实例\n    if (!MessageListener.instance) {\n      MessageListener.instance = new MessageListener();\n    }\n    // 返回单例实例\n    return MessageListener.instance;\n  }\n\n  // 处理消息\n  handleMessage = (message: Message, sender: chrome.runtime.MessageSender, sendResponse: (response?: SendResponse) => void) => {\n    // 打印消息类型\n    if(message.toString() === 'ping') {\n      this.listen();\n      return\n    }\n    // 如果消息类型为获取本地存储\n    if (message.type === MessageType.GetLocalStorage) {\n      // 发射消息\n      this.emit(MessageType.GetLocalStorage, message.payload, sendResponse);\n      // console.log('拦截到 XHR 响应:', message.data);\n      // sendResponse({});\n      // 如果消息类型为设置本地存储\n    } else if (message.type === MessageType.SetLocalStorage) {\n      this.emit(MessageType.SetLocalStorage, message.payload, sendResponse);\n    }\n    return true; // keep the channel open\n  }\n\n  private init(): void {\n    // 此处不能使用 async 函数\n    this.listen(false);\n    // this.ping();\n    // chrome.runtime.onConnect.addListener(port => {\n    //   console.log('connected ', port);\n    //   if (port.name === 'hi') {\n    //     port.onMessage.addListener((evt) => {\n    //       console.log(\"evt\", evt)\n    //     });\n    //   }\n    // });\n    // window.removeEventListener(\"load\", this.listen);\n    // window.addEventListener(\"load\", this.listen);\n    console.log('chrome', chrome, chrome.tabs)\n\n    // var port = chrome.runtime.connect(null as any, { name: 'hi' });\n    // console.log(\"port\", port);\n    // port.onDisconnect.addListener(obj => {\n    //   console.log('disconnected port');\n    // })\n  }\n\n  public ping = () => {\n    chrome.runtime.sendMessage('ping', response => {\n      setTimeout(this.ping, 1000);\n      console.log('pong', response);\n      // if (chrome.runtime.lastError) {\n      // } else {\n      //   // Do whatever you want, background script is ready now\n      // }\n    });\n  }\n\n  public listen = (initSetTimeout = true) => {\n    if(this.timer) {\n      clearTimeout(this.timer);\n    }\n    const fn = this.handleMessage;\n    chrome.runtime.onMessage.removeListener(fn);\n    chrome.runtime.onMessage.addListener(fn);\n    if(initSetTimeout) {\n      this.timer = setTimeout(() => {\n        console.log('listen timeout reset')\n        this.listen();\n      }, 6000)\n    }\n  }\n\n  /**\n   * 监听消息\n   * @param {string} event 消息类型\n   * @param {(...args: any[]) => void} callback 回调函数\n   */\n\n  public on(event: string, callback: (...args: any[]) => void): void {\n    this.emitter.on(event, callback);\n  }\n\n  /**\n   * 取消监听消息\n   * @param {string} event 消息类型\n   * @param {(...args: any[]) => void} sendResponse 回调函数\n   */\n  async emit(event: string, data: any, sendResponse?: (...args: any[]) => void): Promise<void> {\n    console.log(\"data\", data);\n    return new Promise((resolve, reject) => {\n      try {\n        if (sendResponse) {\n          this.emitter.emit(event, data, (message: any) => {\n            if (sendResponse) {\n              sendResponse(message);\n            }\n            resolve();\n          });\n        } else {\n          this.emitter.emit(event, data);\n          resolve();\n        }\n      } catch (error) {\n        reject(error);\n      }\n    });\n  }\n}\n\nexport const messageListener = MessageListener.getInstance();\nwindow.$messageListener = messageListener\n"
  },
  {
    "path": "pages/content/src/localStorage.ts",
    "content": "import { eventHandlerInstance } from \"./observer\";\n\nexport const init = () => {\n  eventHandlerInstance.init();\n  console.log(\"LocalStorage initialized\");\n}\n"
  },
  {
    "path": "pages/content/src/observer.ts",
    "content": "import { DomainPayload, MessageType, SendResponse, SetLocalStorageMessagePayload } from '@sync-your-cookie/shared';\nimport { messageListener } from './listener';\n\n\nclass Observer {\n  observers: any[] = [];\n\n  constructor() {\n  }\n\n  // 订阅获取本地存储\n  subscribeGetLocalStorage(callback: (data: any, sendResponse: (data: SendResponse) => void) => void) {\n    messageListener.on(MessageType.GetLocalStorage, callback);\n  }\n\n  subscribeSetLocalStorage(callback: (data: any, sendResponse: (data: SendResponse) => void) => void) {\n    messageListener.on(MessageType.SetLocalStorage, callback);\n  }\n}\n\nexport const observer = new Observer();\n\n\nclass eventHandler {\n  constructor() {\n    // this.init();\n  }\n\n  init() {\n    this.handleGetLocalStorage();\n    this.handleSetLocalStorage();\n  }\n\n  handleGetLocalStorage() {\n    observer.subscribeGetLocalStorage(async (result: DomainPayload, sendResponse: (data: SendResponse) => void) => {\n      console.log(\"result\", result);\n      if (location.origin.includes(result.domain)) {\n        try {\n          console.log(\"localStorage\", { ...localStorage });\n          const items: {key: string, value: string}[] = [];\n          const localObject = { ...localStorage };\n          for(const key in localObject) {\n            const value:string = localObject[key].toString();\n            items.push({key, value});\n          }\n          console.log(\"items\", items);\n\n          sendResponse({\n              isOk: true,\n              msg: 'get localStorage success',\n              result: items\n          });\n          // const json = JSON.parse(result.response);\n        } catch (error) {\n          console.log('XHR_RESPONSE->error', error);\n          sendResponse({\n            isOk: false,\n            msg: 'get localStorage error',\n            result: error\n          })\n        }\n      } else {\n        console.log(\"localStorage not match domain\", result.domain);\n      }\n\n    });\n  }\n\n  handleSetLocalStorage() {\n    observer.subscribeSetLocalStorage(async (result: SetLocalStorageMessagePayload, sendResponse: (data: SendResponse) => void) => {\n      console.log(\"result\", result);\n      if (location.origin.includes(result.domain)) {\n        try {\n          const values = result.value;\n          const setKey = result.onlyKey;\n          if(setKey){\n            const targetItem = values.find((item: any) => item.key === setKey);\n            if(targetItem){\n              localStorage.setItem(setKey, targetItem.value || \"\");\n            } else {\n              console.log(\"no target item\", setKey);\n            }\n          } else {\n            for(const item of values){\n              localStorage.setItem(item.key || '', item.value || \"\");\n            }\n          }\n          sendResponse({\n              isOk: true,\n              msg: 'set localStorage success',\n          });\n          // const json = JSON.parse(result.response);\n        } catch (error) {\n          console.log('XHR_RESPONSE->error', error);\n          sendResponse({\n            isOk: false,\n            msg: 'set localStorage error',\n            result: error\n          })\n        }\n      } else {\n        console.log(\"localStorage not match domain\", result.domain);\n      }\n\n    });\n  }\n\n}\nexport const eventHandlerInstance = new eventHandler();\n"
  },
  {
    "path": "pages/content/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/utils\",\n  \"compilerOptions\": {\n    \"outDir\": \"../dist/content\",\n    \"jsx\": \"react-jsx\",\n    \"checkJs\": false,\n    \"allowJs\": false,\n    \"baseUrl\": \"./src\",\n    \"declarationMap\": true,\n    \"paths\": {\n      \"@src/*\": [\"src/*\"]\n    },\n    \"types\": [\"chrome\"]\n  },\n  \"include\": [\"index.ts\", \"src\"],\n}\n"
  },
  {
    "path": "pages/content/vite.config.mts",
    "content": "import { watchRebuildPlugin } from '@sync-your-cookie/hmr';\nimport react from '@vitejs/plugin-react-swc';\nimport { resolve } from 'path';\nimport { defineConfig } from 'vite';\n\nconst rootDir = resolve(__dirname);\nconst srcDir = resolve(rootDir, 'src');\n\nconst isDev = process.env.__DEV__ === 'true';\nconst isProduction = !isDev;\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      '@src': srcDir,\n    },\n  },\n  base: '',\n  plugins: [react(), isDev && watchRebuildPlugin({ refresh: true })],\n  publicDir: resolve(rootDir, 'public'),\n  build: {\n    lib: {\n      entry: resolve(__dirname, 'src/index.ts'),\n      formats: ['iife'],\n      name: 'ContentScript',\n      fileName: 'index',\n    },\n    sourcemap: isDev,\n    minify: isProduction,\n    reportCompressedSize: isProduction,\n    rollupOptions: {\n      external: ['chrome'],\n    },\n    outDir: resolve(rootDir, '..', '..', 'dist', 'content'),\n  },\n  define: {\n    'process.env.NODE_ENV': isDev ? `\"development\"` : `\"production\"`,\n  },\n});\n"
  },
  {
    "path": "pages/options/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>SyncYourCookie-Options</title>\n  </head>\n\n  <body>\n    <div id=\"app-container\"></div>\n    <script type=\"module\" src=\"./src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "pages/options/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/options\",\n  \"version\": \"0.0.1\",\n  \"description\": \"chrome extension options\",\n  \"private\": true,\n  \"sideEffects\": true,\n  \"files\": [\n    \"dist/**\"\n  ],\n  \"scripts\": {\n    \"clean\": \"rimraf ./dist && rimraf .turbo\",\n    \"build\": \"pnpm run clean && tsc --noEmit && vite build\",\n    \"build:watch\": \"cross-env __DEV__=true vite build -w --mode development\",\n    \"dev\": \"pnpm build:watch\",\n    \"lint\": \"eslint . --ext .ts,.tsx\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {\n    \"@sync-your-cookie/shared\": \"workspace:*\",\n    \"@sync-your-cookie/storage\": \"workspace:*\",\n    \"lucide-react\": \"^0.394.0\",\n    \"sonner\": \"^1.5.0\"\n  },\n  \"devDependencies\": {\n    \"@sync-your-cookie/hmr\": \"workspace:*\",\n    \"@sync-your-cookie/tailwindcss-config\": \"workspace:*\",\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\",\n    \"@sync-your-cookie/ui\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "pages/options/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "pages/options/src/Options.tsx",
    "content": "import {\n  useStorageSuspense,\n  useTheme,\n  verifyCloudflareToken,\n  withErrorBoundary,\n  withSuspense,\n} from '@sync-your-cookie/shared';\nimport { accountStorage } from '@sync-your-cookie/storage/lib/accountStorage';\nimport { initStorageKey } from '@sync-your-cookie/storage/lib/settingsStorage';\nimport {\n  Button,\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n  Input,\n  Label,\n  SyncTooltip,\n  ThemeDropdown,\n  Toaster,\n} from '@sync-your-cookie/ui';\nimport { Eye, EyeOff, Info, Loader2, LogOut, SlidersVertical } from 'lucide-react';\nimport { useState } from 'react';\nimport { toast } from 'sonner';\nimport { SettingsPopover } from './components/SettingsPopover';\nimport { useGithub } from './hooks/useGithub';\n\nconst Options = () => {\n  const accountInfo = useStorageSuspense(accountStorage);\n  const [token, setToken] = useState(accountInfo.token);\n  const [accountId, setAccountId] = useState(accountInfo.accountId);\n  const [namespaceId, setNamespaceId] = useState(accountInfo.namespaceId);\n  const [openEye, setOpenEye] = useState(false);\n  const { loading, handleLaunchAuth } = useGithub();\n  const [loadingSave, setLoadingSave] = useState(false);\n\n  const { setTheme } = useTheme();\n\n  const handleTokenInput: React.ChangeEventHandler<HTMLInputElement> = evt => {\n    setToken(evt.target.value);\n  };\n\n  const handleAccountInput: React.ChangeEventHandler<HTMLInputElement> = evt => {\n    setAccountId(evt.target.value);\n  };\n\n  const handleNamespaceInput: React.ChangeEventHandler<HTMLInputElement> = evt => {\n    setNamespaceId(evt.target.value);\n  };\n\n  const handleSave = async () => {\n    if (!accountId?.trim() || !token?.trim()) {\n      toast.warning('Account ID and Token are required');\n      return;\n    } else if (!namespaceId?.trim()) {\n      toast.warning('NamespaceId are required');\n      return;\n    }\n    try {\n      setLoadingSave(true);\n      const res = await verifyCloudflareToken(accountId.trim(), token.trim());\n      if (res.success === true) {\n        const [message] = res.messages;\n        if (message?.message) {\n          toast.success('Save Success (' + message.message.replace('API', '') + ')');\n        } else {\n          toast.success('Save Success');\n        }\n        accountStorage.update({\n          selectedProvider: 'cloudflare',\n          accountId: accountId,\n          namespaceId: namespaceId,\n          token: token,\n        });\n      } else {\n        const [error] = res.errors;\n        if (error?.message) {\n          toast.error('Verify Failed: ' + error.message);\n        } else {\n          toast.error('Verify Failed: Unknown Error');\n        }\n      }\n    } catch (err: any) {\n      console.log('error', err);\n      const [error] = err?.errors || [];\n      if (error?.message) {\n        toast.error('Verify Failed: ' + error.message);\n      } else {\n        toast.error('Verify Failed: Unknown Error');\n      }\n    } finally {\n      setLoadingSave(false);\n    }\n  };\n\n  const handleToggleEye = () => {\n    setOpenEye(!openEye);\n  };\n\n  const handleLogout = () => {\n    accountStorage.update({\n      githubAccessToken: '',\n      selectedProvider: 'cloudflare',\n      name: '',\n      avatarUrl: '',\n      bio: '',\n      email: '',\n    });\n    toast.success('Log out Success');\n    initStorageKey();\n  };\n\n  const renderAccount = () => {\n    if (accountInfo.selectedProvider === 'github' && accountInfo.githubAccessToken) {\n      return (\n        <CardContent className=\"w-full\">\n          <div>\n            <div className=\"flex relative items-center\">\n              <img className=\"size-12 rounded-full\" src={accountInfo.avatarUrl} alt=\"\" />\n              <div className=\"flex  flex-col flex-1 ml-4\">\n                <SyncTooltip\n                  title={\n                    <div>\n                      <p>\n                        githubAccessToken: <span className=\"text-orange-500\">{accountInfo.githubAccessToken}</span>\n                      </p>\n                      <p>Your accessToken is only stored on your local device.</p>\n                    </div>\n                  }>\n                  <p className=\"text-base flex items-center font-medium\">\n                    <span className=\"mr-2\">{accountInfo.name}</span>\n                    <Info className=\"\" size={16} />\n                  </p>\n                </SyncTooltip>\n                <p className=\"text-xs\">{accountInfo.email || accountInfo.bio}</p>\n              </div>\n            </div>\n            <Button onClick={handleLogout} type=\"submit\" variant=\"outline\" className=\"w-full mt-4\">\n              <LogOut size={16} className=\"mr-2\" />\n              Log out\n            </Button>\n          </div>\n        </CardContent>\n      );\n    }\n    return (\n      <>\n        <CardContent>\n          <CardDescription className=\"mt-[-16px] mb-4\">\n            Enter your cloudflare account Or using Github Gist\n          </CardDescription>\n          <div className=\"grid gap-4\">\n            <div className=\"grid gap-2\">\n              <div className=\"flex justify-between items-center \">\n                <Label htmlFor=\"token\">Authorization Token</Label>\n                <p className=\"flex items-center text-center text-xs\">\n                  <a\n                    href=\"https://github.com/jackluson/sync-your-cookie/blob/main/how-to-use.md\"\n                    target=\"_blank\"\n                    className=\" cursor-pointer underline mx-2\"\n                    rel=\"noreferrer\">\n                    How to get it?\n                  </a>\n                  <span\n                    role=\"button\"\n                    tabIndex={0}\n                    onClick={() => handleToggleEye()}\n                    onKeyDown={e => {\n                      if (e.key === 'Enter' || e.key === ' ') {\n                        handleToggleEye();\n                      }\n                    }}\n                    className=\"cursor-pointer\">\n                    {openEye ? <EyeOff size={18} /> : <Eye size={18} />}\n                  </span>\n                </p>\n              </div>\n              <Input\n                id=\"token\"\n                value={token}\n                onChange={handleTokenInput}\n                className=\"w-full mb-2\"\n                type={openEye ? 'text' : 'password'}\n                placeholder=\"please input your cloudflare token \"\n                required\n              />\n            </div>\n            <div className=\"grid gap-2\">\n              <div className=\"flex justify-between items-center \">\n                <Label htmlFor=\"accountId\">Account ID</Label>\n                <p className=\"flex items-center text-center text-xs\">\n                  Don’t have a cloudflare Account yet?\n                  <a\n                    href=\"https://dash.cloudflare.com/sign-up\"\n                    target=\"_blank\"\n                    className=\" cursor-pointer underline ml-2\"\n                    rel=\"noreferrer\">\n                    Sign up\n                  </a>\n                </p>\n              </div>\n              <Input\n                id=\"accountId\"\n                value={accountId}\n                onChange={handleAccountInput}\n                className=\"w-full mb-2\"\n                type=\"text\"\n                placeholder=\"please input your cloudflare account ID \"\n                required\n              />\n            </div>\n            <div className=\"grid gap-2\">\n              <div className=\"flex justify-between items-center \">\n                <Label htmlFor=\"namespaceId\">Namespace ID</Label>\n                {namespaceId?.trim() && accountId?.trim() ? (\n                  <a\n                    href={`https://dash.cloudflare.com/${accountId.trim()}/workers/kv/namespaces/${namespaceId.trim()}`}\n                    target=\"_blank\"\n                    className=\" cursor-pointer underline ml-2\"\n                    rel=\"noreferrer\">\n                    Go to namespace\n                  </a>\n                ) : null}\n\n                {/* {namespaceId ? null : (\n                      <div className=\"text-center ml-16 text-sm\">\n                        Don’t have an ID yet?\n                        <span className=\" cursor-pointer underline ml-2\">Create</span>\n                      </div>\n                    )} */}\n              </div>\n              <Input\n                id=\"namespaceId\"\n                value={namespaceId}\n                onChange={handleNamespaceInput}\n                className=\"w-full mb-4\"\n                type=\"text\"\n                placeholder=\"please input namespace ID \"\n              />\n            </div>\n            <Button disabled={loadingSave} onClick={handleSave} type=\"submit\" className=\"w-full\">\n              {loadingSave ? <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" /> : null}\n              Save\n            </Button>\n          </div>\n        </CardContent>\n        <div className=\"after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t\">\n          <span className=\"bg-background text-muted-foreground relative z-10 px-2\">Or continue with</span>\n        </div>\n        <CardContent>\n          <Button disabled={loading} onClick={handleLaunchAuth} className=\"w-full mt-6\" variant=\"outline\" size=\"sm\">\n            <img\n              src={chrome.runtime.getURL('popup/github.svg')}\n              className=\"ml-1 h-4 w-4 overflow-hidden object-contain \"\n              alt=\"logo\"\n            />\n            <span className=\"ml-2\">Using with GitHub</span>\n          </Button>\n        </CardContent>\n      </>\n    );\n  };\n\n  return (\n    <div className=\"w-screen h-screen absolute top-0 left-0 right-0 bottom-0 flex flex-col items-center justify-center p-4 bg-background \">\n      <div className=\"fixed right-8 top-8 \">\n        <ThemeDropdown setTheme={setTheme} />\n      </div>\n      <div className=\" mt-[-80px] flex justify-center flex-col items-center\">\n        <img\n          src={chrome.runtime.getURL('options/logo.png')}\n          className=\"size-40 overflow-hidden object-contain mb-4 animate-[spin_20s_linear_infinite]\"\n          alt=\"logo\"\n        />\n        <div className=\"w-full\">\n          <Card className=\"mx-auto min-w-[400px] max-w-lg\">\n            <CardHeader className=\"relative\">\n              <div className=\"flex justify-between\">\n                <CardTitle className=\"text-xl\">Settings</CardTitle>\n              </div>\n              <SettingsPopover\n                trigger={\n                  <Button variant=\"secondary\" size=\"icon\" className=\"size-6 absolute right-4 top-4\">\n                    <SlidersVertical size={18} />\n                  </Button>\n                }\n              />\n            </CardHeader>\n            {renderAccount()}\n          </Card>\n        </div>\n      </div>\n\n      <div className=\"mt-2 text-sm\">\n        Built by\n        <a\n          className=\"mx-0.5 font-bold text-primary \"\n          target=\"_blank\"\n          href=\"https://github.com/jackluson\"\n          rel=\"noreferrer\">\n          jackluson\n        </a>\n        . The source code is available on{' '}\n        <a\n          className=\"font-bold inline-flex items-center \"\n          href=\"https://github.com/jackluson/sync-your-cookie\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\">\n          GitHub\n          <img\n            src={chrome.runtime.getURL('popup/github.svg')}\n            className=\"ml-1 h-4 w-4 overflow-hidden object-contain \"\n            alt=\"logo\"\n          />\n        </a>\n        .\n      </div>\n\n      <Toaster\n        position=\"top-center\"\n        richColors\n        visibleToasts={1}\n        toastOptions={{\n          duration: 2000,\n        }}\n      />\n    </div>\n  );\n};\n\nexport default withErrorBoundary(withSuspense(Options, <div> Loading ... </div>), <div> Error Occur </div>);\n"
  },
  {
    "path": "pages/options/src/components/SettingsPopover.tsx",
    "content": "import { GithubApi, pullCookies, useStorageSuspense } from '@sync-your-cookie/shared';\nimport { accountStorage } from '@sync-your-cookie/storage/lib/accountStorage';\nimport { cookieStorage } from '@sync-your-cookie/storage/lib/cookieStorage';\nimport { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';\nimport { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';\nimport { Input, Label, Popover, PopoverContent, PopoverTrigger, Switch, SyncTooltip } from '@sync-your-cookie/ui';\nimport { Eye, EyeOff, Info, Lock, SquareArrowOutUpRight } from 'lucide-react';\nimport React, { useEffect, useState } from 'react';\nimport { StorageSelect } from './StorageSelect';\ninterface SettingsPopover {\n  trigger: React.ReactNode;\n}\n\nexport function SettingsPopover({ trigger }: SettingsPopover) {\n  const settingsInfo = useStorageSuspense(settingsStorage);\n  const [selectOpen, setSelectOpen] = useState(false);\n  const [openEye, setOpenEye] = useState(false);\n\n  const handleCheckChange = (\n    checked: boolean,\n    checkedKey: 'protobufEncoding' | 'includeLocalStorage' | 'contextMenu' | 'encryptionEnabled',\n  ) => {\n    settingsStorage.update({\n      [checkedKey]: checked,\n    });\n  };\n\n  const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    settingsStorage.update({\n      encryptionPassword: e.target.value,\n    });\n  };\n\n  const handleValueChange = (value: string) => {\n    settingsStorage.update({\n      storageKey: value,\n    });\n  };\n\n  const reset = async () => {\n    await domainStatusStorage.resetState();\n    await cookieStorage.reset();\n    await pullCookies();\n    console.log('reset finished');\n  };\n\n  useEffect(() => {\n    reset();\n  }, [settingsInfo.storageKey]);\n  const handleToggleEye = () => {\n    setOpenEye(!openEye);\n  };\n  const handleOpenChange = (open: boolean) => {\n    if (selectOpen) return;\n    // if (open === false && (settingsInfo.storageKey !== storageKey || !storageKey)) {\n    //   console.log('open', open);\n    //   settingsStorage.update({\n    //     storageKey: storageKey || defaultKey,\n    //   });\n    //   domainConfigStorage.resetState();\n    // }\n  };\n\n  const handleSelectOpenChange = (open: boolean) => {\n    console.log('select open', open);\n    setSelectOpen(open);\n  };\n\n  const handleAddStorageKey = async (key: string) => {\n    const accountStorageInfo = await accountStorage.getSnapshot();\n    if (accountStorageInfo?.selectedProvider === 'github') {\n      const gistId = settingsInfo.storageKeyGistId;\n      await GithubApi.instance.addGistFile(gistId!, key);\n      await GithubApi.instance.initStorageKeyList();\n    } else {\n      await settingsStorage.addStorageKey(key);\n    }\n  };\n\n  const handleRemoveStorageKey = async (key: string) => {\n    const accountStorageInfo = await accountStorage.getSnapshot();\n    if (accountStorageInfo?.selectedProvider === 'github') {\n      const gistId = settingsInfo.storageKeyGistId;\n      await GithubApi.instance.deleteGistFile(gistId!, key);\n      await GithubApi.instance.initStorageKeyList();\n    } else {\n      await settingsStorage.removeStorageKey(key);\n    }\n  };\n\n  return (\n    <Popover onOpenChange={handleOpenChange}>\n      <PopoverTrigger asChild>{trigger}</PopoverTrigger>\n      <PopoverContent align=\"start\" className=\"w-[328px]\">\n        <div className=\"grid gap-4\">\n          <div className=\"space-y-2\">\n            <h3 className=\"leading-none font-medium text-base\">Save Settings</h3>\n            <p className=\"text-muted-foreground text-sm\">Set the save format. </p>\n          </div>\n          <div className=\"gap-2\">\n            <div className=\"flex items-center gap-4 mb-4\">\n              <Label className=\"w-[136px] block text-right\" htmlFor=\"storage-key\">\n                <div className=\"flex gap-2 justify-end\">\n                  Storage Key\n                  {settingsInfo.gistHtmlUrl ? (\n                    <a href={settingsInfo.gistHtmlUrl} target=\"_blank\" rel=\"noreferrer\">\n                      <SquareArrowOutUpRight size={16} />\n                    </a>\n                  ) : null}\n                </div>\n              </Label>\n              {/* <Input\n                onChange={handleKeyInputChange}\n                id=\"storage-key\"\n                value={storageKey}\n                className=\"h-8 flex-1\"\n                placeholder={defaultKey}\n              /> */}\n              <StorageSelect\n                options={settingsInfo.storageKeyList}\n                open={selectOpen}\n                onOpenChange={handleSelectOpenChange}\n                value={settingsInfo.storageKey || ''}\n                onAdd={handleAddStorageKey}\n                onRemove={handleRemoveStorageKey}\n                onValueChange={handleValueChange}\n              />\n            </div>\n            <div className=\"flex items-center gap-4 mb-4\">\n              <Label className=\"whitespace-nowrap block w-[136px] justify-end text-right\" htmlFor=\"encoding\">\n                Protobuf Encoding\n              </Label>\n              <Switch\n                onCheckedChange={checked => handleCheckChange(checked, 'protobufEncoding')}\n                checked={settingsInfo.protobufEncoding}\n                id=\"encoding\"\n              />\n            </div>\n\n            <div className=\"flex items-center gap-4 mb-4\">\n              <Label\n                className=\"items-center  whitespace-nowrap flex w-[136px] justify-end text-right\"\n                htmlFor=\"include\">\n                Include LocalStorage\n              </Label>\n              <div className=\"flex items-center gap-1\">\n                <Switch\n                  onCheckedChange={checked => handleCheckChange(checked, 'includeLocalStorage')}\n                  checked={settingsInfo.includeLocalStorage}\n                  id=\"include\"\n                />\n                <SyncTooltip title=\"Note: LocalStorage cannot supports Auto Push & If the retrieval fails, the page will be reloaded once to try again.\">\n                  <Info className=\"mx-2\" size={18} />\n                </SyncTooltip>\n              </div>\n            </div>\n\n            <div className=\"flex items-center gap-4 mb-4\">\n              <Label\n                className=\"items-center whitespace-nowrap flex w-[136px] justify-end text-right\"\n                htmlFor=\"contextMenu\">\n                Show ContextMenu\n              </Label>\n              <div className=\"flex items-center gap-1\">\n                <Switch\n                  onCheckedChange={checked => handleCheckChange(checked, 'contextMenu')}\n                  checked={settingsInfo.contextMenu}\n                  id=\"contextMenu\"\n                />\n              </div>\n            </div>\n\n            <div className=\"border-t pt-4 mt-2\">\n              <div className=\"flex items-center gap-4 mb-4\">\n                <Label\n                  className=\"items-center whitespace-nowrap flex w-[136px] justify-end text-right\"\n                  htmlFor=\"encryption\">\n                  <Lock size={14} className=\"mr-1\" />\n                  E2E Encryption\n                </Label>\n                <div className=\"flex items-center gap-1\">\n                  <Switch\n                    onCheckedChange={checked => handleCheckChange(checked, 'encryptionEnabled')}\n                    checked={settingsInfo.encryptionEnabled}\n                    disabled={!settingsInfo.protobufEncoding}\n                    id=\"encryption\"\n                  />\n                  <SyncTooltip title=\"End-to-end encryption requires Protobuf Encoding to be enabled. Your data will be encrypted with AES-256-GCM before being sent to the cloud.\">\n                    <Info className=\"mx-2\" size={18} />\n                  </SyncTooltip>\n                </div>\n              </div>\n\n              {settingsInfo.encryptionEnabled && settingsInfo.protobufEncoding && (\n                <div className=\"flex items-center gap-2\">\n                  <Label\n                    className=\"items-center mr-2 whitespace-nowrap flex w-[136px] justify-end text-right\"\n                    htmlFor=\"encryptionPassword\">\n                    Password\n                  </Label>\n                  <Input\n                    type={openEye ? 'text' : 'password'}\n                    id=\"encryptionPassword\"\n                    value={settingsInfo.encryptionPassword || ''}\n                    onChange={handlePasswordChange}\n                    className=\"h-8 flex-1\"\n                    placeholder=\"Enter encryption password\"\n                  />\n                  <span\n                    role=\"button\"\n                    tabIndex={0}\n                    onClick={() => handleToggleEye()}\n                    onKeyDown={e => {\n                      if (e.key === 'Enter' || e.key === ' ') {\n                        handleToggleEye();\n                      }\n                    }}\n                    className=\"cursor-pointer mr-4\">\n                    {openEye ? <EyeOff size={18} /> : <Eye size={18} />}\n                  </span>\n                </div>\n              )}\n            </div>\n          </div>\n        </div>\n      </PopoverContent>\n    </Popover>\n  );\n}\n"
  },
  {
    "path": "pages/options/src/components/StorageSelect.tsx",
    "content": "import {\n  Button,\n  Input,\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectPortal,\n  SelectTrigger,\n  SelectValue,\n} from '@sync-your-cookie/ui';\nimport { useRef, useState } from 'react';\n\nimport { CircleX, LoaderIcon, Plus } from 'lucide-react';\nimport { toast } from 'sonner';\n\nexport interface IOption {\n  value: string;\n  label: string;\n  [key: string]: any;\n}\n\ninterface StorageSelectProps extends React.ComponentProps<typeof Select> {\n  options: IOption[];\n  value: string;\n  onAdd: (key: string) => void;\n  onRemove: (key: string) => void;\n}\n\nexport function StorageSelect(props: StorageSelectProps) {\n  const { value, onRemove, options, onValueChange, ...rest } = props;\n  const [loading, setLoading] = useState(false);\n  const [inputValue, setInputValue] = useState('');\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  const handleAdd = async () => {\n    if (loading) {\n      return;\n    }\n    const newKey = inputValue.trim().replaceAll(/\\s+/g, '');\n    const exist = options.find(option => option.value === newKey);\n    if (exist) {\n      console.warn('Key already exists or is empty');\n      toast.error('Key already exists');\n      return;\n    }\n    try {\n      setLoading(true);\n      await props.onAdd(newKey);\n      setInputValue('');\n    } catch (error) {\n      console.log('error', error);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const handleRemoveKey = async (option: IOption) => {\n    // Handle removing a storage key\n    console.log('Remove storage key', option);\n    if (loading) {\n      return;\n    }\n    try {\n      setLoading(true);\n      await onRemove(option.value);\n    } catch (error) {\n      console.log('error', error);\n    } finally {\n      setLoading(false);\n    }\n  };\n  return (\n    <div ref={containerRef} className=\"mr-2\">\n      <Select\n        value={value}\n        onValueChange={val => {\n          if (val === value) {\n            return;\n          }\n          onValueChange?.(val);\n        }}\n        {...rest}>\n        <SelectTrigger className=\"w-[150px] scale-90 \">\n          <SelectValue className=\"ml-[-8px]\" placeholder=\"Select a Storage Key\" />\n        </SelectTrigger>\n        <SelectPortal>\n          <SelectContent onCloseAutoFocus={evt => evt.preventDefault()}>\n            {options.map((item, index) => {\n              return (\n                <div key={item.value} className=\"relative group\">\n                  <SelectItem className=\" w-full\" value={item.value}>\n                    <span className=\"cursor-pointer\">{item.label}</span>\n                  </SelectItem>\n                  {options.length > 1 && item.value !== value ? (\n                    <span\n                      ref={containerRef}\n                      onClick={e => handleRemoveKey(item)}\n                      role=\"button\"\n                      tabIndex={index}\n                      className=\"absolute top-2 invisible right-[6px] cursor-pointer group-hover:visible\">\n                      {loading ? <LoaderIcon className=\"animate-spin\" size={18} /> : <CircleX size={18} />}\n                    </span>\n                  ) : null}\n                </div>\n              );\n            })}\n\n            <div className=\"flex mx-2 items-center mt-2 gap-2\">\n              <Input\n                value={inputValue}\n                onChange={event => {\n                  setInputValue(event?.target.value.replaceAll(/\\s+/g, ''));\n                }}\n                onKeyDown={e => {\n                  if (e.key === 'Enter' && inputValue.replaceAll(/\\s+/g, '')) {\n                    e.preventDefault();\n                    handleAdd();\n                  }\n                }}\n                className=\"h-8 \"\n              />\n              <Button\n                disabled={!inputValue.replaceAll(/\\s+/g, '') || loading}\n                onClick={() => handleAdd()}\n                className=\"ml-0 scale-90\"\n                size=\"sm\"\n                type=\"submit\"\n                variant=\"outline\">\n                <Plus size={18} />\n                Add\n              </Button>\n            </div>\n          </SelectContent>\n        </SelectPortal>\n      </Select>\n    </div>\n  );\n}\n"
  },
  {
    "path": "pages/options/src/hooks/useGithub.ts",
    "content": "import { clientId, GithubApi, initGithubApi, scope } from '@sync-your-cookie/shared';\nimport { accountStorage } from '@sync-your-cookie/storage/lib/accountStorage';\nimport { useState } from 'react';\nimport { toast } from 'sonner';\n\ninitGithubApi();\n\nexport const useGithub = () => {\n  const [loading, setLoading] = useState(false);\n  const handleLaunchAuth = async () => {\n    const state = crypto.randomUUID();\n    const redirectUri = chrome.identity.getRedirectURL();\n    const authUrl =\n      `https://github.com/login/oauth/authorize?` +\n      `client_id=${clientId}` +\n      `&redirect_uri=${encodeURIComponent(redirectUri)}` +\n      `&scope=${encodeURIComponent(scope)}` +\n      `&state=${state}`;\n    setLoading(true);\n    try {\n      chrome.identity.launchWebAuthFlow({ url: authUrl, interactive: true }, async redirectUrl => {\n        console.log('redirectUrl', redirectUrl);\n        const code = redirectUrl ? new URL(redirectUrl).searchParams.get('code') : '';\n        if (code) {\n          try {\n            console.log('code', code);\n            const accessToken = await GithubApi.instance.fetchAccessToken(code);\n            console.log('accessToken', accessToken);\n            setLoading(false);\n            const user = await GithubApi.instance.fetchUser();\n            accountStorage.update({\n              githubAccessToken: accessToken,\n              selectedProvider: 'github',\n              name: user.name,\n              avatarUrl: user.avatar_url,\n              bio: user.bio,\n              email: user.email,\n            });\n            GithubApi.instance.reload();\n            console.log('user', user);\n            toast.success('GitHub Authorization Success');\n          } catch (error) {\n            toast.error('GitHub Authorization Failed');\n            setLoading(false);\n          }\n        } else {\n          setLoading(false);\n          toast.error('GitHub Authorization Failed');\n        }\n      });\n    } catch (error) {\n      console.error('Auth error', error);\n      toast.error('GitHub Authorization Failed');\n      setLoading(false);\n    }\n  };\n\n  return {\n    handleLaunchAuth,\n    loading,\n  };\n};\n"
  },
  {
    "path": "pages/options/src/index.css",
    "content": "\nbody {\n  margin: 0;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',\n    'Droid Sans', 'Helvetica Neue', sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n"
  },
  {
    "path": "pages/options/src/index.tsx",
    "content": "import Options from '@src/Options';\nimport '@src/index.css';\nimport { ThemeProvider } from '@sync-your-cookie/shared';\nimport '@sync-your-cookie/ui/css';\nimport { createRoot } from 'react-dom/client';\n\nfunction init() {\n  const appContainer = document.querySelector('#app-container');\n  if (!appContainer) {\n    throw new Error('Can not find #app-container');\n  }\n  const root = createRoot(appContainer);\n  root.render(\n    <ThemeProvider>\n      <Options />\n    </ThemeProvider>,\n  );\n}\n\ninit();\n"
  },
  {
    "path": "pages/options/tailwind.config.js",
    "content": "const uiConfig = require('@sync-your-cookie/ui/tailwind.config');\n\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  ...uiConfig,\n};\n"
  },
  {
    "path": "pages/options/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/base\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@src/*\": [\"src/*\"]\n    },\n    \"jsx\": \"react-jsx\",\n    \"types\": [\"chrome\", \"node\"]\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "pages/options/vite.config.ts",
    "content": "import { watchRebuildPlugin } from '@sync-your-cookie/hmr';\nimport react from '@vitejs/plugin-react-swc';\nimport { resolve } from 'path';\nimport { defineConfig, loadEnv } from 'vite';\n\nconst rootDir = resolve(__dirname);\nconst srcDir = resolve(rootDir, 'src');\n\nconst isDev = process.env.__DEV__ === 'true';\nconst isProduction = !isDev;\nconst envInfo = loadEnv(isDev ? 'development' : 'production', process.cwd(), 'SYNC');\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      '@src': srcDir,\n    },\n  },\n  base: '',\n  plugins: [react(), isDev && watchRebuildPlugin({ refresh: true })],\n  publicDir: resolve(rootDir, 'public'),\n  build: {\n    outDir: resolve(rootDir, '..', '..', 'dist', 'options'),\n    sourcemap: isDev,\n    minify: isProduction,\n    reportCompressedSize: isProduction,\n    rollupOptions: {\n      external: ['chrome'],\n    },\n  },\n  define: {\n    'process.env.NODE_ENV': isDev ? `\"development\"` : `\"production\"`,\n    'process.env.CLIENT_SECRET': JSON.stringify(envInfo.SYNC_CLIENT_SECRET || ''),\n  },\n});\n"
  },
  {
    "path": "pages/popup/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Popup</title>\n  </head>\n\n  <body>\n    <div id=\"app-container\"></div>\n    <script type=\"module\" src=\"./src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "pages/popup/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/popup\",\n  \"version\": \"0.0.1\",\n  \"description\": \"chrome extension popup\",\n  \"private\": true,\n  \"sideEffects\": true,\n  \"files\": [\n    \"dist/**\"\n  ],\n  \"scripts\": {\n    \"clean\": \"rimraf ./dist && rimraf .turbo\",\n    \"build\": \"pnpm run clean && tsc --noEmit && vite build\",\n    \"build:watch\": \"cross-env __DEV__=true vite build -w --mode development\",\n    \"dev\": \"pnpm build:watch\",\n    \"lint\": \"eslint . --ext .ts,.tsx\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {\n    \"@sync-your-cookie/shared\": \"workspace:*\",\n    \"@sync-your-cookie/storage\": \"workspace:*\",\n    \"clsx\": \"^2.1.1\",\n    \"lucide-react\": \"^0.394.0\",\n    \"pako\": \"^2.1.0\",\n    \"sonner\": \"^1.5.0\",\n    \"tailwind-merge\": \"^2.3.0\"\n  },\n  \"devDependencies\": {\n    \"@sync-your-cookie/hmr\": \"workspace:*\",\n    \"@sync-your-cookie/protobuf\": \"workspace:*\",\n    \"@sync-your-cookie/tailwindcss-config\": \"workspace:*\",\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\",\n    \"@sync-your-cookie/ui\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "pages/popup/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    // 'postcss-import': {},\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "pages/popup/src/Popup.tsx",
    "content": "import { extractDomainAndPort, useTheme, withErrorBoundary, withSuspense } from '@sync-your-cookie/shared';\n\nimport { Button, Image, Spinner, Toaster } from '@sync-your-cookie/ui';\nimport { CloudDownload, CloudUpload, Copyright, PanelRightOpen, RotateCw, Settings } from 'lucide-react';\nimport { useEffect, useState } from 'react';\n\nimport { AutoSwitch } from './components/AutoSwtich';\nimport { useDomainConfig } from './hooks/useDomainConfig';\n\nconst Popup = () => {\n  const { theme } = useTheme();\n  const [activeTabUrl, setActiveTabUrl] = useState('');\n  const [favIconUrl, setFavIconUrl] = useState('');\n\n  const {\n    pushing,\n    toggleAutoPushState,\n    toggleAutoPullState,\n    domain,\n    setDomain,\n    domainItemConfig,\n    domainItemStatus,\n    handlePush,\n    handlePull,\n  } = useDomainConfig();\n\n  useEffect(() => {\n    chrome.tabs.query({ active: true, lastFocusedWindow: true }, async function (tabs) {\n      if (tabs.length > 0) {\n        const activeTab = tabs[0];\n        if (activeTab.url && activeTab.url.startsWith('http')) {\n          setFavIconUrl(activeTab?.favIconUrl || '');\n          setActiveTabUrl(activeTab.url);\n          const [domain, tempPort] = await extractDomainAndPort(activeTab.url);\n          setDomain(domain + `${tempPort ? ':' + tempPort : ''}`);\n        }\n      }\n    });\n  }, []);\n\n  const isPushingOrPulling = domainItemStatus.pushing || domainItemStatus.pulling;\n\n  const handleAndReload = () => {\n    handlePull(activeTabUrl, domain, true);\n  };\n\n  return (\n    <div className=\"flex flex-col items-center min-w-[400px] justify-center bg-background \">\n      <header className=\" p-2 flex w-full justify-between items-center bg-card/50 shadow-md border-b border-border \">\n        <div className=\"flex items-center\">\n          <img\n            src={chrome.runtime.getURL('options/logo.png')}\n            className=\"h-10 w-10 overflow-hidden object-contain \"\n            alt=\"logo\"\n          />\n          <h2 className=\"text-base text-foreground\tfont-bold\">SyncYourCookie</h2>\n        </div>\n        <Button\n          variant=\"ghost\"\n          onClick={() => {\n            chrome.runtime.openOptionsPage();\n          }}\n          className=\"cursor-pointer text-sm mr-[-8px] \">\n          <Settings size={20} />\n        </Button>\n      </header>\n      <main className=\"p-4 \">\n        <Spinner show={false}>\n          {domain ? (\n            <div className=\"flex justify-center items-center mb-2  \">\n              <Image src={favIconUrl} />\n              <h3 className=\"text-center whitespace-nowrap text-xl text-primary font-bold\">{domain}</h3>\n            </div>\n          ) : null}\n\n          <div className=\" flex flex-col\">\n            {/* <Button title={cloudflareAccountId} className=\"mb-2\" onClick={handleUpdateToken}>\n            Update Token\n          </Button> */}\n            <div className=\"flex items-center mb-2 \">\n              <Button\n                disabled={!activeTabUrl || isPushingOrPulling || pushing}\n                className=\" mr-2 w-[160px] justify-start\"\n                onClick={() => handlePush(domain, activeTabUrl, favIconUrl)}>\n                {domainItemStatus.pushing ? (\n                  <RotateCw size={16} className=\"mr-2 animate-spin\" />\n                ) : (\n                  <CloudUpload size={16} className=\"mr-2\" />\n                )}\n                Push cookie\n              </Button>\n              <AutoSwitch\n                disabled={!activeTabUrl}\n                onChange={() => toggleAutoPushState(domain)}\n                id=\"autoPush\"\n                value={!!domainItemConfig.autoPush}\n              />\n            </div>\n\n            <div className=\"flex items-center mb-2 \">\n              <Button\n                disabled={!activeTabUrl || isPushingOrPulling}\n                className=\" w-[160px] mr-2 justify-start\"\n                onClick={() => handleAndReload()}>\n                {domainItemStatus?.pulling ? (\n                  <RotateCw size={16} className=\"mr-2 animate-spin\" />\n                ) : (\n                  <CloudDownload size={16} className=\"mr-2\" />\n                )}\n                Pull cookie\n              </Button>\n\n              <AutoSwitch\n                disabled={!activeTabUrl}\n                onChange={() => toggleAutoPullState(domain)}\n                id=\"autoPull\"\n                value={!!domainItemConfig.autoPull}\n              />\n            </div>\n\n            <Button\n              className=\"mb-2 justify-start\"\n              onClick={async () => {\n                chrome.windows.getCurrent(async currentWindow => {\n                  // const res = await chrome.sidePanel.getOptions({\n                  //   tabId: currentWindow.id,\n                  // });\n                  chrome.sidePanel\n                    .open({ windowId: currentWindow.id! })\n                    .then(() => {\n                      console.log('Side panel opened successfully');\n                    })\n                    .catch(error => {\n                      console.error('Error opening side panel:', error);\n                    });\n                });\n              }}>\n              <PanelRightOpen size={16} className=\"mr-2\" />\n              Open Manager\n            </Button>\n          </div>\n          <Toaster\n            theme={theme}\n            closeButton\n            toastOptions={{\n              duration: 1500,\n              style: {\n                // width: 'max-content',\n                // margin: '0 auto',\n              },\n              // className: 'w-[240px]',\n            }}\n            visibleToasts={1}\n            richColors\n            position=\"top-center\"\n          />\n        </Spinner>\n      </main>\n      <footer className=\"w-full text-center justify-center p-4 flex items-center border-t border-border/90 \">\n        <span>\n          <Copyright size={16} />\n        </span>\n        <a\n          className=\" inline-flex items-center mx-1 text-sm underline \"\n          href=\"https://github.com/jackluson\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\">\n          jackluson\n        </a>\n        <a href=\"https://github.com/jackluson/sync-your-cookie\" target=\"_blank\" rel=\"noopener noreferrer\">\n          <img\n            src={chrome.runtime.getURL('popup/github.svg')}\n            className=\"h-4 w-4 overflow-hidden object-contain \"\n            alt=\"logo\"\n          />\n        </a>\n      </footer>\n    </div>\n  );\n};\n\nexport default withErrorBoundary(withSuspense(Popup, <div> Loading ... </div>), <div> Error Occur </div>);\n"
  },
  {
    "path": "pages/popup/src/components/AutoSwtich/index.tsx",
    "content": "import { Label, Switch } from '@sync-your-cookie/ui';\n\ninterface AutoSwitchProps {\n  value: boolean;\n  onChange: (value: boolean) => void;\n  id: string;\n  disabled?: boolean;\n}\nexport function AutoSwitch(props: AutoSwitchProps) {\n  const { value, onChange, id, disabled } = props;\n  return (\n    <div className=\"flex items-center space-x-2\">\n      <Switch disabled={disabled} onCheckedChange={onChange} checked={value} id={id} />\n      <Label htmlFor={id}>Auto</Label>\n    </div>\n  );\n}\n"
  },
  {
    "path": "pages/popup/src/hooks/useDomainConfig.ts",
    "content": "import { useCookieAction } from '@sync-your-cookie/shared';\nimport { useState } from 'react';\nimport { toast } from 'sonner';\nexport const useDomainConfig = () => {\n  const [domain, setDomain] = useState('');\n  const cookieAction = useCookieAction(domain, toast);\n\n  return {\n    domain,\n    setDomain,\n    ...cookieAction,\n  };\n};\n"
  },
  {
    "path": "pages/popup/src/index.css",
    "content": "\nbody {\n  margin: 0;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',\n    'Droid Sans', 'Helvetica Neue', sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n\n  position: relative;\n}\n\ncode {\n  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;\n}\n"
  },
  {
    "path": "pages/popup/src/index.tsx",
    "content": "import Popup from '@src/Popup';\nimport '@src/index.css';\nimport { initGithubApi, ThemeProvider } from '@sync-your-cookie/shared';\nimport '@sync-your-cookie/ui/css';\nimport { createRoot } from 'react-dom/client';\n\nfunction init() {\n  const appContainer = document.querySelector('#app-container');\n  if (!appContainer) {\n    throw new Error('Can not find #app-container');\n  }\n  const root = createRoot(appContainer);\n\n  root.render(\n    <ThemeProvider>\n      <Popup />\n    </ThemeProvider>,\n  );\n}\ninitGithubApi();\ninit();\n"
  },
  {
    "path": "pages/popup/src/utils/index.ts",
    "content": "import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
  },
  {
    "path": "pages/popup/tailwind.config.js",
    "content": "const uiConfig = require('@sync-your-cookie/ui/tailwind.config');\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  ...uiConfig,\n};\n"
  },
  {
    "path": "pages/popup/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/base\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@src/*\": [\"src/*\"]\n    },\n    \"jsx\": \"react-jsx\",\n    \"types\": [\"chrome\"]\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "pages/popup/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react-swc';\nimport { resolve } from 'path';\nimport { watchRebuildPlugin } from '@sync-your-cookie/hmr';\n\nconst rootDir = resolve(__dirname);\nconst srcDir = resolve(rootDir, 'src');\n\nconst isDev = process.env.__DEV__ === 'true';\nconst isProduction = !isDev;\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      '@src': srcDir,\n    },\n  },\n  base: '',\n  plugins: [react(), isDev && watchRebuildPlugin({ refresh: true })],\n  publicDir: resolve(rootDir, 'public'),\n  build: {\n    outDir: resolve(rootDir, '..', '..', 'dist', 'popup'),\n    sourcemap: isDev,\n    minify: isProduction,\n    reportCompressedSize: isProduction,\n    rollupOptions: {\n      external: ['chrome'],\n    },\n  },\n  define: {\n    'process.env.NODE_ENV': isDev ? `\"development\"` : `\"production\"`,\n  },\n});\n"
  },
  {
    "path": "pages/sidepanel/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>SidePanel</title>\n  </head>\n\n  <body>\n    <div id=\"app-container\"></div>\n    <script type=\"module\" src=\"./src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "pages/sidepanel/package.json",
    "content": "{\n  \"name\": \"@sync-your-cookie/sidepanel\",\n  \"version\": \"0.0.1\",\n  \"description\": \"chrome extension sidepanel\",\n  \"private\": true,\n  \"sideEffects\": true,\n  \"files\": [\n    \"dist/**\"\n  ],\n  \"scripts\": {\n    \"clean\": \"rimraf ./dist && rimraf .turbo\",\n    \"build\": \"pnpm run clean && tsc --noEmit && vite build\",\n    \"build:watch\": \"cross-env __DEV__=true vite build -w --mode development\",\n    \"dev\": \"pnpm build:watch\",\n    \"lint\": \"eslint . --ext .ts,.tsx\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"prettier\": \"prettier . --write\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {\n    \"@sync-your-cookie/shared\": \"workspace:*\",\n    \"@sync-your-cookie/storage\": \"workspace:*\",\n    \"clsx\": \"^2.1.1\",\n    \"lucide-react\": \"^0.394.0\",\n    \"pako\": \"^2.1.0\",\n    \"sonner\": \"^1.5.0\",\n    \"tailwind-merge\": \"^2.3.0\"\n  },\n  \"devDependencies\": {\n    \"@sync-your-cookie/protobuf\": \"workspace:*\",\n    \"@sync-your-cookie/tailwindcss-config\": \"workspace:*\",\n    \"@sync-your-cookie/tsconfig\": \"workspace:*\",\n    \"@sync-your-cookie/hmr\": \"workspace:*\",\n    \"@sync-your-cookie/ui\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "pages/sidepanel/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "pages/sidepanel/src/SidePanel.tsx",
    "content": "import { useTheme, withErrorBoundary, withSuspense } from '@sync-your-cookie/shared';\nimport { Toaster } from '@sync-your-cookie/ui';\nimport { useEffect } from 'react';\nimport CookieTable from './components/CookieTable';\nconst SidePanel = () => {\n  useEffect(() => {\n    chrome.runtime.onMessage.addListener(message => {\n      // Might not be as easy if there are multiple side panels open\n      if (message === 'closeSidePanel') {\n        window.close();\n      }\n    });\n  }, []);\n  const { theme } = useTheme();\n\n  return (\n    <div className=\"\">\n      <header></header>\n      <CookieTable />\n      <Toaster\n        theme={theme}\n        closeButton\n        toastOptions={{\n          duration: 1500,\n          style: {\n            // width: 'max-content',\n            // margin: '0 auto',\n          },\n          // className: 'w-[240px]',\n        }}\n        visibleToasts={1}\n        richColors\n        position=\"top-center\"\n      />\n    </div>\n  );\n};\n\nexport default withErrorBoundary(withSuspense(SidePanel, <div> Loading ... </div>), <div> Error Occur </div>);\n"
  },
  {
    "path": "pages/sidepanel/src/components/CookieTable/SearchInput.tsx",
    "content": "import { Input } from '@sync-your-cookie/ui';\nimport { Search, X } from 'lucide-react';\nimport { FC, useState } from 'react';\n\nexport interface SearchInputProps {\n  onEnter?: (val: string) => void;\n}\n\nexport const SearchInput: FC<SearchInputProps> = props => {\n  const { onEnter } = props;\n  const [searchVal, setSearchVal] = useState('');\n  return (\n    <div className=\"flex relative \">\n      <Search size={18} className=\"absolute top-[11px] left-[10px]\" />\n      <Input\n        value={searchVal}\n        onChange={evt => {\n          setSearchVal(evt.target.value);\n        }}\n        onBlur={() => {\n          onEnter?.(searchVal.trim());\n        }}\n        onKeyDown={evt => {\n          if (evt.key === 'Enter' || evt.code === 'Enter') {\n            onEnter?.(searchVal.trim());\n          }\n        }}\n        className=\"bg-gray-100 pl-[36px]\"\n        placeholder=\"Filter\"\n      />\n      {searchVal && (\n        <X\n          onClick={() => {\n            setSearchVal('');\n            onEnter?.('');\n          }}\n          size={16}\n          className=\"absolute top-[13px] right-[10px] cursor-pointer\"\n        />\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "pages/sidepanel/src/components/CookieTable/hooks/useAction.ts",
    "content": "import { useCookieAction } from '@sync-your-cookie/shared';\nimport type { Cookie } from '@sync-your-cookie/storage/lib/cookieStorage';\nimport { useEffect, useState } from 'react';\nimport { toast } from 'sonner';\nimport { CookieItem } from './../index';\nimport { useSelected } from './useSelected';\n\nexport const useAction = (cookie: Cookie) => {\n  const [loading, setLoading] = useState(false);\n  const [currentSearchStr, setCurrentSearchStr] = useState('');\n  const {\n    loading: loadingWithSelected,\n    selectedDomain,\n    showCookiesColumns,\n    setSelectedDomain,\n    cookieList,\n    renderKeyValue,\n    localStorageItems,\n    ...rest\n  } = useSelected(cookie, currentSearchStr);\n\n  useEffect(() => {\n    setCurrentSearchStr('');\n  }, [selectedDomain]);\n\n  const cookieAction = useCookieAction(selectedDomain, toast);\n  const handleDelete = async (cookie: CookieItem) => {\n    try {\n      setLoading(true);\n      await cookieAction.handleRemove(cookie.host);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const handlePull = async (activeTabUrl: string, cookie: CookieItem) => {\n    try {\n      setLoading(true);\n      await cookieAction.handlePull(activeTabUrl, cookie.host, true);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const handlePush = async (cookie: CookieItem, sourceUrl?: string) => {\n    try {\n      setLoading(true);\n      await cookieAction.handlePush(cookie.host, sourceUrl);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const handleViewCookies = async (domain: string) => {\n    setSelectedDomain(domain);\n  };\n\n  const handleBack = () => {\n    setCurrentSearchStr('');\n    setSelectedDomain('');\n  };\n\n  const handleCopy = (domain: string, isJSON: boolean = false) => {\n    const cookies = cookie.domainCookieMap?.[domain]?.cookies || [];\n    if (cookies.length === 0) {\n      toast.warning('no cookie to copy, check again.');\n      return;\n    }\n    if (!navigator.clipboard) {\n      toast.warning('please check clipboard permission settings before copy ');\n      return;\n    }\n    let copyText = '';\n    if (isJSON) {\n      copyText = JSON.stringify(cookies, undefined, 2);\n    } else {\n      const pairs = [];\n      for (const ck of cookies) {\n        if (ck.value) {\n          const pair = `${ck.name}=${ck.value}`;\n          pairs.push(pair);\n        }\n      }\n      copyText = pairs.join('; ');\n    }\n    navigator?.clipboard?.writeText(copyText).then(\n      () => {\n        toast.success('Copy success');\n      },\n      err => {\n        console.log('err', err);\n        toast.error('Copy failed');\n      },\n    );\n  };\n\n  const handleSearch = (val: string) => {\n    setCurrentSearchStr(val);\n  };\n\n  return {\n    handleDelete,\n    handlePull,\n    handlePush,\n    handleViewCookies,\n    loading: loading || loadingWithSelected,\n    selectedDomain,\n    setSelectedDomain,\n    handleBack,\n    showCookiesColumns,\n    cookieAction,\n    handleCopy,\n    currentSearchStr,\n    // handlePush,\n    handleSearch,\n    renderKeyValue,\n    cookieList: cookieList.filter(item => {\n      if (currentSearchStr.trim()) {\n        return (\n          item.domain.includes(currentSearchStr) ||\n          item.name.includes(currentSearchStr) ||\n          item.value.includes(currentSearchStr)\n        );\n      }\n      return true;\n    }),\n    localStorageItems: localStorageItems.filter(item => {\n      if (currentSearchStr.trim()) {\n        return item.key?.includes(currentSearchStr) || item.value?.includes(currentSearchStr);\n      }\n      return true;\n    }),\n    ...rest,\n  };\n};\n"
  },
  {
    "path": "pages/sidepanel/src/components/CookieTable/hooks/useCookieItem.ts",
    "content": "import {\n  catchHandler,\n  editCookieItemUsingMessage,\n  ICookie,\n  removeCookieItemUsingMessage,\n} from '@sync-your-cookie/shared';\nimport { useState } from 'react';\nimport { toast } from 'sonner';\n\nexport const useCookieItem = (selectedDomain: string) => {\n  const [loading, setLoading] = useState(false);\n\n  const handleDeleteItem = async (id: string) => {\n    try {\n      setLoading(true);\n      await removeCookieItemUsingMessage({\n        domain: selectedDomain,\n        id,\n      })\n        .then(async res => {\n          if (res.isOk) {\n            toast.success(res.msg || 'success');\n          } else {\n            toast.error(res.msg || 'Deleted fail');\n          }\n        })\n        .catch(err => {\n          catchHandler(err, 'delete', toast);\n        });\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const handleEditItem = async (oldItem: ICookie, newItem: ICookie) => {\n    try {\n      setLoading(true);\n      await editCookieItemUsingMessage({\n        domain: selectedDomain,\n        oldItem,\n        newItem,\n      })\n        .then(async res => {\n          if (res.isOk) {\n            toast.success(res.msg || 'success');\n          } else {\n            toast.error(res.msg || 'Edited fail');\n            return Promise.reject(res);\n          }\n        })\n        .catch(err => {\n          catchHandler(err, 'edit', toast);\n          return Promise.reject(err);\n        });\n    } finally {\n      setLoading(false);\n    }\n  };\n  return {\n    loading,\n    handleDeleteItem,\n    handleEditItem,\n  };\n};\n"
  },
  {
    "path": "pages/sidepanel/src/components/CookieTable/hooks/useSelected.tsx",
    "content": "import { useStorageSuspense } from '@sync-your-cookie/shared';\nimport { Cookie } from '@sync-your-cookie/storage/lib/cookieStorage';\nimport { domainConfigStorage } from '@sync-your-cookie/storage/lib/domainConfigStorage';\nimport { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';\n\nimport {\n  Button,\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n  Input,\n  Label,\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n  cn,\n  type ColumnDef,\n} from '@sync-your-cookie/ui';\n\nimport { Ellipsis, PencilLine, Trash2, Wrench } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\nimport { toast } from 'sonner';\nimport { useCookieItem } from './useCookieItem';\nexport type CookieShowItem = {\n  id: string;\n  domain: string;\n  name: string;\n  value: string;\n  expirationDate?: number | null;\n  hostOnly?: boolean | null;\n  httpOnly?: boolean | null;\n  path?: string | null;\n  sameSite?: string | null;\n  secure?: boolean | null;\n  session?: boolean | null;\n  storeId?: string | null;\n};\n\nexport type LocalStorageShowItem = {\n  // id: string;\n  key: string;\n  value: string;\n};\n\nexport const useSelected = (cookieMap: Cookie, currentSearchStr: string) => {\n  const [selectedDomain, setSelectedDomain] = useState('');\n  const domainStatus = useStorageSuspense(domainStatusStorage);\n\n  const [selectedRow, setSelectedRow] = useState<Record<string, string> | null>(null);\n  const inputRowRef = useRef<Partial<CookieShowItem>>({});\n  const [selectedKey, setSelectedKey] = useState<string>('');\n  const [hasLocalStorage, setHasLocalStorage] = useState(false);\n  const [localStorageMode, setLocalStorageMode] = useState(false);\n  const handleEdit = (key: string, row: Record<string, string>) => {\n    setSelectedKey(key);\n    setSelectedRow(row);\n    inputRowRef.current = {\n      ...row,\n    };\n  };\n  const { loading, handleEditItem, handleDeleteItem } = useCookieItem(selectedDomain);\n\n  const handleCancel = () => {\n    setSelectedRow(null);\n  };\n\n  const handleSave = async (isSet: boolean = false) => {\n    const prevSelectedRow = selectedRow;\n    setSelectedRow(null);\n    if (JSON.stringify(inputRowRef.current) !== JSON.stringify(prevSelectedRow)) {\n      await handleEditItem(prevSelectedRow!, inputRowRef.current!);\n    }\n    if (isSet && inputRowRef.current) {\n      await new Promise(resolve => setTimeout(resolve, 500));\n      handleSet(inputRowRef.current as CookieShowItem);\n    }\n  };\n\n  const renderKeyValue = (value: string, key?: string) => {\n    let nameSearchFlag = false;\n    let startIndex = 0;\n    let endIndex = 0;\n    if (currentSearchStr.trim() && value.includes(currentSearchStr.trim())) {\n      startIndex = value.indexOf(currentSearchStr);\n      endIndex = startIndex + currentSearchStr.length;\n      nameSearchFlag = true;\n    }\n    return (\n      <div\n        style={{\n          overflowWrap: 'anywhere',\n        }}\n        className=\" flex min-w-[80px] mr-2 mt-1 \">\n        {key && (\n          <span className=\"inline-block w-12 flex-shrink-0 bg-slate-200 h-[28px] leading-7 mr-1 text-center rounded-sm \">\n            {key}:\n          </span>\n        )}\n        {nameSearchFlag ? (\n          <span className=\" inline-block  max-h-[320px] overflow-auto p-1 rounded-sm bg-[#f4f4f5] text-[#ea580c] \">\n            <span>\n              {value.slice(0, startIndex)}\n              <span className=\"text-primary font-bold\">{currentSearchStr}</span>\n              {value.slice(endIndex)}\n            </span>\n          </span>\n        ) : (\n          <p className=\" min-w-[80px] inline-block max-h-[320px] overflow-auto  p-1 rounded-sm bg-[#f4f4f5] text-[#ea580c] \">\n            {value}\n          </p>\n        )}\n      </div>\n    );\n  };\n\n  const renderPopver = (key: keyof CookieShowItem, row: Record<string, any>, nameKey = 'name') => {\n    const keyName = nameKey;\n    const sameId = selectedRow?.id === row.id;\n    if (localStorageMode) {\n      return null;\n    }\n    return (\n      <Popover\n        open={key === selectedKey && sameId && !!selectedRow}\n        onOpenChange={val => {\n          console.log('val', val);\n          if (val === false) {\n            handleCancel();\n          }\n          // if (val) {\n          //   handleEdit(key, row);\n          // } else {\n          //   handleCancel();\n          // }\n        }}>\n        <PopoverTrigger>\n          <button\n            onClick={() => {\n              if (selectedRow && selectedRow.id === row.id) {\n                handleCancel();\n              } else {\n                handleEdit(key, row);\n              }\n            }}\n            style={{\n              visibility: selectedRow && selectedRow?.id === row.id ? 'visible' : undefined,\n            }}\n            className=\" invisible ml-2 cursor-pointer group-hover/item:visible\">\n            <PencilLine className=\"h-4 w-4\" />\n          </button>\n        </PopoverTrigger>\n        <PopoverContent className=\"w-80 \">\n          <div className=\"grid gap-2\">\n            <p className=\"text-lg font-semibold \">Edit</p>\n            <div className=\"grid grid-cols-5 items-center gap-2\">\n              <Label htmlFor={keyName}>{keyName}</Label>\n              <Input\n                id={keyName}\n                defaultValue={selectedRow?.[keyName]}\n                onChange={evt => {\n                  (inputRowRef.current as Record<string, any>)![keyName] = evt.target.value || '';\n                }}\n                className=\"col-span-4 h-8\"\n              />\n            </div>\n            <div className=\"grid grid-cols-5 items-center gap-2\">\n              <Label htmlFor={key}>{key}</Label>\n              <Input\n                id={key}\n                defaultValue={String(selectedRow?.[key])}\n                onChange={evt => {\n                  if (inputRowRef.current && key) {\n                    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n                    (inputRowRef.current as any)[key] = evt.target.value || '';\n                  }\n                }}\n                className=\"col-span-4 h-8\"\n              />\n            </div>\n            <div className=\"flex justify-end mt-2\">\n              <Button size=\"sm\" variant=\"outline\" onClick={() => handleCancel()}>\n                Cancel\n              </Button>\n              <Button disabled={loading} onClick={() => handleSave()} size=\"sm\" className=\"ml-2\">\n                Save\n              </Button>\n              <Button onClick={() => handleSave(true)} disabled={loading} size=\"sm\" className=\"ml-2\">\n                Save And Apply\n              </Button>\n            </div>\n          </div>\n        </PopoverContent>\n      </Popover>\n    );\n  };\n\n  const renderEditCell = (key: keyof CookieShowItem, row: Record<string, any>, nameKey = 'name') => {\n    const isEdit = false && key === selectedKey && selectedRow?.id === row.id;\n    const keyName = nameKey;\n    const nameValue = row[keyName];\n    const value = row.value;\n    const sameId = localStorageMode && 0 ? true : selectedRow?.id === row.id;\n    return (\n      <div key={key + row[key]} className=\" min-w-[25vw] flex items-center group/item\">\n        {isEdit ? (\n          <div className=\"relative w-full\">\n            <Input\n              type=\"text\"\n              value={String(selectedRow?.[key] || '')}\n              className={cn('min-w-[200px] h-8 ', 'pr-[128px]')}\n              onChange={e => {\n                console.log('e.target.value', e.target.value);\n                setSelectedRow({\n                  ...selectedRow,\n                  [key]: e.target.value,\n                });\n              }}\n            />\n            <Button\n              size=\"sm\"\n              variant=\"outline\"\n              onClick={() => handleCancel()}\n              className=\"absolute my-1 w-[64px] h-[24px] right-[62px] top-0 bottom-0 mx-auto \">\n              Cancel\n            </Button>\n            <Button size=\"sm\" className=\"absolute my-1  w-[48px] h-[24px]  right-2 top-0 bottom-0 mx-auto \">\n              Save\n            </Button>\n          </div>\n        ) : (\n          <div className=\"flex items-center justify-center\">\n            <div>\n              {renderKeyValue(nameValue, keyName)}\n              {renderKeyValue(value, 'value')}\n            </div>\n\n            {renderPopver(key, row, 'name')}\n          </div>\n        )}\n      </div>\n    );\n  };\n\n  const handleSet = async (item: Record<string, any>) => {\n    const domainCnf = await domainConfigStorage.get();\n    const itemDomainCnf = domainCnf.domainMap[selectedDomain];\n    const sourceUrl = itemDomainCnf?.sourceUrl;\n    const protocol = sourceUrl ? new URL(sourceUrl).protocol : 'http:';\n    const itemHost = item.domain.startsWith('.') ? item.domain.slice(1) : item.domain;\n    const href = `${protocol}//${itemHost || selectedDomain}`;\n    const setVal = {\n      domain: item.domain,\n      name: item.name ?? undefined,\n      url: href,\n      storeId: item.storeId ?? undefined,\n      value: item.value ?? undefined,\n      expirationDate: item.expirationDate ?? undefined,\n      path: item.path ?? undefined,\n      httpOnly: item.httpOnly ?? undefined,\n      secure: item.secure ?? undefined,\n      sameSite: (item.sameSite ?? undefined) as chrome.cookies.SameSiteStatus,\n    };\n    try {\n      await chrome.cookies.set(setVal);\n      toast.success('set success');\n    } catch (error) {\n      console.log('set cookie error', setVal, error);\n      toast.error('set failed');\n    }\n  };\n\n  const handleDelete = async (item: Record<string, any>) => {\n    handleDeleteItem(`${item.domain}_${item.name}`);\n  };\n\n  const showCookiesColumns: ColumnDef<CookieShowItem>[] = [\n    {\n      id: 'Domain',\n      accessorKey: 'domain',\n      header: 'Domain',\n      cell: ({ row }) => {\n        const domainValue = row.original.domain;\n        if (currentSearchStr.trim() && domainValue.includes(currentSearchStr)) {\n          const startIndex = domainValue.indexOf(currentSearchStr);\n          const endIndex = startIndex + currentSearchStr.length;\n          return (\n            <p\n              style={{\n                overflowWrap: 'anywhere',\n              }}\n              className=\"min-w-[80px]\">\n              {domainValue.slice(0, startIndex)}\n              <span className=\"text-primary font-bold\">{currentSearchStr}</span>\n              {domainValue.slice(endIndex)}\n            </p>\n          );\n        } else {\n          return (\n            <p\n              style={{\n                overflowWrap: 'anywhere',\n              }}\n              className=\"min-w-[80px]\">\n              {domainValue}\n            </p>\n          );\n        }\n      },\n    },\n    // {\n    //   id: 'Name',\n    //   accessorKey: 'name',\n    //   header: 'Name',\n    //   // cell: ({ row }) => {\n    //   //   return renderEditCell('name', row.original);\n    //   // },\n    // },\n    {\n      id: 'Value',\n      accessorKey: 'value',\n      header: 'Key / Value',\n      cell: ({ row }) => {\n        return renderEditCell('value', row.original);\n      },\n    },\n\n    {\n      id: 'actions',\n      enableHiding: false,\n      cell: ({ row }) => {\n        const itemStatus = domainStatus.domainMap[selectedDomain] || {};\n        const disabled = domainStatus.pushing || itemStatus.pulling || itemStatus.pushing;\n        return (\n          <DropdownMenu>\n            <DropdownMenuTrigger asChild>\n              <Button variant=\"ghost\" className=\"h-8 w-8 p-0\">\n                <span className=\"sr-only\">Open menu</span>\n                <Ellipsis className=\"h-4 w-4\" />\n              </Button>\n            </DropdownMenuTrigger>\n            <DropdownMenuContent align=\"end\">\n              <DropdownMenuItem\n                // disabled={domainConfig.pushing}\n                className=\"cursor-pointer\"\n                onClick={() => handleSet(row.original)}>\n                <Wrench size={16} className=\"mr-2 h-4 w-4\" />\n                Apply\n              </DropdownMenuItem>\n              <DropdownMenuSeparator />\n              <DropdownMenuItem\n                disabled={disabled}\n                className=\"cursor-pointer\"\n                onClick={() => handleDelete(row.original)}>\n                <Trash2 size={16} className=\"mr-2 h-4 w-4\" />\n                Delete\n              </DropdownMenuItem>\n              {/* <DropdownMenuItem\n                disabled={disabled}\n                className=\"cursor-pointer\"\n                onClick={() => handleDelete(row.original)}>\n                <Trash2 size={16} className=\"mr-2 h-4 w-4\" />\n                Delete And Set\n              </DropdownMenuItem> */}\n            </DropdownMenuContent>\n          </DropdownMenu>\n        );\n      },\n    },\n  ];\n\n  const showLocalStorageColumns: ColumnDef<LocalStorageShowItem>[] = [\n    {\n      id: 'Value',\n      accessorKey: 'value',\n      header: 'Key / Value',\n      cell: ({ row, cell }) => {\n        const id = cell.id;\n        return renderEditCell('value', { id, ...row.original }, 'key');\n      },\n    },\n  ];\n\n  const cookieList =\n    cookieMap.domainCookieMap?.[selectedDomain]?.cookies?.map((item, index) => {\n      return {\n        ...item,\n        id: item.name + '_' + index,\n        domain: item.domain ?? '',\n        name: item.name ?? '',\n        value: item.value ?? '',\n        // name: cookie.name ?? undefined,\n        // url: activeTabUrl,\n        // storeId: cookie.storeId ?? undefined,\n        // value: cookie.value ?? undefined,\n        // expirationDate: cookie.expirationDate ?? undefined,\n        // path: cookie.path ?? undefined,\n        // httpOnly: cookie.httpOnly ?? undefined,\n        // secure: cookie.secure ?? undefined,\n        // sameSite: (cookie.sameSite ?? undefined) as chrome.cookies.SameSiteStatus,\n      };\n    }) || [];\n\n  const localStorageItems = cookieMap.domainCookieMap?.[selectedDomain]?.localStorageItems || [];\n  useEffect(() => {\n    if (localStorageItems && localStorageItems.length > 0) {\n      setHasLocalStorage(true);\n    } else {\n      setHasLocalStorage(false);\n      setLocalStorageMode(false);\n    }\n  }, [localStorageItems]);\n  return {\n    loading,\n    selectedDomain,\n    showCookiesColumns,\n    localStorageItems,\n    showLocalStorageColumns,\n    setSelectedDomain,\n    cookieList,\n    renderKeyValue,\n    localStorageMode,\n    hasLocalStorage,\n    setLocalStorageMode,\n  };\n};\n"
  },
  {
    "path": "pages/sidepanel/src/components/CookieTable/index.tsx",
    "content": "/* eslint-disable react/no-unescaped-entities */\n/* eslint-disable jsx-a11y/click-events-have-key-events */\nimport { getTabsByHost, useStorageSuspense } from '@sync-your-cookie/shared';\nimport {\n  Button,\n  DataTable,\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n  Image,\n  Spinner,\n  Switch,\n  SyncTooltip,\n  Toggle,\n} from '@sync-your-cookie/ui';\nimport {\n  ArrowUpRight,\n  ChevronLeft,\n  ClipboardList,\n  CloudDownload,\n  CloudUpload,\n  Copy,\n  Database,\n  Ellipsis,\n  Info,\n  RotateCw,\n  Table as TableIcon,\n  Trash,\n} from 'lucide-react';\n\nimport { cookieStorage } from '@sync-your-cookie/storage/lib/cookieStorage';\nimport { domainConfigStorage } from '@sync-your-cookie/storage/lib/domainConfigStorage';\nimport { domainStatusStorage } from '@sync-your-cookie/storage/lib/domainStatusStorage';\nimport { settingsStorage } from '@sync-your-cookie/storage/lib/settingsStorage';\n\nimport type { ColumnDef } from '@sync-your-cookie/ui';\nimport { useAction } from './hooks/useAction';\nimport { SearchInput } from './SearchInput';\nexport type CookieItem = {\n  id: string;\n  host: string;\n  sourceUrl?: string;\n  favIconUrl?: string;\n  autoPush: boolean;\n  autoPull: boolean;\n};\n\nconst CookieTable = () => {\n  const domainConfig = useStorageSuspense(domainConfigStorage);\n  const domainStatus = useStorageSuspense(domainStatusStorage);\n\n  const cookieMap = useStorageSuspense(cookieStorage);\n\n  const {\n    showCookiesColumns,\n    cookieList,\n    handleBack,\n    setSelectedDomain,\n    selectedDomain,\n    loading,\n    cookieAction,\n    handleDelete,\n    handlePush,\n    handlePull,\n    handleCopy,\n    handleViewCookies,\n    handleSearch,\n    currentSearchStr,\n    hasLocalStorage,\n    localStorageMode,\n    setLocalStorageMode,\n    showLocalStorageColumns,\n    localStorageItems,\n  } = useAction(cookieMap);\n  let domainList = [];\n  let totalCookieItem = 0;\n  let totalLocalStorageItem = 0;\n  for (const [key, value] of Object.entries(cookieMap?.domainCookieMap || {})) {\n    const config = domainConfig.domainMap[key];\n    if (!selectedDomain && currentSearchStr.trim() && !key.includes(currentSearchStr.trim())) continue;\n    if (value.cookies?.length) {\n      totalCookieItem += value.cookies.length;\n    }\n    if (value.localStorageItems?.length) {\n      totalLocalStorageItem += value.localStorageItems.length;\n    }\n    domainList.push({\n      id: key,\n      host: key,\n      sourceUrl: config?.sourceUrl,\n      favIconUrl: config?.favIconUrl,\n      list: value.cookies,\n      autoPush: config?.autoPush ?? false,\n      autoPull: config?.autoPull ?? false,\n      createTime: value.createTime,\n    });\n  }\n  domainList = domainList.sort((a, b) => {\n    return b.createTime - a.createTime;\n  });\n\n  const handleAndCheckPushCookie = async (row: CookieItem) => {\n    const protocol = row.sourceUrl ? new URL(row.sourceUrl).protocol : 'https:';\n    const href = `${protocol}//${row.host}`;\n    const includeLocalStorage = settingsStorage.getSnapshot()?.includeLocalStorage;\n    if (includeLocalStorage) {\n      const matchedTabs = await getTabsByHost(row.host);\n      if (matchedTabs.length === 0) {\n        window.open(href, '_blank');\n        setTimeout(async () => {\n          handlePush(row, href);\n        }, 500);\n      } else {\n        handlePush(row, href);\n      }\n    } else {\n      handlePush(row, href);\n    }\n    // console.log('handleAndCheckPushCookie->row', row);\n  };\n\n  const columns: ColumnDef<CookieItem>[] = [\n    {\n      accessorKey: 'host',\n      header: 'Host',\n      cell: ({ row, getValue }) => {\n        const value = getValue<string>() || '';\n        const sourceUrl = row.original.sourceUrl;\n        const protocol = sourceUrl ? new URL(sourceUrl).protocol : 'https:';\n        const href = `${protocol}//${row.original.host}`;\n        const src = row.original.favIconUrl ?? `https://${row.original.host}/favicon.ico`;\n        return (\n          <div className=\"relative group/item \">\n            <div className=\"block w-[100%] h-[120%] \">\n              <div className=\"flex items-center\">\n                <div\n                  role=\"button\"\n                  className=\"flex items-center justify-center cursor-pointer \"\n                  tabIndex={0}\n                  onClick={() => {\n                    setSelectedDomain(value);\n                  }}>\n                  <Image key={row.original.host} index={row.index} src={src} value={value} />\n                  <p\n                    style={{\n                      overflowWrap: 'anywhere',\n                    }}\n                    className=\" cursor-pointer hover:underline min-w-[100px] \">\n                    {value}\n                  </p>\n                </div>\n                <a\n                  key={row.original.host}\n                  target=\"_blank\"\n                  title={href}\n                  className=\"block ml-4 \"\n                  href={href}\n                  onClick={evt => {\n                    // evt.preventDefault();\n                    evt.stopPropagation();\n                  }}\n                  rel=\"noreferrer\">\n                  <Button variant=\"ghost\" className=\"text-sm \">\n                    <ArrowUpRight className=\"invisible group-hover/item:visible h-4 w-4 hover:inline cursor-pointer \" />\n                  </Button>\n                </a>\n              </div>\n            </div>\n          </div>\n        );\n      },\n      id: 'host',\n    },\n    {\n      accessorKey: 'autoPush',\n      header: 'AutoPush',\n      id: 'autoPush',\n      cell: record => {\n        return (\n          <p className=\"w-[60px]\">\n            <Switch\n              className=\"scale-75\"\n              id={`autoPush-${record.row.original.host}`}\n              checked={record.row.original.autoPush}\n              onCheckedChange={async () => {\n                await domainConfigStorage.updateItem(record.row.original.host, {\n                  autoPush: !record.row.original.autoPush,\n                });\n              }}\n            />\n          </p>\n        );\n      },\n    },\n    {\n      accessorKey: 'autoPull',\n      header: 'AutoPull',\n      id: 'autoPull',\n      cell: record => {\n        return (\n          <p className=\"w-[60px]\">\n            <Switch\n              className=\"scale-75\"\n              id={`autoPull-${record.row.original.host}`}\n              checked={record.row.original.autoPull}\n              onCheckedChange={async () => {\n                await domainConfigStorage.updateItem(record.row.original.host, {\n                  autoPull: !record.row.original.autoPull,\n                });\n              }}\n            />\n          </p>\n        );\n      },\n    },\n    {\n      id: 'actions',\n      enableHiding: false,\n      cell: ({ row }) => {\n        const itemStatus = cookieAction.getDomainItemStatus(row.original.host) || {};\n        const sourceUrl = row.original.sourceUrl;\n        const protocol = sourceUrl ? new URL(sourceUrl).protocol : 'http:';\n        const href = `${protocol}//${row.original.host}`;\n        const disabled = itemStatus.pushing || cookieAction.pushing;\n\n        return (\n          <DropdownMenu>\n            <DropdownMenuTrigger asChild>\n              <Button variant=\"ghost\" className=\"h-8 w-8 p-0\">\n                <span className=\"sr-only\">Open menu</span>\n                <Ellipsis className=\"h-4 w-4\" />\n              </Button>\n            </DropdownMenuTrigger>\n            <DropdownMenuContent align=\"end\">\n              <DropdownMenuLabel>Cookie Actions</DropdownMenuLabel>\n              {/* <DropdownMenuItem onClick={() => navigator.clipboard.writeText(.id)}>\n                Copy payment ID\n              </DropdownMenuItem> */}\n              <DropdownMenuSeparator />\n\n              <DropdownMenuItem\n                className=\"cursor-pointer flex items-center\"\n                disabled={disabled}\n                onClick={() => {\n                  handleAndCheckPushCookie(row.original);\n                }}>\n                {itemStatus.pushing ? (\n                  <RotateCw size={16} className=\" h-4 w-4 mr-2 animate-spin\" />\n                ) : (\n                  <CloudUpload size={16} className=\"mr-2 h-4 w-4\" />\n                )}\n                Push\n                <SyncTooltip\n                  align=\"start\"\n                  alignOffset={80}\n                  title={\n                    <p>\n                      <p>If 'Include LocalStorage' is enabled and no 'host' tab is present,</p>a 'host' tab will\n                      automatically open.\n                    </p>\n                  }>\n                  <p className=\"text-base flex items-center font-medium\">\n                    <Info className=\"ml-14\" size={16} />\n                  </p>\n                </SyncTooltip>\n              </DropdownMenuItem>\n              <DropdownMenuItem\n                className=\"cursor-pointer\"\n                disabled={itemStatus.pulling}\n                onClick={() => {\n                  handlePull(href, row.original);\n                }}>\n                {itemStatus.pulling ? (\n                  <RotateCw size={16} className=\" h-4 w-4 mr-2 animate-spin\" />\n                ) : (\n                  <CloudDownload size={16} className=\"mr-2 h-4 w-4\" />\n                )}\n                Pull\n              </DropdownMenuItem>\n              <DropdownMenuSeparator />\n              <DropdownMenuItem\n                className=\"cursor-pointer\"\n                onClick={() => {\n                  handleViewCookies(row.original.host);\n                }}>\n                <TableIcon size={16} className=\"mr-2 h-4 w-4\" />\n                View\n              </DropdownMenuItem>\n              <DropdownMenuItem\n                className=\"cursor-pointer\"\n                onClick={() => {\n                  handleCopy(row.original.host);\n                }}>\n                <Copy size={16} className=\"mr-2 h-4 w-4\" />\n                Copy\n              </DropdownMenuItem>\n              <DropdownMenuItem\n                className=\"cursor-pointer\"\n                onClick={() => {\n                  handleCopy(row.original.host, true);\n                }}>\n                <ClipboardList size={16} className=\"mr-2 h-4 w-4\" />\n                Copy With JSON\n              </DropdownMenuItem>\n              <DropdownMenuSeparator />\n              <DropdownMenuItem\n                disabled={domainStatus.pushing}\n                className=\"cursor-pointer\"\n                onClick={() => handleDelete(row.original)}>\n                {itemStatus.pulling ? (\n                  <RotateCw size={16} className=\" h-4 w-4 mr-2 animate-spin\" />\n                ) : (\n                  <Trash size={16} className=\"mr-2 h-4 w-4\" />\n                )}\n                Delete\n              </DropdownMenuItem>\n            </DropdownMenuContent>\n          </DropdownMenu>\n        );\n      },\n    },\n  ];\n  const selectedRow = domainConfig.domainMap[selectedDomain];\n  const sourceUrl = selectedRow?.sourceUrl;\n  const protocol = sourceUrl ? new URL(sourceUrl).protocol : 'https:';\n  const href = `${protocol}//${selectedDomain}`;\n  const handlePressChange = (pressed: boolean) => {\n    setLocalStorageMode(pressed);\n  };\n  const renderTable = () => {\n    return (\n      <div className=\"flex flex-col h-full \">\n        <div className=\"flex justify-between px-4 mb-4 \">\n          <div className=\"flex items-center \">\n            <Button\n              variant=\"outline\"\n              size=\"icon\"\n              className=\"h-7 w-7 mr-2\"\n              onClick={() => {\n                handleBack();\n              }}>\n              <ChevronLeft className=\"h-4 w-4\" />\n              <span className=\"sr-only\">Back</span>\n            </Button>\n            <a\n              href={href}\n              target=\"_blank\"\n              className=\" flex text-xl items-center font-semibold hover:underline \"\n              rel=\"noreferrer\">\n              {selectedRow?.favIconUrl ? <Image src={selectedRow?.favIconUrl} /> : null}\n              {selectedDomain}\n            </a>\n          </div>\n          {hasLocalStorage ? (\n            <SyncTooltip title=\"Toggle LocalStorage View\">\n              <Toggle pressed={localStorageMode} onPressedChange={handlePressChange} className=\"ml-6\" variant=\"outline\">\n                <Database size={16} className=\"h-4 w-4\" />\n              </Toggle>\n            </SyncTooltip>\n          ) : null}\n        </div>\n        <div className=\"mb-1 px-4\">\n          <SearchInput onEnter={handleSearch} />\n        </div>\n        <div className=\"flex-1 pl-4 pr-2 mt-4 overflow-auto\">\n          {localStorageMode ? (\n            <DataTable columns={showLocalStorageColumns as any} data={localStorageItems} />\n          ) : (\n            <DataTable columns={showCookiesColumns} data={cookieList} />\n          )}\n        </div>\n      </div>\n    );\n  };\n  return (\n    <div className=\"h-screen flex flex-col\">\n      <div className=\"space-y-4 p-4 \">\n        <div>\n          <h2 className=\"text-xl font-bold tracking-tight\">Welcome back!</h2>\n          <p className=\"text-muted-foreground text-sm\">\n            Here&apos;s a list of your pushed {localStorageMode ? 'localStorage items' : 'cookies'}{' '}\n          </p>\n        </div>\n      </div>\n      <div className=\"h-0 flex-1 overflow-auto\">\n        <Spinner show={loading}>\n          {selectedDomain ? (\n            <>{renderTable()}</>\n          ) : (\n            <div className=\"flex flex-col h-full\">\n              <div>\n                <div className=\" mx-4 w-1/3 min-w-[328px] bg-primary/10 mb-4 rounded-xl border text-card-foreground shadow\">\n                  <div className=\"p-3\">\n                    <div className=\"flex flex-row items-center justify-between\">\n                      <p className=\"tracking-tight text-sm font-normal\">Total Cookie and LocalStorage</p>\n                    </div>\n                    <div className=\"\">\n                      <p className=\"text-2xl font-bold\">\n                        {domainList.length} <span className=\"text-xl\">sites</span>\n                      </p>\n                      <p className=\"text-xs text-muted-foreground\">\n                        <span>{totalCookieItem} cookie items</span>\n                        <span className=\"mx-1\">&</span>\n                        <span>{totalLocalStorageItem} localStorage items</span>\n                      </p>\n                    </div>\n                  </div>\n                </div>\n              </div>\n              <div className=\"px-4\">\n                <SearchInput onEnter={handleSearch} />\n              </div>\n              <div className=\"flex-1 overflow-auto my-4 pl-4 pr-1\">\n                <DataTable columns={columns} data={domainList} />\n              </div>\n            </div>\n          )}\n        </Spinner>\n      </div>\n    </div>\n  );\n};\n\nexport default CookieTable;\n"
  },
  {
    "path": "pages/sidepanel/src/index.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
  },
  {
    "path": "pages/sidepanel/src/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Options</title>\n  </head>\n\n  <body>\n    <div id=\"app-container\"></div>\n    <script type=\"module\" src=\"./index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "pages/sidepanel/src/index.tsx",
    "content": "import '@src/index.css';\nimport SidePanel from '@src/SidePanel';\nimport { initGithubApi, ThemeProvider } from '@sync-your-cookie/shared';\nimport '@sync-your-cookie/ui/css';\nimport { createRoot } from 'react-dom/client';\n\nfunction init() {\n  const appContainer = document.querySelector('#app-container');\n  if (!appContainer) {\n    throw new Error('Can not find #app-container');\n  }\n  const root = createRoot(appContainer);\n  root.render(\n    <ThemeProvider>\n      <SidePanel />\n    </ThemeProvider>,\n  );\n}\n\ninitGithubApi();\ninit();\n"
  },
  {
    "path": "pages/sidepanel/tailwind.config.js",
    "content": "const uiConfig = require('@sync-your-cookie/ui/tailwind.config');\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  ...uiConfig,\n};\n"
  },
  {
    "path": "pages/sidepanel/tsconfig.json",
    "content": "{\n  \"extends\": \"@sync-your-cookie/tsconfig/base\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@src/*\": [\"src/*\"]\n    },\n    \"jsx\": \"react-jsx\",\n    \"types\": [\"chrome\"]\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "pages/sidepanel/vite.config.ts",
    "content": "import { watchRebuildPlugin } from '@sync-your-cookie/hmr';\nimport react from '@vitejs/plugin-react-swc';\nimport { resolve } from 'path';\nimport { defineConfig } from 'vite';\n\nconst rootDir = resolve(__dirname);\nconst srcDir = resolve(rootDir, 'src');\n\nconst isDev = process.env.__DEV__ === 'true';\nconsole.log(\"isDev\", isDev);\nconst isProduction = !isDev;\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      '@src': srcDir,\n    },\n  },\n  base: '',\n  plugins: [react(), isDev && watchRebuildPlugin({ refresh: true })],\n  publicDir: resolve(rootDir, 'public'),\n  build: {\n    outDir: resolve(rootDir, '..', '..', 'dist', 'sidepanel'),\n    sourcemap: isDev,\n    minify: isProduction,\n    reportCompressedSize: isProduction,\n    rollupOptions: {\n      external: ['chrome'],\n    },\n  },\n  define: {\n    'process.env.NODE_ENV': isDev ? `\"development\"` : `\"production\"`,\n  },\n});\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - \"chrome-extension\"\n  - \"pages/*\"\n  - \"packages/*\"\n"
  },
  {
    "path": "private-policy.md",
    "content": "Privacy Policy\n\n`SYNC COOKIE` does not collect any personal information.\n\n`SYNC COOKIE` doesn't embed any kind of analytics in the code\n\n`SYNC COOKIE` does not track its users in any way possible\n\n`SYNC COOKIE` stores your cookie data in your browser and, after you actively configure your cloudflare account settings, transmits the cookie-encoded data to your cloudflare account according to your settings.\n\n\nlink: [Privacy Policy](https://www.freeprivacypolicy.com/live/0744ab35-18ca-4e12-af35-524666eba493)\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"ui\": \"tui\",\n  \"tasks\": {\n    \"dev\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"dist/**\", \"build/**\"],\n      \"persistent\": true\n    },\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"../../dist/**\", \"dist/**\", \"build/**\"],\n      \"cache\": false\n    },\n    \"type-check\": {\n      \"cache\": false\n    },\n    \"lint\": {\n      \"cache\": false\n    },\n    \"lint:fix\": {\n      \"cache\": false\n    },\n    \"prettier\": {\n      \"cache\": false\n    },\n    \"test\": {\n      \"dependsOn\": [\n        \"^test\", \"^build\"\n      ],\n      \"cache\": false\n    },\n    \"clean\": {\n      \"cache\": false\n    }\n  }\n}\n"
  }
]