From 72e974672e5091dda58c91a26e86ad22f261f554 Mon Sep 17 00:00:00 2001 From: XuJiacheng Date: Fri, 20 Mar 2026 18:32:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E5=90=8E=E7=AB=AF=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E4=B8=8E=E7=94=A8=E6=88=B7=E8=AE=A4=E8=AF=81=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增微信登录/注册合一接口、资料完善接口和token刷新接口 重构用户服务层,支持自动维护用户类型和资料完整度 引入JWT认证中间件和请求验证中间件 更新文档与测试用例,支持dist构建部署 --- README.md | 13 + back-end/.env | 22 +- back-end/Dockerfile | 14 +- back-end/dist/.env | 23 + back-end/dist/eslint.config.js | 13 + back-end/dist/package-lock.json | 3436 +++++++++++++++++ back-end/dist/package.json | 32 + back-end/dist/spec/openapi.yaml | 377 ++ back-end/dist/src/config/env.js | 28 + .../dist/src/controllers/wechatController.js | 36 + back-end/dist/src/index.js | 39 + .../dist/src/middlewares/duplicateGuard.js | 22 + back-end/dist/src/middlewares/errorHandler.js | 19 + back-end/dist/src/middlewares/jwtAuth.js | 20 + .../dist/src/middlewares/requestLogger.js | 17 + .../dist/src/middlewares/requireJsonBody.js | 15 + .../src/middlewares/requireWechatOpenid.js | 12 + .../src/middlewares/validateWechatAuth.js | 35 + .../dist/src/middlewares/wechatHeadersAuth.js | 31 + back-end/dist/src/routes/apiRoutes.js | 24 + back-end/dist/src/routes/wechatRoutes.js | 14 + back-end/dist/src/services/jwtService.js | 12 + .../dist/src/services/pocketbaseService.js | 90 + back-end/dist/src/services/userService.js | 208 + back-end/dist/src/services/wechatService.js | 117 + back-end/dist/src/utils/appError.js | 10 + back-end/dist/src/utils/asyncHandler.js | 5 + back-end/dist/src/utils/logger.js | 27 + back-end/dist/src/utils/response.js | 20 + back-end/dist/src/utils/sanitize.js | 17 + back-end/eslint.config.js | 13 + back-end/package-lock.json | 1401 ++++++- back-end/package.json | 18 +- back-end/scripts/build.js | 52 + back-end/spec/openapi.yaml | 385 +- back-end/src/config/env.js | 28 + back-end/src/controllers/wechatController.js | 36 + back-end/src/index.js | 62 +- back-end/src/middlewares/duplicateGuard.js | 22 + back-end/src/middlewares/errorHandler.js | 19 + back-end/src/middlewares/jwtAuth.js | 20 + back-end/src/middlewares/requestLogger.js | 17 + back-end/src/middlewares/requireJsonBody.js | 15 + .../src/middlewares/requireWechatOpenid.js | 12 + .../src/middlewares/validateWechatAuth.js | 35 + back-end/src/middlewares/wechatHeadersAuth.js | 31 + back-end/src/routes/apiRoutes.js | 24 + back-end/src/routes/wechatRoutes.js | 14 + back-end/src/services/jwtService.js | 12 + back-end/src/services/pocketbaseService.js | 90 + back-end/src/services/userService.js | 208 + back-end/src/services/wechatService.js | 117 + back-end/src/utils/appError.js | 10 + back-end/src/utils/asyncHandler.js | 5 + back-end/src/utils/logger.js | 27 + back-end/src/utils/response.js | 20 + back-end/src/utils/sanitize.js | 17 + back-end/tests/integration/wechat.test.js | 188 + back-end/tests/userService.test.js | 181 + docs/ARCHIVE.md | 193 + docs/api.md | 511 ++- docs/backend.md | 5 +- docs/deployment.md | 95 +- docs/example.md | 42 + front-end/.env.production | 6 +- script/database_schema.md | 96 + script/node_modules/.package-lock.json | 14 + script/node_modules/pocketbase/CHANGELOG.md | 855 ++++ script/node_modules/pocketbase/LICENSE.md | 17 + script/node_modules/pocketbase/README.md | 1091 ++++++ .../pocketbase/dist/pocketbase.cjs.d.ts | 1468 +++++++ .../pocketbase/dist/pocketbase.cjs.js | 2 + .../pocketbase/dist/pocketbase.cjs.js.map | 1 + .../pocketbase/dist/pocketbase.es.d.mts | 1583 ++++++++ .../pocketbase/dist/pocketbase.es.d.ts | 1583 ++++++++ .../pocketbase/dist/pocketbase.es.js | 2 + .../pocketbase/dist/pocketbase.es.js.map | 1 + .../pocketbase/dist/pocketbase.es.mjs | 2 + .../pocketbase/dist/pocketbase.es.mjs.map | 1 + .../pocketbase/dist/pocketbase.iife.d.ts | 1468 +++++++ .../pocketbase/dist/pocketbase.iife.js | 2 + .../pocketbase/dist/pocketbase.iife.js.map | 1 + .../pocketbase/dist/pocketbase.umd.d.ts | 1468 +++++++ .../pocketbase/dist/pocketbase.umd.js | 2 + .../pocketbase/dist/pocketbase.umd.js.map | 1 + script/node_modules/pocketbase/package.json | 47 + script/package-lock.json | 22 + script/package.json | 16 + script/pocketbase.js | 176 + 89 files changed, 18233 insertions(+), 365 deletions(-) create mode 100644 back-end/dist/.env create mode 100644 back-end/dist/eslint.config.js create mode 100644 back-end/dist/package-lock.json create mode 100644 back-end/dist/package.json create mode 100644 back-end/dist/spec/openapi.yaml create mode 100644 back-end/dist/src/config/env.js create mode 100644 back-end/dist/src/controllers/wechatController.js create mode 100644 back-end/dist/src/index.js create mode 100644 back-end/dist/src/middlewares/duplicateGuard.js create mode 100644 back-end/dist/src/middlewares/errorHandler.js create mode 100644 back-end/dist/src/middlewares/jwtAuth.js create mode 100644 back-end/dist/src/middlewares/requestLogger.js create mode 100644 back-end/dist/src/middlewares/requireJsonBody.js create mode 100644 back-end/dist/src/middlewares/requireWechatOpenid.js create mode 100644 back-end/dist/src/middlewares/validateWechatAuth.js create mode 100644 back-end/dist/src/middlewares/wechatHeadersAuth.js create mode 100644 back-end/dist/src/routes/apiRoutes.js create mode 100644 back-end/dist/src/routes/wechatRoutes.js create mode 100644 back-end/dist/src/services/jwtService.js create mode 100644 back-end/dist/src/services/pocketbaseService.js create mode 100644 back-end/dist/src/services/userService.js create mode 100644 back-end/dist/src/services/wechatService.js create mode 100644 back-end/dist/src/utils/appError.js create mode 100644 back-end/dist/src/utils/asyncHandler.js create mode 100644 back-end/dist/src/utils/logger.js create mode 100644 back-end/dist/src/utils/response.js create mode 100644 back-end/dist/src/utils/sanitize.js create mode 100644 back-end/eslint.config.js create mode 100644 back-end/scripts/build.js create mode 100644 back-end/src/config/env.js create mode 100644 back-end/src/controllers/wechatController.js create mode 100644 back-end/src/middlewares/duplicateGuard.js create mode 100644 back-end/src/middlewares/errorHandler.js create mode 100644 back-end/src/middlewares/jwtAuth.js create mode 100644 back-end/src/middlewares/requestLogger.js create mode 100644 back-end/src/middlewares/requireJsonBody.js create mode 100644 back-end/src/middlewares/requireWechatOpenid.js create mode 100644 back-end/src/middlewares/validateWechatAuth.js create mode 100644 back-end/src/middlewares/wechatHeadersAuth.js create mode 100644 back-end/src/routes/apiRoutes.js create mode 100644 back-end/src/routes/wechatRoutes.js create mode 100644 back-end/src/services/jwtService.js create mode 100644 back-end/src/services/pocketbaseService.js create mode 100644 back-end/src/services/userService.js create mode 100644 back-end/src/services/wechatService.js create mode 100644 back-end/src/utils/appError.js create mode 100644 back-end/src/utils/asyncHandler.js create mode 100644 back-end/src/utils/logger.js create mode 100644 back-end/src/utils/response.js create mode 100644 back-end/src/utils/sanitize.js create mode 100644 back-end/tests/integration/wechat.test.js create mode 100644 back-end/tests/userService.test.js create mode 100644 docs/ARCHIVE.md create mode 100644 docs/example.md create mode 100644 script/database_schema.md create mode 100644 script/node_modules/.package-lock.json create mode 100644 script/node_modules/pocketbase/CHANGELOG.md create mode 100644 script/node_modules/pocketbase/LICENSE.md create mode 100644 script/node_modules/pocketbase/README.md create mode 100644 script/node_modules/pocketbase/dist/pocketbase.cjs.d.ts create mode 100644 script/node_modules/pocketbase/dist/pocketbase.cjs.js create mode 100644 script/node_modules/pocketbase/dist/pocketbase.cjs.js.map create mode 100644 script/node_modules/pocketbase/dist/pocketbase.es.d.mts create mode 100644 script/node_modules/pocketbase/dist/pocketbase.es.d.ts create mode 100644 script/node_modules/pocketbase/dist/pocketbase.es.js create mode 100644 script/node_modules/pocketbase/dist/pocketbase.es.js.map create mode 100644 script/node_modules/pocketbase/dist/pocketbase.es.mjs create mode 100644 script/node_modules/pocketbase/dist/pocketbase.es.mjs.map create mode 100644 script/node_modules/pocketbase/dist/pocketbase.iife.d.ts create mode 100644 script/node_modules/pocketbase/dist/pocketbase.iife.js create mode 100644 script/node_modules/pocketbase/dist/pocketbase.iife.js.map create mode 100644 script/node_modules/pocketbase/dist/pocketbase.umd.d.ts create mode 100644 script/node_modules/pocketbase/dist/pocketbase.umd.js create mode 100644 script/node_modules/pocketbase/dist/pocketbase.umd.js.map create mode 100644 script/node_modules/pocketbase/package.json create mode 100644 script/package-lock.json create mode 100644 script/package.json create mode 100644 script/pocketbase.js diff --git a/README.md b/README.md index 6523b5c..61f71f6 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,19 @@ Web_BAI_Manage_ApiServer/ ## 快速开始 +## 域名与 HTTPS 配置 + +项目正式环境后端域名为:`https://bai-api.blv-oa.com` + +- 后端公开地址建议通过 `back-end/.env` 中的以下配置统一控制: + - `APP_PROTOCOL=https` + - `APP_DOMAIN=bai-api.blv-oa.com` + - `APP_BASE_URL=https://bai-api.blv-oa.com` +- 前端生产环境接口地址建议通过 `front-end/.env.production` 中的 `VUE_APP_BASE_URL` 控制: + - `VUE_APP_BASE_URL='https://bai-api.blv-oa.com/api'` + +如后续更换域名,优先修改 `.env` 文件,不建议在代码中硬编码域名。 + ### 后端服务 1. 进入后端目录 diff --git a/back-end/.env b/back-end/.env index 63d18de..75bdffd 100644 --- a/back-end/.env +++ b/back-end/.env @@ -1,15 +1,23 @@ # Server Configuration -PORT=3000 +PORT=3002 # Environment NODE_ENV=development # API Configuration API_PREFIX=/api +APP_PROTOCOL=https +APP_DOMAIN=bai-api.blv-oa.com +APP_BASE_URL=https://bai-api.blv-oa.com -# Database Configuration (placeholder) -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=bai_management -DB_USER=postgres -DB_PASSWORD=password \ No newline at end of file +# Database Configuration (Pocketbase) +POCKETBASE_API_URL=https://bai-api.blv-oa.com/pb/ +POCKETBASE_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3NzQzNjEzMzIsImlkIjoiazQ0aHI5MW90bnBydG10IiwicmVmcmVzaGFibGUiOmZhbHNlLCJ0eXBlIjoiYXV0aCJ9.qm4E6xYrDbEpAfdxZnHHRZs_EqiwHgDIIwSBz2k90Nk + +# WeChat Configuration +WECHAT_APPID=wx3bd7a7b19679da7a +WECHAT_SECRET=57e40438c2a9151257b1927674db10e1 + +# JWT Configuration +JWT_SECRET=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 +JWT_EXPIRES_IN=2h diff --git a/back-end/Dockerfile b/back-end/Dockerfile index 6ad89f6..ac28c04 100644 --- a/back-end/Dockerfile +++ b/back-end/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine +FROM node:22-alpine AS build WORKDIR /app @@ -6,7 +6,17 @@ COPY package*.json ./ RUN npm install COPY . . +RUN npm run build + +FROM node:22-alpine AS production + +WORKDIR /app + +COPY --from=build /app/package*.json ./ +RUN npm install --omit=dev + +COPY --from=build /app/dist ./dist EXPOSE 3000 -CMD ["node", "src/index.js"] \ No newline at end of file +CMD ["node", "dist/src/index.js"] \ No newline at end of file diff --git a/back-end/dist/.env b/back-end/dist/.env new file mode 100644 index 0000000..75bdffd --- /dev/null +++ b/back-end/dist/.env @@ -0,0 +1,23 @@ +# Server Configuration +PORT=3002 + +# Environment +NODE_ENV=development + +# API Configuration +API_PREFIX=/api +APP_PROTOCOL=https +APP_DOMAIN=bai-api.blv-oa.com +APP_BASE_URL=https://bai-api.blv-oa.com + +# Database Configuration (Pocketbase) +POCKETBASE_API_URL=https://bai-api.blv-oa.com/pb/ +POCKETBASE_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3NzQzNjEzMzIsImlkIjoiazQ0aHI5MW90bnBydG10IiwicmVmcmVzaGFibGUiOmZhbHNlLCJ0eXBlIjoiYXV0aCJ9.qm4E6xYrDbEpAfdxZnHHRZs_EqiwHgDIIwSBz2k90Nk + +# WeChat Configuration +WECHAT_APPID=wx3bd7a7b19679da7a +WECHAT_SECRET=57e40438c2a9151257b1927674db10e1 + +# JWT Configuration +JWT_SECRET=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 +JWT_EXPIRES_IN=2h diff --git a/back-end/dist/eslint.config.js b/back-end/dist/eslint.config.js new file mode 100644 index 0000000..38d080f --- /dev/null +++ b/back-end/dist/eslint.config.js @@ -0,0 +1,13 @@ +module.exports = [ + { + files: ['src/**/*.js', 'tests/**/*.js'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'commonjs', + }, + rules: { + semi: ['error', 'never'], + 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + }, + }, +] diff --git a/back-end/dist/package-lock.json b/back-end/dist/package-lock.json new file mode 100644 index 0000000..95d1336 --- /dev/null +++ b/back-end/dist/package-lock.json @@ -0,0 +1,3436 @@ +{ + "name": "web-bai-manage-api-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web-bai-manage-api-server", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.13.2", + "dotenv": "^16.3.1", + "express": "^4.18.2", + "jsonwebtoken": "^9.0.2" + }, + "devDependencies": { + "@fission-ai/openspec": "^1.0.0", + "eslint": "^9.23.0", + "supertest": "^7.1.1" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fission-ai/openspec": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@fission-ai/openspec/-/openspec-1.2.0.tgz", + "integrity": "sha512-2XDmPZcVY0Bs014lP9aoxe3VoEU8hFvqaBFxQaiJO2nhC8vTKCyo6sT/5YpQcOTfR/a64Hht2anTyqLR4eNhlg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.2", + "@inquirer/prompts": "^7.8.0", + "chalk": "^5.5.0", + "commander": "^14.0.0", + "fast-glob": "^3.3.3", + "ora": "^8.2.0", + "posthog-node": "^5.20.0", + "yaml": "^2.8.2", + "zod": "^4.0.17" + }, + "bin": { + "openspec": "bin/openspec.js" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmmirror.com/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmmirror.com/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmmirror.com/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmmirror.com/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmmirror.com/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmmirror.com/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmmirror.com/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmmirror.com/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmmirror.com/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@posthog/core": { + "version": "1.23.4", + "resolved": "https://registry.npmmirror.com/@posthog/core/-/core-1.23.4.tgz", + "integrity": "sha512-gSM1gnIuw5UOBUOTz0IhCTH8jOHoFr5rzSDb5m7fn9ofLHvz3boZT1L1f+bcuk+mvzNJfrJ3ByVQGKmUQnKQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.6", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmmirror.com/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmmirror.com/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmmirror.com/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/posthog-node": { + "version": "5.28.2", + "resolved": "https://registry.npmmirror.com/posthog-node/-/posthog-node-5.28.2.tgz", + "integrity": "sha512-a+unFAKU8Vtez1DAEgCXB/KOZbroQZE+GvnSr9B35u3uMUxtyPO5ulgLJo8AUcZ4prhv6ia8R1Xjr4BrxPfdsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@posthog/core": "1.23.4" + }, + "engines": { + "node": "^20.20.0 || >=22.22.0" + }, + "peerDependencies": { + "rxjs": "^7.0.0" + }, + "peerDependenciesMeta": { + "rxjs": { + "optional": true + } + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superagent": { + "version": "10.3.0", + "resolved": "https://registry.npmmirror.com/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.5", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.14.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/supertest": { + "version": "7.2.2", + "resolved": "https://registry.npmmirror.com/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie-signature": "^1.2.2", + "methods": "^1.1.2", + "superagent": "^10.3.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supertest/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/back-end/dist/package.json b/back-end/dist/package.json new file mode 100644 index 0000000..2fd1e7e --- /dev/null +++ b/back-end/dist/package.json @@ -0,0 +1,32 @@ +{ + "name": "web-bai-manage-api-server", + "version": "1.0.0", + "description": "Backend API server for BAI Management System", + "main": "dist/src/index.js", + "scripts": { + "dev": "node src/index.js", + "build": "node scripts/build.js", + "start": "node dist/src/index.js", + "prestart": "node -e \"require('fs').accessSync('dist/src/index.js')\"", + "test": "node --test tests/**/*.test.js", + "lint": "eslint src tests --ext .js", + "spec:lint": "npx @fission-ai/openspec lint spec/", + "spec:validate": "npx @fission-ai/openspec validate spec/" + }, + "dependencies": { + "axios": "^1.13.2", + "express": "^4.18.2", + "dotenv": "^16.3.1", + "jsonwebtoken": "^9.0.2" + }, + "devDependencies": { + "@fission-ai/openspec": "^1.0.0", + "eslint": "^9.23.0", + "supertest": "^7.1.1" + }, + "engines": { + "node": ">=22.0.0" + }, + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/back-end/dist/spec/openapi.yaml b/back-end/dist/spec/openapi.yaml new file mode 100644 index 0000000..c6647c3 --- /dev/null +++ b/back-end/dist/spec/openapi.yaml @@ -0,0 +1,377 @@ +openapi: 3.1.0 +info: + title: BAI Management API + description: BAI 管理系统后端 API 文档 + version: 1.0.0 +servers: + - url: https://bai-api.blv-oa.com + description: BAI-api生产环境 + - url: http://localhost:3000 + description: BAI-api本地开发环境 +tags: + - name: 系统 + description: 基础健康检查接口 + - name: 微信小程序用户 + description: 微信小程序注册、登录与鉴权接口 +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + ApiResponse: + type: object + required: [code, msg, data] + properties: + code: + type: integer + description: 业务状态码 + example: 200 + msg: + type: string + description: 响应消息 + example: 操作成功 + data: + type: object + description: 响应数据 + additionalProperties: true + HealthData: + type: object + properties: + status: + type: string + example: healthy + timestamp: + type: string + format: date-time + HelloWorldData: + type: object + properties: + message: + type: string + example: Hello, World! + timestamp: + type: string + format: date-time + status: + type: string + example: success + WechatProfileRequest: + type: object + required: [users_name, users_phone_code, users_picture] + properties: + users_name: + type: string + description: 用户姓名 + example: 张三 + users_phone_code: + type: string + description: 微信手机号获取凭证 code,由后端换取真实手机号后写入 users_phone + example: 2b7d9f2e3c4a5b6d7e8f + users_picture: + type: string + description: 用户头像 URL + example: https://example.com/avatar.png + WechatAuthRequest: + type: object + required: [users_wx_code] + properties: + users_wx_code: + type: string + description: 微信小程序登录临时凭证 code + example: 0a1b2c3d4e5f6g + CompanyInfo: + type: object + properties: + company_id: + type: string + example: C10001 + company_name: + type: string + example: 示例科技有限公司 + company_type: + type: string + example: 科技服务 + company_entity: + type: string + example: 李四 + company_usci: + type: string + example: 91330100XXXXXXXXXX + company_nationality: + type: string + example: 中国 + company_province: + type: string + example: 浙江省 + company_city: + type: string + example: 杭州市 + company_postalcode: + type: string + example: "310000" + company_add: + type: string + example: 某某大道 100 号 + company_status: + type: string + example: 正常 + company_level: + type: string + example: A + company_remark: + type: string + example: 重点合作客户 + UserInfo: + type: object + properties: + users_id: + type: string + example: U202603190001 + users_type: + type: string + description: | + 用户类型。 + - `游客`:仅完成微信新号注册,尚未首次完整补充 `users_name`、`users_phone`、`users_picture` + - `注册用户`:用户曾从“三项资料均为空”首次补充为“三项资料均完整” + enum: [游客, 注册用户] + example: 游客 + users_name: + type: string + example: 张三 + users_phone: + type: string + example: "13800138000" + users_phone_masked: + type: string + example: "138****8000" + users_picture: + type: string + example: https://example.com/avatar.png + users_wx_openid: + type: string + example: oAbCdEfGh123456789 + company_id: + type: string + example: C10001 + company: + $ref: '#/components/schemas/CompanyInfo' + pb_id: + type: string + description: PocketBase 记录 id + example: abc123xyz + created: + type: string + format: date-time + updated: + type: string + format: date-time + WechatProfileResponseData: + type: object + properties: + status: + type: string + description: 信息编辑状态 + enum: [update_success, update_failed] + user: + $ref: '#/components/schemas/UserInfo' + WechatAuthResponseData: + type: object + properties: + status: + type: string + description: 登录/注册结果状态 + enum: [register_success, login_success] + is_info_complete: + type: boolean + description: 用户资料是否已完善 + example: true + token: + type: string + description: JWT 令牌 + user: + $ref: '#/components/schemas/UserInfo' +paths: + /api/test-helloworld: + post: + tags: [系统] + summary: Test endpoint + description: Returns a hello world message + responses: + '200': + description: Successful response + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/HelloWorldData' + /api/health: + post: + tags: [系统] + summary: 健康检查 + description: 检查服务是否正常运行 + responses: + '200': + description: 服务状态正常 + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/HealthData' + /api/wechat/login: + post: + tags: [微信小程序用户] + summary: 微信小程序登录/注册合一 + description: | + 使用微信小程序临时登录凭证换取 openid。 + 若用户不存在,则自动创建新账号并返回完整用户信息;若已存在,则直接登录并返回完整用户信息。 + 登录/注册合一接口不处理手机号获取。 + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - users_wx_code + properties: + users_wx_code: + type: string + description: 微信小程序登录临时凭证 code + example: 0a1b2c3d4e5f6g + responses: + '200': + description: 登录或注册成功 + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/WechatAuthResponseData' + '400': + description: 参数错误 + '415': + description: 请求体格式错误,仅支持 application/json + '429': + description: 重复提交过于频繁 + '500': + description: 服务端异常 + /api/wechat/profile: + post: + tags: [微信小程序用户] + summary: 微信小程序用户信息编辑 + description: | + 从请求头 `users_wx_openid` 定位用户,不再从 body 中传 `users_wx_code`。 + body 中传入 `users_name`、`users_phone_code`、`users_picture`。 + 后端会通过微信 `users_phone_code` 调用官方接口换取真实手机号,再写入 `users_phone`。 + `users_name`、`users_phone_code`、`users_picture` 均为必填项。 + 当且仅当用户原先这三项资料全部为空,且本次首次完整补充三项资料时,系统自动将 `users_type` 从 `游客` 更新为 `注册用户`。 + 后续资料修改不会再影响已确定的 `users_type`。 + 返回更新后的完整用户信息,其中手机号等敏感字段需脱敏处理。 + parameters: + - in: header + name: users_wx_openid + required: true + schema: + type: string + description: 微信用户唯一标识,用于数据库查询 + - in: header + name: Authorization + required: true + schema: + type: string + description: 标准 JWT 认证头,格式为 `Bearer ` + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - users_name + - users_phone_code + - users_picture + properties: + users_name: + type: string + description: 用户姓名 + example: 张三 + users_phone_code: + type: string + description: 微信手机号获取凭证 code + example: 2b7d9f2e3c4a5b6d7e8f + users_picture: + type: string + description: 用户头像 URL + example: https://example.com/avatar.png + responses: + '200': + description: 信息更新成功 + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/WechatProfileResponseData' + '400': + description: 参数错误 + '401': + description: 请求头缺少 users_wx_openid 或 Authorization,或令牌无效 + '415': + description: 请求体格式错误,仅支持 application/json + '404': + description: 用户不存在 + '500': + description: 服务端异常 + /api/wechat/refresh-token: + post: + tags: [微信小程序用户] + summary: 微信小程序刷新 token + description: | + 小程序通过请求头中的 `users_wx_openid` 定位用户,并返回新的 JWT token。 + 本接口无 body 参数,请求方法固定为 POST。 + 本接口不要求旧 `Authorization`,仅依赖 `users_wx_openid` 识别用户并签发新 token。 + parameters: + - in: header + name: users_wx_openid + required: true + schema: + type: string + description: 微信用户唯一标识,用于数据库查询 + responses: + '200': + description: 刷新成功 + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + token: + type: string + description: 新的 JWT 令牌 + '404': + description: 未注册用户 + '401': + description: 请求头缺少 users_wx_openid + '500': + description: 服务端异常 diff --git a/back-end/dist/src/config/env.js b/back-end/dist/src/config/env.js new file mode 100644 index 0000000..26a7203 --- /dev/null +++ b/back-end/dist/src/config/env.js @@ -0,0 +1,28 @@ +const dotenv = require('dotenv') + +dotenv.config() + +const requiredEnv = ['WECHAT_APPID', 'WECHAT_SECRET', 'JWT_SECRET'] + +for (const key of requiredEnv) { + if (!process.env[key]) { + console.warn(`[config] 缺少环境变量: ${key}`) + } +} + +module.exports = { + nodeEnv: process.env.NODE_ENV || 'development', + port: Number(process.env.PORT || 3000), + apiPrefix: process.env.API_PREFIX || '/api', + appProtocol: process.env.APP_PROTOCOL || 'http', + appDomain: process.env.APP_DOMAIN || 'localhost', + appBaseUrl: + process.env.APP_BASE_URL + || `${process.env.APP_PROTOCOL || 'http'}://${process.env.APP_DOMAIN || 'localhost'}${process.env.PORT ? `:${process.env.PORT}` : ''}`, + pocketbaseUrl: process.env.POCKETBASE_API_URL || '', + pocketbaseAuthToken: process.env.POCKETBASE_AUTH_TOKEN || '', + wechatAppId: process.env.WECHAT_APPID || '', + wechatSecret: process.env.WECHAT_SECRET || '', + jwtSecret: process.env.JWT_SECRET || 'change_me', + jwtExpiresIn: process.env.JWT_EXPIRES_IN || '2h', +} diff --git a/back-end/dist/src/controllers/wechatController.js b/back-end/dist/src/controllers/wechatController.js new file mode 100644 index 0000000..ecae0f0 --- /dev/null +++ b/back-end/dist/src/controllers/wechatController.js @@ -0,0 +1,36 @@ +const asyncHandler = require('../utils/asyncHandler') +const { success } = require('../utils/response') +const { + validateLoginBody, + validateProfileEditBody, +} = require('../middlewares/validateWechatAuth') +const userService = require('../services/userService') + +const login = asyncHandler(async (req, res) => { + const payload = validateLoginBody(req.body) + const data = await userService.authenticateWechatUser(payload) + + const messageMap = { + register_success: '注册成功', + login_success: '登录成功', + } + + return success(res, messageMap[data.status] || '登录成功', data) +}) + +const updateProfile = asyncHandler(async (req, res) => { + const payload = validateProfileEditBody(req.body) + const data = await userService.updateWechatUserProfile(req.usersWxOpenid, payload) + return success(res, '信息更新成功', data) +}) + +const refreshToken = asyncHandler(async (req, res) => { + const data = await userService.refreshWechatToken(req.usersWxOpenid) + return success(res, '刷新成功', data) +}) + +module.exports = { + updateProfile, + login, + refreshToken, +} diff --git a/back-end/dist/src/index.js b/back-end/dist/src/index.js new file mode 100644 index 0000000..826585e --- /dev/null +++ b/back-end/dist/src/index.js @@ -0,0 +1,39 @@ +const express = require('express') +const env = require('./config/env') +const { fail } = require('./utils/response') +const requestLogger = require('./middlewares/requestLogger') +const errorHandler = require('./middlewares/errorHandler') +const apiRoutes = require('./routes/apiRoutes') + +function createApp() { + const app = express() + + app.use(express.json({ limit: '1mb' })) + app.use(requestLogger) + + app.use(env.apiPrefix, apiRoutes) + + app.use((req, res) => { + return fail(res, 404, 'Route not found', { + path: req.path, + }) + }) + + app.use(errorHandler) + + return app +} + +if (require.main === module) { + const app = createApp() + app.listen(env.port, () => { + console.log(`Server running on port ${env.port}`) + console.log(`Public base URL: ${env.appBaseUrl}`) + console.log(`Test endpoint: ${env.appBaseUrl}${env.apiPrefix}/test-helloworld`) + console.log(`Health check: ${env.appBaseUrl}${env.apiPrefix}/health`) + }) +} + +module.exports = { + createApp, +} \ No newline at end of file diff --git a/back-end/dist/src/middlewares/duplicateGuard.js b/back-end/dist/src/middlewares/duplicateGuard.js new file mode 100644 index 0000000..bc188f8 --- /dev/null +++ b/back-end/dist/src/middlewares/duplicateGuard.js @@ -0,0 +1,22 @@ +const AppError = require('../utils/appError') + +const requestCache = new Map() +const WINDOW_MS = 5000 + +module.exports = function duplicateGuard(req, res, next) { + const key = `${req.ip}:${req.originalUrl}:${JSON.stringify(req.body || {})}` + const now = Date.now() + const lastTime = requestCache.get(key) + + if (lastTime && now - lastTime < WINDOW_MS) { + return next(new AppError('请求过于频繁,请稍后重试', 429)) + } + + requestCache.set(key, now) + + setTimeout(() => { + requestCache.delete(key) + }, WINDOW_MS) + + next() +} diff --git a/back-end/dist/src/middlewares/errorHandler.js b/back-end/dist/src/middlewares/errorHandler.js new file mode 100644 index 0000000..78e3ce5 --- /dev/null +++ b/back-end/dist/src/middlewares/errorHandler.js @@ -0,0 +1,19 @@ +const { fail } = require('../utils/response') +const logger = require('../utils/logger') + +module.exports = function errorHandler(err, req, res, next) { + logger.error('接口处理异常', { + path: req.originalUrl, + method: req.method, + error: err.message, + details: err.details || {}, + }) + + if (res.headersSent) { + return next(err) + } + + return fail(res, err.statusCode || 500, err.message || '服务器内部错误', { + ...(err.details || {}), + }) +} diff --git a/back-end/dist/src/middlewares/jwtAuth.js b/back-end/dist/src/middlewares/jwtAuth.js new file mode 100644 index 0000000..72adc09 --- /dev/null +++ b/back-end/dist/src/middlewares/jwtAuth.js @@ -0,0 +1,20 @@ +const jwt = require('jsonwebtoken') +const env = require('../config/env') +const AppError = require('../utils/appError') + +module.exports = function jwtAuth(req, res, next) { + const authHeader = req.headers.authorization || '' + const [scheme, token] = authHeader.split(' ') + + if (scheme !== 'Bearer' || !token) { + return next(new AppError('未提供有效的认证令牌', 401)) + } + + try { + const decoded = jwt.verify(token, env.jwtSecret) + req.user = decoded + next() + } catch { + next(new AppError('认证令牌无效或已过期', 401)) + } +} diff --git a/back-end/dist/src/middlewares/requestLogger.js b/back-end/dist/src/middlewares/requestLogger.js new file mode 100644 index 0000000..208ca4e --- /dev/null +++ b/back-end/dist/src/middlewares/requestLogger.js @@ -0,0 +1,17 @@ +const logger = require('../utils/logger') + +module.exports = function requestLogger(req, res, next) { + const start = Date.now() + + res.on('finish', () => { + logger.info('请求完成', { + method: req.method, + path: req.originalUrl, + statusCode: res.statusCode, + durationMs: Date.now() - start, + ip: req.ip, + }) + }) + + next() +} diff --git a/back-end/dist/src/middlewares/requireJsonBody.js b/back-end/dist/src/middlewares/requireJsonBody.js new file mode 100644 index 0000000..4333dc8 --- /dev/null +++ b/back-end/dist/src/middlewares/requireJsonBody.js @@ -0,0 +1,15 @@ +const AppError = require('../utils/appError') + +module.exports = function requireJsonBody(req, res, next) { + const methods = ['POST', 'PUT', 'PATCH'] + + if (!methods.includes(req.method)) { + return next() + } + + if (!req.is('application/json')) { + return next(new AppError('请求体必须为 application/json', 415)) + } + + next() +} diff --git a/back-end/dist/src/middlewares/requireWechatOpenid.js b/back-end/dist/src/middlewares/requireWechatOpenid.js new file mode 100644 index 0000000..65dd8d8 --- /dev/null +++ b/back-end/dist/src/middlewares/requireWechatOpenid.js @@ -0,0 +1,12 @@ +const AppError = require('../utils/appError') + +module.exports = function requireWechatOpenid(req, res, next) { + const usersWxOpenid = req.headers['users_wx_openid'] + + if (!usersWxOpenid) { + return next(new AppError('请求头缺少 users_wx_openid', 401)) + } + + req.usersWxOpenid = usersWxOpenid + next() +} diff --git a/back-end/dist/src/middlewares/validateWechatAuth.js b/back-end/dist/src/middlewares/validateWechatAuth.js new file mode 100644 index 0000000..7a98a76 --- /dev/null +++ b/back-end/dist/src/middlewares/validateWechatAuth.js @@ -0,0 +1,35 @@ +const AppError = require('../utils/appError') +const { sanitizePayload } = require('../utils/sanitize') + +function validateLoginBody(body = {}) { + const payload = sanitizePayload(body) + + if (!payload.users_wx_code) { + throw new AppError('users_wx_code 为必填项', 400) + } + + return payload +} + +function validateProfileEditBody(body = {}) { + const payload = sanitizePayload(body) + + if (!payload.users_name) { + throw new AppError('users_name 为必填项', 400) + } + + if (!payload.users_phone_code) { + throw new AppError('users_phone_code 为必填项', 400) + } + + if (!payload.users_picture) { + throw new AppError('users_picture 为必填项', 400) + } + + return payload +} + +module.exports = { + validateLoginBody, + validateProfileEditBody, +} diff --git a/back-end/dist/src/middlewares/wechatHeadersAuth.js b/back-end/dist/src/middlewares/wechatHeadersAuth.js new file mode 100644 index 0000000..db2d188 --- /dev/null +++ b/back-end/dist/src/middlewares/wechatHeadersAuth.js @@ -0,0 +1,31 @@ +const jwt = require('jsonwebtoken') +const env = require('../config/env') +const AppError = require('../utils/appError') +const requireWechatOpenid = require('./requireWechatOpenid') + +module.exports = function wechatHeadersAuth(req, res, next) { + requireWechatOpenid(req, res, (openidError) => { + if (openidError) { + return next(openidError) + } + + const usersWxOpenid = req.usersWxOpenid + const authHeader = req.headers.authorization || '' + const [scheme, token] = authHeader.split(' ') + + if (scheme !== 'Bearer' || !token) { + return next(new AppError('请求头缺少 Authorization', 401)) + } + + try { + const decoded = jwt.verify(token, env.jwtSecret) + if (decoded.users_wx_openid && decoded.users_wx_openid !== usersWxOpenid) { + return next(new AppError('请求头中的 users_wx_openid 与令牌不匹配', 401)) + } + req.user = decoded + next() + } catch { + next(new AppError('认证令牌无效或已过期', 401)) + } + }) +} diff --git a/back-end/dist/src/routes/apiRoutes.js b/back-end/dist/src/routes/apiRoutes.js new file mode 100644 index 0000000..cf9e8b6 --- /dev/null +++ b/back-end/dist/src/routes/apiRoutes.js @@ -0,0 +1,24 @@ +const express = require('express') +const { success } = require('../utils/response') +const wechatRoutes = require('./wechatRoutes') + +const router = express.Router() + +router.post('/test-helloworld', (req, res) => { + return success(res, '请求成功', { + message: 'Hello, World!', + timestamp: new Date().toISOString(), + status: 'success', + }) +}) + +router.post('/health', (req, res) => { + return success(res, '服务运行正常', { + status: 'healthy', + timestamp: new Date().toISOString(), + }) +}) + +router.use('/wechat', wechatRoutes) + +module.exports = router diff --git a/back-end/dist/src/routes/wechatRoutes.js b/back-end/dist/src/routes/wechatRoutes.js new file mode 100644 index 0000000..7b00d38 --- /dev/null +++ b/back-end/dist/src/routes/wechatRoutes.js @@ -0,0 +1,14 @@ +const express = require('express') +const duplicateGuard = require('../middlewares/duplicateGuard') +const requireJsonBody = require('../middlewares/requireJsonBody') +const requireWechatOpenid = require('../middlewares/requireWechatOpenid') +const wechatHeadersAuth = require('../middlewares/wechatHeadersAuth') +const controller = require('../controllers/wechatController') + +const router = express.Router() + +router.post('/login', requireJsonBody, duplicateGuard, controller.login) +router.post('/profile', requireJsonBody, wechatHeadersAuth, duplicateGuard, controller.updateProfile) +router.post('/refresh-token', requireWechatOpenid, controller.refreshToken) + +module.exports = router diff --git a/back-end/dist/src/services/jwtService.js b/back-end/dist/src/services/jwtService.js new file mode 100644 index 0000000..bcdd926 --- /dev/null +++ b/back-end/dist/src/services/jwtService.js @@ -0,0 +1,12 @@ +const jwt = require('jsonwebtoken') +const env = require('../config/env') + +function signAccessToken(payload) { + return jwt.sign(payload, env.jwtSecret, { + expiresIn: env.jwtExpiresIn, + }) +} + +module.exports = { + signAccessToken, +} diff --git a/back-end/dist/src/services/pocketbaseService.js b/back-end/dist/src/services/pocketbaseService.js new file mode 100644 index 0000000..7837a41 --- /dev/null +++ b/back-end/dist/src/services/pocketbaseService.js @@ -0,0 +1,90 @@ +const axios = require('axios') +const env = require('../config/env') +const AppError = require('../utils/appError') + +function getHeaders() { + const headers = { + 'Content-Type': 'application/json', + } + + if (env.pocketbaseAuthToken) { + headers.Authorization = env.pocketbaseAuthToken.startsWith('Bearer ') + ? env.pocketbaseAuthToken + : `Bearer ${env.pocketbaseAuthToken}` + } + + return headers +} + +function buildUrl(path) { + const base = env.pocketbaseUrl.replace(/\/+$/, '') + const normalizedPath = path.replace(/^\/+/, '') + return `${base}/${normalizedPath}` +} + +async function request(config) { + try { + const response = await axios({ + timeout: 10000, + headers: getHeaders(), + ...config, + }) + return response.data + } catch (error) { + const detail = error.response?.data || error.message + throw new AppError('PocketBase 数据操作失败', 500, { + detail, + }) + } +} + +async function listUsersByFilter(filter) { + const data = await request({ + method: 'get', + url: buildUrl('collections/tbl_users/records'), + params: { + filter, + perPage: 1, + }, + }) + + return data.items || [] +} + +async function createUser(payload) { + return request({ + method: 'post', + url: buildUrl('collections/tbl_users/records'), + data: payload, + }) +} + +async function updateUser(recordId, payload) { + return request({ + method: 'patch', + url: buildUrl(`collections/tbl_users/records/${recordId}`), + data: payload, + }) +} + +async function getCompanyByCompanyId(companyId) { + if (!companyId) return null + + const data = await request({ + method: 'get', + url: buildUrl('collections/tbl_company/records'), + params: { + filter: `company_id = "${companyId}"`, + perPage: 1, + }, + }) + + return data.items?.[0] || null +} + +module.exports = { + listUsersByFilter, + createUser, + updateUser, + getCompanyByCompanyId, +} diff --git a/back-end/dist/src/services/userService.js b/back-end/dist/src/services/userService.js new file mode 100644 index 0000000..cda2d23 --- /dev/null +++ b/back-end/dist/src/services/userService.js @@ -0,0 +1,208 @@ +const crypto = require('crypto') +const AppError = require('../utils/appError') +const logger = require('../utils/logger') +const wechatService = require('./wechatService') +const jwtService = require('./jwtService') +const pocketbaseService = require('./pocketbaseService') + +const userMutationLocks = new Map() +const GUEST_USER_TYPE = '游客' +const REGISTERED_USER_TYPE = '注册用户' + +function buildUserId() { + const now = new Date() + const date = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}` + const suffix = crypto.randomInt(1000, 9999) + return `U${date}${suffix}` +} + +async function enrichUser(user) { + const company = await pocketbaseService.getCompanyByCompanyId(user.company_id) + return { + pb_id: user.id, + users_id: user.users_id, + users_type: user.users_type || GUEST_USER_TYPE, + users_name: user.users_name, + users_phone: user.users_phone, + users_phone_masked: maskPhone(user.users_phone), + users_picture: user.users_picture, + users_wx_openid: user.users_wx_openid, + company_id: user.company_id || '', + company, + created: user.created, + updated: user.updated, + raw: user, + } +} + +async function findUserByOpenid(usersWxOpenid) { + const users = await pocketbaseService.listUsersByFilter(`users_wx_openid = "${usersWxOpenid}"`) + return users[0] || null +} + +function maskPhone(phone = '') { + if (!phone || phone.length < 7) return '' + return `${phone.slice(0, 3)}****${phone.slice(-4)}` +} + +function isInfoComplete(user = {}) { + return Boolean(user.users_name && user.users_phone && user.users_picture) +} + +function isAllProfileFieldsEmpty(user = {}) { + return !user.users_name && !user.users_phone && !user.users_picture +} + +async function withUserLock(lockKey, handler) { + const previous = userMutationLocks.get(lockKey) || Promise.resolve() + let release + const current = new Promise((resolve) => { + release = resolve + }) + + userMutationLocks.set(lockKey, previous.then(() => current)) + + await previous + + try { + return await handler() + } finally { + release() + if (userMutationLocks.get(lockKey) === current) { + userMutationLocks.delete(lockKey) + } + } +} + +async function authenticateWechatUser(payload) { + const openid = await wechatService.getWxOpenId(payload.users_wx_code) + return withUserLock(`auth:${openid}`, async () => { + const existing = await findUserByOpenid(openid) + + if (existing) { + logger.warn('微信注册命中已存在账号', { + users_wx_openid: openid, + users_type: existing.users_type || GUEST_USER_TYPE, + }) + + const user = await enrichUser(existing) + const token = jwtService.signAccessToken({ + users_id: user.users_id, + users_wx_openid: user.users_wx_openid, + }) + + return { + status: 'login_success', + is_info_complete: isInfoComplete(existing), + token, + user, + } + } + + const created = await pocketbaseService.createUser({ + users_id: buildUserId(), + users_wx_openid: openid, + users_type: GUEST_USER_TYPE, + }) + + const user = await enrichUser(created) + const token = jwtService.signAccessToken({ + users_id: user.users_id, + users_wx_openid: user.users_wx_openid, + }) + + logger.info('微信用户注册成功', { + users_id: user.users_id, + users_phone: user.users_phone, + users_type: user.users_type, + }) + + return { + status: 'register_success', + is_info_complete: false, + token, + user, + } + }) +} + +async function updateWechatUserProfile(usersWxOpenid, payload) { + return withUserLock(`profile:${usersWxOpenid}`, async () => { + const currentUser = await findUserByOpenid(usersWxOpenid) + + if (!currentUser) { + throw new AppError('未找到待编辑的用户', 404) + } + + const usersPhone = await wechatService.getWxPhoneNumber(payload.users_phone_code) + + if (usersPhone && usersPhone !== currentUser.users_phone) { + const samePhoneUsers = await pocketbaseService.listUsersByFilter(`users_phone = "${usersPhone}"`) + const phoneUsedByOther = samePhoneUsers.some((item) => item.id !== currentUser.id) + if (phoneUsedByOther) { + throw new AppError('手机号已被注册', 400) + } + } + + const shouldPromoteUserType = + isAllProfileFieldsEmpty(currentUser) + && payload.users_name + && usersPhone + && payload.users_picture + && (currentUser.users_type === GUEST_USER_TYPE || !currentUser.users_type) + + const updatePayload = { + users_name: payload.users_name, + users_phone: usersPhone, + users_picture: payload.users_picture, + } + + if (shouldPromoteUserType) { + updatePayload.users_type = REGISTERED_USER_TYPE + } + + const updated = await pocketbaseService.updateUser(currentUser.id, updatePayload) + const user = await enrichUser(updated) + + logger.info('微信用户资料更新成功', { + users_id: user.users_id, + users_phone: user.users_phone, + users_type_before: currentUser.users_type || GUEST_USER_TYPE, + users_type_after: user.users_type, + users_type_promoted: shouldPromoteUserType, + }) + + return { + status: 'update_success', + user, + } + }) +} + +async function refreshWechatToken(usersWxOpenid) { + const userRecord = await findUserByOpenid(usersWxOpenid) + + if (!userRecord) { + throw new AppError('未注册用户', 404) + } + + const token = jwtService.signAccessToken({ + users_id: userRecord.users_id, + users_wx_openid: userRecord.users_wx_openid, + }) + + logger.info('微信用户刷新令牌成功', { + users_id: userRecord.users_id, + users_wx_openid: userRecord.users_wx_openid, + }) + + return { + token, + } +} + +module.exports = { + authenticateWechatUser, + updateWechatUserProfile, + refreshWechatToken, +} diff --git a/back-end/dist/src/services/wechatService.js b/back-end/dist/src/services/wechatService.js new file mode 100644 index 0000000..85ba310 --- /dev/null +++ b/back-end/dist/src/services/wechatService.js @@ -0,0 +1,117 @@ +const axios = require('axios') +const env = require('../config/env') +const AppError = require('../utils/appError') + +let accessTokenCache = { + token: '', + expiresAt: 0, +} + +async function getWxOpenId(code) { + if (!env.wechatAppId || !env.wechatSecret) { + throw new AppError('微信小程序配置缺失', 500) + } + + try { + const url = 'https://api.weixin.qq.com/sns/jscode2session' + const response = await axios.get(url, { + params: { + appid: env.wechatAppId, + secret: env.wechatSecret, + js_code: code, + grant_type: 'authorization_code', + }, + timeout: 10000, + }) + + const data = response.data || {} + + if (data.openid) { + return data.openid + } + + if (data.errcode || data.errmsg) { + throw new AppError(`获取OpenID失败: ${data.errcode || ''} ${data.errmsg || ''}`.trim(), 502, { + wechat: data, + }) + } + + throw new AppError('获取OpenID失败: 响应中未包含openid', 502) + } catch (error) { + if (error instanceof AppError) throw error + throw new AppError(`获取微信OpenID时发生错误: ${error.message}`, 502) + } +} + +async function getWechatAccessToken() { + if (accessTokenCache.token && Date.now() < accessTokenCache.expiresAt) { + return accessTokenCache.token + } + + try { + const response = await axios.get('https://api.weixin.qq.com/cgi-bin/token', { + params: { + grant_type: 'client_credential', + appid: env.wechatAppId, + secret: env.wechatSecret, + }, + timeout: 10000, + }) + + const data = response.data || {} + + if (!data.access_token) { + throw new AppError(`获取微信 access_token 失败: ${data.errcode || ''} ${data.errmsg || ''}`.trim(), 502, { + wechat: data, + }) + } + + accessTokenCache = { + token: data.access_token, + expiresAt: Date.now() + Math.max((data.expires_in || 7200) - 300, 60) * 1000, + } + + return accessTokenCache.token + } catch (error) { + if (error instanceof AppError) throw error + throw new AppError(`获取微信 access_token 时发生错误: ${error.message}`, 502) + } +} + +async function getWxPhoneNumber(phoneCode) { + const accessToken = await getWechatAccessToken() + + try { + const response = await axios.post( + `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${accessToken}`, + { + code: phoneCode, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + timeout: 10000, + } + ) + + const data = response.data || {} + const phone = data.phone_info?.purePhoneNumber || data.phone_info?.phoneNumber + + if (phone) { + return phone + } + + throw new AppError(`获取微信手机号失败: ${data.errcode || ''} ${data.errmsg || ''}`.trim(), 502, { + wechat: data, + }) + } catch (error) { + if (error instanceof AppError) throw error + throw new AppError(`获取微信手机号时发生错误: ${error.message}`, 502) + } +} + +module.exports = { + getWxOpenId, + getWxPhoneNumber, +} diff --git a/back-end/dist/src/utils/appError.js b/back-end/dist/src/utils/appError.js new file mode 100644 index 0000000..5cfa9d5 --- /dev/null +++ b/back-end/dist/src/utils/appError.js @@ -0,0 +1,10 @@ +class AppError extends Error { + constructor(message, statusCode = 500, details = {}) { + super(message) + this.name = 'AppError' + this.statusCode = statusCode + this.details = details + } +} + +module.exports = AppError diff --git a/back-end/dist/src/utils/asyncHandler.js b/back-end/dist/src/utils/asyncHandler.js new file mode 100644 index 0000000..10bd82e --- /dev/null +++ b/back-end/dist/src/utils/asyncHandler.js @@ -0,0 +1,5 @@ +module.exports = function asyncHandler(fn) { + return function wrappedHandler(req, res, next) { + Promise.resolve(fn(req, res, next)).catch(next) + } +} diff --git a/back-end/dist/src/utils/logger.js b/back-end/dist/src/utils/logger.js new file mode 100644 index 0000000..9483dce --- /dev/null +++ b/back-end/dist/src/utils/logger.js @@ -0,0 +1,27 @@ +function createLog(method, message, meta = {}) { + const payload = { + time: new Date().toISOString(), + method, + message, + ...meta, + } + return JSON.stringify(payload) +} + +function info(message, meta) { + console.log(createLog('INFO', message, meta)) +} + +function warn(message, meta) { + console.warn(createLog('WARN', message, meta)) +} + +function error(message, meta) { + console.error(createLog('ERROR', message, meta)) +} + +module.exports = { + info, + warn, + error, +} diff --git a/back-end/dist/src/utils/response.js b/back-end/dist/src/utils/response.js new file mode 100644 index 0000000..bb1d6cd --- /dev/null +++ b/back-end/dist/src/utils/response.js @@ -0,0 +1,20 @@ +function success(res, msg = '操作成功', data = {}, code = 200) { + return res.status(code).json({ + code, + msg, + data, + }) +} + +function fail(res, code = 500, msg = '服务器内部错误', data = {}) { + return res.status(code).json({ + code, + msg, + data, + }) +} + +module.exports = { + success, + fail, +} diff --git a/back-end/dist/src/utils/sanitize.js b/back-end/dist/src/utils/sanitize.js new file mode 100644 index 0000000..8ed5012 --- /dev/null +++ b/back-end/dist/src/utils/sanitize.js @@ -0,0 +1,17 @@ +function sanitizeString(value) { + if (typeof value !== 'string') return '' + return value.replace(/[<>\\]/g, '').trim() +} + +function sanitizePayload(payload = {}) { + return Object.keys(payload).reduce((acc, key) => { + const value = payload[key] + acc[key] = typeof value === 'string' ? sanitizeString(value) : value + return acc + }, {}) +} + +module.exports = { + sanitizeString, + sanitizePayload, +} diff --git a/back-end/eslint.config.js b/back-end/eslint.config.js new file mode 100644 index 0000000..38d080f --- /dev/null +++ b/back-end/eslint.config.js @@ -0,0 +1,13 @@ +module.exports = [ + { + files: ['src/**/*.js', 'tests/**/*.js'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'commonjs', + }, + rules: { + semi: ['error', 'never'], + 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + }, + }, +] diff --git a/back-end/package-lock.json b/back-end/package-lock.json index 13d55bc..95d1336 100644 --- a/back-end/package-lock.json +++ b/back-end/package-lock.json @@ -9,16 +9,214 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "axios": "^1.13.2", "dotenv": "^16.3.1", - "express": "^4.18.2" + "express": "^4.18.2", + "jsonwebtoken": "^9.0.2" }, "devDependencies": { - "@fission-ai/openspec": "^1.0.0" + "@fission-ai/openspec": "^1.0.0", + "eslint": "^9.23.0", + "supertest": "^7.1.1" }, "engines": { "node": ">=22.0.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@fission-ai/openspec": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/@fission-ai/openspec/-/openspec-1.2.0.tgz", @@ -44,6 +242,58 @@ "node": ">=20.19.0" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@inquirer/ansi": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/@inquirer/ansi/-/ansi-1.0.2.tgz", @@ -394,6 +644,19 @@ } } }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -432,6 +695,16 @@ "node": ">= 8" } }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, "node_modules/@posthog/core": { "version": "1.23.4", "resolved": "https://registry.npmmirror.com/@posthog/core/-/core-1.23.4.tgz", @@ -442,6 +715,20 @@ "cross-spawn": "^7.0.6" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", @@ -455,6 +742,47 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -484,12 +812,50 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.6", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.4", "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.4.tgz", @@ -526,6 +892,17 @@ "node": ">=0.10.0" } }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", @@ -539,6 +916,12 @@ "node": ">=8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", @@ -577,6 +960,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", @@ -656,6 +1049,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "14.0.3", "resolved": "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz", @@ -666,6 +1071,23 @@ "node": ">=20" } }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", @@ -702,6 +1124,13 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -726,6 +1155,22 @@ "ms": "2.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", @@ -745,6 +1190,17 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz", @@ -771,6 +1227,15 @@ "node": ">= 0.4" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", @@ -823,12 +1288,250 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmmirror.com/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", @@ -884,6 +1587,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", @@ -901,6 +1611,27 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", @@ -911,6 +1642,19 @@ "reusify": "^1.0.4" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", @@ -942,6 +1686,98 @@ "node": ">= 0.8" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmmirror.com/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", @@ -1032,6 +1868,19 @@ "node": ">= 6" } }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", @@ -1044,6 +1893,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1056,6 +1915,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", @@ -1105,6 +1979,43 @@ "url": "https://opencollective.com/express" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", @@ -1196,6 +2107,178 @@ "dev": true, "license": "ISC" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/log-symbols": { "version": "6.0.0", "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-6.0.0.tgz", @@ -1332,6 +2415,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", @@ -1348,6 +2444,13 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", @@ -1381,6 +2484,16 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/onetime": { "version": "7.0.0", "resolved": "https://registry.npmmirror.com/onetime/-/onetime-7.0.0.tgz", @@ -1397,6 +2510,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "8.2.0", "resolved": "https://registry.npmmirror.com/ora/-/ora-8.2.0.tgz", @@ -1421,6 +2552,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", @@ -1430,6 +2606,16 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", @@ -1480,6 +2666,16 @@ } } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1493,6 +2689,22 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.14.2", "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.2.tgz", @@ -1565,6 +2777,16 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -1643,6 +2865,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.2", "resolved": "https://registry.npmmirror.com/send/-/send-0.19.2.tgz", @@ -1858,6 +3092,116 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superagent": { + "version": "10.3.0", + "resolved": "https://registry.npmmirror.com/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.5", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.14.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/supertest": { + "version": "7.2.2", + "resolved": "https://registry.npmmirror.com/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie-signature": "^1.2.2", + "methods": "^1.1.2", + "superagent": "^10.3.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supertest/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1880,6 +3224,19 @@ "node": ">=0.6" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", @@ -1902,6 +3259,16 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1936,6 +3303,16 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -1996,6 +3373,13 @@ "node": ">=8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/yaml": { "version": "2.8.2", "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.2.tgz", @@ -2012,6 +3396,19 @@ "url": "https://github.com/sponsors/eemeli" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yoctocolors-cjs": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", diff --git a/back-end/package.json b/back-end/package.json index e1d5445..2fd1e7e 100644 --- a/back-end/package.json +++ b/back-end/package.json @@ -2,21 +2,27 @@ "name": "web-bai-manage-api-server", "version": "1.0.0", "description": "Backend API server for BAI Management System", - "main": "src/index.js", + "main": "dist/src/index.js", "scripts": { "dev": "node src/index.js", - "build": "echo 'No build needed for backend'", - "test": "echo 'No tests implemented yet'", - "lint": "echo 'No linting implemented yet'", + "build": "node scripts/build.js", + "start": "node dist/src/index.js", + "prestart": "node -e \"require('fs').accessSync('dist/src/index.js')\"", + "test": "node --test tests/**/*.test.js", + "lint": "eslint src tests --ext .js", "spec:lint": "npx @fission-ai/openspec lint spec/", "spec:validate": "npx @fission-ai/openspec validate spec/" }, "dependencies": { + "axios": "^1.13.2", "express": "^4.18.2", - "dotenv": "^16.3.1" + "dotenv": "^16.3.1", + "jsonwebtoken": "^9.0.2" }, "devDependencies": { - "@fission-ai/openspec": "^1.0.0" + "@fission-ai/openspec": "^1.0.0", + "eslint": "^9.23.0", + "supertest": "^7.1.1" }, "engines": { "node": ">=22.0.0" diff --git a/back-end/scripts/build.js b/back-end/scripts/build.js new file mode 100644 index 0000000..1fb64e9 --- /dev/null +++ b/back-end/scripts/build.js @@ -0,0 +1,52 @@ +const fs = require('fs') +const path = require('path') + +const rootDir = path.resolve(__dirname, '..') +const distDir = path.join(rootDir, 'dist') +const sourceDirs = ['src', 'spec'] +const sourceFiles = ['package.json', 'package-lock.json', '.env', 'eslint.config.js'] + +function ensureCleanDir(dirPath) { + fs.rmSync(dirPath, { recursive: true, force: true }) + fs.mkdirSync(dirPath, { recursive: true }) +} + +function copyRecursive(sourcePath, targetPath) { + const stats = fs.statSync(sourcePath) + + if (stats.isDirectory()) { + fs.mkdirSync(targetPath, { recursive: true }) + for (const entry of fs.readdirSync(sourcePath)) { + copyRecursive( + path.join(sourcePath, entry), + path.join(targetPath, entry) + ) + } + return + } + + fs.mkdirSync(path.dirname(targetPath), { recursive: true }) + fs.copyFileSync(sourcePath, targetPath) +} + +function build() { + ensureCleanDir(distDir) + + for (const dir of sourceDirs) { + const sourcePath = path.join(rootDir, dir) + if (fs.existsSync(sourcePath)) { + copyRecursive(sourcePath, path.join(distDir, dir)) + } + } + + for (const file of sourceFiles) { + const sourcePath = path.join(rootDir, file) + if (fs.existsSync(sourcePath)) { + copyRecursive(sourcePath, path.join(distDir, file)) + } + } + + console.log('Build completed. Deployable files generated in dist/.') +} + +build() diff --git a/back-end/spec/openapi.yaml b/back-end/spec/openapi.yaml index 58783ee..c6647c3 100644 --- a/back-end/spec/openapi.yaml +++ b/back-end/spec/openapi.yaml @@ -1,14 +1,201 @@ openapi: 3.1.0 info: title: BAI Management API - description: Backend API for BAI Management System + description: BAI 管理系统后端 API 文档 version: 1.0.0 servers: + - url: https://bai-api.blv-oa.com + description: BAI-api生产环境 - url: http://localhost:3000 - description: Development server + description: BAI-api本地开发环境 +tags: + - name: 系统 + description: 基础健康检查接口 + - name: 微信小程序用户 + description: 微信小程序注册、登录与鉴权接口 +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + ApiResponse: + type: object + required: [code, msg, data] + properties: + code: + type: integer + description: 业务状态码 + example: 200 + msg: + type: string + description: 响应消息 + example: 操作成功 + data: + type: object + description: 响应数据 + additionalProperties: true + HealthData: + type: object + properties: + status: + type: string + example: healthy + timestamp: + type: string + format: date-time + HelloWorldData: + type: object + properties: + message: + type: string + example: Hello, World! + timestamp: + type: string + format: date-time + status: + type: string + example: success + WechatProfileRequest: + type: object + required: [users_name, users_phone_code, users_picture] + properties: + users_name: + type: string + description: 用户姓名 + example: 张三 + users_phone_code: + type: string + description: 微信手机号获取凭证 code,由后端换取真实手机号后写入 users_phone + example: 2b7d9f2e3c4a5b6d7e8f + users_picture: + type: string + description: 用户头像 URL + example: https://example.com/avatar.png + WechatAuthRequest: + type: object + required: [users_wx_code] + properties: + users_wx_code: + type: string + description: 微信小程序登录临时凭证 code + example: 0a1b2c3d4e5f6g + CompanyInfo: + type: object + properties: + company_id: + type: string + example: C10001 + company_name: + type: string + example: 示例科技有限公司 + company_type: + type: string + example: 科技服务 + company_entity: + type: string + example: 李四 + company_usci: + type: string + example: 91330100XXXXXXXXXX + company_nationality: + type: string + example: 中国 + company_province: + type: string + example: 浙江省 + company_city: + type: string + example: 杭州市 + company_postalcode: + type: string + example: "310000" + company_add: + type: string + example: 某某大道 100 号 + company_status: + type: string + example: 正常 + company_level: + type: string + example: A + company_remark: + type: string + example: 重点合作客户 + UserInfo: + type: object + properties: + users_id: + type: string + example: U202603190001 + users_type: + type: string + description: | + 用户类型。 + - `游客`:仅完成微信新号注册,尚未首次完整补充 `users_name`、`users_phone`、`users_picture` + - `注册用户`:用户曾从“三项资料均为空”首次补充为“三项资料均完整” + enum: [游客, 注册用户] + example: 游客 + users_name: + type: string + example: 张三 + users_phone: + type: string + example: "13800138000" + users_phone_masked: + type: string + example: "138****8000" + users_picture: + type: string + example: https://example.com/avatar.png + users_wx_openid: + type: string + example: oAbCdEfGh123456789 + company_id: + type: string + example: C10001 + company: + $ref: '#/components/schemas/CompanyInfo' + pb_id: + type: string + description: PocketBase 记录 id + example: abc123xyz + created: + type: string + format: date-time + updated: + type: string + format: date-time + WechatProfileResponseData: + type: object + properties: + status: + type: string + description: 信息编辑状态 + enum: [update_success, update_failed] + user: + $ref: '#/components/schemas/UserInfo' + WechatAuthResponseData: + type: object + properties: + status: + type: string + description: 登录/注册结果状态 + enum: [register_success, login_success] + is_info_complete: + type: boolean + description: 用户资料是否已完善 + example: true + token: + type: string + description: JWT 令牌 + user: + $ref: '#/components/schemas/UserInfo' paths: - /test-helloworld: - get: + /api/test-helloworld: + post: + tags: [系统] summary: Test endpoint description: Returns a hello world message responses: @@ -17,32 +204,174 @@ paths: content: application/json: schema: - type: object - properties: - message: - type: string - example: Hello, World! - timestamp: - type: string - format: date-time - status: - type: string - example: success - /health: - get: - summary: Health check - description: Checks if the server is running + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/HelloWorldData' + /api/health: + post: + tags: [系统] + summary: 健康检查 + description: 检查服务是否正常运行 responses: '200': - description: Server is healthy + description: 服务状态正常 content: application/json: schema: - type: object - properties: - status: - type: string - example: healthy - timestamp: - type: string - format: date-time \ No newline at end of file + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/HealthData' + /api/wechat/login: + post: + tags: [微信小程序用户] + summary: 微信小程序登录/注册合一 + description: | + 使用微信小程序临时登录凭证换取 openid。 + 若用户不存在,则自动创建新账号并返回完整用户信息;若已存在,则直接登录并返回完整用户信息。 + 登录/注册合一接口不处理手机号获取。 + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - users_wx_code + properties: + users_wx_code: + type: string + description: 微信小程序登录临时凭证 code + example: 0a1b2c3d4e5f6g + responses: + '200': + description: 登录或注册成功 + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/WechatAuthResponseData' + '400': + description: 参数错误 + '415': + description: 请求体格式错误,仅支持 application/json + '429': + description: 重复提交过于频繁 + '500': + description: 服务端异常 + /api/wechat/profile: + post: + tags: [微信小程序用户] + summary: 微信小程序用户信息编辑 + description: | + 从请求头 `users_wx_openid` 定位用户,不再从 body 中传 `users_wx_code`。 + body 中传入 `users_name`、`users_phone_code`、`users_picture`。 + 后端会通过微信 `users_phone_code` 调用官方接口换取真实手机号,再写入 `users_phone`。 + `users_name`、`users_phone_code`、`users_picture` 均为必填项。 + 当且仅当用户原先这三项资料全部为空,且本次首次完整补充三项资料时,系统自动将 `users_type` 从 `游客` 更新为 `注册用户`。 + 后续资料修改不会再影响已确定的 `users_type`。 + 返回更新后的完整用户信息,其中手机号等敏感字段需脱敏处理。 + parameters: + - in: header + name: users_wx_openid + required: true + schema: + type: string + description: 微信用户唯一标识,用于数据库查询 + - in: header + name: Authorization + required: true + schema: + type: string + description: 标准 JWT 认证头,格式为 `Bearer ` + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - users_name + - users_phone_code + - users_picture + properties: + users_name: + type: string + description: 用户姓名 + example: 张三 + users_phone_code: + type: string + description: 微信手机号获取凭证 code + example: 2b7d9f2e3c4a5b6d7e8f + users_picture: + type: string + description: 用户头像 URL + example: https://example.com/avatar.png + responses: + '200': + description: 信息更新成功 + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/WechatProfileResponseData' + '400': + description: 参数错误 + '401': + description: 请求头缺少 users_wx_openid 或 Authorization,或令牌无效 + '415': + description: 请求体格式错误,仅支持 application/json + '404': + description: 用户不存在 + '500': + description: 服务端异常 + /api/wechat/refresh-token: + post: + tags: [微信小程序用户] + summary: 微信小程序刷新 token + description: | + 小程序通过请求头中的 `users_wx_openid` 定位用户,并返回新的 JWT token。 + 本接口无 body 参数,请求方法固定为 POST。 + 本接口不要求旧 `Authorization`,仅依赖 `users_wx_openid` 识别用户并签发新 token。 + parameters: + - in: header + name: users_wx_openid + required: true + schema: + type: string + description: 微信用户唯一标识,用于数据库查询 + responses: + '200': + description: 刷新成功 + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + token: + type: string + description: 新的 JWT 令牌 + '404': + description: 未注册用户 + '401': + description: 请求头缺少 users_wx_openid + '500': + description: 服务端异常 diff --git a/back-end/src/config/env.js b/back-end/src/config/env.js new file mode 100644 index 0000000..26a7203 --- /dev/null +++ b/back-end/src/config/env.js @@ -0,0 +1,28 @@ +const dotenv = require('dotenv') + +dotenv.config() + +const requiredEnv = ['WECHAT_APPID', 'WECHAT_SECRET', 'JWT_SECRET'] + +for (const key of requiredEnv) { + if (!process.env[key]) { + console.warn(`[config] 缺少环境变量: ${key}`) + } +} + +module.exports = { + nodeEnv: process.env.NODE_ENV || 'development', + port: Number(process.env.PORT || 3000), + apiPrefix: process.env.API_PREFIX || '/api', + appProtocol: process.env.APP_PROTOCOL || 'http', + appDomain: process.env.APP_DOMAIN || 'localhost', + appBaseUrl: + process.env.APP_BASE_URL + || `${process.env.APP_PROTOCOL || 'http'}://${process.env.APP_DOMAIN || 'localhost'}${process.env.PORT ? `:${process.env.PORT}` : ''}`, + pocketbaseUrl: process.env.POCKETBASE_API_URL || '', + pocketbaseAuthToken: process.env.POCKETBASE_AUTH_TOKEN || '', + wechatAppId: process.env.WECHAT_APPID || '', + wechatSecret: process.env.WECHAT_SECRET || '', + jwtSecret: process.env.JWT_SECRET || 'change_me', + jwtExpiresIn: process.env.JWT_EXPIRES_IN || '2h', +} diff --git a/back-end/src/controllers/wechatController.js b/back-end/src/controllers/wechatController.js new file mode 100644 index 0000000..ecae0f0 --- /dev/null +++ b/back-end/src/controllers/wechatController.js @@ -0,0 +1,36 @@ +const asyncHandler = require('../utils/asyncHandler') +const { success } = require('../utils/response') +const { + validateLoginBody, + validateProfileEditBody, +} = require('../middlewares/validateWechatAuth') +const userService = require('../services/userService') + +const login = asyncHandler(async (req, res) => { + const payload = validateLoginBody(req.body) + const data = await userService.authenticateWechatUser(payload) + + const messageMap = { + register_success: '注册成功', + login_success: '登录成功', + } + + return success(res, messageMap[data.status] || '登录成功', data) +}) + +const updateProfile = asyncHandler(async (req, res) => { + const payload = validateProfileEditBody(req.body) + const data = await userService.updateWechatUserProfile(req.usersWxOpenid, payload) + return success(res, '信息更新成功', data) +}) + +const refreshToken = asyncHandler(async (req, res) => { + const data = await userService.refreshWechatToken(req.usersWxOpenid) + return success(res, '刷新成功', data) +}) + +module.exports = { + updateProfile, + login, + refreshToken, +} diff --git a/back-end/src/index.js b/back-end/src/index.js index 00b1b9d..826585e 100644 --- a/back-end/src/index.js +++ b/back-end/src/index.js @@ -1,35 +1,39 @@ -const express = require('express'); -const dotenv = require('dotenv'); +const express = require('express') +const env = require('./config/env') +const { fail } = require('./utils/response') +const requestLogger = require('./middlewares/requestLogger') +const errorHandler = require('./middlewares/errorHandler') +const apiRoutes = require('./routes/apiRoutes') -// 加载环境变量 -dotenv.config(); +function createApp() { + const app = express() -const app = express(); -const port = process.env.PORT || 3000; + app.use(express.json({ limit: '1mb' })) + app.use(requestLogger) -// 解析JSON请求体 -app.use(express.json()); + app.use(env.apiPrefix, apiRoutes) -// 测试接口 -app.get('/test-helloworld', (req, res) => { - res.json({ - message: 'Hello, World!', - timestamp: new Date().toISOString(), - status: 'success' - }); -}); + app.use((req, res) => { + return fail(res, 404, 'Route not found', { + path: req.path, + }) + }) -// 健康检查接口 -app.get('/health', (req, res) => { - res.json({ - status: 'healthy', - timestamp: new Date().toISOString() - }); -}); + app.use(errorHandler) -// 启动服务器 -app.listen(port, () => { - console.log(`Server running on port ${port}`); - console.log(`Test endpoint: http://localhost:${port}/test-helloworld`); - console.log(`Health check: http://localhost:${port}/health`); -}); \ No newline at end of file + return app +} + +if (require.main === module) { + const app = createApp() + app.listen(env.port, () => { + console.log(`Server running on port ${env.port}`) + console.log(`Public base URL: ${env.appBaseUrl}`) + console.log(`Test endpoint: ${env.appBaseUrl}${env.apiPrefix}/test-helloworld`) + console.log(`Health check: ${env.appBaseUrl}${env.apiPrefix}/health`) + }) +} + +module.exports = { + createApp, +} \ No newline at end of file diff --git a/back-end/src/middlewares/duplicateGuard.js b/back-end/src/middlewares/duplicateGuard.js new file mode 100644 index 0000000..bc188f8 --- /dev/null +++ b/back-end/src/middlewares/duplicateGuard.js @@ -0,0 +1,22 @@ +const AppError = require('../utils/appError') + +const requestCache = new Map() +const WINDOW_MS = 5000 + +module.exports = function duplicateGuard(req, res, next) { + const key = `${req.ip}:${req.originalUrl}:${JSON.stringify(req.body || {})}` + const now = Date.now() + const lastTime = requestCache.get(key) + + if (lastTime && now - lastTime < WINDOW_MS) { + return next(new AppError('请求过于频繁,请稍后重试', 429)) + } + + requestCache.set(key, now) + + setTimeout(() => { + requestCache.delete(key) + }, WINDOW_MS) + + next() +} diff --git a/back-end/src/middlewares/errorHandler.js b/back-end/src/middlewares/errorHandler.js new file mode 100644 index 0000000..78e3ce5 --- /dev/null +++ b/back-end/src/middlewares/errorHandler.js @@ -0,0 +1,19 @@ +const { fail } = require('../utils/response') +const logger = require('../utils/logger') + +module.exports = function errorHandler(err, req, res, next) { + logger.error('接口处理异常', { + path: req.originalUrl, + method: req.method, + error: err.message, + details: err.details || {}, + }) + + if (res.headersSent) { + return next(err) + } + + return fail(res, err.statusCode || 500, err.message || '服务器内部错误', { + ...(err.details || {}), + }) +} diff --git a/back-end/src/middlewares/jwtAuth.js b/back-end/src/middlewares/jwtAuth.js new file mode 100644 index 0000000..72adc09 --- /dev/null +++ b/back-end/src/middlewares/jwtAuth.js @@ -0,0 +1,20 @@ +const jwt = require('jsonwebtoken') +const env = require('../config/env') +const AppError = require('../utils/appError') + +module.exports = function jwtAuth(req, res, next) { + const authHeader = req.headers.authorization || '' + const [scheme, token] = authHeader.split(' ') + + if (scheme !== 'Bearer' || !token) { + return next(new AppError('未提供有效的认证令牌', 401)) + } + + try { + const decoded = jwt.verify(token, env.jwtSecret) + req.user = decoded + next() + } catch { + next(new AppError('认证令牌无效或已过期', 401)) + } +} diff --git a/back-end/src/middlewares/requestLogger.js b/back-end/src/middlewares/requestLogger.js new file mode 100644 index 0000000..208ca4e --- /dev/null +++ b/back-end/src/middlewares/requestLogger.js @@ -0,0 +1,17 @@ +const logger = require('../utils/logger') + +module.exports = function requestLogger(req, res, next) { + const start = Date.now() + + res.on('finish', () => { + logger.info('请求完成', { + method: req.method, + path: req.originalUrl, + statusCode: res.statusCode, + durationMs: Date.now() - start, + ip: req.ip, + }) + }) + + next() +} diff --git a/back-end/src/middlewares/requireJsonBody.js b/back-end/src/middlewares/requireJsonBody.js new file mode 100644 index 0000000..4333dc8 --- /dev/null +++ b/back-end/src/middlewares/requireJsonBody.js @@ -0,0 +1,15 @@ +const AppError = require('../utils/appError') + +module.exports = function requireJsonBody(req, res, next) { + const methods = ['POST', 'PUT', 'PATCH'] + + if (!methods.includes(req.method)) { + return next() + } + + if (!req.is('application/json')) { + return next(new AppError('请求体必须为 application/json', 415)) + } + + next() +} diff --git a/back-end/src/middlewares/requireWechatOpenid.js b/back-end/src/middlewares/requireWechatOpenid.js new file mode 100644 index 0000000..65dd8d8 --- /dev/null +++ b/back-end/src/middlewares/requireWechatOpenid.js @@ -0,0 +1,12 @@ +const AppError = require('../utils/appError') + +module.exports = function requireWechatOpenid(req, res, next) { + const usersWxOpenid = req.headers['users_wx_openid'] + + if (!usersWxOpenid) { + return next(new AppError('请求头缺少 users_wx_openid', 401)) + } + + req.usersWxOpenid = usersWxOpenid + next() +} diff --git a/back-end/src/middlewares/validateWechatAuth.js b/back-end/src/middlewares/validateWechatAuth.js new file mode 100644 index 0000000..7a98a76 --- /dev/null +++ b/back-end/src/middlewares/validateWechatAuth.js @@ -0,0 +1,35 @@ +const AppError = require('../utils/appError') +const { sanitizePayload } = require('../utils/sanitize') + +function validateLoginBody(body = {}) { + const payload = sanitizePayload(body) + + if (!payload.users_wx_code) { + throw new AppError('users_wx_code 为必填项', 400) + } + + return payload +} + +function validateProfileEditBody(body = {}) { + const payload = sanitizePayload(body) + + if (!payload.users_name) { + throw new AppError('users_name 为必填项', 400) + } + + if (!payload.users_phone_code) { + throw new AppError('users_phone_code 为必填项', 400) + } + + if (!payload.users_picture) { + throw new AppError('users_picture 为必填项', 400) + } + + return payload +} + +module.exports = { + validateLoginBody, + validateProfileEditBody, +} diff --git a/back-end/src/middlewares/wechatHeadersAuth.js b/back-end/src/middlewares/wechatHeadersAuth.js new file mode 100644 index 0000000..db2d188 --- /dev/null +++ b/back-end/src/middlewares/wechatHeadersAuth.js @@ -0,0 +1,31 @@ +const jwt = require('jsonwebtoken') +const env = require('../config/env') +const AppError = require('../utils/appError') +const requireWechatOpenid = require('./requireWechatOpenid') + +module.exports = function wechatHeadersAuth(req, res, next) { + requireWechatOpenid(req, res, (openidError) => { + if (openidError) { + return next(openidError) + } + + const usersWxOpenid = req.usersWxOpenid + const authHeader = req.headers.authorization || '' + const [scheme, token] = authHeader.split(' ') + + if (scheme !== 'Bearer' || !token) { + return next(new AppError('请求头缺少 Authorization', 401)) + } + + try { + const decoded = jwt.verify(token, env.jwtSecret) + if (decoded.users_wx_openid && decoded.users_wx_openid !== usersWxOpenid) { + return next(new AppError('请求头中的 users_wx_openid 与令牌不匹配', 401)) + } + req.user = decoded + next() + } catch { + next(new AppError('认证令牌无效或已过期', 401)) + } + }) +} diff --git a/back-end/src/routes/apiRoutes.js b/back-end/src/routes/apiRoutes.js new file mode 100644 index 0000000..cf9e8b6 --- /dev/null +++ b/back-end/src/routes/apiRoutes.js @@ -0,0 +1,24 @@ +const express = require('express') +const { success } = require('../utils/response') +const wechatRoutes = require('./wechatRoutes') + +const router = express.Router() + +router.post('/test-helloworld', (req, res) => { + return success(res, '请求成功', { + message: 'Hello, World!', + timestamp: new Date().toISOString(), + status: 'success', + }) +}) + +router.post('/health', (req, res) => { + return success(res, '服务运行正常', { + status: 'healthy', + timestamp: new Date().toISOString(), + }) +}) + +router.use('/wechat', wechatRoutes) + +module.exports = router diff --git a/back-end/src/routes/wechatRoutes.js b/back-end/src/routes/wechatRoutes.js new file mode 100644 index 0000000..7b00d38 --- /dev/null +++ b/back-end/src/routes/wechatRoutes.js @@ -0,0 +1,14 @@ +const express = require('express') +const duplicateGuard = require('../middlewares/duplicateGuard') +const requireJsonBody = require('../middlewares/requireJsonBody') +const requireWechatOpenid = require('../middlewares/requireWechatOpenid') +const wechatHeadersAuth = require('../middlewares/wechatHeadersAuth') +const controller = require('../controllers/wechatController') + +const router = express.Router() + +router.post('/login', requireJsonBody, duplicateGuard, controller.login) +router.post('/profile', requireJsonBody, wechatHeadersAuth, duplicateGuard, controller.updateProfile) +router.post('/refresh-token', requireWechatOpenid, controller.refreshToken) + +module.exports = router diff --git a/back-end/src/services/jwtService.js b/back-end/src/services/jwtService.js new file mode 100644 index 0000000..bcdd926 --- /dev/null +++ b/back-end/src/services/jwtService.js @@ -0,0 +1,12 @@ +const jwt = require('jsonwebtoken') +const env = require('../config/env') + +function signAccessToken(payload) { + return jwt.sign(payload, env.jwtSecret, { + expiresIn: env.jwtExpiresIn, + }) +} + +module.exports = { + signAccessToken, +} diff --git a/back-end/src/services/pocketbaseService.js b/back-end/src/services/pocketbaseService.js new file mode 100644 index 0000000..7837a41 --- /dev/null +++ b/back-end/src/services/pocketbaseService.js @@ -0,0 +1,90 @@ +const axios = require('axios') +const env = require('../config/env') +const AppError = require('../utils/appError') + +function getHeaders() { + const headers = { + 'Content-Type': 'application/json', + } + + if (env.pocketbaseAuthToken) { + headers.Authorization = env.pocketbaseAuthToken.startsWith('Bearer ') + ? env.pocketbaseAuthToken + : `Bearer ${env.pocketbaseAuthToken}` + } + + return headers +} + +function buildUrl(path) { + const base = env.pocketbaseUrl.replace(/\/+$/, '') + const normalizedPath = path.replace(/^\/+/, '') + return `${base}/${normalizedPath}` +} + +async function request(config) { + try { + const response = await axios({ + timeout: 10000, + headers: getHeaders(), + ...config, + }) + return response.data + } catch (error) { + const detail = error.response?.data || error.message + throw new AppError('PocketBase 数据操作失败', 500, { + detail, + }) + } +} + +async function listUsersByFilter(filter) { + const data = await request({ + method: 'get', + url: buildUrl('collections/tbl_users/records'), + params: { + filter, + perPage: 1, + }, + }) + + return data.items || [] +} + +async function createUser(payload) { + return request({ + method: 'post', + url: buildUrl('collections/tbl_users/records'), + data: payload, + }) +} + +async function updateUser(recordId, payload) { + return request({ + method: 'patch', + url: buildUrl(`collections/tbl_users/records/${recordId}`), + data: payload, + }) +} + +async function getCompanyByCompanyId(companyId) { + if (!companyId) return null + + const data = await request({ + method: 'get', + url: buildUrl('collections/tbl_company/records'), + params: { + filter: `company_id = "${companyId}"`, + perPage: 1, + }, + }) + + return data.items?.[0] || null +} + +module.exports = { + listUsersByFilter, + createUser, + updateUser, + getCompanyByCompanyId, +} diff --git a/back-end/src/services/userService.js b/back-end/src/services/userService.js new file mode 100644 index 0000000..cda2d23 --- /dev/null +++ b/back-end/src/services/userService.js @@ -0,0 +1,208 @@ +const crypto = require('crypto') +const AppError = require('../utils/appError') +const logger = require('../utils/logger') +const wechatService = require('./wechatService') +const jwtService = require('./jwtService') +const pocketbaseService = require('./pocketbaseService') + +const userMutationLocks = new Map() +const GUEST_USER_TYPE = '游客' +const REGISTERED_USER_TYPE = '注册用户' + +function buildUserId() { + const now = new Date() + const date = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}` + const suffix = crypto.randomInt(1000, 9999) + return `U${date}${suffix}` +} + +async function enrichUser(user) { + const company = await pocketbaseService.getCompanyByCompanyId(user.company_id) + return { + pb_id: user.id, + users_id: user.users_id, + users_type: user.users_type || GUEST_USER_TYPE, + users_name: user.users_name, + users_phone: user.users_phone, + users_phone_masked: maskPhone(user.users_phone), + users_picture: user.users_picture, + users_wx_openid: user.users_wx_openid, + company_id: user.company_id || '', + company, + created: user.created, + updated: user.updated, + raw: user, + } +} + +async function findUserByOpenid(usersWxOpenid) { + const users = await pocketbaseService.listUsersByFilter(`users_wx_openid = "${usersWxOpenid}"`) + return users[0] || null +} + +function maskPhone(phone = '') { + if (!phone || phone.length < 7) return '' + return `${phone.slice(0, 3)}****${phone.slice(-4)}` +} + +function isInfoComplete(user = {}) { + return Boolean(user.users_name && user.users_phone && user.users_picture) +} + +function isAllProfileFieldsEmpty(user = {}) { + return !user.users_name && !user.users_phone && !user.users_picture +} + +async function withUserLock(lockKey, handler) { + const previous = userMutationLocks.get(lockKey) || Promise.resolve() + let release + const current = new Promise((resolve) => { + release = resolve + }) + + userMutationLocks.set(lockKey, previous.then(() => current)) + + await previous + + try { + return await handler() + } finally { + release() + if (userMutationLocks.get(lockKey) === current) { + userMutationLocks.delete(lockKey) + } + } +} + +async function authenticateWechatUser(payload) { + const openid = await wechatService.getWxOpenId(payload.users_wx_code) + return withUserLock(`auth:${openid}`, async () => { + const existing = await findUserByOpenid(openid) + + if (existing) { + logger.warn('微信注册命中已存在账号', { + users_wx_openid: openid, + users_type: existing.users_type || GUEST_USER_TYPE, + }) + + const user = await enrichUser(existing) + const token = jwtService.signAccessToken({ + users_id: user.users_id, + users_wx_openid: user.users_wx_openid, + }) + + return { + status: 'login_success', + is_info_complete: isInfoComplete(existing), + token, + user, + } + } + + const created = await pocketbaseService.createUser({ + users_id: buildUserId(), + users_wx_openid: openid, + users_type: GUEST_USER_TYPE, + }) + + const user = await enrichUser(created) + const token = jwtService.signAccessToken({ + users_id: user.users_id, + users_wx_openid: user.users_wx_openid, + }) + + logger.info('微信用户注册成功', { + users_id: user.users_id, + users_phone: user.users_phone, + users_type: user.users_type, + }) + + return { + status: 'register_success', + is_info_complete: false, + token, + user, + } + }) +} + +async function updateWechatUserProfile(usersWxOpenid, payload) { + return withUserLock(`profile:${usersWxOpenid}`, async () => { + const currentUser = await findUserByOpenid(usersWxOpenid) + + if (!currentUser) { + throw new AppError('未找到待编辑的用户', 404) + } + + const usersPhone = await wechatService.getWxPhoneNumber(payload.users_phone_code) + + if (usersPhone && usersPhone !== currentUser.users_phone) { + const samePhoneUsers = await pocketbaseService.listUsersByFilter(`users_phone = "${usersPhone}"`) + const phoneUsedByOther = samePhoneUsers.some((item) => item.id !== currentUser.id) + if (phoneUsedByOther) { + throw new AppError('手机号已被注册', 400) + } + } + + const shouldPromoteUserType = + isAllProfileFieldsEmpty(currentUser) + && payload.users_name + && usersPhone + && payload.users_picture + && (currentUser.users_type === GUEST_USER_TYPE || !currentUser.users_type) + + const updatePayload = { + users_name: payload.users_name, + users_phone: usersPhone, + users_picture: payload.users_picture, + } + + if (shouldPromoteUserType) { + updatePayload.users_type = REGISTERED_USER_TYPE + } + + const updated = await pocketbaseService.updateUser(currentUser.id, updatePayload) + const user = await enrichUser(updated) + + logger.info('微信用户资料更新成功', { + users_id: user.users_id, + users_phone: user.users_phone, + users_type_before: currentUser.users_type || GUEST_USER_TYPE, + users_type_after: user.users_type, + users_type_promoted: shouldPromoteUserType, + }) + + return { + status: 'update_success', + user, + } + }) +} + +async function refreshWechatToken(usersWxOpenid) { + const userRecord = await findUserByOpenid(usersWxOpenid) + + if (!userRecord) { + throw new AppError('未注册用户', 404) + } + + const token = jwtService.signAccessToken({ + users_id: userRecord.users_id, + users_wx_openid: userRecord.users_wx_openid, + }) + + logger.info('微信用户刷新令牌成功', { + users_id: userRecord.users_id, + users_wx_openid: userRecord.users_wx_openid, + }) + + return { + token, + } +} + +module.exports = { + authenticateWechatUser, + updateWechatUserProfile, + refreshWechatToken, +} diff --git a/back-end/src/services/wechatService.js b/back-end/src/services/wechatService.js new file mode 100644 index 0000000..85ba310 --- /dev/null +++ b/back-end/src/services/wechatService.js @@ -0,0 +1,117 @@ +const axios = require('axios') +const env = require('../config/env') +const AppError = require('../utils/appError') + +let accessTokenCache = { + token: '', + expiresAt: 0, +} + +async function getWxOpenId(code) { + if (!env.wechatAppId || !env.wechatSecret) { + throw new AppError('微信小程序配置缺失', 500) + } + + try { + const url = 'https://api.weixin.qq.com/sns/jscode2session' + const response = await axios.get(url, { + params: { + appid: env.wechatAppId, + secret: env.wechatSecret, + js_code: code, + grant_type: 'authorization_code', + }, + timeout: 10000, + }) + + const data = response.data || {} + + if (data.openid) { + return data.openid + } + + if (data.errcode || data.errmsg) { + throw new AppError(`获取OpenID失败: ${data.errcode || ''} ${data.errmsg || ''}`.trim(), 502, { + wechat: data, + }) + } + + throw new AppError('获取OpenID失败: 响应中未包含openid', 502) + } catch (error) { + if (error instanceof AppError) throw error + throw new AppError(`获取微信OpenID时发生错误: ${error.message}`, 502) + } +} + +async function getWechatAccessToken() { + if (accessTokenCache.token && Date.now() < accessTokenCache.expiresAt) { + return accessTokenCache.token + } + + try { + const response = await axios.get('https://api.weixin.qq.com/cgi-bin/token', { + params: { + grant_type: 'client_credential', + appid: env.wechatAppId, + secret: env.wechatSecret, + }, + timeout: 10000, + }) + + const data = response.data || {} + + if (!data.access_token) { + throw new AppError(`获取微信 access_token 失败: ${data.errcode || ''} ${data.errmsg || ''}`.trim(), 502, { + wechat: data, + }) + } + + accessTokenCache = { + token: data.access_token, + expiresAt: Date.now() + Math.max((data.expires_in || 7200) - 300, 60) * 1000, + } + + return accessTokenCache.token + } catch (error) { + if (error instanceof AppError) throw error + throw new AppError(`获取微信 access_token 时发生错误: ${error.message}`, 502) + } +} + +async function getWxPhoneNumber(phoneCode) { + const accessToken = await getWechatAccessToken() + + try { + const response = await axios.post( + `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${accessToken}`, + { + code: phoneCode, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + timeout: 10000, + } + ) + + const data = response.data || {} + const phone = data.phone_info?.purePhoneNumber || data.phone_info?.phoneNumber + + if (phone) { + return phone + } + + throw new AppError(`获取微信手机号失败: ${data.errcode || ''} ${data.errmsg || ''}`.trim(), 502, { + wechat: data, + }) + } catch (error) { + if (error instanceof AppError) throw error + throw new AppError(`获取微信手机号时发生错误: ${error.message}`, 502) + } +} + +module.exports = { + getWxOpenId, + getWxPhoneNumber, +} diff --git a/back-end/src/utils/appError.js b/back-end/src/utils/appError.js new file mode 100644 index 0000000..5cfa9d5 --- /dev/null +++ b/back-end/src/utils/appError.js @@ -0,0 +1,10 @@ +class AppError extends Error { + constructor(message, statusCode = 500, details = {}) { + super(message) + this.name = 'AppError' + this.statusCode = statusCode + this.details = details + } +} + +module.exports = AppError diff --git a/back-end/src/utils/asyncHandler.js b/back-end/src/utils/asyncHandler.js new file mode 100644 index 0000000..10bd82e --- /dev/null +++ b/back-end/src/utils/asyncHandler.js @@ -0,0 +1,5 @@ +module.exports = function asyncHandler(fn) { + return function wrappedHandler(req, res, next) { + Promise.resolve(fn(req, res, next)).catch(next) + } +} diff --git a/back-end/src/utils/logger.js b/back-end/src/utils/logger.js new file mode 100644 index 0000000..9483dce --- /dev/null +++ b/back-end/src/utils/logger.js @@ -0,0 +1,27 @@ +function createLog(method, message, meta = {}) { + const payload = { + time: new Date().toISOString(), + method, + message, + ...meta, + } + return JSON.stringify(payload) +} + +function info(message, meta) { + console.log(createLog('INFO', message, meta)) +} + +function warn(message, meta) { + console.warn(createLog('WARN', message, meta)) +} + +function error(message, meta) { + console.error(createLog('ERROR', message, meta)) +} + +module.exports = { + info, + warn, + error, +} diff --git a/back-end/src/utils/response.js b/back-end/src/utils/response.js new file mode 100644 index 0000000..bb1d6cd --- /dev/null +++ b/back-end/src/utils/response.js @@ -0,0 +1,20 @@ +function success(res, msg = '操作成功', data = {}, code = 200) { + return res.status(code).json({ + code, + msg, + data, + }) +} + +function fail(res, code = 500, msg = '服务器内部错误', data = {}) { + return res.status(code).json({ + code, + msg, + data, + }) +} + +module.exports = { + success, + fail, +} diff --git a/back-end/src/utils/sanitize.js b/back-end/src/utils/sanitize.js new file mode 100644 index 0000000..8ed5012 --- /dev/null +++ b/back-end/src/utils/sanitize.js @@ -0,0 +1,17 @@ +function sanitizeString(value) { + if (typeof value !== 'string') return '' + return value.replace(/[<>\\]/g, '').trim() +} + +function sanitizePayload(payload = {}) { + return Object.keys(payload).reduce((acc, key) => { + const value = payload[key] + acc[key] = typeof value === 'string' ? sanitizeString(value) : value + return acc + }, {}) +} + +module.exports = { + sanitizeString, + sanitizePayload, +} diff --git a/back-end/tests/integration/wechat.test.js b/back-end/tests/integration/wechat.test.js new file mode 100644 index 0000000..7b803f0 --- /dev/null +++ b/back-end/tests/integration/wechat.test.js @@ -0,0 +1,188 @@ +const test = require('node:test') +const assert = require('node:assert/strict') +const request = require('supertest') + +const env = require('../../src/config/env') +const { createApp } = require('../../src/index') +const userService = require('../../src/services/userService') + +test('POST /api/health 返回统一结构', async () => { + const app = createApp() + const response = await request(app).post('/api/health').send({}) + + assert.equal(response.status, 200) + assert.equal(response.body.code, 200) + assert.equal(response.body.msg, '服务运行正常') + assert.equal(response.body.data.status, 'healthy') +}) + +test('POST /api/test-helloworld 返回统一结构', async () => { + const app = createApp() + const response = await request(app).post('/api/test-helloworld').send({}) + + assert.equal(response.status, 200) + assert.equal(response.body.code, 200) + assert.equal(response.body.msg, '请求成功') + assert.equal(response.body.data.message, 'Hello, World!') +}) + +test('未匹配路由返回统一 404', async () => { + const app = createApp() + const response = await request(app).get('/not-found-route') + + assert.equal(response.status, 404) + assert.equal(response.body.code, 404) + assert.equal(response.body.msg, 'Route not found') + assert.equal(response.body.data.path, '/not-found-route') +}) + +test('POST /api/wechat/login 未注册用户返回 404', async (t) => { + const origin = userService.authenticateWechatUser + userService.authenticateWechatUser = async () => { + const error = new Error('未注册用户') + error.statusCode = 404 + throw error + } + + t.after(() => { + userService.authenticateWechatUser = origin + }) + + const app = createApp() + const response = await request(app) + .post('/api/wechat/login') + .send({ users_wx_code: 'mock-code-success' }) + + assert.equal(response.status, 404) + assert.equal(response.body.code, 404) + assert.equal(response.body.msg, '未注册用户') +}) + +test('POST /api/wechat/login 非 JSON 请求体返回 415', async () => { + const app = createApp() + const response = await request(app) + .post('/api/wechat/login') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('users_wx_code=mock-code') + + assert.equal(response.status, 415) + assert.equal(response.body.code, 415) + assert.equal(response.body.msg, '请求体必须为 application/json') +}) + +test('POST /api/wechat/login 成功时返回 is_info_complete', async (t) => { + const origin = userService.authenticateWechatUser + userService.authenticateWechatUser = async () => ({ + is_info_complete: true, + status: 'login_success', + token: 'mock-token', + user: { + users_id: 'U1003', + users_type: '注册用户', + users_name: '李四', + }, + }) + + t.after(() => { + userService.authenticateWechatUser = origin + }) + + const app = createApp() + const response = await request(app) + .post('/api/wechat/login') + .send({ users_wx_code: 'mock-code' }) + + assert.equal(response.status, 200) + assert.equal(response.body.code, 200) + assert.equal(response.body.msg, '登录成功') + assert.equal(response.body.data.is_info_complete, true) + assert.equal(response.body.data.user.users_type, '注册用户') +}) + +test('POST /api/wechat/profile 更新成功后返回脱敏手机号', async (t) => { + const origin = userService.updateWechatUserProfile + userService.updateWechatUserProfile = async () => ({ + status: 'update_success', + user: { + users_id: 'U1002', + users_type: '注册用户', + users_name: '张三', + users_phone: '13800138000', + users_phone_masked: '138****8000', + users_picture: 'https://example.com/a.png', + }, + }) + + t.after(() => { + userService.updateWechatUserProfile = origin + }) + + const token = require('jsonwebtoken').sign({ users_id: 'U1002', users_wx_openid: 'openid-1' }, env.jwtSecret, { + expiresIn: env.jwtExpiresIn, + }) + + const app = createApp() + const response = await request(app) + .post('/api/wechat/profile') + .set('users_wx_openid', 'openid-1') + .set('Authorization', `Bearer ${token}`) + .send({ + users_name: '张三', + users_phone_code: 'phone-code', + users_picture: 'https://example.com/a.png', + }) + + assert.equal(response.status, 200) + assert.equal(response.body.code, 200) + assert.equal(response.body.msg, '信息更新成功') + assert.equal(response.body.data.user.users_phone_masked, '138****8000') + assert.equal(response.body.data.user.users_type, '注册用户') +}) + +test('POST /api/wechat/profile 缺少 token 返回 401', async () => { + const app = createApp() + const response = await request(app) + .post('/api/wechat/profile') + .set('users_wx_openid', 'openid-1') + .send({ + users_name: '张三', + users_phone_code: 'phone-code', + users_picture: 'https://example.com/a.png', + }) + + assert.equal(response.status, 401) + assert.equal(response.body.code, 401) +}) + +test('POST /api/wechat/refresh-token 返回 token', async (t) => { + const origin = userService.refreshWechatToken + userService.refreshWechatToken = async () => ({ + token: 'new-token', + }) + + t.after(() => { + userService.refreshWechatToken = origin + }) + + const app = createApp() + const response = await request(app) + .post('/api/wechat/refresh-token') + .set('users_wx_openid', 'openid-1') + .send({}) + + assert.equal(response.status, 200) + assert.equal(response.body.code, 200) + assert.equal(response.body.msg, '刷新成功') + assert.equal(response.body.data.token, 'new-token') +}) + +test('POST /api/wechat/refresh-token 缺少 users_wx_openid 返回 401', async () => { + const app = createApp() + const response = await request(app) + .post('/api/wechat/refresh-token') + .send({}) + + assert.equal(response.status, 401) + assert.equal(response.body.code, 401) + assert.equal(response.body.msg, '请求头缺少 users_wx_openid') +}) diff --git a/back-end/tests/userService.test.js b/back-end/tests/userService.test.js new file mode 100644 index 0000000..eb5dbe1 --- /dev/null +++ b/back-end/tests/userService.test.js @@ -0,0 +1,181 @@ +const test = require('node:test') +const assert = require('node:assert/strict') + +const wechatService = require('../src/services/wechatService') +const pocketbaseService = require('../src/services/pocketbaseService') +const jwtService = require('../src/services/jwtService') +const userService = require('../src/services/userService') + +test('authenticateWechatUser 首次注册默认写入游客类型', async (t) => { + const originGetWxOpenId = wechatService.getWxOpenId + const originListUsersByFilter = pocketbaseService.listUsersByFilter + const originCreateUser = pocketbaseService.createUser + const originSignAccessToken = jwtService.signAccessToken + const originGetCompanyByCompanyId = pocketbaseService.getCompanyByCompanyId + + wechatService.getWxOpenId = async () => 'openid-register' + pocketbaseService.listUsersByFilter = async () => [] + pocketbaseService.createUser = async (payload) => ({ + id: 'pb-1', + created: '2026-03-19T00:00:00Z', + updated: '2026-03-19T00:00:00Z', + ...payload, + }) + pocketbaseService.getCompanyByCompanyId = async () => null + jwtService.signAccessToken = () => 'token-1' + + t.after(() => { + wechatService.getWxOpenId = originGetWxOpenId + pocketbaseService.listUsersByFilter = originListUsersByFilter + pocketbaseService.createUser = originCreateUser + pocketbaseService.getCompanyByCompanyId = originGetCompanyByCompanyId + jwtService.signAccessToken = originSignAccessToken + }) + + const result = await userService.authenticateWechatUser({ + users_wx_code: 'code-1', + }) + + assert.equal(result.status, 'register_success') + assert.equal(result.is_info_complete, false) + assert.equal(result.user.users_type, '游客') +}) + +test('updateWechatUserProfile 首次完整补充资料时升级为注册用户', async (t) => { + const originGetWxOpenId = wechatService.getWxOpenId + const originGetWxPhoneNumber = wechatService.getWxPhoneNumber + const originListUsersByFilter = pocketbaseService.listUsersByFilter + const originUpdateUser = pocketbaseService.updateUser + const originGetCompanyByCompanyId = pocketbaseService.getCompanyByCompanyId + + wechatService.getWxOpenId = async () => 'openid-profile' + wechatService.getWxPhoneNumber = async () => '13800138000' + pocketbaseService.listUsersByFilter = async (filter) => { + if (filter.includes('users_wx_openid')) { + return [ + { + id: 'pb-2', + users_id: 'U2001', + users_type: '游客', + users_name: '', + users_phone: '', + users_picture: '', + users_wx_openid: 'openid-profile', + created: '2026-03-19T00:00:00Z', + updated: '2026-03-19T00:00:00Z', + }, + ] + } + return [] + } + pocketbaseService.updateUser = async (_id, payload) => ({ + id: 'pb-2', + users_id: 'U2001', + users_wx_openid: 'openid-profile', + users_type: payload.users_type || '游客', + users_name: payload.users_name, + users_phone: payload.users_phone, + users_picture: payload.users_picture, + created: '2026-03-19T00:00:00Z', + updated: '2026-03-19T00:10:00Z', + }) + pocketbaseService.getCompanyByCompanyId = async () => null + + t.after(() => { + wechatService.getWxOpenId = originGetWxOpenId + wechatService.getWxPhoneNumber = originGetWxPhoneNumber + pocketbaseService.listUsersByFilter = originListUsersByFilter + pocketbaseService.updateUser = originUpdateUser + pocketbaseService.getCompanyByCompanyId = originGetCompanyByCompanyId + }) + + const result = await userService.updateWechatUserProfile('openid-profile', { + users_name: '张三', + users_phone_code: 'phone-code-1', + users_picture: 'https://example.com/a.png', + }) + + assert.equal(result.status, 'update_success') + assert.equal(result.user.users_type, '注册用户') + assert.equal(result.user.users_phone_masked, '138****8000') +}) + +test('updateWechatUserProfile 非首次补充资料时不覆盖已确定类型', async (t) => { + const originGetWxOpenId = wechatService.getWxOpenId + const originGetWxPhoneNumber = wechatService.getWxPhoneNumber + const originListUsersByFilter = pocketbaseService.listUsersByFilter + const originUpdateUser = pocketbaseService.updateUser + const originGetCompanyByCompanyId = pocketbaseService.getCompanyByCompanyId + + wechatService.getWxOpenId = async () => 'openid-registered' + wechatService.getWxPhoneNumber = async () => '13900139000' + pocketbaseService.listUsersByFilter = async (filter) => { + if (filter.includes('users_wx_openid')) { + return [ + { + id: 'pb-3', + users_id: 'U2002', + users_type: '注册用户', + users_name: '老用户', + users_phone: '13800138000', + users_picture: 'https://example.com/old.png', + users_wx_openid: 'openid-registered', + created: '2026-03-19T00:00:00Z', + updated: '2026-03-19T00:00:00Z', + }, + ] + } + return [] + } + pocketbaseService.updateUser = async (_id, payload) => ({ + id: 'pb-3', + users_id: 'U2002', + users_wx_openid: 'openid-registered', + users_type: '注册用户', + users_name: payload.users_name, + users_phone: payload.users_phone, + users_picture: payload.users_picture, + created: '2026-03-19T00:00:00Z', + updated: '2026-03-19T00:10:00Z', + }) + pocketbaseService.getCompanyByCompanyId = async () => null + + t.after(() => { + wechatService.getWxOpenId = originGetWxOpenId + wechatService.getWxPhoneNumber = originGetWxPhoneNumber + pocketbaseService.listUsersByFilter = originListUsersByFilter + pocketbaseService.updateUser = originUpdateUser + pocketbaseService.getCompanyByCompanyId = originGetCompanyByCompanyId + }) + + const result = await userService.updateWechatUserProfile('openid-registered', { + users_name: '新名字', + users_phone_code: 'phone-code-2', + users_picture: 'https://example.com/new.png', + }) + + assert.equal(result.user.users_type, '注册用户') +}) + +test('refreshWechatToken 返回新的 token', async (t) => { + const originListUsersByFilter = pocketbaseService.listUsersByFilter + const originSignAccessToken = jwtService.signAccessToken + + pocketbaseService.listUsersByFilter = async () => [ + { + id: 'pb-4', + users_id: 'U3001', + users_wx_openid: 'openid-refresh', + }, + ] + jwtService.signAccessToken = () => 'refresh-token' + + t.after(() => { + pocketbaseService.listUsersByFilter = originListUsersByFilter + jwtService.signAccessToken = originSignAccessToken + }) + + const result = await userService.refreshWechatToken('openid-refresh') + + assert.equal(result.token, 'refresh-token') +}) diff --git a/docs/ARCHIVE.md b/docs/ARCHIVE.md new file mode 100644 index 0000000..8d19464 --- /dev/null +++ b/docs/ARCHIVE.md @@ -0,0 +1,193 @@ +# OpenSpec 开发归档 + +## 归档日期 + +- 2026-03-20 + +## 归档范围 + +本次归档覆盖微信小程序后端交互相关接口的设计、实现、规范同步与部署调整,涉及: + +- 接口路径统一收敛到 `/api` +- 微信登录/注册合一 +- 微信资料完善接口重构 +- 微信手机号服务端换取 +- `users_type` 自动维护 +- JWT 认证与刷新 token +- PocketBase Token 模式访问 +- OpenAPI 与项目文档同步 +- dist 构建与部署产物 + +--- + +## 一、接口演进结果 + +### 系统接口 + +- `POST /api/test-helloworld` +- `POST /api/health` + +### 微信小程序接口 + +- `POST /api/wechat/login` + - 登录/注册合一 + - 接收 `users_wx_code` + - 自动换取 `users_wx_openid` + - 若无账号则自动创建游客账号 + - 返回 `status`、`is_info_complete`、`token`、完整用户信息 + +- `POST /api/wechat/profile` + - 从 headers 读取 `users_wx_openid` + - 需要 `Authorization` + - body 接收: + - `users_name` + - `users_phone_code` + - `users_picture` + - 服务端调用微信接口换取真实手机号后写入 `users_phone` + +- `POST /api/wechat/refresh-token` + - 仅依赖 `users_wx_openid` + - 不要求旧 `Authorization` + - 返回新的 JWT token + +--- + +## 二、关键业务规则 + +### 1. 用户类型 `users_type` + +- 新账号初始化:`游客` +- 当且仅当用户首次从: + - `users_name` 为空 + - `users_phone` 为空 + - `users_picture` 为空 + + 变为: + - 三项全部完整 + + 时,自动升级为:`注册用户` + +- 后续资料修改不再覆盖已确定类型 + +### 2. 用户资料完整度 `is_info_complete` + +以下三项同时存在时为 `true`: + +- `users_name` +- `users_phone` +- `users_picture` + +否则为 `false` + +### 3. 微信手机号获取 + +服务端使用微信官方接口: + +- `getuserphonenumber` + +通过 `users_phone_code` 换取真实手机号,再写入数据库字段 `users_phone`。 + +--- + +## 三、鉴权规则 + +### 标准请求头 + +- `Authorization: Bearer ` +- `users_wx_openid: ` + +### 当前规则 + +- `/api/wechat/login`:不需要 token +- `/api/wechat/profile`:需要 `users_wx_openid + Authorization` +- `/api/wechat/refresh-token`:**只需要** `users_wx_openid` + +--- + +## 四、请求格式规则 + +所有微信写接口统一要求: + +- `Content-Type: application/json` + +不符合时返回: + +- `415 请求体必须为 application/json` + +--- + +## 五、PocketBase 访问策略 + +当前统一使用: + +- `POCKETBASE_API_URL` +- `POCKETBASE_AUTH_TOKEN` + +已移除: + +- `POCKETBASE_USER_NAME` +- `POCKETBASE_PASSWORD` + +--- + +## 六、部署与产物 + +后端已支持 dist 构建: + +- `npm run build` +- 产物目录:`back-end/dist/` + +当前发布目录包含: + +- `dist/src/` +- `dist/spec/` +- `dist/package.json` +- `dist/package-lock.json` +- `dist/.env` + +生产启动方式: + +- `npm start` +- 实际运行:`node dist/src/index.js` + +--- + +## 七、文档同步结果 + +已同步更新: + +- `back-end/spec/openapi.yaml` +- `docs/api.md` +- `docs/deployment.md` +- `README.md` + +其中 `openapi.yaml` 已与当前真实接口行为对齐。 + +--- + +## 八、质量验证结果 + +本次归档前已验证通过: + +- `npm run lint` +- `npm run test` +- `npm run build` + +测试覆盖包括: + +- 系统接口 +- 统一 404 +- 登录/注册合一 +- 非 JSON 拒绝 +- 资料更新 +- token 刷新 +- `users_type` 升级逻辑 +- `is_info_complete` 返回逻辑 + +--- + +## 九、当前已知边界 + +1. `refresh-token` 当前仅依赖 `users_wx_openid`,未校验旧 token +2. 微信手机号能力依赖微信官方 `access_token` 与 `users_phone_code` +3. 当前后端为 JavaScript + Express 架构,未引入 TypeScript 编译链 diff --git a/docs/api.md b/docs/api.md index ea14e77..83137b1 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,332 +1,285 @@ -# API接口文档 +# API 接口文档 -## 接口概述 +## 文档说明 -本文档定义了微信小程序前端调用的API接口,包括认证、用户、数据等相关接口。 +本文档描述当前项目中**已经真实实现**并可直接调用的后端接口。 +当前接口统一特征如下: -## 基础信息 +- 基础路径(生产):`https://bai-api.blv-oa.com/api` +- 基础路径(本地):`http://localhost:3000/api` +- 响应格式:JSON +- 业务响应结构统一为:`code`、`msg`、`data` +- 当前公开接口统一使用 **POST** 方法 +- 微信写接口统一要求 `Content-Type: application/json` -- API基础路径: `http://localhost:3000/api` -- 响应格式: JSON -- 认证方式: JWT +--- -## 认证接口 +## 一、统一响应格式 -### 1. 用户登录 - -**接口地址**: `/auth/login` -**请求方式**: POST -**请求参数**: - -| 参数名 | 类型 | 必选 | 描述 | -|-------|------|------|------| -| username | string | 是 | 用户名 | -| password | string | 是 | 密码 | - -**响应示例**: +### 成功响应 ```json { "code": 200, - "message": "登录成功", + "msg": "操作成功", + "data": {} +} +``` + +### 失败响应 + +```json +{ + "code": 400, + "msg": "错误信息", + "data": {} +} +``` + +--- + +## 二、系统接口 + +### 1. HelloWorld 测试接口 + +- **接口地址**:`/test-helloworld` +- **请求方式**:`POST` +- **请求头**:无特殊要求 +- **请求体**:可为空 `{}` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "请求成功", "data": { - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "message": "Hello, World!", + "timestamp": "2026-03-20T00:00:00.000Z", + "status": "success" + } +} +``` + +### 2. 健康检查接口 + +- **接口地址**:`/health` +- **请求方式**:`POST` +- **请求头**:无特殊要求 +- **请求体**:可为空 `{}` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "服务运行正常", + "data": { + "status": "healthy", + "timestamp": "2026-03-20T00:00:00.000Z" + } +} +``` + +--- + +## 三、微信小程序接口 + +## 1. 登录/注册合一 + +- **接口地址**:`/wechat/login` +- **请求方式**:`POST` +- **请求头**: + - `Content-Type: application/json` + +### 请求参数 + +```json +{ + "users_wx_code": "0a1b2c3d4e5f6g" +} +``` + +### 参数说明 + +| 参数名 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `users_wx_code` | string | 是 | 微信小程序登录临时凭证 code,用于换取 `users_wx_openid` | + +### 处理逻辑 + +- 使用 `users_wx_code` 向微信服务端换取 `users_wx_openid` +- 如果数据库中不存在该用户,则自动创建新账号: + - 初始化 `users_type = 游客` +- 如果数据库中已存在该用户,则直接登录 +- 返回: + - `status` + - `is_info_complete` + - `token` + - 完整用户信息 + +### 响应示例 + +```json +{ + "code": 200, + "msg": "登录成功", + "data": { + "status": "login_success", + "is_info_complete": true, + "token": "jwt-token", "user": { - "id": "123", - "username": "admin", - "nickname": "管理员" + "users_id": "U202603190001", + "users_type": "注册用户", + "users_name": "张三", + "users_phone": "13800138000", + "users_phone_masked": "138****8000", + "users_picture": "https://example.com/avatar.png", + "users_wx_openid": "oAbCdEfGh123456789", + "company_id": "C10001", + "company": null, + "pb_id": "abc123xyz", + "created": "2026-03-20T00:00:00.000Z", + "updated": "2026-03-20T00:00:00.000Z" } } } ``` -### 2. 用户注册 +--- -**接口地址**: `/auth/register` -**请求方式**: POST -**请求参数**: +## 2. 完善/修改用户资料 -| 参数名 | 类型 | 必选 | 描述 | -|-------|------|------|------| -| username | string | 是 | 用户名 | -| password | string | 是 | 密码 | -| nickname | string | 是 | 昵称 | +- **接口地址**:`/wechat/profile` +- **请求方式**:`POST` +- **请求头**: + - `Content-Type: application/json` + - `users_wx_openid: 微信用户唯一标识` + - `Authorization: Bearer ` -**响应示例**: +### 请求参数 + +```json +{ + "users_name": "张三", + "users_phone_code": "2b7d9f2e3c4a5b6d7e8f", + "users_picture": "https://example.com/avatar.png" +} +``` + +### 参数说明 + +| 参数名 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `users_name` | string | 是 | 用户姓名 | +| `users_phone_code` | string | 是 | 微信手机号获取凭证 code,后端将据此换取真实手机号 | +| `users_picture` | string | 是 | 用户头像 URL | + +### 处理逻辑 + +- 从请求头 `users_wx_openid` 读取当前用户身份 +- 校验 `Authorization` +- 不再从 body 读取 `users_wx_code` +- 使用 `users_phone_code` 调微信官方接口换取真实手机号 +- 将真实手机号写入数据库字段 `users_phone` +- 若用户首次从“三项资料均为空”变为“三项资料均完整”,则将 `users_type` 从 `游客` 升级为 `注册用户` +- 返回更新后的完整用户信息 + +### 响应示例 ```json { "code": 200, - "message": "注册成功", + "msg": "信息更新成功", "data": { - "id": "123", - "username": "user1", - "nickname": "新用户" + "status": "update_success", + "user": { + "users_id": "U202603190001", + "users_type": "注册用户", + "users_name": "张三", + "users_phone": "13800138000", + "users_phone_masked": "138****8000", + "users_picture": "https://example.com/avatar.png", + "users_wx_openid": "oAbCdEfGh123456789", + "company_id": "", + "company": null, + "pb_id": "abc123xyz", + "created": "2026-03-20T00:00:00.000Z", + "updated": "2026-03-20T00:10:00.000Z" + } } } ``` -## 用户接口 +--- -### 1. 获取用户信息 +## 3. 刷新 token -**接口地址**: `/user/info` -**请求方式**: GET -**请求头**: -- Authorization: Bearer {token} +- **接口地址**:`/wechat/refresh-token` +- **请求方式**:`POST` +- **请求头**: + - `users_wx_openid: 微信用户唯一标识` -**响应示例**: +> 说明:本接口**不要求旧 `Authorization`**。 + +### 请求体 + +- 无 body 参数,可传 `{}` 或空体 + +### 处理逻辑 + +- 仅通过请求头中的 `users_wx_openid` 定位用户 +- 若用户存在,则签发新的 JWT token +- 若用户不存在,则返回 `404` + +### 响应示例 ```json { "code": 200, - "message": "获取成功", + "msg": "刷新成功", "data": { - "id": "123", - "username": "admin", - "nickname": "管理员", - "avatar": "https://example.com/avatar.jpg", - "createdAt": "2026-03-18T00:00:00Z" + "token": "new-jwt-token" } } ``` -### 2. 更新用户信息 +--- -**接口地址**: `/user/update` -**请求方式**: PUT -**请求头**: -- Authorization: Bearer {token} -**请求参数**: +## 四、错误码说明 -| 参数名 | 类型 | 必选 | 描述 | -|-------|------|------|------| -| nickname | string | 否 | 昵称 | -| avatar | string | 否 | 头像URL | +| 错误码 | 说明 | +|---|---| +| `200` | 成功 | +| `400` | 请求参数错误 | +| `401` | 请求头缺失、令牌无效或用户身份不匹配 | +| `404` | 用户不存在或路由不存在 | +| `415` | 请求体不是 `application/json` | +| `429` | 请求过于频繁 | +| `500` | 服务器内部错误 | -**响应示例**: +--- -```json -{ - "code": 200, - "message": "更新成功", - "data": { - "id": "123", - "username": "admin", - "nickname": "新昵称", - "avatar": "https://example.com/new-avatar.jpg" - } -} +## 五、调用建议 + +### 1. 所有微信写接口都使用 JSON + +请求头应设置: + +```http +Content-Type: application/json ``` -## 数据接口 +### 2. 资料接口与资料详情接口都要带标准 JWT 头 -### 1. 获取数据列表 - -**接口地址**: `/data/list` -**请求方式**: GET -**请求参数**: - -| 参数名 | 类型 | 必选 | 描述 | -|-------|------|------|------| -| page | number | 否 | 页码,默认1 | -| size | number | 否 | 每页条数,默认10 | -| keyword | string | 否 | 搜索关键词 | - -**响应示例**: - -```json -{ - "code": 200, - "message": "获取成功", - "data": { - "list": [ - { - "id": "1", - "title": "数据1", - "content": "内容1", - "createdAt": "2026-03-18T00:00:00Z" - } - ], - "total": 1, - "page": 1, - "size": 10 - } -} +```http +Authorization: Bearer ``` -### 2. 获取数据详情 +### 3. `refresh-token` 接口当前只需要: -**接口地址**: `/data/detail/{id}` -**请求方式**: GET -**路径参数**: -- id: 数据ID - -**响应示例**: - -```json -{ - "code": 200, - "message": "获取成功", - "data": { - "id": "1", - "title": "数据1", - "content": "内容1", - "createdAt": "2026-03-18T00:00:00Z", - "updatedAt": "2026-03-18T00:00:00Z" - } -} +```http +users_wx_openid: ``` -## AI接口 - -### 1. 智能问答 - -**接口地址**: `/ai/chat` -**请求方式**: POST -**请求参数**: - -| 参数名 | 类型 | 必选 | 描述 | -|-------|------|------|------| -| question | string | 是 | 问题 | -| context | string | 否 | 上下文 | - -**响应示例**: - -```json -{ - "code": 200, - "message": "获取成功", - "data": { - "answer": "这是AI的回答", - "thinking": "AI的思考过程", - "tokens": 100 - } -} -``` - -## 视频接口 - -### 1. 上传视频 - -**接口地址**: `/video/upload` -**请求方式**: POST -**请求头**: -- Content-Type: multipart/form-data -**请求参数**: - -| 参数名 | 类型 | 必选 | 描述 | -|-------|------|------|------| -| video | file | 是 | 视频文件 | -| title | string | 是 | 视频标题 | - -**响应示例**: - -```json -{ - "code": 200, - "message": "上传成功", - "data": { - "id": "1", - "title": "视频标题", - "url": "https://example.com/video.mp4", - "duration": 60 - } -} -``` - -### 2. 获取视频列表 - -**接口地址**: `/video/list` -**请求方式**: GET -**请求参数**: - -| 参数名 | 类型 | 必选 | 描述 | -|-------|------|------|------| -| page | number | 否 | 页码,默认1 | -| size | number | 否 | 每页条数,默认10 | - -**响应示例**: - -```json -{ - "code": 200, - "message": "获取成功", - "data": { - "list": [ - { - "id": "1", - "title": "视频1", - "url": "https://example.com/video1.mp4", - "duration": 60, - "createdAt": "2026-03-18T00:00:00Z" - } - ], - "total": 1, - "page": 1, - "size": 10 - } -} -``` - -## 错误码说明 - -| 错误码 | 描述 | -|-------|------| -| 400 | 请求参数错误 | -| 401 | 未授权,请登录 | -| 403 | 禁止访问 | -| 404 | 资源不存在 | -| 500 | 服务器内部错误 | - -## 调用示例 - -### 使用axios调用 - -```javascript -// 登录 -axios.post('/api/auth/login', { - username: 'admin', - password: '123456' -}).then(response => { - const token = response.data.data.token; - // 存储token - localStorage.setItem('token', token); -}); - -// 带认证的请求 -axios.get('/api/user/info', { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } -}); -``` - -### 使用fetch调用 - -```javascript -// 登录 -fetch('/api/auth/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - username: 'admin', - password: '123456' - }) -}).then(response => response.json()) - .then(data => { - const token = data.data.token; - // 存储token - localStorage.setItem('token', token); - }); - -// 带认证的请求 -fetch('/api/user/info', { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } -}).then(response => response.json()) - .then(data => console.log(data)); -``` \ No newline at end of file +不需要旧 `Authorization`。 diff --git a/docs/backend.md b/docs/backend.md index 71c34f2..8b2b3d6 100644 --- a/docs/backend.md +++ b/docs/backend.md @@ -91,13 +91,14 @@ npm run test ### 数据库操作 -使用Pocketbase作为数据库,提供轻量级的数据存储解决方案。 +使用Pocketbase作为数据库,提供轻量级的数据存储解决方案。通过Pocketbase API进行数据操作,支持CRUD操作和实时数据同步。 ### 环境变量 环境变量配置位于 `.env` 文件,包括: - 服务器端口 -- 数据库连接信息 +- Pocketbase API URL +- Pocketbase认证信息 - JWT密钥 - 其他配置参数 diff --git a/docs/deployment.md b/docs/deployment.md index 3049cf6..a07b1d4 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -60,12 +60,20 @@ wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo b ## 后端部署 +后端建议采用 **dist 发布目录部署**,即: + +1. 在构建机执行 `npm run build` +2. 生成 `back-end/dist/` 发布产物 +3. 服务器仅部署 `dist/`、`package.json`、`package-lock.json` 与 `.env` +4. 服务器执行 `npm install --omit=dev` +5. 服务器执行 `npm start` + ### 1. 创建Dockerfile 在 `back-end` 目录创建 `Dockerfile` 文件: ```dockerfile -FROM node:22-alpine +FROM node:22-alpine AS build WORKDIR /app @@ -73,10 +81,57 @@ COPY package*.json ./ RUN npm install COPY . . +RUN npm run build + +FROM node:22-alpine AS production + +WORKDIR /app + +COPY --from=build /app/package*.json ./ +RUN npm install --omit=dev + +COPY --from=build /app/dist ./dist EXPOSE 3000 -CMD ["node", "src/index.js"] +CMD ["node", "dist/src/index.js"] +``` + +### 1.1 本地构建与上传发布包 + +在 `back-end/` 目录执行: + +```bash +npm install +npm run lint +npm run test +npm run build +``` + +构建成功后会生成: + +```text +back-end/dist/ + src/ + spec/ + package.json + package-lock.json + .env + eslint.config.js +``` + +如果你采用服务器源码分离部署,建议上传以下内容到服务器: + +- `dist/` +- `package.json` +- `package-lock.json` +- `.env` + +然后在服务器执行: + +```bash +npm install --omit=dev +npm start ``` ### 2. 创建docker-compose.yml @@ -202,20 +257,21 @@ chmod +x deploy.sh 3. 配置前端代理: - 代理名称:frontend - 目标URL:http://localhost:80 - - 发送域名:你的域名 + - 发送域名:`bai-api.blv-oa.com`(如前后端分域,请按实际前端域名填写) 4. 配置后端代理: - 代理名称:backend - 目标URL:http://localhost:3000 - - 发送域名:你的域名 + - 发送域名:`bai-api.blv-oa.com` - 路径:/api 5. 点击「保存」 -### 3. 配置SSL证书(可选) +### 3. 配置SSL证书(必须) 1. 进入网站设置 2. 点击「SSL」→「Let's Encrypt」 3. 申请并安装SSL证书 4. 开启「强制HTTPS」 +5. 确保域名 `bai-api.blv-oa.com` 已正确解析到服务器公网 IP ## 环境变量配置 @@ -227,6 +283,9 @@ chmod +x deploy.sh # Server Configuration PORT=3000 NODE_ENV=production +APP_PROTOCOL=https +APP_DOMAIN=bai-api.blv-oa.com +APP_BASE_URL=https://bai-api.blv-oa.com # Database Configuration DB_HOST=db @@ -238,7 +297,7 @@ JWT_SECRET=your_jwt_secret_key JWT_EXPIRES_IN=24h # CORS Configuration -CORS_ORIGIN=* +CORS_ORIGIN=https://bai-api.blv-oa.com ``` ### 前端环境变量 @@ -246,11 +305,21 @@ CORS_ORIGIN=* 在 `front-end/.env.production` 文件中配置: ```env -VUE_APP_API_BASE_URL=http://your-domain.com/api +VUE_APP_BASE_URL=https://bai-api.blv-oa.com/api VUE_APP_TITLE=BAI管理系统 VUE_APP_VERSION=1.0.0 ``` +## 域名解析与 HTTPS 部署建议 + +正式环境建议按以下方式部署: + +1. 将域名 `bai-api.blv-oa.com` 的 DNS A 记录指向服务器公网 IP +2. 宝塔/Nginx 为该域名签发并启用 SSL 证书 +3. Nginx 对外暴露 `443`,再反向代理到容器内 `backend:3000` +4. 前端生产环境接口地址统一使用:`https://bai-api.blv-oa.com/api` +5. 后端对外公开地址统一使用 `APP_BASE_URL=https://bai-api.blv-oa.com` + ## 数据库配置 ### Pocketbase设置 @@ -265,6 +334,18 @@ VUE_APP_VERSION=1.0.0 ## 监控与维护 +### 后端发布命令 + +后端推荐命令: + +```bash +# 构建发布产物 +npm run build + +# 生产启动 +npm start +``` + ### 查看日志 ```bash diff --git a/docs/example.md b/docs/example.md new file mode 100644 index 0000000..5f6a56c --- /dev/null +++ b/docs/example.md @@ -0,0 +1,42 @@ +// 获取微信小程序OpenID +private async Task GetWxOpenIdAsync(string code) +{ + try + { + var appId = configuration["WeChat:AppId"]; + var appSecret = configuration["WeChat:AppSecret"]; + + if (string.IsNullOrEmpty(appId) || string.IsNullOrEmpty(appSecret)) + { + throw new Exception("微信小程序配置缺失"); + } + + var httpClient = _httpClientFactory.CreateClient(); + var url = $"https://api.weixin.qq.com/sns/jscode2session?appid={appId}&secret={appSecret}&js_code={code}&grant_type=authorization_code"; + + var response = await httpClient.GetAsync(url); + response.EnsureSuccessStatusCode(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var jsonDocument = JsonDocument.Parse(responseContent); + + if (jsonDocument.RootElement.TryGetProperty("openid", out var openidElement)) + { + return openidElement.GetString(); + } + else + { + // 如果有错误信息,抛出异常 + if (jsonDocument.RootElement.TryGetProperty("errcode", out var errcodeElement) && + jsonDocument.RootElement.TryGetProperty("errmsg", out var errmsgElement)) + { + throw new Exception($"获取OpenID失败: {errcodeElement.GetInt32()} - {errmsgElement.GetString()}"); + } + throw new Exception("获取OpenID失败: 响应中未包含openid"); + } + } + catch (Exception ex) + { + throw new Exception($"获取微信OpenID时发生错误: {ex.Message}"); + } +} \ No newline at end of file diff --git a/front-end/.env.production b/front-end/.env.production index f277a08..79e837b 100644 --- a/front-end/.env.production +++ b/front-end/.env.production @@ -1,4 +1,4 @@ -# 生产环境,VUE_APP_BASE_URL可以选择自己配置成需要的接口地址,如"https://api.xxx.com" -# 此文件修改后需要重启项目 +# 生产环境接口地址,部署到正式域名后请保持与后端公开地址一致 +# 此文件修改后需要重新构建项目 NODE_ENV=production -VUE_APP_BASE_URL='/vab-mock-server' \ No newline at end of file +VUE_APP_BASE_URL='https://bai-api.blv-oa.com/api' diff --git a/script/database_schema.md b/script/database_schema.md new file mode 100644 index 0000000..70c7dde --- /dev/null +++ b/script/database_schema.md @@ -0,0 +1,96 @@ +# 平台后台数据库表结构 (PocketBase) + +本方案采用纯业务 ID 关联模式。PocketBase 底层的 `id` 字段由系统自动维护,业务逻辑中完全使用自定义的 `_id` 字段进行读写和关联。 + +--- + +### 1. tbl_system_dict (系统词典) +**类型:** Base Collection + +| 字段名 | 类型 | 备注 | +| :--- | :--- | :--- | +| system_dict_id | text | 自定义词id | +| dict_name | text | 词典名称 | +| dict_word_enum | text | 词典枚举 | +| dict_word_description | text | 词典描述 | +| dict_word_is_enabled | bool | 是否有效 | +| dict_word_sort_order | number | 显示顺序 | +| dict_word_parent_id | text | 实现父级字段 (存储其他 dict 的 system_dict_id) | +| dict_word_remark | text | 备注 | + +**索引规划 (Indexes):** +* `CREATE UNIQUE INDEX` 针对 `system_dict_id` (确保业务主键唯一) +* `CREATE INDEX` 针对 `dict_word_parent_id` (加速父子级联查询) + +--- + +### 2. tbl_company (公司) +**类型:** Base Collection + +| 字段名 | 类型 | 备注 | +| :--- | :--- | :--- | +| company_id | text | 自定义公司id | +| company_name | text | 公司名称 | +| company_type | text | 公司类型 | +| company_entity | text | 公司法人 | +| company_usci | text | 统一社会信用代码 | +| company_nationality | text | 国家 | +| company_province | text | 省份 | +| company_city | text | 城市 | +| company_postalcode | text | 邮编 | +| company_add | text | 地址 | +| company_status | text | 公司状态 | +| company_level | text | 公司等级 | +| company_remark | text | 备注 | + +**索引规划 (Indexes):** +* `CREATE UNIQUE INDEX` 针对 `company_id` (确保业务主键唯一) +* `CREATE INDEX` 针对 `company_usci` (加速信用代码检索) + +--- + +### 3. tbl_user_groups (用户组) +**类型:** Base Collection + +| 字段名 | 类型 | 备注 | +| :--- | :--- | :--- | +| usergroups_id | text | 自定义用户组id | +| usergroups_name | text | 用户组名 | +| usergroups_level | number | 权限等级 | +| usergroups_remark | text | 备注 | + +**索引规划 (Indexes):** +* `CREATE UNIQUE INDEX` 针对 `usergroups_id` (确保业务主键唯一) + +--- + +### 4. tbl_users (用户) +**类型:** Base Collection (采用纯业务记录,不使用系统 Auth,以便完美适配图示字段) + +| 字段名 | 类型 | 备注 | +| :--- | :--- | :--- | +| users_id | text | 自定义用户id | +| users_name | text | 用户名 | +| users_idtype | text | 证件类别 | +| users_id_number | text | 证件号 | +| users_phone | text | 用户电话号码 | +| users_wx_openid | text | 微信号 | +| users_level | text | 用户等级 | +| users_type | text | 用户类型 | +| users_status | text | 用户状态 | +| company_id | text | 公司id (存储 tbl_company.company_id) | +| users_parent_id | text | 用户父级id (存储 tbl_users.users_id) | +| users_promo_code | text | 用户推广码 | +| users_id_pic_a | file | 用户证件照片(正) | +| users_id_pic_b | file | 用户证件照片(反) | +| users_title_picture | file | 用户资质照片 | +| users_picture | file | 用户头像 | +| usergroups_id | text | 用户组id (存储 tbl_user_groups.usergroups_id) | + +**索引规划 (Indexes):** +* `CREATE UNIQUE INDEX` 针对 `users_id` (确保业务主键唯一) +* `CREATE UNIQUE INDEX` 针对 `users_phone` (确保手机号唯一,加速登录查询) +* `CREATE UNIQUE INDEX` 针对 `users_wx_openid` (确保微信开放ID唯一) +* `CREATE INDEX` 针对 `company_id`, `usergroups_id`, `users_parent_id` (加速这三个高频业务外键的匹配查询) + + diff --git a/script/node_modules/.package-lock.json b/script/node_modules/.package-lock.json new file mode 100644 index 0000000..3249efa --- /dev/null +++ b/script/node_modules/.package-lock.json @@ -0,0 +1,14 @@ +{ + "name": "script", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/pocketbase": { + "version": "0.26.8", + "resolved": "https://registry.npmmirror.com/pocketbase/-/pocketbase-0.26.8.tgz", + "integrity": "sha512-aQ/ewvS7ncvAE8wxoW10iAZu6ElgbeFpBhKPnCfvRovNzm2gW8u/sQNPGN6vNgVEagz44kK//C61oKjfa+7Low==", + "license": "MIT" + } + } +} diff --git a/script/node_modules/pocketbase/CHANGELOG.md b/script/node_modules/pocketbase/CHANGELOG.md new file mode 100644 index 0000000..465c8e3 --- /dev/null +++ b/script/node_modules/pocketbase/CHANGELOG.md @@ -0,0 +1,855 @@ +## 0.26.8 + +- Properly reject the `authWithOAuth2()` `Promise` when manually calling `pb.cancelRequest(requestKey)` _(previously the manual cancellation didn't account for the waiting realtime subscription)_. + + +## 0.26.7 + +- Normalized `pb.files.getURL()` to serialize the URL query params in the same manner as in the fetch methods (e.g. passing `null` or `undefined` as query param value will be skipped from the generated URL). + + +## 0.26.6 + +- Fixed abort request error detection on React Native Android/iOS ([#361](https://github.com/pocketbase/js-sdk/pull/361); thanks @nathanstitt). + +- Updated the default `getFullList()` batch size to 1000 for consistency with the Dart SDK and the v0.23+ API limits. + + +## 0.26.5 + +- Fixed abort request error detection on Safari introduced with the previous release because it seems to throw `DOMException.SyntaxError` on `response.json()` failure ([#pocketbase/pocketbase#7369](https://github.com/pocketbase/pocketbase/issues/7369)). + + +## 0.26.4 + +- Catch aborted request error during `response.json()` failure _(e.g. in case of tcp connection reset)_ and rethrow it as normalized `ClientResponseError.isAbort=true` error. + + +## 0.26.3 + +- Fixed outdated `OAuth2Provider` TS fields ([pocketbase/site#110](https://github.com/pocketbase/site/pull/110)). + + +## 0.26.2 + +- Allow body object without constructor ([#352](https://github.com/pocketbase/js-sdk/issues/352)). + + +## 0.26.1 + +- Set the `cause` property of `ClientResponseError` to the original thrown error/data for easier debugging ([#349](https://github.com/pocketbase/js-sdk/pull/349); thanks @shish). + + +## 0.26.0 + +- Ignore `undefined` properties when submitting an object that has `Blob`/`File` fields (_which is under the hood converted to `FormData`_) +for consistency with how `JSON.stringify` works (see [pocketbase#6731](https://github.com/pocketbase/pocketbase/issues/6731#issuecomment-2812382827)). + + +## 0.25.2 + +- Removed unnecessary checks in `serializeQueryParams` and added automated tests. + + +## 0.25.1 + +- Ignore query parameters with `undefined` value ([#330](https://github.com/pocketbase/js-sdk/issues/330)). + + +## 0.25.0 + +- Added `pb.crons` service to interact with the cron Web APIs. + + +## 0.24.0 + +- Added support for assigning `FormData` as body to individual batch requests ([pocketbase#6145](https://github.com/pocketbase/pocketbase/discussions/6145)). + + +## 0.23.0 + +- Added optional `pb.realtime.onDisconnect` hook function. + _Note that the realtime client autoreconnect on its own and this hook is useful only for the cases where you want to apply a special behavior on server error or after closing the realtime connection._ + + +## 0.22.1 + +- Fixed old `pb.authStore.isAdmin`/`pb.authStore.isAuthRecord` and marked them as deprecated in favour of `pb.authStore.isSuperuser` ([#323](https://github.com/pocketbase/js-sdk/issues/323)). + _Note that with PocketBase v0.23.0 superusers are converted to a system auth collection so you can always simply check the value of `pb.authStore.record?.collectionName`._ + + +## 0.22.0 + +**⚠️ This release introduces some breaking changes and works only with PocketBase v0.23.0+.** + +- Added support for sending batch/transactional create/updated/delete/**upsert** requests with the new batch Web APIs. + ```js + const batch = pb.createBatch(); + + batch.collection("example1").create({ ... }); + batch.collection("example2").update("RECORD_ID", { ... }); + batch.collection("example3").delete("RECORD_ID"); + batch.collection("example4").upsert({ ... }); + + const result = await batch.send(); + ``` + +- Added support for authenticating with OTP (email code): + ```js + const result = await pb.collection("users").requestOTP("test@example.com"); + + // ... show a modal for users to check their email and to enter the received code ... + + await pb.collection("users").authWithOTP(result.otpId, "EMAIL_CODE"); + ``` + + Note that PocketBase v0.23.0 comes also with Multi-factor authentication (MFA) support. + When enabled from the dashboard, the first auth attempt will result in 401 response and a `mfaId` response, + that will have to be submitted with the second auth request. For example: + ```js + try { + await pb.collection("users").authWithPassword("test@example.com", "1234567890"); + } catch (err) { + const mfaId = err.response?.mfaId; + if (!mfaId) { + throw err; // not mfa -> rethrow + } + + // the user needs to authenticate again with another auth method, for example OTP + const result = await pb.collection("users").requestOTP("test@example.com"); + // ... show a modal for users to check their email and to enter the received code ... + await pb.collection("users").authWithOTP(result.otpId, "EMAIL_CODE", { "mfaId": mfaId }); + } + ``` + +- Added new `pb.collection("users").impersonate("RECORD_ID")` method for superusers. + It authenticates with the specified record id and returns a new client with the impersonated auth state loaded in a memory store. + ```js + // authenticate as superusers (with v0.23.0 admins is converted to a special system auth collection "_superusers"): + await pb.collection("_superusers").authWithPassword("test@example.com", "1234567890"); + + // impersonate + const impersonateClient = pb.collection("users").impersonate("USER_RECORD_ID", 3600 /* optional token duration in seconds */) + + // log the impersonate token and user data + console.log(impersonateClient.authStore.token); + console.log(impersonateClient.authStore.record); + + // send requests as the impersonated user + impersonateClient.collection("example").getFullList(); + ``` + +- Added new `pb.collections.getScaffolds()` method to retrieve a type indexed map with the collection models (base, auth, view) loaded with their defaults. + +- Added new `pb.collections.truncate(idOrName)` to delete all records associated with the specified collection. + +- Added the submitted fetch options as 3rd last argument in the `pb.afterSend` hook. + +- Instead of replacing the entire `pb.authStore.record`, on auth record update we now only replace the available returned response record data ([pocketbase#5638](https://github.com/pocketbase/pocketbase/issues/5638)). + +- ⚠️ Admins are converted to `_superusers` auth collection and there is no longer `AdminService` and `AdminModel` types. + `pb.admins` is soft-deprecated and aliased to `pb.collection("_superusers")`. + ```js + // before -> after + pb.admins.* -> pb.collection("_superusers").* + ``` + +- ⚠️ `pb.authStore.model` is soft-deprecated and superseded by `pb.authStore.record`. + +- ⚠️ Soft-deprecated the OAuth2 success auth `meta.avatarUrl` response field in favour of `meta.avatarURL` for consistency with the Go conventions. + +- ⚠️ Changed `AuthMethodsList` inerface fields to accomodate the new auth methods and `listAuthMethods()` response. + ``` + { + "mfa": { + "duration": 100, + "enabled": true + }, + "otp": { + "duration": 0, + "enabled": false + }, + "password": { + "enabled": true, + "identityFields": ["email", "username"] + }, + "oauth2": { + "enabled": true, + "providers": [{"name": "gitlab", ...}, {"name": "google", ...}] + } + } + ``` + +- ⚠️ Require specifying collection id or name when sending test email because the email templates can be changed per collection. + ```js + // old + pb.settings.testEmail(email, "verification") + + // new + pb.settings.testEmail("users", email, "verification") + ``` + +- ⚠️ Soft-deprecated and aliased `*Url()` -> `*URL()` methods for consistency with other similar native JS APIs and the accepted Go conventions. + _The old methods still works but you may get a console warning to replace them because they will be removed in the future._ + ```js + pb.baseUrl -> pb.baseURL + pb.buildUrl() -> pb.buildURL() + pb.files.getUrl() -> pb.files.getURL() + pb.backups.getDownloadUrl() -> pb.backups.getDownloadURL() + ``` + +- ⚠️ Renamed `CollectionModel.schema` to `CollectionModel.fields`. + +- ⚠️ Renamed type `SchemaField` to `CollectionField`. + + +## 0.21.5 + +- Shallow copy the realtime subscribe `options` argument for consistency with the other methods ([#308](https://github.com/pocketbase/js-sdk/issues/308)). + + +## 0.21.4 + +- Fixed the `requestKey` handling in `authWithOAuth2({...})` to allow manually cancelling the entire OAuth2 pending request flow using `pb.cancelRequest(requestKey)`. + _Due to the [`window.close` caveats](https://developer.mozilla.org/en-US/docs/Web/API/Window/close) note that the OAuth2 popup window may still remain open depending on which stage of the OAuth2 flow the cancellation has been invoked._ + + +## 0.21.3 + +- Enforce temporary the `atob` polyfill for ReactNative until [Expo 51+ and React Native v0.74+ `atob` fix get released](https://github.com/reactwg/react-native-releases/issues/287). + + +## 0.21.2 + +- Exported `HealthService` types ([#289](https://github.com/pocketbase/js-sdk/issues/289)). + + +## 0.21.1 + +- Manually update the verified state of the current matching `AuthStore` model on successful "confirm-verification" call. + +- Manually clear the current matching `AuthStore` on "confirm-email-change" call because previous tokens are always invalidated. + +- Updated the `fetch` mock tests to check also the sent body params. + +- Formatted the source and tests with prettier. + + +## 0.21.0 + +**⚠️ This release works only with PocketBase v0.21.0+ due to changes of how the `multipart/form-data` body is handled.** + +- Properly sent json body with `multipart/form-data` requests. + _This should fix the edge cases mentioned in the v0.20.3 release._ + +- Gracefully handle OAuth2 redirect error with the `authWithOAuth2()` call. + + +## 0.20.3 + +- Partial and temporary workaround for the auto `application/json` -> `multipart/form-data` request serialization of a `json` field when a `Blob`/`File` is found in the request body ([#274](https://github.com/pocketbase/js-sdk/issues/274)). + + The "fix" is partial because there are still 2 edge cases that are not handled - when a `json` field value is empty array (eg. `[]`) or array of strings (eg. `["a","b"]`). + The reason for this is because the SDK doesn't have information about the field types and doesn't know which field is a `json` or an arrayable `select`, `file` or `relation`, so it can't serialize it properly on its own as `FormData` string value. + + If you are having troubles with persisting `json` values as part of a `multipart/form-data` request the easiest fix for now is to manually stringify the `json` field value: + ```js + await pb.collection("example").create({ + // having a Blob/File as object value will convert the request to multipart/form-data + "someFileField": new Blob([123]), + "someJsonField": JSON.stringify(["a","b","c"]), + }) + ``` + + A proper fix for this will be implemented with PocketBase v0.21.0 where we'll have support for a special `@jsonPayload` multipart body key, which will allow us to submit mixed `multipart/form-data` content (_kindof similar to the `multipart/mixed` MIME_). + + +## 0.20.2 + +- Throw 404 error for `getOne("")` when invoked with empty id ([#271](https://github.com/pocketbase/js-sdk/issues/271)). + +- Added `@throw {ClientResponseError}` jsdoc annotation to the regular request methods ([#262](https://github.com/pocketbase/js-sdk/issues/262)). + + +## 0.20.1 + +- Propagate the `PB_CONNECT` event to allow listening to the realtime connect/reconnect events. + ```js + pb.realtime.subscribe("PB_CONNECT", (e) => { + console.log(e.clientId); + }) + ``` + +## 0.20.0 + +- Added `expand`, `filter`, `fields`, custom query and headers parameters support for the realtime subscriptions. + ```js + pb.collection("example").subscribe("*", (e) => { + ... + }, { filter: "someField > 10" }); + ``` + _This works only with PocketBase v0.20.0+._ + +- Changes to the logs service methods in relation to the logs generalization in PocketBase v0.20.0+: + ```js + pb.logs.getRequestsList(...) -> pb.logs.getList(...) + pb.logs.getRequest(...) -> pb.logs.getOne(...) + pb.logs.getRequestsStats(...) -> pb.logs.getStats(...) + ``` + +- Added missing `SchemaField.presentable` field. + +- Added new `AuthProviderInfo.displayName` string field. + +- Added new `AuthMethodsList.onlyVerified` bool field. + + +## 0.19.0 + +- Added `pb.filter(rawExpr, params?)` helper to construct a filter string with placeholder parameters populated from an object. + + ```js + const record = await pb.collection("example").getList(1, 20, { + // the same as: "title ~ 'te\\'st' && (totalA = 123 || totalB = 123)" + filter: pb.filter("title ~ {:title} && (totalA = {:num} || totalB = {:num})", { title: "te'st", num: 123 }) + }) + ``` + + The supported placeholder parameter values are: + + - `string` (_single quotes will be autoescaped_) + - `number` + - `boolean` + - `Date` object (_will be stringified into the format expected by PocketBase_) + - `null` + - anything else is converted to a string using `JSON.stringify()` + + +## 0.18.3 + +- Added optional generic support for the `RecordService` ([#251](https://github.com/pocketbase/js-sdk/issues/251)). + This should allow specifying a single TypeScript definition for the client, eg. using type assertion: + ```ts + interface Task { + id: string; + name: string; + } + + interface Post { + id: string; + title: string; + active: boolean; + } + + interface TypedPocketBase extends PocketBase { + collection(idOrName: string): RecordService // default fallback for any other collection + collection(idOrName: 'tasks'): RecordService + collection(idOrName: 'posts'): RecordService + } + + ... + + const pb = new PocketBase("http://127.0.0.1:8090") as TypedPocketBase; + + // the same as pb.collection('tasks').getOne("RECORD_ID") + await pb.collection('tasks').getOne("RECORD_ID") // -> results in Task + + // the same as pb.collection('posts').getOne("RECORD_ID") + await pb.collection('posts').getOne("RECORD_ID") // -> results in Post + ``` + + +## 0.18.2 + +- Added support for assigning a `Promise` as `AsyncAuthStore` initial value ([#249](https://github.com/pocketbase/js-sdk/issues/249)). + + +## 0.18.1 + +- Fixed realtime subscriptions auto cancellation to use the proper `requestKey` param. + + +## 0.18.0 + +- Added `pb.backups.upload(data)` action (_available with PocketBase v0.18.0_). + +- Added _experimental_ `autoRefreshThreshold` option to auto refresh (or reauthenticate) the AuthStore when authenticated as admin. + _This could be used as an alternative to fixed Admin API keys._ + ```js + await pb.admins.authWithPassword("test@example.com", "1234567890", { + // This will trigger auto refresh or auto reauthentication in case + // the token has expired or is going to expire in the next 30 minutes. + autoRefreshThreshold: 30 * 60 + }) + ``` + + +## 0.17.3 + +- Loosen the type check when calling `pb.files.getUrl(user, filename)` to allow passing the `pb.authStore.model` without type assertion. + + +## 0.17.2 + +- Fixed mulitple File/Blob array values not transformed properly to their FormData equivalent when an object syntax is used. + + +## 0.17.1 + +- Fixed typo in the deprecation console.warn messages ([#235](https://github.com/pocketbase/js-sdk/pull/235); thanks @heloineto). + + +## 0.17.0 + +- To simplify file uploads, we now allow sending the `multipart/form-data` request body also as plain object if at least one of the object props has `File` or `Blob` value. + ```js + // the standard way to create multipart/form-data body + const data = new FormData(); + data.set("title", "lorem ipsum...") + data.set("document", new File(...)) + + // this is the same as above + // (it will be converted behind the scenes to FormData) + const data = { + "title": "lorem ipsum...", + "document": new File(...), + }; + + await pb.collection("example").create(data); + ``` + +- Added new `pb.authStore.isAdmin` and `pb.authStore.isAuthRecord` helpers to check the type of the current auth state. + +- The default `LocalAuthStore` now listen to the browser [storage event](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event), + so that we can sync automatically the `pb.authStore` state between multiple tabs. + +- Added new helper `AsyncAuthStore` class that can be used to integrate with any 3rd party async storage implementation (_usually this is needed when working with React Native_): + ```js + import AsyncStorage from "@react-native-async-storage/async-storage"; + import PocketBase, { AsyncAuthStore } from "pocketbase"; + + const store = new AsyncAuthStore({ + save: async (serialized) => AsyncStorage.setItem("pb_auth", serialized), + initial: AsyncStorage.getItem("pb_auth"), + }); + + const pb = new PocketBase("https://example.com", store) + ``` + +- `pb.files.getUrl()` now returns empty string in case an empty filename is passed. + +- ⚠️ All API actions now return plain object (POJO) as response, aka. the custom class wrapping was removed and you no longer need to manually call `structuredClone(response)` when using with SSR frameworks. + + This could be a breaking change if you use the below classes (_and respectively their helper methods like `$isNew`, `$load()`, etc._) since they were replaced with plain TS interfaces: + ```ts + class BaseModel -> interface BaseModel + class Admin -> interface AdminModel + class Record -> interface RecordModel + class LogRequest -> interface LogRequestModel + class ExternalAuth -> interface ExternalAuthModel + class Collection -> interface CollectionModel + class SchemaField -> interface SchemaField + class ListResult -> interface ListResult + ``` + + _Side-note:_ If you use somewhere in your code the `Record` and `Admin` classes to determine the type of your `pb.authStore.model`, + you can safely replace it with the new `pb.authStore.isAdmin` and `pb.authStore.isAuthRecord` getters. + +- ⚠️ Added support for per-request `fetch` options, including also specifying completely custom `fetch` implementation. + + In addition to the default [`fetch` options](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options), the following configurable fields are supported: + + ```ts + interface SendOptions extends RequestInit { + // any other custom key will be merged with the query parameters + // for backward compatibility and to minimize the verbosity + [key: string]: any; + + // optional custom fetch function to use for sending the request + fetch?: (url: RequestInfo | URL, config?: RequestInit) => Promise; + + // custom headers to send with the requests + headers?: { [key: string]: string }; + + // the body of the request (serialized automatically for json requests) + body?: any; + + // query params that will be appended to the request url + query?: { [key: string]: any }; + + // the request identifier that can be used to cancel pending requests + requestKey?: string|null; + + // @deprecated use `requestKey:string` instead + $cancelKey?: string; + + // @deprecated use `requestKey:null` instead + $autoCancel?: boolean; + } + ``` + + For most users the above will not be a breaking change since there are available function overloads (_when possible_) to preserve the old behavior, but you can get a warning message in the console to update to the new format. + For example: + ```js + // OLD (should still work but with a warning in the console) + await pb.collection("example").authRefresh({}, { + "expand": "someRelField", + }) + + // NEW + await pb.collection("example").authRefresh({ + "expand": "someRelField", + // send some additional header + "headers": { + "X-Custom-Header": "123", + }, + "cache": "no-store" // also usually used by frameworks like Next.js + }) + ``` + +- Eagerly open the default OAuth2 signin popup in case no custom `urlCallback` is provided as a workaround for Safari. + +- Internal refactoring (updated dev dependencies, refactored the tests to use Vitest instead of Mocha, etc.). + + +## 0.16.0 + +- Added `skipTotal=1` query parameter by default for the `getFirstListItem()` and `getFullList()` requests. + _Note that this have performance boost only with PocketBase v0.17+._ + +- Added optional `download=1` query parameter to force file urls with `Content-Disposition: attachment` (_supported with PocketBase v0.17+_). + + +## 0.15.3 + +- Automatically resolve pending realtime connect `Promise`s in case `unsubscribe` is called before + `subscribe` is being able to complete ([pocketbase#2897](https://github.com/pocketbase/pocketbase/discussions/2897#discussioncomment-6423818)). + + +## 0.15.2 + +- Replaced `new URL(...)` with manual url parsing as it is not fully supported in React Native ([pocketbase#2484](https://github.com/pocketbase/pocketbase/discussions/2484#discussioncomment-6114540)). + +- Fixed nested `ClientResponseError.originalError` wrapping and added `ClientResponseError` constructor tests. + + +## 0.15.1 + +- Cancel any pending subscriptions submit requests on realtime disconnect ([#204](https://github.com/pocketbase/js-sdk/issues/204)). + + +## 0.15.0 + +- Added `fields` to the optional query parameters for limiting the returned API fields (_available with PocketBase v0.16.0_). + +- Added `pb.backups` service for the new PocketBase backup and restore APIs (_available with PocketBase v0.16.0_). + +- Updated `pb.settings.testS3(filesystem)` to allow specifying a filesystem to test - `storage` or `backups` (_available with PocketBase v0.16.0_). + + +## 0.14.4 + +- Removed the legacy aliased `BaseModel.isNew` getter since it conflicts with similarly named record fields ([pocketbase#2385](https://github.com/pocketbase/pocketbase/discussions/2385)). + _This helper is mainly used in the Admin UI, but if you are also using it in your code you can replace it with the `$` prefixed version, aka. `BaseModel.$isNew`._ + + +## 0.14.3 + +- Added `OAuth2AuthConfig.query` prop to send optional query parameters with the `authWithOAuth2(config)` call. + + +## 0.14.2 + +- Use `location.origin + location.pathname` instead of full `location.href` when constructing the browser absolute url to ignore any extra hash or query parameter passed to the base url. + _This is a small addition to the earlier change from v0.14.1._ + + +## 0.14.1 + +- Use an absolute url when the SDK is initialized with a relative base path in a browser env to ensure that the generated OAuth2 redirect and file urls are absolute. + + +## 0.14.0 + +- Added simplified `authWithOAuth2()` version without having to implement custom redirect, deeplink or even page reload: + ```js + const authData = await pb.collection('users').authWithOAuth2({ + provider: 'google' + }) + ``` + + Works with PocketBase v0.15.0+. + + This method initializes a one-off realtime subscription and will + open a popup window with the OAuth2 vendor page to authenticate. + Once the external OAuth2 sign-in/sign-up flow is completed, the popup + window will be automatically closed and the OAuth2 data sent back + to the user through the previously established realtime connection. + + _Site-note_: when creating the OAuth2 app in the provider dashboard + you have to configure `https://yourdomain.com/api/oauth2-redirect` + as redirect URL. + + _The "manual" code exchange flow is still supported as `authWithOAuth2Code(provider, code, codeVerifier, redirectUrl)`._ + + _For backward compatibility it is also available as soft-deprecated function overload of `authWithOAuth2(provider, code, codeVerifier, redirectUrl)`._ + +- Added new `pb.files` service: + ```js + // Builds and returns an absolute record file url for the provided filename. + 🔓 pb.files.getUrl(record, filename, queryParams = {}); + + // Requests a new private file access token for the current auth model (admin or record). + 🔐 pb.files.getToken(queryParams = {}); + ``` + _`pb.getFileUrl()` is soft-deprecated and acts as alias calling `pb.files.getUrl()` under the hood._ + + Works with PocketBase v0.15.0+. + + +## 0.13.1 + +- Added option to specify a generic `send()` return type and defined `SendOptions` type ([#171](https://github.com/pocketbase/js-sdk/pull/171); thanks @iamelevich). + +- Deprecated `SchemaField.unique` prop since its function is replaced by `Collection.indexes` in the upcoming PocketBase v0.14.0 release. + + +## 0.13.0 + +- Aliased all `BaseModel` helpers with `$` equivalent to avoid conflicts with the dynamic record props ([#169](https://github.com/pocketbase/js-sdk/issues/169)). + ```js + isNew -> $isNew + load(data) -> $load(data) + clone() -> $clone() + export() -> $export() + // ... + ``` + _For backward compatibility, the old helpers will still continue to work if the record doesn't have a conflicting field name._ + +- Updated `pb.beforeSend` and `pb.afterSend` signatures to allow returning and awaiting an optional `Promise` ([#166](https://github.com/pocketbase/js-sdk/pull/166); thanks @Bobby-McBobface). + +- Added `Collection.indexes` field for the new collection indexes support in the upcoming PocketBase v0.14.0. + +- Added `pb.settings.generateAppleClientSecret()` for sending a request to generate Apple OAuth2 client secret in the upcoming PocketBase v0.14.0. + + +## 0.12.1 + +- Fixed request `multipart/form-data` body check to allow the React Native Android and iOS custom `FormData` implementation as valid `fetch` body ([#2002](https://github.com/pocketbase/pocketbase/discussions/2002)). + + +## 0.12.0 + +- Changed the return type of `pb.beforeSend` hook to allow modifying the request url ([#1930](https://github.com/pocketbase/pocketbase/discussions/1930)). + ```js + // old + pb.beforeSend = function (url, options) { + ... + return options; + } + + // new + pb.beforeSend = function (url, options) { + ... + return { url, options }; + } + ``` + The old return format is soft-deprecated and will still work, but you'll get a `console.warn` message to replace it. + + +## 0.11.1 + +- Exported the services class definitions to allow being used as argument types ([#153](https://github.com/pocketbase/js-sdk/issues/153)). + ```js + CrudService + AdminService + CollectionService + LogService + RealtimeService + RecordService + SettingsService + ``` + +## 0.11.0 + +- Aliased/soft-deprecated `ClientResponseError.data` in favor of `ClientResponseError.response` to avoid the stuttering when accessing the inner error response `data` key (aka. `err.data.data` now is `err.response.data`). + The `ClientResponseError.data` will still work but it is recommend for new code to use the `response` key. + +- Added `getFullList(queryParams = {})` overload since the default batch size in most cases doesn't need to change (it can be defined as query parameter). + The old form `getFullList(batch = 200, queryParams = {})` will still work, but it is recommend for new code to use the shorter form. + + +## 0.10.2 + +- Updated `getFileUrl()` to accept custom types as record argument. + + +## 0.10.1 + +- Added check for the collection name before auto updating the `pb.authStore` state on auth record update/delete. + + +## 0.10.0 + +- Added more helpful message for the `ECONNREFUSED ::1` localhost error (related to [#21](https://github.com/pocketbase/js-sdk/issues/21)). + +- Preserved the "original" function and class names in the minified output for those who rely on `*.prototype.name`. + +- Allowed sending the existing valid auth token with the `authWithPassword()` calls. + +- Updated the Nuxt3 SSR examples to use the built-in `useCookie()` helper. + + +## 0.9.1 + +- Normalized nested `expand` items to `Record|Array` instances. + + +## 0.9.0 + +- Added `pb.health.check()` that checks the health status of the API service (_available in PocketBase v0.10.0_) + + +## 0.8.4 + +- Added type declarations for the action query parameters ([#102](https://github.com/pocketbase/js-sdk/pull/102); thanks @sewera). + ```js + BaseQueryParams + ListQueryParams + RecordQueryParams + RecordListQueryParams + LogStatsQueryParams + FileQueryParams + ``` + + +## 0.8.3 + +- Renamed the declaration file extension from `.d.ts` to `.d.mts` to prevent type resolution issues ([#92](https://github.com/pocketbase/js-sdk/issues/92)). + + +## 0.8.2 + +- Allowed catching the initial realtime connect error as part of the `subscribe()` Promise resolution. + +- Reimplemented the default `EventSource` retry mechanism for better control and more consistent behavior across different browsers. + + +## 0.8.1 + +This release contains only documentation fixes: + +- Fixed code comment typos. + +- Added note about loadFromCookie that you may need to call authRefresh to validate the loaded cookie state server-side. + +- Updated the SSR examples to show the authRefresh call. _For the examples the authRefresh call is not required but it is there to remind users that it needs to be called if you want to do permission checks in a node env (eg. SSR) and rely on the `pb.authStore.isValid`._ + + +## 0.8.0 + +> ⚠️ Please note that this release works only with the new PocketBase v0.8+ API! +> +> See the breaking changes below for what has changed since v0.7.x. + +#### Non breaking changes + +- Added support for optional custom `Record` types using TypeScript generics, eg. + `pb.collection('example').getList()`. + +- Added new `pb.autoCancellation(bool)` method to globally enable or disable auto cancellation (`true` by default). + +- Added new crud method `getFirstListItem(filter)` to fetch a single item by a list filter. + +- You can now set additional account `createData` when authenticating with OAuth2. + +- Added `AuthMethodsList.usernamePassword` return field (we now support combined username/email authentication; see below `authWithPassword`). + +#### Breaking changes + +- Changed the contstructor from `PocketBase(url, lang?, store?)` to `PocketBase(url, store?, lang?)` (aka. the `lang` option is now last). + +- For easier and more conventional parsing, all DateTime strings now have `Z` as suffix, so that you can do directly `new Date('2022-01-01 01:02:03.456Z')`. + +- Moved `pb.records.getFileUrl()` to `pb.getFileUrl()`. + +- Moved all `pb.records.*` handlers under `pb.collection().*`: + ``` + pb.records.getFullList('example'); => pb.collection('example').getFullList(); + pb.records.getList('example'); => pb.collection('example').getList(); + pb.records.getOne('example', 'RECORD_ID'); => pb.collection('example').getOne('RECORD_ID'); + (no old equivalent) => pb.collection('example').getFirstListItem(filter); + pb.records.create('example', {...}); => pb.collection('example').create({...}); + pb.records.update('example', 'RECORD_ID', {...}); => pb.collection('example').update('RECORD_ID', {...}); + pb.records.delete('example', 'RECORD_ID'); => pb.collection('example').delete('RECORD_ID'); + ``` + +- The `pb.realtime` service has now a more general callback form so that it can be used with custom realtime handlers. + Dedicated records specific subscribtions could be found under `pb.collection().*`: + ``` + pb.realtime.subscribe('example', callback) => pb.collection('example').subscribe("*", callback) + pb.realtime.subscribe('example/RECORD_ID', callback) => pb.collection('example').subscribe('RECORD_ID', callback) + pb.realtime.unsubscribe('example') => pb.collection('example').unsubscribe("*") + pb.realtime.unsubscribe('example/RECORD_ID') => pb.collection('example').unsubscribe('RECORD_ID') + (no old equivalent) => pb.collection('example').unsubscribe() + ``` + Additionally, `subscribe()` now return `UnsubscribeFunc` that could be used to unsubscribe only from a single subscription listener. + +- Moved all `pb.users.*` handlers under `pb.collection().*`: + ``` + pb.users.listAuthMethods() => pb.collection('users').listAuthMethods() + pb.users.authViaEmail(email, password) => pb.collection('users').authWithPassword(usernameOrEmail, password) + pb.users.authViaOAuth2(provider, code, codeVerifier, redirectUrl) => pb.collection('users').authWithOAuth2(provider, code, codeVerifier, redirectUrl, createData = {}) + pb.users.refresh() => pb.collection('users').authRefresh() + pb.users.requestPasswordReset(email) => pb.collection('users').requestPasswordReset(email) + pb.users.confirmPasswordReset(resetToken, newPassword, newPasswordConfirm) => pb.collection('users').confirmPasswordReset(resetToken, newPassword, newPasswordConfirm) + pb.users.requestVerification(email) => pb.collection('users').requestVerification(email) + pb.users.confirmVerification(verificationToken) => pb.collection('users').confirmVerification(verificationToken) + pb.users.requestEmailChange(newEmail) => pb.collection('users').requestEmailChange(newEmail) + pb.users.confirmEmailChange(emailChangeToken, password) => pb.collection('users').confirmEmailChange(emailChangeToken, password) + pb.users.listExternalAuths(recordId) => pb.collection('users').listExternalAuths(recordId) + pb.users.unlinkExternalAuth(recordId, provider) => pb.collection('users').unlinkExternalAuth(recordId, provider) + ``` + +- Changes in `pb.admins` for consistency with the new auth handlers in `pb.collection().*`: + ``` + pb.admins.authViaEmail(email, password); => pb.admins.authWithPassword(email, password); + pb.admins.refresh(); => pb.admins.authRefresh(); + ``` + +- To prevent confusion with the auth method responses, the following methods now returns 204 with empty body (previously 200 with token and auth model): + ```js + pb.admins.confirmPasswordReset(...): Promise + pb.collection("users").confirmPasswordReset(...): Promise + pb.collection("users").confirmVerification(...): Promise + pb.collection("users").confirmEmailChange(...): Promise + ``` + +- Removed the `User` model because users are now regular records (aka. `Record`). + **The old user fields `lastResetSentAt`, `lastVerificationSentAt` and `profile` are no longer available** + (the `profile` fields are available under the `Record.*` property like any other fields). + +- Renamed the special `Record` props: + ``` + @collectionId => collectionId + @collectionName => collectionName + @expand => expand + ``` + +- Since there is no longer `User` model, `pb.authStore.model` can now be of type `Record`, `Admin` or `null`. + +- Removed `lastResetSentAt` from the `Admin` model. + +- Replaced `ExternalAuth.userId` with 2 new `recordId` and `collectionId` props. + +- Removed the deprecated uppercase service aliases: + ``` + client.Users => client.collection(*) + client.Records => client.collection(*) + client.AuthStore => client.authStore + client.Realtime => client.realtime + client.Admins => client.admins + client.Collections => client.collections + client.Logs => client.logs + client.Settings => client.settings + ``` diff --git a/script/node_modules/pocketbase/LICENSE.md b/script/node_modules/pocketbase/LICENSE.md new file mode 100644 index 0000000..e3b8465 --- /dev/null +++ b/script/node_modules/pocketbase/LICENSE.md @@ -0,0 +1,17 @@ +The MIT License (MIT) +Copyright (c) 2022 - present, Gani Georgiev + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/script/node_modules/pocketbase/README.md b/script/node_modules/pocketbase/README.md new file mode 100644 index 0000000..b4c783f --- /dev/null +++ b/script/node_modules/pocketbase/README.md @@ -0,0 +1,1091 @@ +PocketBase JavaScript SDK +====================================================================== + +Official JavaScript SDK (browser and node) for interacting with the [PocketBase API](https://pocketbase.io/docs). + +- [Installation](#installation) +- [Usage](#usage) +- [Caveats](#caveats) + - [Binding filter parameters](#binding-filter-parameters) + - [File upload](#file-upload) + - [Error handling](#error-handling) + - [Auth store](#auth-store) + - [LocalAuthStore (default)](#localauthstore-default) + - [AsyncAuthStore (_usually used with React Native_)](#asyncauthstore) + - [Custom auth store](#custom-auth-store) + - [Common auth store fields and methods](#common-auth-store-fields-and-methods) + - [Auto cancellation](#auto-cancellation) + - [Specify TypeScript definitions](#specify-typescript-definitions) + - [Custom request options](#custom-request-options) + - [Send hooks](#send-hooks) + - [SSR integration](#ssr-integration) + - [Security](#security) +- [Definitions](#definitions) +- [Development](#development) + + +## Installation + +### Browser (manually via script tag) + +```html + + +``` + +_OR if you are using ES modules:_ +```html + +``` + +### Node.js (via npm) + +```sh +npm install pocketbase --save +``` + +```js +// Using ES modules (default) +import PocketBase from 'pocketbase' + +// OR if you are using CommonJS modules +const PocketBase = require('pocketbase/cjs') +``` + +> 🔧 For **Node < 17** you'll need to load a `fetch()` polyfill. +> I recommend [lquixada/cross-fetch](https://github.com/lquixada/cross-fetch): +> ```js +> // npm install cross-fetch --save +> import 'cross-fetch/polyfill'; +> ``` +--- +> 🔧 Node doesn't have native `EventSource` implementation, so in order to use the realtime subscriptions you'll need to load a `EventSource` polyfill. +> ```js +> // for server: npm install eventsource --save +> import { EventSource } from "eventsource"; +> +> // for React Native: npm install react-native-sse --save +> import EventSource from "react-native-sse"; +> +> global.EventSource = EventSource; +> ``` + + +## Usage + +```js +import PocketBase from 'pocketbase'; + +const pb = new PocketBase('http://127.0.0.1:8090'); + +... + +// authenticate as auth collection record +const userData = await pb.collection('users').authWithPassword('test@example.com', '123456'); + +// list and filter "example" collection records +const result = await pb.collection('example').getList(1, 20, { + filter: 'status = true && created > "2022-08-01 10:00:00"' +}); + +// and much more... +``` +> More detailed API docs and copy-paste examples could be found in the [API documentation for each service](https://pocketbase.io/docs/api-records/). + + +## Caveats + +### Binding filter parameters + +The SDK comes with a helper `pb.filter(expr, params)` method to generate a filter string with placeholder parameters (`{:paramName}`) populated from an object. + +**This method is also recommended when using the SDK in Node/Deno/Bun server-side list queries and accepting untrusted user input as `filter` string arguments, because it will take care to properly escape the generated string expression, avoiding eventual string injection attacks** (_on the client-side this is not much of an issue_). + +```js +const records = await pb.collection("example").getList(1, 20, { + // the same as: "title ~ 'te\\'st' && (totalA = 123 || totalB = 123)" + filter: pb.filter("title ~ {:title} && (totalA = {:num} || totalB = {:num})", { title: "te'st", num: 123 }) +}) +``` + +The supported placeholder parameter values are: + +- `string` (_single quotes are autoescaped_) +- `number` +- `boolean` +- `Date` object (_will be stringified into the format expected by PocketBase_) +- `null` +- everything else is converted to a string using `JSON.stringify()` + + +### File upload + +PocketBase Web API supports file upload via `multipart/form-data` requests, +which means that to upload a file it is enough to provide either a [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) instance OR plain object with `File`/`Blob` prop values. + +- Using plain object as body _(this is the same as above and it will be converted to `FormData` behind the scenes)_: + ```js + const data = { + 'title': 'lorem ipsum...', + 'document': new File(...), + }; + + await pb.collection('example').create(data); + ``` + +- Using `FormData` as body: + ```js + // the standard way to create multipart/form-data body + const data = new FormData(); + data.set('title', 'lorem ipsum...') + data.set('document', new File(...)) + + await pb.collection('example').create(data); + ``` + +### Error handling + +All services return a standard [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)-based response, so the error handling is straightforward: +```js +pb.collection('example').getList(1, 50).then((result) => { + // success... + console.log('Result:', result); +}).catch((error) => { + // error... + console.log('Error:', error); +}); + +// OR if you are using the async/await syntax: +try { + const result = await pb.collection('example').getList(1, 50); + console.log('Result:', result); +} catch (error) { + console.log('Error:', error); +} +``` + +The response error is normalized and always returned as `ClientResponseError` object with the following public fields that you could use: +```js +ClientResponseError { + url: string, // requested url + status: number, // response status code + response: { ... }, // the API JSON error response + isAbort: boolean, // is abort/cancellation error + originalError: Error|null, // the original non-normalized error +} +``` + +### Auth store + +The SDK keeps track of the authenticated token and auth model for you via the `pb.authStore` instance. + +##### LocalAuthStore (default) + +The default [`LocalAuthStore`](https://github.com/pocketbase/js-sdk/blob/master/src/stores/LocalAuthStore.ts) uses the browser's `LocalStorage` if available, otherwise - will fallback to runtime/memory (aka. on page refresh or service restart you'll have to authenticate again). + +Conveniently, the default store also takes care to automatically sync the auth store state between multiple tabs. + +> _**NB!** Deno also supports `LocalStorage` but keep in mind that, unlike in browsers where the client is the only user, by default Deno `LocalStorage` will be shared by all clients making requests to your server!_ + +##### AsyncAuthStore + +The SDK comes also with a helper [`AsyncAuthStore`](https://github.com/pocketbase/js-sdk/blob/master/src/stores/AsyncAuthStore.ts) that you can use to integrate with any 3rd party async storage implementation (_usually this is needed when working with React Native_): +```js +import AsyncStorage from '@react-native-async-storage/async-storage'; +import PocketBase, { AsyncAuthStore } from 'pocketbase'; + +const store = new AsyncAuthStore({ + save: async (serialized) => AsyncStorage.setItem('pb_auth', serialized), + initial: AsyncStorage.getItem('pb_auth'), +}); + +const pb = new PocketBase('http://127.0.0.1:8090', store) +``` + +##### Custom auth store + +In some situations it could be easier to create your own custom auth store. For this you can extend [`BaseAuthStore`](https://github.com/pocketbase/js-sdk/blob/master/src/stores/BaseAuthStore.ts) and pass the new custom instance as constructor argument to the client: + +```js +import PocketBase, { BaseAuthStore } from 'pocketbase'; + +class CustomAuthStore extends BaseAuthStore { + save(token, model) { + super.save(token, model); + + // your custom business logic... + } +} + +const pb = new PocketBase('http://127.0.0.1:8090', new CustomAuthStore()); +``` + +##### Common auth store fields and methods + +The default `pb.authStore` extends [`BaseAuthStore`](https://github.com/pocketbase/js-sdk/blob/master/src/stores/BaseAuthStore.ts) and has the following public members that you can use: + +```js +BaseAuthStore { + // base fields + record: RecordModel|null // the authenticated auth record + token: string // the authenticated token + isValid: boolean // checks if the store has existing and unexpired token + isSuperuser: boolean // checks if the store state is for superuser + + // main methods + clear() // "logout" the authenticated record + save(token, record) // update the store with the new auth data + onChange(callback, fireImmediately = false) // register a callback that will be called on store change + + // cookie parse and serialize helpers + loadFromCookie(cookieHeader, key = 'pb_auth') + exportToCookie(options = {}, key = 'pb_auth') +} +``` + +To _"logout"_ the authenticated record you can call `pb.authStore.clear()`. + +To _"listen"_ for changes in the auth store, you can register a new listener via `pb.authStore.onChange`, eg: +```js +// triggered everytime on store change +const removeListener1 = pb.authStore.onChange((token, record) => { + console.log('New store data 1:', token, record) +}); + +// triggered once right after registration and everytime on store change +const removeListener2 = pb.authStore.onChange((token, record) => { + console.log('New store data 2:', token, record) +}, true); + +// (optional) removes the attached listeners +removeListener1(); +removeListener2(); +``` + + +### Auto cancellation + +The SDK client will auto cancel duplicated pending requests for you. +For example, if you have the following 3 duplicated endpoint calls, only the last one will be executed, while the first 2 will be cancelled with `ClientResponseError` error: + +```js +pb.collection('example').getList(1, 20) // cancelled +pb.collection('example').getList(2, 20) // cancelled +pb.collection('example').getList(3, 20) // executed +``` + +To change this behavior per request basis, you can adjust the `requestKey: null|string` special query parameter. +Set it to `null` to unset the default request identifier and to disable auto cancellation for the specific request. +Or set it to a unique string that will be used as request identifier and based on which pending requests will be matched (default to `HTTP_METHOD + path`, eg. "GET /api/users") + +Example: + +```js +pb.collection('example').getList(1, 20); // cancelled +pb.collection('example').getList(1, 20); // executed +pb.collection('example').getList(1, 20, { requestKey: "test" }) // cancelled +pb.collection('example').getList(1, 20, { requestKey: "test" }) // executed +pb.collection('example').getList(1, 20, { requestKey: null }) // executed +pb.collection('example').getList(1, 20, { requestKey: null }) // executed + +// globally disable auto cancellation +pb.autoCancellation(false); + +pb.collection('example').getList(1, 20); // executed +pb.collection('example').getList(1, 20); // executed +pb.collection('example').getList(1, 20); // executed +``` + +**If you want to globally disable the auto cancellation behavior, you could set `pb.autoCancellation(false)`.** + +To manually cancel pending requests, you could use `pb.cancelAllRequests()` or `pb.cancelRequest(requestKey)`. + + +### Specify TypeScript definitions + +You could specify custom TypeScript definitions for your Record models using generics: + +```ts +interface Task { + // type the collection fields you want to use... + id: string; + name: string; +} + +pb.collection('tasks').getList(1, 20) // -> results in Promise> +pb.collection('tasks').getOne("RECORD_ID") // -> results in Promise +``` + +Alternatively, if you don't want to type the generic argument every time you can define a global PocketBase type using type assertion: + +```ts +interface Task { + id: string; + name: string; +} + +interface Post { + id: string; + title: string; + active: boolean; +} + +interface TypedPocketBase extends PocketBase { + collection(idOrName: string): RecordService // default fallback for any other collection + collection(idOrName: 'tasks'): RecordService + collection(idOrName: 'posts'): RecordService +} + +... + +const pb = new PocketBase("http://127.0.0.1:8090") as TypedPocketBase; + +pb.collection('tasks').getOne("RECORD_ID") // -> results in Promise +pb.collection('posts').getOne("RECORD_ID") // -> results in Promise +``` + + +### Custom request options + +All API services accept an optional `options` argument (usually the last one and of type [`SendOptions`](https://github.com/pocketbase/js-sdk/blob/master/src/tools/options.ts)), that can be used to provide: + +- custom headers for a single request +- custom fetch options +- or even your own `fetch` implementation + +For example: + +```js +pb.collection('example').getList(1, 20, { + expand: 'someRel', + otherQueryParam: '123', + + // custom headers + headers: { + 'X-Custom-Header': 'example', + }, + + // custom fetch options + keepalive: false, + cache: 'no-store', + + // or custom fetch implementation + fetch: async (url, config) => { ... }, +}) +``` + +_Note that for backward compatability and to minimize the verbosity, any "unknown" top-level field will be treated as query parameter._ + + +### Send hooks + +Sometimes you may want to modify the request data globally or to customize the response. + +To accomplish this, the SDK provides 2 function hooks: + +- `beforeSend` - triggered right before sending the `fetch` request, allowing you to inspect/modify the request config. + ```js + const pb = new PocketBase('http://127.0.0.1:8090'); + + pb.beforeSend = function (url, options) { + // For list of the possible request options properties check + // https://developer.mozilla.org/en-US/docs/Web/API/fetch#options + options.headers = Object.assign({}, options.headers, { + 'X-Custom-Header': 'example', + }); + + return { url, options }; + }; + + // use the created client as usual... + ``` + +- `afterSend` - triggered after successfully sending the `fetch` request, allowing you to inspect/modify the response object and its parsed data. + ```js + const pb = new PocketBase('http://127.0.0.1:8090'); + + pb.afterSend = function (response, data) { + // do something with the response state + console.log(response.status); + + return Object.assign(data, { + // extend the data... + "additionalField": 123, + }); + }; + + // use the created client as usual... + ``` + +### SSR integration + +Unfortunately, **there is no "one size fits all" solution** because each framework handle SSR differently (_and even in a single framework there is more than one way of doing things_). + +But in general, the idea is to use a cookie based flow: + +1. Create a new `PocketBase` instance for each server-side request +2. "Load/Feed" your `pb.authStore` with data from the request cookie +3. Perform your application server-side actions +4. Before returning the response to the client, update the cookie with the latest `pb.authStore` state + +All [`BaseAuthStore`](https://github.com/pocketbase/js-sdk/blob/master/src/stores/BaseAuthStore.ts) instances have 2 helper methods that +should make working with cookies a little bit easier: + +```js +// update the store with the parsed data from the cookie string +pb.authStore.loadFromCookie('pb_auth=...'); + +// exports the store data as cookie, with option to extend the default SameSite, Secure, HttpOnly, Path and Expires attributes +pb.authStore.exportToCookie({ httpOnly: false }); // Output: 'pb_auth=...' +``` + +Below you could find several examples: + +
+ SvelteKit + +One way to integrate with SvelteKit SSR could be to create the PocketBase client in a [hook handle](https://kit.svelte.dev/docs/hooks#handle) +and pass it to the other server-side actions using the `event.locals`. + +```js +// src/hooks.server.js +import PocketBase from 'pocketbase'; + +/** @type {import('@sveltejs/kit').Handle} */ +export async function handle({ event, resolve }) { + event.locals.pb = new PocketBase('http://127.0.0.1:8090'); + + // load the store data from the request cookie string + event.locals.pb.authStore.loadFromCookie(event.request.headers.get('cookie') || ''); + + try { + // get an up-to-date auth store state by verifying and refreshing the loaded auth model (if any) + event.locals.pb.authStore.isValid && await event.locals.pb.collection('users').authRefresh(); + } catch (_) { + // clear the auth store on failed refresh + event.locals.pb.authStore.clear(); + } + + const response = await resolve(event); + + // send back the default 'pb_auth' cookie to the client with the latest store state + response.headers.append('set-cookie', event.locals.pb.authStore.exportToCookie()); + + return response; +} +``` + +And then, in some of your server-side actions, you could directly access the previously created `event.locals.pb` instance: + +```js +// src/routes/login/+server.js +/** + * Creates a `POST /login` server-side endpoint + * + * @type {import('./$types').RequestHandler} + */ +export async function POST({ request, locals }) { + const { email, password } = await request.json(); + + const { token, record } = await locals.pb.collection('users').authWithPassword(email, password); + + return new Response('Success...'); +} +``` + +For proper `locals.pb` type detection, you can also add `PocketBase` in your your global types definition: + +```ts +// src/app.d.ts +import PocketBase from 'pocketbase'; + +declare global { + declare namespace App { + interface Locals { + pb: PocketBase + } + } +} +``` +
+ +
+ Astro + +To integrate with Astro SSR, you could create the PocketBase client in the [Middleware](https://docs.astro.build/en/guides/middleware) and pass it to the Astro components using the `Astro.locals`. + +```ts +// src/middleware/index.ts +import PocketBase from 'pocketbase'; + +import { defineMiddleware } from 'astro/middleware'; + +export const onRequest = defineMiddleware(async ({ locals, request }: any, next: () => any) => { + locals.pb = new PocketBase('http://127.0.0.1:8090'); + + // load the store data from the request cookie string + locals.pb.authStore.loadFromCookie(request.headers.get('cookie') || ''); + + try { + // get an up-to-date auth store state by verifying and refreshing the loaded auth record (if any) + locals.pb.authStore.isValid && await locals.pb.collection('users').authRefresh(); + } catch (_) { + // clear the auth store on failed refresh + locals.pb.authStore.clear(); + } + + const response = await next(); + + // send back the default 'pb_auth' cookie to the client with the latest store state + response.headers.append('set-cookie', locals.pb.authStore.exportToCookie()); + + return response; +}); +``` + +And then, in your Astro file's component script, you could directly access the previously created `locals.pb` instance: + +```ts +// src/pages/index.astro +--- +const locals = Astro.locals; + +const userAuth = async () => { + const { token, record } = await locals.pb.collection('users').authWithPassword('test@example.com', '123456'); + + return new Response('Success...'); +}; +--- +``` + +Although middleware functionality is available in both `SSG` and `SSR` projects, you would likely want to handle any sensitive data on the server side. Update your `output` configuration to `'server'`: + +```mjs +// astro.config.mjs +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: 'server' +}); +``` +
+ +
+ Nuxt 3 + +One way to integrate with Nuxt 3 SSR could be to create the PocketBase client in a [nuxt plugin](https://v3.nuxtjs.org/guide/directory-structure/plugins) +and provide it as a helper to the `nuxtApp` instance: + +```js +// plugins/pocketbase.js +import PocketBase from 'pocketbase'; + +export default defineNuxtPlugin(async () => { + const pb = new PocketBase('http://127.0.0.1:8090'); + + const cookie = useCookie('pb_auth', { + path: '/', + secure: true, + sameSite: 'strict', + httpOnly: false, // change to "true" if you want only server-side access + maxAge: 604800, + }) + + // load the store data from the cookie value + pb.authStore.save(cookie.value?.token, cookie.value?.record); + + // send back the default 'pb_auth' cookie to the client with the latest store state + pb.authStore.onChange(() => { + cookie.value = { + token: pb.authStore.token, + record: pb.authStore.record, + }; + }); + + try { + // get an up-to-date auth store state by verifying and refreshing the loaded auth model (if any) + pb.authStore.isValid && await pb.collection('users').authRefresh(); + } catch (_) { + // clear the auth store on failed refresh + pb.authStore.clear(); + } + + return { + provide: { pb } + } +}); +``` + +And then in your component you could access it like this: + +```html + + + +``` +
+ +
+ Nuxt 2 + +One way to integrate with Nuxt 2 SSR could be to create the PocketBase client in a [nuxt plugin](https://nuxtjs.org/docs/directory-structure/plugins#plugins-directory) and provide it as a helper to the `$root` context: + +```js +// plugins/pocketbase.js +import PocketBase from 'pocketbase'; + +export default async (ctx, inject) => { + const pb = new PocketBase('http://127.0.0.1:8090'); + + // load the store data from the request cookie string + pb.authStore.loadFromCookie(ctx.req?.headers?.cookie || ''); + + // send back the default 'pb_auth' cookie to the client with the latest store state + pb.authStore.onChange(() => { + ctx.res?.setHeader('set-cookie', pb.authStore.exportToCookie()); + }); + + try { + // get an up-to-date auth store state by verifying and refreshing the loaded auth record (if any) + pb.authStore.isValid && await pb.collection('users').authRefresh(); + } catch (_) { + // clear the auth store on failed refresh + pb.authStore.clear(); + } + + inject('pocketbase', pb); +}; +``` + +And then in your component you could access it like this: + +```html + + + +``` +
+ +
+ Next.js + +Next.js doesn't seem to have a central place where you can read/modify the server request and response. +[There is support for middlewares](https://nextjs.org/docs/advanced-features/middleware), +but they are very limited and, at the time of writing, you can't pass data from a middleware to the `getServerSideProps` functions (https://github.com/vercel/next.js/discussions/31792). + +One way to integrate with Next.js SSR could be to create a custom `PocketBase` instance in each of your `getServerSideProps`: + +```jsx +import PocketBase from 'pocketbase'; + +// you can place this helper in a separate file so that it can be reused +async function initPocketBase(req, res) { + const pb = new PocketBase('http://127.0.0.1:8090'); + + // load the store data from the request cookie string + pb.authStore.loadFromCookie(req?.headers?.cookie || ''); + + // send back the default 'pb_auth' cookie to the client with the latest store state + pb.authStore.onChange(() => { + res?.setHeader('set-cookie', pb.authStore.exportToCookie()); + }); + + try { + // get an up-to-date auth store state by verifying and refreshing the loaded auth record (if any) + pb.authStore.isValid && await pb.collection('users').authRefresh(); + } catch (_) { + // clear the auth store on failed refresh + pb.authStore.clear(); + } + + return pb +} + +export async function getServerSideProps({ req, res }) { + const pb = await initPocketBase(req, res) + + // fetch example records... + const result = await pb.collection('example').getList(1, 30); + + return { + props: { + // ... + }, + } +} + +export default function Home() { + return ( +
Hello world!
+ ) +} +``` +
+ +### Security + +The most common frontend related vulnerability is XSS (and CSRF when dealing with cookies). +Fortunately, modern browsers can detect and mitigate most of this type of attacks if [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) is provided. + +**To prevent a malicious user or 3rd party script to steal your PocketBase auth token, it is recommended to configure a basic CSP for your application (either as `meta` tag or HTTP header).** + +This is out of the scope of the SDK, but you could find more resources about CSP at: + +- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP +- https://content-security-policy.com + + +**Depending on how and where you use the JS SDK, it is also recommended to use the helper `pb.filter(expr, params)` when constructing filter strings with untrusted user input to avoid eventual string injection attacks (see [Binding filter parameters](#binding-filter-parameters)).** + + +## Definitions + +### Creating new client instance + +```js +const pb = new PocketBase(baseURL = '/', authStore = LocalAuthStore); +``` + +### Instance methods + +> Each instance method returns the `PocketBase` instance allowing chaining. + +| Method | Description | +|:----------------------------------|:------------------------------------------------------------------------------| +| `pb.send(path, sendOptions = {})` | Sends an api http request. | +| `pb.autoCancellation(enable)` | Globally enable or disable auto cancellation for pending duplicated requests. | +| `pb.cancelAllRequests()` | Cancels all pending requests. | +| `pb.cancelRequest(cancelKey)` | Cancels single request by its cancellation token key. | +| `pb.buildURL(path)` | Builds a full client url by safely concatenating the provided path. | + + +### Services + +> Each service call returns a `Promise` object with the API response. + +##### RecordService + +###### _Crud handlers_ + +```js +// Returns a paginated records list. +🔓 pb.collection(collectionIdOrName).getList(page = 1, perPage = 30, options = {}); + +// Returns a list with all records batch fetched at once +// (by default 1000 items per request; to change it set the `batch` param). +🔓 pb.collection(collectionIdOrName).getFullList(options = {}); + +// Returns the first found record matching the specified filter. +🔓 pb.collection(collectionIdOrName).getFirstListItem(filter, options = {}); + +// Returns a single record by its id. +🔓 pb.collection(collectionIdOrName).getOne(recordId, options = {}); + +// Creates (aka. register) a new record. +🔓 pb.collection(collectionIdOrName).create(bodyParams = {}, options = {}); + +// Updates an existing record by its id. +🔓 pb.collection(collectionIdOrName).update(recordId, bodyParams = {}, options = {}); + +// Deletes a single record by its id. +🔓 pb.collection(collectionIdOrName).delete(recordId, options = {}); + +``` + +###### _Realtime handlers_ + +```js +// Subscribe to realtime changes to the specified topic ("*" or recordId). +// +// It is safe to subscribe multiple times to the same topic. +// +// You can use the returned UnsubscribeFunc to remove a single registered subscription. +// If you want to remove all subscriptions related to the topic use unsubscribe(topic). +🔓 pb.collection(collectionIdOrName).subscribe(topic, callback, options = {}); + +// Unsubscribe from all registered subscriptions to the specified topic ("*" or recordId). +// If topic is not set, then it will remove all registered collection subscriptions. +🔓 pb.collection(collectionIdOrName).unsubscribe([topic]); +``` + +###### _Auth handlers_ + +> Available only for "auth" type collections. + +```js +// Returns all available application auth methods. +🔓 pb.collection(collectionIdOrName).listAuthMethods(options = {}); + +// Authenticates a record with their username/email and password. +🔓 pb.collection(collectionIdOrName).authWithPassword(usernameOrEmail, password, options = {}); + +// Authenticates a record with an OTP. +🔓 pb.collection(collectionIdOrName).authWithOTP(otpId, password, options = {}); + +// Authenticates a record with OAuth2 provider without custom redirects, deeplinks or even page reload. +🔓 pb.collection(collectionIdOrName).authWithOAuth2(authConfig); + +// Authenticates a record with OAuth2 code. +🔓 pb.collection(collectionIdOrName).authWithOAuth2Code(provider, code, codeVerifier, redirectUrl, createData = {}, options = {}); + +// Refreshes the current authenticated record and auth token. +🔐 pb.collection(collectionIdOrName).authRefresh(options = {}); + +// Sends a record OTP email request. +🔓 pb.collection(collectionIdOrName).requestOTP(email, options = {}); + +// Sends a record password reset email. +🔓 pb.collection(collectionIdOrName).requestPasswordReset(email, options = {}); + +// Confirms a record password reset request. +🔓 pb.collection(collectionIdOrName).confirmPasswordReset(resetToken, newPassword, newPasswordConfirm, options = {}); + +// Sends a record verification email request. +🔓 pb.collection(collectionIdOrName).requestVerification(email, options = {}); + +// Confirms a record email verification request. +🔓 pb.collection(collectionIdOrName).confirmVerification(verificationToken, options = {}); + +// Sends a record email change request to the provider email. +🔐 pb.collection(collectionIdOrName).requestEmailChange(newEmail, options = {}); + +// Confirms record new email address. +🔓 pb.collection(collectionIdOrName).confirmEmailChange(emailChangeToken, userPassword, options = {}); + +// Lists all linked external auth providers for the specified record. +🔐 pb.collection(collectionIdOrName).listExternalAuths(recordId, options = {}); + +// Unlinks a single external auth provider relation from the specified record. +🔐 pb.collection(collectionIdOrName).unlinkExternalAuth(recordId, provider, options = {}); + +// Impersonate authenticates with the specified recordId and returns a new client with the received auth token in a memory store. +🔐 pb.collection(collectionIdOrName).impersonate(recordId, duration, options = {}); +``` + +--- + +#### BatchService + +```js +// create a new batch instance +const batch = pb.createBatch(); + +// register create/update/delete/upsert requests to the created batch +batch.collection('example1').create({ ... }); +batch.collection('example2').update('RECORD_ID', { ... }); +batch.collection('example3').delete('RECORD_ID'); +batch.collection('example4').upsert({ ... }); + +// send the batch request +const result = await batch.send() +``` + +--- + +##### FileService + +```js +// Builds and returns an absolute record file url for the provided filename. +🔓 pb.files.getURL(record, filename, options = {}); + +// Requests a new private file access token for the current authenticated record. +🔐 pb.files.getToken(options = {}); +``` + +--- + +##### CollectionService + +```js +// Returns a paginated collections list. +🔐 pb.collections.getList(page = 1, perPage = 30, options = {}); + +// Returns a list with all collections batch fetched at once +// (by default 200 items per request; to change it set the `batch` query param). +🔐 pb.collections.getFullList(options = {}); + +// Returns the first found collection matching the specified filter. +🔐 pb.collections.getFirstListItem(filter, options = {}); + +// Returns a single collection by its id or name. +🔐 pb.collections.getOne(idOrName, options = {}); + +// Creates (aka. register) a new collection. +🔐 pb.collections.create(bodyParams = {}, options = {}); + +// Updates an existing collection by its id or name. +🔐 pb.collections.update(idOrName, bodyParams = {}, options = {}); + +// Deletes a single collection by its id or name. +🔐 pb.collections.delete(idOrName, options = {}); + +// Deletes all records associated with the specified collection. +🔐 pb.collections.truncate(idOrName, options = {}); + +// Imports the provided collections. +🔐 pb.collections.import(collections, deleteMissing = false, options = {}); + +// Returns type indexed map with scaffolded collection models populated with their default field values. +🔐 pb.collections.getScaffolds(options = {}); +``` + +--- + +##### LogService + +```js +// Returns a paginated logs list. +🔐 pb.logs.getList(page = 1, perPage = 30, options = {}); + +// Returns a single log by its id. +🔐 pb.logs.getOne(id, options = {}); + +// Returns logs statistics. +🔐 pb.logs.getStats(options = {}); +``` + +--- + +##### SettingsService + +```js +// Returns a map with all available app settings. +🔐 pb.settings.getAll(options = {}); + +// Bulk updates app settings. +🔐 pb.settings.update(bodyParams = {}, options = {}); + +// Performs a S3 storage connection test. +🔐 pb.settings.testS3(filesystem = "storage", options = {}); + +// Sends a test email (verification, password-reset, email-change). +🔐 pb.settings.testEmail(collectionIdOrName, toEmail, template, options = {}); + +// Generates a new Apple OAuth2 client secret. +🔐 pb.settings.generateAppleClientSecret(clientId, teamId, keyId, privateKey, duration, options = {}); +``` + +--- + +##### RealtimeService + +> This service is usually used with custom realtime actions. +> For records realtime subscriptions you can use the subscribe/unsubscribe +> methods available in the `pb.collection()` RecordService. + +```js +// Initialize the realtime connection (if not already) and register the subscription listener. +// +// You can subscribe to the `PB_CONNECT` event if you want to listen to the realtime connection connect/reconnect events. +🔓 pb.realtime.subscribe(topic, callback, options = {}); + +// Unsubscribe from all subscription listeners with the specified topic. +🔓 pb.realtime.unsubscribe(topic?); + +// Unsubscribe from all subscription listeners starting with the specified topic prefix. +🔓 pb.realtime.unsubscribeByPrefix(topicPrefix); + +// Unsubscribe from all subscriptions matching the specified topic and listener function. +🔓 pb.realtime.unsubscribeByTopicAndListener(topic, callback); + +// Getter that checks whether the realtime connection has been established. +pb.realtime.isConnected + +// An optional hook that is invoked when the realtime client disconnects +// either when unsubscribing from all subscriptions or when the connection +// was interrupted or closed by the server. +// +// Note that the realtime client autoreconnect on its own and this hook is +// useful only for the cases where you want to apply a special behavior on +// server error or after closing the realtime connection. +pb.realtime.onDisconnect = function(activeSubscriptions) +``` + +--- + +##### BackupService + +```js +// Returns list with all available backup files. +🔐 pb.backups.getFullList(options = {}); + +// Initializes a new backup. +🔐 pb.backups.create(basename = "", options = {}); + +// Upload an existing app data backup. +🔐 pb.backups.upload({ file: File/Blob }, options = {}); + +// Deletes a single backup by its name. +🔐 pb.backups.delete(key, options = {}); + +// Initializes an app data restore from an existing backup. +🔐 pb.backups.restore(key, options = {}); + +// Builds a download url for a single existing backup using a +// superuser file token and the backup file key. +🔐 pb.backups.getDownloadURL(token, key); +``` + +##### CronService + +```js +// Returns list with all available cron jobs. +🔐 pb.crons.getFullList(options = {}); + +// Runs the specified cron job. +🔐 pb.crons.run(jobId, options = {}); +``` + +--- + +##### HealthService + +```js +// Checks the health status of the api. +🔓 pb.health.check(options = {}); +``` + + +## Development +```sh +# run unit tests +npm test + +# run prettier +npm run format + +# build and minify for production +npm run build +``` diff --git a/script/node_modules/pocketbase/dist/pocketbase.cjs.d.ts b/script/node_modules/pocketbase/dist/pocketbase.cjs.d.ts new file mode 100644 index 0000000..beeecc0 --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.cjs.d.ts @@ -0,0 +1,1468 @@ +interface SerializeOptions { + encode?: (val: string | number | boolean) => string; + maxAge?: number; + domain?: string; + path?: string; + expires?: Date; + httpOnly?: boolean; + secure?: boolean; + priority?: string; + sameSite?: boolean | string; +} +interface ListResult { + page: number; + perPage: number; + totalItems: number; + totalPages: number; + items: Array; +} +interface BaseModel { + [key: string]: any; + id: string; +} +interface LogModel extends BaseModel { + level: string; + message: string; + created: string; + updated: string; + data: { + [key: string]: any; + }; +} +interface RecordModel extends BaseModel { + collectionId: string; + collectionName: string; + expand?: { + [key: string]: any; + }; +} +// ------------------------------------------------------------------- +// Collection types +// ------------------------------------------------------------------- +interface CollectionField { + [key: string]: any; + id: string; + name: string; + type: string; + system: boolean; + hidden: boolean; + presentable: boolean; +} +interface TokenConfig { + duration: number; + secret?: string; +} +interface AuthAlertConfig { + enabled: boolean; + emailTemplate: EmailTemplate; +} +interface OTPConfig { + enabled: boolean; + duration: number; + length: number; + emailTemplate: EmailTemplate; +} +interface MFAConfig { + enabled: boolean; + duration: number; + rule: string; +} +interface PasswordAuthConfig { + enabled: boolean; + identityFields: Array; +} +interface OAuth2Provider { + pkce?: boolean; + clientId: string; + name: string; + clientSecret: string; + authURL: string; + tokenURL: string; + userInfoURL: string; + displayName: string; + extra?: { + [key: string]: any; + }; +} +interface OAuth2Config { + enabled: boolean; + mappedFields: { + [key: string]: string; + }; + providers: Array; +} +interface EmailTemplate { + subject: string; + body: string; +} +interface collection extends BaseModel { + name: string; + fields: Array; + indexes: Array; + system: boolean; + listRule?: string; + viewRule?: string; + createRule?: string; + updateRule?: string; + deleteRule?: string; +} +interface BaseCollectionModel extends collection { + type: "base"; +} +interface ViewCollectionModel extends collection { + type: "view"; + viewQuery: string; +} +interface AuthCollectionModel extends collection { + type: "auth"; + authRule?: string; + manageRule?: string; + authAlert: AuthAlertConfig; + oauth2: OAuth2Config; + passwordAuth: PasswordAuthConfig; + mfa: MFAConfig; + otp: OTPConfig; + authToken: TokenConfig; + passwordResetToken: TokenConfig; + emailChangeToken: TokenConfig; + verificationToken: TokenConfig; + fileToken: TokenConfig; + verificationTemplate: EmailTemplate; + resetPasswordTemplate: EmailTemplate; + confirmEmailChangeTemplate: EmailTemplate; +} +type CollectionModel = BaseCollectionModel | ViewCollectionModel | AuthCollectionModel; +type AuthRecord = RecordModel | null; +// for backward compatibility +type OnStoreChangeFunc = (token: string, record: AuthRecord) => void; +/** + * Base AuthStore class that stores the auth state in runtime memory (aka. only for the duration of the store instane). + * + * Usually you wouldn't use it directly and instead use the builtin LocalAuthStore, AsyncAuthStore + * or extend it with your own custom implementation. + */ +declare class BaseAuthStore { + protected baseToken: string; + protected baseModel: AuthRecord; + private _onChangeCallbacks; + /** + * Retrieves the stored token (if any). + */ + get token(): string; + /** + * Retrieves the stored model data (if any). + */ + get record(): AuthRecord; + /** + * @deprecated use `record` instead. + */ + get model(): AuthRecord; + /** + * Loosely checks if the store has valid token (aka. existing and unexpired exp claim). + */ + get isValid(): boolean; + /** + * Loosely checks whether the currently loaded store state is for superuser. + * + * Alternatively you can also compare directly `pb.authStore.record?.collectionName`. + */ + get isSuperuser(): boolean; + /** + * @deprecated use `isSuperuser` instead or simply check the record.collectionName property. + */ + get isAdmin(): boolean; + /** + * @deprecated use `!isSuperuser` instead or simply check the record.collectionName property. + */ + get isAuthRecord(): boolean; + /** + * Saves the provided new token and model data in the auth store. + */ + save(token: string, record?: AuthRecord): void; + /** + * Removes the stored token and model data form the auth store. + */ + clear(): void; + /** + * Parses the provided cookie string and updates the store state + * with the cookie's token and model data. + * + * NB! This function doesn't validate the token or its data. + * Usually this isn't a concern if you are interacting only with the + * PocketBase API because it has the proper server-side security checks in place, + * but if you are using the store `isValid` state for permission controls + * in a node server (eg. SSR), then it is recommended to call `authRefresh()` + * after loading the cookie to ensure an up-to-date token and model state. + * For example: + * + * ```js + * pb.authStore.loadFromCookie("cookie string..."); + * + * try { + * // get an up-to-date auth store state by veryfing and refreshing the loaded auth model (if any) + * pb.authStore.isValid && await pb.collection('users').authRefresh(); + * } catch (_) { + * // clear the auth store on failed refresh + * pb.authStore.clear(); + * } + * ``` + */ + loadFromCookie(cookie: string, key?: string): void; + /** + * Exports the current store state as cookie string. + * + * By default the following optional attributes are added: + * - Secure + * - HttpOnly + * - SameSite=Strict + * - Path=/ + * - Expires={the token expiration date} + * + * NB! If the generated cookie exceeds 4096 bytes, this method will + * strip the model data to the bare minimum to try to fit within the + * recommended size in https://www.rfc-editor.org/rfc/rfc6265#section-6.1. + */ + exportToCookie(options?: SerializeOptions, key?: string): string; + /** + * Register a callback function that will be called on store change. + * + * You can set the `fireImmediately` argument to true in order to invoke + * the provided callback right after registration. + * + * Returns a removal function that you could call to "unsubscribe" from the changes. + */ + onChange(callback: OnStoreChangeFunc, fireImmediately?: boolean): () => void; + protected triggerChange(): void; +} +/** + * BaseService class that should be inherited from all API services. + */ +declare abstract class BaseService { + readonly client: Client; + constructor(client: Client); +} +interface SendOptions extends RequestInit { + // for backward compatibility and to minimize the verbosity, + // any top-level field that doesn't exist in RequestInit or the + // fields below will be treated as query parameter. + [key: string]: any; + /** + * Optional custom fetch function to use for sending the request. + */ + fetch?: (url: RequestInfo | URL, config?: RequestInit) => Promise; + /** + * Custom headers to send with the requests. + */ + headers?: { + [key: string]: string; + }; + /** + * The body of the request (serialized automatically for json requests). + */ + body?: any; + /** + * Query parameters that will be appended to the request url. + */ + query?: { + [key: string]: any; + }; + /** + * @deprecated use `query` instead + * + * for backward-compatibility `params` values are merged with `query`, + * but this option may get removed in the final v1 release + */ + params?: { + [key: string]: any; + }; + /** + * The request identifier that can be used to cancel pending requests. + */ + requestKey?: string | null; + /** + * @deprecated use `requestKey:string` instead + */ + $cancelKey?: string; + /** + * @deprecated use `requestKey:null` instead + */ + $autoCancel?: boolean; +} +interface CommonOptions extends SendOptions { + fields?: string; +} +interface ListOptions extends CommonOptions { + page?: number; + perPage?: number; + sort?: string; + filter?: string; + skipTotal?: boolean; +} +interface FullListOptions extends ListOptions { + batch?: number; +} +interface RecordOptions extends CommonOptions { + expand?: string; +} +interface RecordListOptions extends ListOptions, RecordOptions { +} +interface RecordFullListOptions extends FullListOptions, RecordOptions { +} +interface RecordSubscribeOptions extends SendOptions { + fields?: string; + filter?: string; + expand?: string; +} +interface LogStatsOptions extends CommonOptions { + filter?: string; +} +interface FileOptions extends CommonOptions { + thumb?: string; + download?: boolean; +} +interface appleClientSecret { + secret: string; +} +declare class SettingsService extends BaseService { + /** + * Fetch all available app settings. + * + * @throws {ClientResponseError} + */ + getAll(options?: CommonOptions): Promise<{ + [key: string]: any; + }>; + /** + * Bulk updates app settings. + * + * @throws {ClientResponseError} + */ + update(bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise<{ + [key: string]: any; + }>; + /** + * Performs a S3 filesystem connection test. + * + * The currently supported `filesystem` are "storage" and "backups". + * + * @throws {ClientResponseError} + */ + testS3(filesystem?: string, options?: CommonOptions): Promise; + /** + * Sends a test email. + * + * The possible `emailTemplate` values are: + * - verification + * - password-reset + * - email-change + * + * @throws {ClientResponseError} + */ + testEmail(collectionIdOrName: string, toEmail: string, emailTemplate: string, options?: CommonOptions): Promise; + /** + * Generates a new Apple OAuth2 client secret. + * + * @throws {ClientResponseError} + */ + generateAppleClientSecret(clientId: string, teamId: string, keyId: string, privateKey: string, duration: number, options?: CommonOptions): Promise; +} +type UnsubscribeFunc = () => Promise; +declare class RealtimeService extends BaseService { + clientId: string; + private eventSource; + private subscriptions; + private lastSentSubscriptions; + private connectTimeoutId; + private maxConnectTimeout; + private reconnectTimeoutId; + private reconnectAttempts; + private maxReconnectAttempts; + private predefinedReconnectIntervals; + private pendingConnects; + /** + * Returns whether the realtime connection has been established. + */ + get isConnected(): boolean; + /** + * An optional hook that is invoked when the realtime client disconnects + * either when unsubscribing from all subscriptions or when the + * connection was interrupted or closed by the server. + * + * The received argument could be used to determine whether the disconnect + * is a result from unsubscribing (`activeSubscriptions.length == 0`) + * or because of network/server error (`activeSubscriptions.length > 0`). + * + * If you want to listen for the opposite, aka. when the client connection is established, + * subscribe to the `PB_CONNECT` event. + */ + onDisconnect?: (activeSubscriptions: Array) => void; + /** + * Register the subscription listener. + * + * You can subscribe multiple times to the same topic. + * + * If the SSE connection is not started yet, + * this method will also initialize it. + */ + subscribe(topic: string, callback: (data: any) => void, options?: SendOptions): Promise; + /** + * Unsubscribe from all subscription listeners with the specified topic. + * + * If `topic` is not provided, then this method will unsubscribe + * from all active subscriptions. + * + * This method is no-op if there are no active subscriptions. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribe(topic?: string): Promise; + /** + * Unsubscribe from all subscription listeners starting with the specified topic prefix. + * + * This method is no-op if there are no active subscriptions with the specified topic prefix. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribeByPrefix(keyPrefix: string): Promise; + /** + * Unsubscribe from all subscriptions matching the specified topic and listener function. + * + * This method is no-op if there are no active subscription with + * the specified topic and listener. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribeByTopicAndListener(topic: string, listener: EventListener): Promise; + private hasSubscriptionListeners; + private submitSubscriptions; + private getSubscriptionsCancelKey; + private getSubscriptionsByTopic; + private getNonEmptySubscriptionKeys; + private addAllSubscriptionListeners; + private removeAllSubscriptionListeners; + private connect; + private initConnect; + private hasUnsentSubscriptions; + private connectErrorHandler; + private disconnect; +} +declare abstract class CrudService extends BaseService { + /** + * Base path for the crud actions (without trailing slash, eg. '/admins'). + */ + abstract get baseCrudPath(): string; + /** + * Response data decoder. + */ + decode(data: { + [key: string]: any; + }): T; + /** + * Returns a promise with all list items batch fetched at once + * (by default 1000 items per request; to change it set the `batch` query param). + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + getFullList(options?: FullListOptions): Promise>; + /** + * Legacy version of getFullList with explicitly specified batch size. + */ + getFullList(batch?: number, options?: ListOptions): Promise>; + /** + * Returns paginated items list. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + getList(page?: number, perPage?: number, options?: ListOptions): Promise>; + /** + * Returns the first found item by the specified filter. + * + * Internally it calls `getList(1, 1, { filter, skipTotal })` and + * returns the first found item. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * For consistency with `getOne`, this method will throw a 404 + * ClientResponseError if no item was found. + * + * @throws {ClientResponseError} + */ + getFirstListItem(filter: string, options?: CommonOptions): Promise; + /** + * Returns single item by its id. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * If `id` is empty it will throw a 404 error. + * + * @throws {ClientResponseError} + */ + getOne(id: string, options?: CommonOptions): Promise; + /** + * Creates a new item. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Updates an existing item by its id. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Deletes an existing item by its id. + * + * @throws {ClientResponseError} + */ + delete(id: string, options?: CommonOptions): Promise; + /** + * Returns a promise with all list items batch fetched at once. + */ + protected _getFullList(batchSize?: number, options?: ListOptions): Promise>; +} +interface RecordAuthResponse { + /** + * The signed PocketBase auth record. + */ + record: T; + /** + * The PocketBase record auth token. + * + * If you are looking for the OAuth2 access and refresh tokens + * they are available under the `meta.accessToken` and `meta.refreshToken` props. + */ + token: string; + /** + * Auth meta data usually filled when OAuth2 is used. + */ + meta?: { + [key: string]: any; + }; +} +interface AuthProviderInfo { + name: string; + displayName: string; + state: string; + authURL: string; + codeVerifier: string; + codeChallenge: string; + codeChallengeMethod: string; +} +interface AuthMethodsList { + mfa: { + enabled: boolean; + duration: number; + }; + otp: { + enabled: boolean; + duration: number; + }; + password: { + enabled: boolean; + identityFields: Array; + }; + oauth2: { + enabled: boolean; + providers: Array; + }; +} +interface RecordSubscription { + action: string; // eg. create, update, delete + record: T; +} +type OAuth2UrlCallback = (url: string) => void | Promise; +interface OAuth2AuthConfig extends SendOptions { + // the name of the OAuth2 provider (eg. "google") + provider: string; + // custom scopes to overwrite the default ones + scopes?: Array; + // optional record create data + createData?: { + [key: string]: any; + }; + // optional callback that is triggered after the OAuth2 sign-in/sign-up url generation + urlCallback?: OAuth2UrlCallback; + // optional query params to send with the PocketBase auth request (eg. fields, expand, etc.) + query?: RecordOptions; +} +interface OTPResponse { + otpId: string; +} +declare class RecordService extends CrudService { + readonly collectionIdOrName: string; + constructor(client: Client, collectionIdOrName: string); + /** + * @inheritdoc + */ + get baseCrudPath(): string; + /** + * Returns the current collection service base path. + */ + get baseCollectionPath(): string; + /** + * Returns whether the current service collection is superusers. + */ + get isSuperusers(): boolean; + // --------------------------------------------------------------- + // Realtime handlers + // --------------------------------------------------------------- + /** + * Subscribe to realtime changes to the specified topic ("*" or record id). + * + * If `topic` is the wildcard "*", then this method will subscribe to + * any record changes in the collection. + * + * If `topic` is a record id, then this method will subscribe only + * to changes of the specified record id. + * + * It's OK to subscribe multiple times to the same topic. + * You can use the returned `UnsubscribeFunc` to remove only a single subscription. + * Or use `unsubscribe(topic)` if you want to remove all subscriptions attached to the topic. + */ + subscribe(topic: string, callback: (data: RecordSubscription) => void, options?: RecordSubscribeOptions): Promise; + /** + * Unsubscribe from all subscriptions of the specified topic + * ("*" or record id). + * + * If `topic` is not set, then this method will unsubscribe from + * all subscriptions associated to the current collection. + */ + unsubscribe(topic?: string): Promise; + // --------------------------------------------------------------- + // Crud handers + // --------------------------------------------------------------- + /** + * @inheritdoc + */ + getFullList(options?: RecordFullListOptions): Promise>; + /** + * @inheritdoc + */ + getFullList(batch?: number, options?: RecordListOptions): Promise>; + /** + * @inheritdoc + */ + getList(page?: number, perPage?: number, options?: RecordListOptions): Promise>; + /** + * @inheritdoc + */ + getFirstListItem(filter: string, options?: RecordListOptions): Promise; + /** + * @inheritdoc + */ + getOne(id: string, options?: RecordOptions): Promise; + /** + * @inheritdoc + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): Promise; + /** + * @inheritdoc + * + * If the current `client.authStore.record` matches with the updated id, then + * on success the `client.authStore.record` will be updated with the new response record fields. + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): Promise; + /** + * @inheritdoc + * + * If the current `client.authStore.record` matches with the deleted id, + * then on success the `client.authStore` will be cleared. + */ + delete(id: string, options?: CommonOptions): Promise; + // --------------------------------------------------------------- + // Auth handlers + // --------------------------------------------------------------- + /** + * Prepare successful collection authorization response. + */ + protected authResponse(responseData: any): RecordAuthResponse; + /** + * Returns all available collection auth methods. + * + * @throws {ClientResponseError} + */ + listAuthMethods(options?: CommonOptions): Promise; + /** + * Authenticate a single auth collection record via its username/email and password. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * + * @throws {ClientResponseError} + */ + authWithPassword(usernameOrEmail: string, password: string, options?: RecordOptions): Promise>; + /** + * Authenticate a single auth collection record with OAuth2 code. + * + * If you don't have an OAuth2 code you may also want to check `authWithOAuth2` method. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * - the OAuth2 account data (eg. name, email, avatar, etc.) + * + * @throws {ClientResponseError} + */ + authWithOAuth2Code(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, options?: RecordOptions): Promise>; + /** + * @deprecated + * Consider using authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createdData, options?). + */ + authWithOAuth2Code(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, body?: any, query?: any): Promise>; + /** + * @deprecated This form of authWithOAuth2 is deprecated. + * + * Please use `authWithOAuth2Code()` OR its simplified realtime version + * as shown in https://pocketbase.io/docs/authentication/#oauth2-integration. + */ + authWithOAuth2(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, bodyParams?: { + [key: string]: any; + }, queryParams?: RecordOptions): Promise>; + /** + * Authenticate a single auth collection record with OAuth2 + * **without custom redirects, deeplinks or even page reload**. + * + * This method initializes a one-off realtime subscription and will + * open a popup window with the OAuth2 vendor page to authenticate. + * Once the external OAuth2 sign-in/sign-up flow is completed, the popup + * window will be automatically closed and the OAuth2 data sent back + * to the user through the previously established realtime connection. + * + * You can specify an optional `urlCallback` prop to customize + * the default url `window.open` behavior. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * - the OAuth2 account data (eg. name, email, avatar, etc.) + * + * Example: + * + * ```js + * const authData = await pb.collection("users").authWithOAuth2({ + * provider: "google", + * }) + * ``` + * + * Note1: When creating the OAuth2 app in the provider dashboard + * you have to configure `https://yourdomain.com/api/oauth2-redirect` + * as redirect URL. + * + * Note2: Safari may block the default `urlCallback` popup because + * it doesn't allow `window.open` calls as part of an `async` click functions. + * To workaround this you can either change your click handler to not be marked as `async` + * OR manually call `window.open` before your `async` function and use the + * window reference in your own custom `urlCallback` (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061). + * For example: + * ```js + * + * ... + * document.getElementById("btn").addEventListener("click", () => { + * pb.collection("users").authWithOAuth2({ + * provider: "gitlab", + * }).then((authData) => { + * console.log(authData) + * }).catch((err) => { + * console.log(err, err.originalError); + * }); + * }) + * ``` + * + * @throws {ClientResponseError} + */ + authWithOAuth2(options: OAuth2AuthConfig): Promise>; + /** + * Refreshes the current authenticated record instance and + * returns a new token and record data. + * + * On success this method also automatically updates the client's AuthStore. + * + * @throws {ClientResponseError} + */ + authRefresh(options?: RecordOptions): Promise>; + /** + * @deprecated + * Consider using authRefresh(options?). + */ + authRefresh(body?: any, query?: any): Promise>; + /** + * Sends auth record password reset request. + * + * @throws {ClientResponseError} + */ + requestPasswordReset(email: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestPasswordReset(email, options?). + */ + requestPasswordReset(email: string, body?: any, query?: any): Promise; + /** + * Confirms auth record password reset request. + * + * @throws {ClientResponseError} + */ + confirmPasswordReset(passwordResetToken: string, password: string, passwordConfirm: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmPasswordReset(passwordResetToken, password, passwordConfirm, options?). + */ + confirmPasswordReset(passwordResetToken: string, password: string, passwordConfirm: string, body?: any, query?: any): Promise; + /** + * Sends auth record verification email request. + * + * @throws {ClientResponseError} + */ + requestVerification(email: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestVerification(email, options?). + */ + requestVerification(email: string, body?: any, query?: any): Promise; + /** + * Confirms auth record email verification request. + * + * If the current `client.authStore.record` matches with the auth record from the token, + * then on success the `client.authStore.record.verified` will be updated to `true`. + * + * @throws {ClientResponseError} + */ + confirmVerification(verificationToken: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmVerification(verificationToken, options?). + */ + confirmVerification(verificationToken: string, body?: any, query?: any): Promise; + /** + * Sends an email change request to the authenticated record model. + * + * @throws {ClientResponseError} + */ + requestEmailChange(newEmail: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestEmailChange(newEmail, options?). + */ + requestEmailChange(newEmail: string, body?: any, query?: any): Promise; + /** + * Confirms auth record's new email address. + * + * If the current `client.authStore.record` matches with the auth record from the token, + * then on success the `client.authStore` will be cleared. + * + * @throws {ClientResponseError} + */ + confirmEmailChange(emailChangeToken: string, password: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmEmailChange(emailChangeToken, password, options?). + */ + confirmEmailChange(emailChangeToken: string, password: string, body?: any, query?: any): Promise; + /** + * @deprecated use collection("_externalAuths").* + * + * Lists all linked external auth providers for the specified auth record. + * + * @throws {ClientResponseError} + */ + listExternalAuths(recordId: string, options?: CommonOptions): Promise>; + /** + * @deprecated use collection("_externalAuths").* + * + * Unlink a single external auth provider from the specified auth record. + * + * @throws {ClientResponseError} + */ + unlinkExternalAuth(recordId: string, provider: string, options?: CommonOptions): Promise; + /** + * Sends auth record OTP to the provided email. + * + * @throws {ClientResponseError} + */ + requestOTP(email: string, options?: CommonOptions): Promise; + /** + * Authenticate a single auth collection record via OTP. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * + * @throws {ClientResponseError} + */ + authWithOTP(otpId: string, password: string, options?: CommonOptions): Promise>; + /** + * Impersonate authenticates with the specified recordId and + * returns a new client with the received auth token in a memory store. + * + * If `duration` is 0 the generated auth token will fallback + * to the default collection auth token duration. + * + * This action currently requires superusers privileges. + * + * @throws {ClientResponseError} + */ + impersonate(recordId: string, duration: number, options?: CommonOptions): Promise; + // --------------------------------------------------------------- + // very rudimentary url query params replacement because at the moment + // URL (and URLSearchParams) doesn't seem to be fully supported in React Native + // + // note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html + private _replaceQueryParams; +} +declare class CollectionService extends CrudService { + /** + * @inheritdoc + */ + get baseCrudPath(): string; + /** + * Imports the provided collections. + * + * If `deleteMissing` is `true`, all local collections and their fields, + * that are not present in the imported configuration, WILL BE DELETED + * (including their related records data)! + * + * @throws {ClientResponseError} + */ + import(collections: Array, deleteMissing?: boolean, options?: CommonOptions): Promise; + /** + * Returns type indexed map with scaffolded collection models + * populated with their default field values. + * + * @throws {ClientResponseError} + */ + getScaffolds(options?: CommonOptions): Promise<{ + [key: string]: CollectionModel; + }>; + /** + * Deletes all records associated with the specified collection. + * + * @throws {ClientResponseError} + */ + truncate(collectionIdOrName: string, options?: CommonOptions): Promise; +} +interface HourlyStats { + total: number; + date: string; +} +declare class LogService extends BaseService { + /** + * Returns paginated logs list. + * + * @throws {ClientResponseError} + */ + getList(page?: number, perPage?: number, options?: ListOptions): Promise>; + /** + * Returns a single log by its id. + * + * If `id` is empty it will throw a 404 error. + * + * @throws {ClientResponseError} + */ + getOne(id: string, options?: CommonOptions): Promise; + /** + * Returns logs statistics. + * + * @throws {ClientResponseError} + */ + getStats(options?: LogStatsOptions): Promise>; +} +interface HealthCheckResponse { + code: number; + message: string; + data: { + [key: string]: any; + }; +} +declare class HealthService extends BaseService { + /** + * Checks the health status of the api. + * + * @throws {ClientResponseError} + */ + check(options?: CommonOptions): Promise; +} +declare class FileService extends BaseService { + /** + * @deprecated Please replace with `pb.files.getURL()`. + */ + getUrl(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * Builds and returns an absolute record file url for the provided filename. + */ + getURL(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * Requests a new private file access token for the current auth model. + * + * @throws {ClientResponseError} + */ + getToken(options?: CommonOptions): Promise; +} +interface BackupFileInfo { + key: string; + size: number; + modified: string; +} +declare class BackupService extends BaseService { + /** + * Returns list with all available backup files. + * + * @throws {ClientResponseError} + */ + getFullList(options?: CommonOptions): Promise>; + /** + * Initializes a new backup. + * + * @throws {ClientResponseError} + */ + create(basename: string, options?: CommonOptions): Promise; + /** + * Uploads an existing backup file. + * + * Example: + * + * ```js + * await pb.backups.upload({ + * file: new Blob([...]), + * }); + * ``` + * + * @throws {ClientResponseError} + */ + upload(bodyParams: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Deletes a single backup file. + * + * @throws {ClientResponseError} + */ + delete(key: string, options?: CommonOptions): Promise; + /** + * Initializes an app data restore from an existing backup. + * + * @throws {ClientResponseError} + */ + restore(key: string, options?: CommonOptions): Promise; + /** + * @deprecated Please use `getDownloadURL()`. + */ + getDownloadUrl(token: string, key: string): string; + /** + * Builds a download url for a single existing backup using a + * superuser file token and the backup file key. + * + * The file token can be generated via `pb.files.getToken()`. + */ + getDownloadURL(token: string, key: string): string; +} +interface CronJob { + id: string; + expression: string; +} +declare class CronService extends BaseService { + /** + * Returns list with all registered cron jobs. + * + * @throws {ClientResponseError} + */ + getFullList(options?: CommonOptions): Promise>; + /** + * Runs the specified cron job. + * + * @throws {ClientResponseError} + */ + run(jobId: string, options?: CommonOptions): Promise; +} +interface BatchRequest { + method: string; + url: string; + json?: { + [key: string]: any; + }; + files?: { + [key: string]: Array; + }; + headers?: { + [key: string]: string; + }; +} +interface BatchRequestResult { + status: number; + body: any; +} +declare class BatchService extends BaseService { + private requests; + private subs; + /** + * Starts constructing a batch request entry for the specified collection. + */ + collection(collectionIdOrName: string): SubBatchService; + /** + * Sends the batch requests. + * + * @throws {ClientResponseError} + */ + send(options?: SendOptions): Promise>; +} +declare class SubBatchService { + private requests; + private readonly collectionIdOrName; + constructor(requests: Array, collectionIdOrName: string); + /** + * Registers a record upsert request into the current batch queue. + * + * The request will be executed as update if `bodyParams` have a valid existing record `id` value, otherwise - create. + */ + upsert(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record create request into the current batch queue. + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record update request into the current batch queue. + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record delete request into the current batch queue. + */ + delete(id: string, options?: SendOptions): void; + private prepareRequest; +} +interface BeforeSendResult { + [key: string]: any; + url?: string; + options?: { + [key: string]: any; + }; +} +/** + * PocketBase JS Client. + */ +declare class Client { + /** + * The base PocketBase backend url address (eg. 'http://127.0.0.1.8090'). + */ + baseURL: string; + /** + * Legacy getter alias for baseURL. + * @deprecated Please replace with baseURL. + */ + get baseUrl(): string; + /** + * Legacy setter alias for baseURL. + * @deprecated Please replace with baseURL. + */ + set baseUrl(v: string); + /** + * Hook that get triggered right before sending the fetch request, + * allowing you to inspect and modify the url and request options. + * + * For list of the possible options check https://developer.mozilla.org/en-US/docs/Web/API/fetch#options + * + * You can return a non-empty result object `{ url, options }` to replace the url and request options entirely. + * + * Example: + * ```js + * const pb = new PocketBase("https://example.com") + * + * pb.beforeSend = function (url, options) { + * options.headers = Object.assign({}, options.headers, { + * 'X-Custom-Header': 'example', + * }) + * + * return { url, options } + * } + * + * // use the created client as usual... + * ``` + */ + beforeSend?: (url: string, options: SendOptions) => BeforeSendResult | Promise; + /** + * Hook that get triggered after successfully sending the fetch request, + * allowing you to inspect/modify the response object and its parsed data. + * + * Returns the new Promise resolved `data` that will be returned to the client. + * + * Example: + * ```js + * const pb = new PocketBase("https://example.com") + * + * pb.afterSend = function (response, data, options) { + * if (response.status != 200) { + * throw new ClientResponseError({ + * url: response.url, + * status: response.status, + * response: { ... }, + * }) + * } + * + * return data; + * } + * + * // use the created client as usual... + * ``` + */ + afterSend?: ((response: Response, data: any) => any) & ((response: Response, data: any, options: SendOptions) => any); + /** + * Optional language code (default to `en-US`) that will be sent + * with the requests to the server as `Accept-Language` header. + */ + lang: string; + /** + * A replaceable instance of the local auth store service. + */ + authStore: BaseAuthStore; + /** + * An instance of the service that handles the **Settings APIs**. + */ + readonly settings: SettingsService; + /** + * An instance of the service that handles the **Collection APIs**. + */ + readonly collections: CollectionService; + /** + * An instance of the service that handles the **File APIs**. + */ + readonly files: FileService; + /** + * An instance of the service that handles the **Log APIs**. + */ + readonly logs: LogService; + /** + * An instance of the service that handles the **Realtime APIs**. + */ + readonly realtime: RealtimeService; + /** + * An instance of the service that handles the **Health APIs**. + */ + readonly health: HealthService; + /** + * An instance of the service that handles the **Backup APIs**. + */ + readonly backups: BackupService; + /** + * An instance of the service that handles the **Cron APIs**. + */ + readonly crons: CronService; + private cancelControllers; + private recordServices; + private enableAutoCancellation; + constructor(baseURL?: string, authStore?: BaseAuthStore | null, lang?: string); + /** + * @deprecated + * With PocketBase v0.23.0 admins are converted to a regular auth + * collection named "_superusers", aka. you can use directly collection("_superusers"). + */ + get admins(): RecordService; + /** + * Creates a new batch handler for sending multiple transactional + * create/update/upsert/delete collection requests in one network call. + * + * Example: + * ```js + * const batch = pb.createBatch(); + * + * batch.collection("example1").create({ ... }) + * batch.collection("example2").update("RECORD_ID", { ... }) + * batch.collection("example3").delete("RECORD_ID") + * batch.collection("example4").upsert({ ... }) + * + * await batch.send() + * ``` + */ + /** + * Creates a new batch handler for sending multiple transactional + * create/update/upsert/delete collection requests in one network call. + * + * Example: + * ```js + * const batch = pb.createBatch(); + * + * batch.collection("example1").create({ ... }) + * batch.collection("example2").update("RECORD_ID", { ... }) + * batch.collection("example3").delete("RECORD_ID") + * batch.collection("example4").upsert({ ... }) + * + * await batch.send() + * ``` + */ + createBatch(): BatchService; + /** + * Returns the RecordService associated to the specified collection. + */ + /** + * Returns the RecordService associated to the specified collection. + */ + collection(idOrName: string): RecordService; + /** + * Globally enable or disable auto cancellation for pending duplicated requests. + */ + /** + * Globally enable or disable auto cancellation for pending duplicated requests. + */ + autoCancellation(enable: boolean): Client; + /** + * Cancels single request by its cancellation key. + */ + /** + * Cancels single request by its cancellation key. + */ + cancelRequest(requestKey: string): Client; + /** + * Cancels all pending requests. + */ + /** + * Cancels all pending requests. + */ + cancelAllRequests(): Client; + /** + * Constructs a filter expression with placeholders populated from a parameters object. + * + * Placeholder parameters are defined with the `{:paramName}` notation. + * + * The following parameter values are supported: + * + * - `string` (_single quotes are autoescaped_) + * - `number` + * - `boolean` + * - `Date` object (_stringified into the PocketBase datetime format_) + * - `null` + * - everything else is converted to a string using `JSON.stringify()` + * + * Example: + * + * ```js + * pb.collection("example").getFirstListItem(pb.filter( + * 'title ~ {:title} && created >= {:created}', + * { title: "example", created: new Date()} + * )) + * ``` + */ + /** + * Constructs a filter expression with placeholders populated from a parameters object. + * + * Placeholder parameters are defined with the `{:paramName}` notation. + * + * The following parameter values are supported: + * + * - `string` (_single quotes are autoescaped_) + * - `number` + * - `boolean` + * - `Date` object (_stringified into the PocketBase datetime format_) + * - `null` + * - everything else is converted to a string using `JSON.stringify()` + * + * Example: + * + * ```js + * pb.collection("example").getFirstListItem(pb.filter( + * 'title ~ {:title} && created >= {:created}', + * { title: "example", created: new Date()} + * )) + * ``` + */ + filter(raw: string, params?: { + [key: string]: any; + }): string; + /** + * @deprecated Please use `pb.files.getURL()`. + */ + /** + * @deprecated Please use `pb.files.getURL()`. + */ + getFileUrl(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * @deprecated Please use `pb.buildURL()`. + */ + /** + * @deprecated Please use `pb.buildURL()`. + */ + buildUrl(path: string): string; + /** + * Builds a full client url by safely concatenating the provided path. + */ + /** + * Builds a full client url by safely concatenating the provided path. + */ + buildURL(path: string): string; + /** + * Sends an api http request. + * + * @throws {ClientResponseError} + */ + /** + * Sends an api http request. + * + * @throws {ClientResponseError} + */ + send(path: string, options: SendOptions): Promise; + /** + * Shallow copy the provided object and takes care to initialize + * any options required to preserve the backward compatability. + * + * @param {SendOptions} options + * @return {SendOptions} + */ + /** + * Shallow copy the provided object and takes care to initialize + * any options required to preserve the backward compatability. + * + * @param {SendOptions} options + * @return {SendOptions} + */ + private initSendOptions; + /** + * Extracts the header with the provided name in case-insensitive manner. + * Returns `null` if no header matching the name is found. + */ + /** + * Extracts the header with the provided name in case-insensitive manner. + * Returns `null` if no header matching the name is found. + */ + private getHeader; +} +export { BeforeSendResult, Client as default }; diff --git a/script/node_modules/pocketbase/dist/pocketbase.cjs.js b/script/node_modules/pocketbase/dist/pocketbase.cjs.js new file mode 100644 index 0000000..bcd6758 --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.cjs.js @@ -0,0 +1,2 @@ +"use strict";class ClientResponseError extends Error{constructor(e){super("ClientResponseError"),this.url="",this.status=0,this.response={},this.isAbort=!1,this.originalError=null,Object.setPrototypeOf(this,ClientResponseError.prototype),null!==e&&"object"==typeof e&&(this.originalError=e.originalError,this.url="string"==typeof e.url?e.url:"",this.status="number"==typeof e.status?e.status:0,this.isAbort=!!e.isAbort||"AbortError"===e.name||"Aborted"===e.message,null!==e.response&&"object"==typeof e.response?this.response=e.response:null!==e.data&&"object"==typeof e.data?this.response=e.data:this.response={}),this.originalError||e instanceof ClientResponseError||(this.originalError=e),this.name="ClientResponseError "+this.status,this.message=this.response?.message,this.message||(this.isAbort?this.message="The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).":this.originalError?.cause?.message?.includes("ECONNREFUSED ::1")?this.message="Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).":this.message="Something went wrong."),this.cause=this.originalError}get data(){return this.response}toJSON(){return{...this}}}const e=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function cookieSerialize(t,s,i){const n=Object.assign({},i||{}),r=n.encode||defaultEncode;if(!e.test(t))throw new TypeError("argument name is invalid");const o=r(s);if(o&&!e.test(o))throw new TypeError("argument val is invalid");let a=t+"="+o;if(null!=n.maxAge){const e=n.maxAge-0;if(isNaN(e)||!isFinite(e))throw new TypeError("option maxAge is invalid");a+="; Max-Age="+Math.floor(e)}if(n.domain){if(!e.test(n.domain))throw new TypeError("option domain is invalid");a+="; Domain="+n.domain}if(n.path){if(!e.test(n.path))throw new TypeError("option path is invalid");a+="; Path="+n.path}if(n.expires){if(!function isDate(e){return"[object Date]"===Object.prototype.toString.call(e)||e instanceof Date}(n.expires)||isNaN(n.expires.valueOf()))throw new TypeError("option expires is invalid");a+="; Expires="+n.expires.toUTCString()}if(n.httpOnly&&(a+="; HttpOnly"),n.secure&&(a+="; Secure"),n.priority){switch("string"==typeof n.priority?n.priority.toLowerCase():n.priority){case"low":a+="; Priority=Low";break;case"medium":a+="; Priority=Medium";break;case"high":a+="; Priority=High";break;default:throw new TypeError("option priority is invalid")}}if(n.sameSite){switch("string"==typeof n.sameSite?n.sameSite.toLowerCase():n.sameSite){case!0:a+="; SameSite=Strict";break;case"lax":a+="; SameSite=Lax";break;case"strict":a+="; SameSite=Strict";break;case"none":a+="; SameSite=None";break;default:throw new TypeError("option sameSite is invalid")}}return a}function defaultDecode(e){return-1!==e.indexOf("%")?decodeURIComponent(e):e}function defaultEncode(e){return encodeURIComponent(e)}const t="undefined"!=typeof navigator&&"ReactNative"===navigator.product||"undefined"!=typeof global&&global.HermesInternal;let s;function getTokenPayload(e){if(e)try{const t=decodeURIComponent(s(e.split(".")[1]).split("").map((function(e){return"%"+("00"+e.charCodeAt(0).toString(16)).slice(-2)})).join(""));return JSON.parse(t)||{}}catch(e){}return{}}function isTokenExpired(e,t=0){let s=getTokenPayload(e);return!(Object.keys(s).length>0&&(!s.exp||s.exp-t>Date.now()/1e3))}s="function"!=typeof atob||t?e=>{let t=String(e).replace(/=+$/,"");if(t.length%4==1)throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");for(var s,i,n=0,r=0,o="";i=t.charAt(r++);~i&&(s=n%4?64*s+i:i,n++%4)?o+=String.fromCharCode(255&s>>(-2*n&6)):0)i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(i);return o}:atob;const i="pb_auth";class BaseAuthStore{constructor(){this.baseToken="",this.baseModel=null,this._onChangeCallbacks=[]}get token(){return this.baseToken}get record(){return this.baseModel}get model(){return this.baseModel}get isValid(){return!isTokenExpired(this.token)}get isSuperuser(){let e=getTokenPayload(this.token);return"auth"==e.type&&("_superusers"==this.record?.collectionName||!this.record?.collectionName&&"pbc_3142635823"==e.collectionId)}get isAdmin(){return console.warn("Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName"),this.isSuperuser}get isAuthRecord(){return console.warn("Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName"),"auth"==getTokenPayload(this.token).type&&!this.isSuperuser}save(e,t){this.baseToken=e||"",this.baseModel=t||null,this.triggerChange()}clear(){this.baseToken="",this.baseModel=null,this.triggerChange()}loadFromCookie(e,t=i){const s=function cookieParse(e,t){const s={};if("string"!=typeof e)return s;const i=Object.assign({},{}).decode||defaultDecode;let n=0;for(;n4096){r.record={id:r.record?.id,email:r.record?.email};const s=["collectionId","collectionName","verified"];for(const e in this.record)s.includes(e)&&(r.record[e]=this.record[e]);o=cookieSerialize(t,JSON.stringify(r),e)}return o}onChange(e,t=!1){return this._onChangeCallbacks.push(e),t&&e(this.token,this.record),()=>{for(let t=this._onChangeCallbacks.length-1;t>=0;t--)if(this._onChangeCallbacks[t]==e)return delete this._onChangeCallbacks[t],void this._onChangeCallbacks.splice(t,1)}}triggerChange(){for(const e of this._onChangeCallbacks)e&&e(this.token,this.record)}}class LocalAuthStore extends BaseAuthStore{constructor(e="pocketbase_auth"){super(),this.storageFallback={},this.storageKey=e,this._bindStorageEvent()}get token(){return(this._storageGet(this.storageKey)||{}).token||""}get record(){const e=this._storageGet(this.storageKey)||{};return e.record||e.model||null}get model(){return this.record}save(e,t){this._storageSet(this.storageKey,{token:e,record:t}),super.save(e,t)}clear(){this._storageRemove(this.storageKey),super.clear()}_storageGet(e){if("undefined"!=typeof window&&window?.localStorage){const t=window.localStorage.getItem(e)||"";try{return JSON.parse(t)}catch(e){return t}}return this.storageFallback[e]}_storageSet(e,t){if("undefined"!=typeof window&&window?.localStorage){let s=t;"string"!=typeof t&&(s=JSON.stringify(t)),window.localStorage.setItem(e,s)}else this.storageFallback[e]=t}_storageRemove(e){"undefined"!=typeof window&&window?.localStorage&&window.localStorage?.removeItem(e),delete this.storageFallback[e]}_bindStorageEvent(){"undefined"!=typeof window&&window?.localStorage&&window.addEventListener&&window.addEventListener("storage",(e=>{if(e.key!=this.storageKey)return;const t=this._storageGet(this.storageKey)||{};super.save(t.token||"",t.record||t.model||null)}))}}class BaseService{constructor(e){this.client=e}}class SettingsService extends BaseService{async getAll(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/settings",e)}async update(e,t){return t=Object.assign({method:"PATCH",body:e},t),this.client.send("/api/settings",t)}async testS3(e="storage",t){return t=Object.assign({method:"POST",body:{filesystem:e}},t),this.client.send("/api/settings/test/s3",t).then((()=>!0))}async testEmail(e,t,s,i){return i=Object.assign({method:"POST",body:{email:t,template:s,collection:e}},i),this.client.send("/api/settings/test/email",i).then((()=>!0))}async generateAppleClientSecret(e,t,s,i,n,r){return r=Object.assign({method:"POST",body:{clientId:e,teamId:t,keyId:s,privateKey:i,duration:n}},r),this.client.send("/api/settings/apple/generate-client-secret",r)}}const n=["requestKey","$cancelKey","$autoCancel","fetch","headers","body","query","params","cache","credentials","headers","integrity","keepalive","method","mode","redirect","referrer","referrerPolicy","signal","window"];function normalizeUnknownQueryParams(e){if(e){e.query=e.query||{};for(let t in e)n.includes(t)||(e.query[t]=e[t],delete e[t])}}function serializeQueryParams(e){const t=[];for(const s in e){const i=encodeURIComponent(s),n=Array.isArray(e[s])?e[s]:[e[s]];for(let e of n)e=prepareQueryParamValue(e),null!==e&&t.push(i+"="+e)}return t.join("&")}function prepareQueryParamValue(e){return null==e?null:e instanceof Date?encodeURIComponent(e.toISOString().replace("T"," ")):"object"==typeof e?encodeURIComponent(JSON.stringify(e)):encodeURIComponent(e)}class RealtimeService extends BaseService{constructor(){super(...arguments),this.clientId="",this.eventSource=null,this.subscriptions={},this.lastSentSubscriptions=[],this.maxConnectTimeout=15e3,this.reconnectAttempts=0,this.maxReconnectAttempts=1/0,this.predefinedReconnectIntervals=[200,300,500,1e3,1200,1500,2e3],this.pendingConnects=[]}get isConnected(){return!!this.eventSource&&!!this.clientId&&!this.pendingConnects.length}async subscribe(e,t,s){if(!e)throw new Error("topic must be set.");let i=e;if(s){normalizeUnknownQueryParams(s=Object.assign({},s));const e="options="+encodeURIComponent(JSON.stringify({query:s.query,headers:s.headers}));i+=(i.includes("?")?"&":"?")+e}const listener=function(e){const s=e;let i;try{i=JSON.parse(s?.data)}catch{}t(i||{})};return this.subscriptions[i]||(this.subscriptions[i]=[]),this.subscriptions[i].push(listener),this.isConnected?1===this.subscriptions[i].length?await this.submitSubscriptions():this.eventSource?.addEventListener(i,listener):await this.connect(),async()=>this.unsubscribeByTopicAndListener(e,listener)}async unsubscribe(e){let t=!1;if(e){const s=this.getSubscriptionsByTopic(e);for(let e in s)if(this.hasSubscriptionListeners(e)){for(let t of this.subscriptions[e])this.eventSource?.removeEventListener(e,t);delete this.subscriptions[e],t||(t=!0)}}else this.subscriptions={};this.hasSubscriptionListeners()?t&&await this.submitSubscriptions():this.disconnect()}async unsubscribeByPrefix(e){let t=!1;for(let s in this.subscriptions)if((s+"?").startsWith(e)){t=!0;for(let e of this.subscriptions[s])this.eventSource?.removeEventListener(s,e);delete this.subscriptions[s]}t&&(this.hasSubscriptionListeners()?await this.submitSubscriptions():this.disconnect())}async unsubscribeByTopicAndListener(e,t){let s=!1;const i=this.getSubscriptionsByTopic(e);for(let e in i){if(!Array.isArray(this.subscriptions[e])||!this.subscriptions[e].length)continue;let i=!1;for(let s=this.subscriptions[e].length-1;s>=0;s--)this.subscriptions[e][s]===t&&(i=!0,delete this.subscriptions[e][s],this.subscriptions[e].splice(s,1),this.eventSource?.removeEventListener(e,t));i&&(this.subscriptions[e].length||delete this.subscriptions[e],s||this.hasSubscriptionListeners(e)||(s=!0))}this.hasSubscriptionListeners()?s&&await this.submitSubscriptions():this.disconnect()}hasSubscriptionListeners(e){if(this.subscriptions=this.subscriptions||{},e)return!!this.subscriptions[e]?.length;for(let e in this.subscriptions)if(this.subscriptions[e]?.length)return!0;return!1}async submitSubscriptions(){if(this.clientId)return this.addAllSubscriptionListeners(),this.lastSentSubscriptions=this.getNonEmptySubscriptionKeys(),this.client.send("/api/realtime",{method:"POST",body:{clientId:this.clientId,subscriptions:this.lastSentSubscriptions},requestKey:this.getSubscriptionsCancelKey()}).catch((e=>{if(!e?.isAbort)throw e}))}getSubscriptionsCancelKey(){return"realtime_"+this.clientId}getSubscriptionsByTopic(e){const t={};e=e.includes("?")?e:e+"?";for(let s in this.subscriptions)(s+"?").startsWith(e)&&(t[s]=this.subscriptions[s]);return t}getNonEmptySubscriptionKeys(){const e=[];for(let t in this.subscriptions)this.subscriptions[t].length&&e.push(t);return e}addAllSubscriptionListeners(){if(this.eventSource){this.removeAllSubscriptionListeners();for(let e in this.subscriptions)for(let t of this.subscriptions[e])this.eventSource.addEventListener(e,t)}}removeAllSubscriptionListeners(){if(this.eventSource)for(let e in this.subscriptions)for(let t of this.subscriptions[e])this.eventSource.removeEventListener(e,t)}async connect(){if(!(this.reconnectAttempts>0))return new Promise(((e,t)=>{this.pendingConnects.push({resolve:e,reject:t}),this.pendingConnects.length>1||this.initConnect()}))}initConnect(){this.disconnect(!0),clearTimeout(this.connectTimeoutId),this.connectTimeoutId=setTimeout((()=>{this.connectErrorHandler(new Error("EventSource connect took too long."))}),this.maxConnectTimeout),this.eventSource=new EventSource(this.client.buildURL("/api/realtime")),this.eventSource.onerror=e=>{this.connectErrorHandler(new Error("Failed to establish realtime connection."))},this.eventSource.addEventListener("PB_CONNECT",(e=>{const t=e;this.clientId=t?.lastEventId,this.submitSubscriptions().then((async()=>{let e=3;for(;this.hasUnsentSubscriptions()&&e>0;)e--,await this.submitSubscriptions()})).then((()=>{for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[],this.reconnectAttempts=0,clearTimeout(this.reconnectTimeoutId),clearTimeout(this.connectTimeoutId);const t=this.getSubscriptionsByTopic("PB_CONNECT");for(let s in t)for(let i of t[s])i(e)})).catch((e=>{this.clientId="",this.connectErrorHandler(e)}))}))}hasUnsentSubscriptions(){const e=this.getNonEmptySubscriptionKeys();if(e.length!=this.lastSentSubscriptions.length)return!0;for(const t of e)if(!this.lastSentSubscriptions.includes(t))return!0;return!1}connectErrorHandler(e){if(clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),!this.clientId&&!this.reconnectAttempts||this.reconnectAttempts>this.maxReconnectAttempts){for(let t of this.pendingConnects)t.reject(new ClientResponseError(e));return this.pendingConnects=[],void this.disconnect()}this.disconnect(!0);const t=this.predefinedReconnectIntervals[this.reconnectAttempts]||this.predefinedReconnectIntervals[this.predefinedReconnectIntervals.length-1];this.reconnectAttempts++,this.reconnectTimeoutId=setTimeout((()=>{this.initConnect()}),t)}disconnect(e=!1){if(this.clientId&&this.onDisconnect&&this.onDisconnect(Object.keys(this.subscriptions)),clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),this.removeAllSubscriptionListeners(),this.client.cancelRequest(this.getSubscriptionsCancelKey()),this.eventSource?.close(),this.eventSource=null,this.clientId="",!e){this.reconnectAttempts=0;for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[]}}}class CrudService extends BaseService{decode(e){return e}async getFullList(e,t){if("number"==typeof e)return this._getFullList(e,t);let s=1e3;return(t=Object.assign({},e,t)).batch&&(s=t.batch,delete t.batch),this._getFullList(s,t)}async getList(e=1,t=30,s){return(s=Object.assign({method:"GET"},s)).query=Object.assign({page:e,perPage:t},s.query),this.client.send(this.baseCrudPath,s).then((e=>(e.items=e.items?.map((e=>this.decode(e)))||[],e)))}async getFirstListItem(e,t){return(t=Object.assign({requestKey:"one_by_filter_"+this.baseCrudPath+"_"+e},t)).query=Object.assign({filter:e,skipTotal:1},t.query),this.getList(1,1,t).then((e=>{if(!e?.items?.length)throw new ClientResponseError({status:404,response:{code:404,message:"The requested resource wasn't found.",data:{}}});return e.items[0]}))}async getOne(e,t){if(!e)throw new ClientResponseError({url:this.client.buildURL(this.baseCrudPath+"/"),status:404,response:{code:404,message:"Missing required record id.",data:{}}});return t=Object.assign({method:"GET"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),t).then((e=>this.decode(e)))}async create(e,t){return t=Object.assign({method:"POST",body:e},t),this.client.send(this.baseCrudPath,t).then((e=>this.decode(e)))}async update(e,t,s){return s=Object.assign({method:"PATCH",body:t},s),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),s).then((e=>this.decode(e)))}async delete(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),t).then((()=>!0))}_getFullList(e=1e3,t){(t=t||{}).query=Object.assign({skipTotal:1},t.query);let s=[],request=async i=>this.getList(i,e||1e3,t).then((e=>{const t=e.items;return s=s.concat(t),t.length==e.perPage?request(i+1):s}));return request(1)}}function normalizeLegacyOptionsArgs(e,t,s,i){const n=void 0!==i;return n||void 0!==s?n?(console.warn(e),t.body=Object.assign({},t.body,s),t.query=Object.assign({},t.query,i),t):Object.assign(t,s):t}function resetAutoRefresh(e){e._resetAutoRefresh?.()}class RecordService extends CrudService{constructor(e,t){super(e),this.collectionIdOrName=t}get baseCrudPath(){return this.baseCollectionPath+"/records"}get baseCollectionPath(){return"/api/collections/"+encodeURIComponent(this.collectionIdOrName)}get isSuperusers(){return"_superusers"==this.collectionIdOrName||"_pbc_2773867675"==this.collectionIdOrName}async subscribe(e,t,s){if(!e)throw new Error("Missing topic.");if(!t)throw new Error("Missing subscription callback.");return this.client.realtime.subscribe(this.collectionIdOrName+"/"+e,t,s)}async unsubscribe(e){return e?this.client.realtime.unsubscribe(this.collectionIdOrName+"/"+e):this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName)}async getFullList(e,t){if("number"==typeof e)return super.getFullList(e,t);const s=Object.assign({},e,t);return super.getFullList(s)}async getList(e=1,t=30,s){return super.getList(e,t,s)}async getFirstListItem(e,t){return super.getFirstListItem(e,t)}async getOne(e,t){return super.getOne(e,t)}async create(e,t){return super.create(e,t)}async update(e,t,s){return super.update(e,t,s).then((e=>{if(this.client.authStore.record?.id===e?.id&&(this.client.authStore.record?.collectionId===this.collectionIdOrName||this.client.authStore.record?.collectionName===this.collectionIdOrName)){let t=Object.assign({},this.client.authStore.record.expand),s=Object.assign({},this.client.authStore.record,e);t&&(s.expand=Object.assign(t,e.expand)),this.client.authStore.save(this.client.authStore.token,s)}return e}))}async delete(e,t){return super.delete(e,t).then((t=>(!t||this.client.authStore.record?.id!==e||this.client.authStore.record?.collectionId!==this.collectionIdOrName&&this.client.authStore.record?.collectionName!==this.collectionIdOrName||this.client.authStore.clear(),t)))}authResponse(e){const t=this.decode(e?.record||{});return this.client.authStore.save(e?.token,t),Object.assign({},e,{token:e?.token||"",record:t})}async listAuthMethods(e){return e=Object.assign({method:"GET",fields:"mfa,otp,password,oauth2"},e),this.client.send(this.baseCollectionPath+"/auth-methods",e)}async authWithPassword(e,t,s){let i;s=Object.assign({method:"POST",body:{identity:e,password:t}},s),this.isSuperusers&&(i=s.autoRefreshThreshold,delete s.autoRefreshThreshold,s.autoRefresh||resetAutoRefresh(this.client));let n=await this.client.send(this.baseCollectionPath+"/auth-with-password",s);return n=this.authResponse(n),i&&this.isSuperusers&&function registerAutoRefresh(e,t,s,i){resetAutoRefresh(e);const n=e.beforeSend,r=e.authStore.record,o=e.authStore.onChange(((t,s)=>{(!t||s?.id!=r?.id||(s?.collectionId||r?.collectionId)&&s?.collectionId!=r?.collectionId)&&resetAutoRefresh(e)}));e._resetAutoRefresh=function(){o(),e.beforeSend=n,delete e._resetAutoRefresh},e.beforeSend=async(r,o)=>{const a=e.authStore.token;if(o.query?.autoRefresh)return n?n(r,o):{url:r,sendOptions:o};let c=e.authStore.isValid;if(c&&isTokenExpired(e.authStore.token,t))try{await s()}catch(e){c=!1}c||await i();const l=o.headers||{};for(let t in l)if("authorization"==t.toLowerCase()&&a==l[t]&&e.authStore.token){l[t]=e.authStore.token;break}return o.headers=l,n?n(r,o):{url:r,sendOptions:o}}}(this.client,i,(()=>this.authRefresh({autoRefresh:!0})),(()=>this.authWithPassword(e,t,Object.assign({autoRefresh:!0},s)))),n}async authWithOAuth2Code(e,t,s,i,n,r,o){let a={method:"POST",body:{provider:e,code:t,codeVerifier:s,redirectURL:i,createData:n}};return a=normalizeLegacyOptionsArgs("This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).",a,r,o),this.client.send(this.baseCollectionPath+"/auth-with-oauth2",a).then((e=>this.authResponse(e)))}authWithOAuth2(...e){if(e.length>1||"string"==typeof e?.[0])return console.warn("PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration."),this.authWithOAuth2Code(e?.[0]||"",e?.[1]||"",e?.[2]||"",e?.[3]||"",e?.[4]||{},e?.[5]||{},e?.[6]||{});const t=e?.[0]||{};let s=null;t.urlCallback||(s=openBrowserPopup(void 0));const i=new RealtimeService(this.client);function cleanup(){s?.close(),i.unsubscribe()}const n={},r=t.requestKey;return r&&(n.requestKey=r),this.listAuthMethods(n).then((e=>{const n=e.oauth2.providers.find((e=>e.name===t.provider));if(!n)throw new ClientResponseError(new Error(`Missing or invalid provider "${t.provider}".`));const o=this.client.buildURL("/api/oauth2-redirect");return new Promise((async(e,a)=>{const c=r?this.client.cancelControllers?.[r]:void 0;c&&(c.signal.onabort=()=>{cleanup(),a(new ClientResponseError({isAbort:!0,message:"manually cancelled"}))}),i.onDisconnect=e=>{e.length&&a&&(cleanup(),a(new ClientResponseError(new Error("realtime connection interrupted"))))};try{await i.subscribe("@oauth2",(async s=>{const r=i.clientId;try{if(!s.state||r!==s.state)throw new Error("State parameters don't match.");if(s.error||!s.code)throw new Error("OAuth2 redirect error or missing code: "+s.error);const i=Object.assign({},t);delete i.provider,delete i.scopes,delete i.createData,delete i.urlCallback,c?.signal?.onabort&&(c.signal.onabort=null);const a=await this.authWithOAuth2Code(n.name,s.code,n.codeVerifier,o,t.createData,i);e(a)}catch(e){a(new ClientResponseError(e))}cleanup()}));const r={state:i.clientId};t.scopes?.length&&(r.scope=t.scopes.join(" "));const l=this._replaceQueryParams(n.authURL+o,r);let h=t.urlCallback||function(e){s?s.location.href=e:s=openBrowserPopup(e)};await h(l)}catch(e){c?.signal?.onabort&&(c.signal.onabort=null),cleanup(),a(new ClientResponseError(e))}}))})).catch((e=>{throw cleanup(),e}))}async authRefresh(e,t){let s={method:"POST"};return s=normalizeLegacyOptionsArgs("This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).",s,e,t),this.client.send(this.baseCollectionPath+"/auth-refresh",s).then((e=>this.authResponse(e)))}async requestPasswordReset(e,t,s){let i={method:"POST",body:{email:e}};return i=normalizeLegacyOptionsArgs("This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-password-reset",i).then((()=>!0))}async confirmPasswordReset(e,t,s,i,n){let r={method:"POST",body:{token:e,password:t,passwordConfirm:s}};return r=normalizeLegacyOptionsArgs("This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).",r,i,n),this.client.send(this.baseCollectionPath+"/confirm-password-reset",r).then((()=>!0))}async requestVerification(e,t,s){let i={method:"POST",body:{email:e}};return i=normalizeLegacyOptionsArgs("This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-verification",i).then((()=>!0))}async confirmVerification(e,t,s){let i={method:"POST",body:{token:e}};return i=normalizeLegacyOptionsArgs("This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/confirm-verification",i).then((()=>{const t=getTokenPayload(e),s=this.client.authStore.record;return s&&!s.verified&&s.id===t.id&&s.collectionId===t.collectionId&&(s.verified=!0,this.client.authStore.save(this.client.authStore.token,s)),!0}))}async requestEmailChange(e,t,s){let i={method:"POST",body:{newEmail:e}};return i=normalizeLegacyOptionsArgs("This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-email-change",i).then((()=>!0))}async confirmEmailChange(e,t,s,i){let n={method:"POST",body:{token:e,password:t}};return n=normalizeLegacyOptionsArgs("This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).",n,s,i),this.client.send(this.baseCollectionPath+"/confirm-email-change",n).then((()=>{const t=getTokenPayload(e),s=this.client.authStore.record;return s&&s.id===t.id&&s.collectionId===t.collectionId&&this.client.authStore.clear(),!0}))}async listExternalAuths(e,t){return this.client.collection("_externalAuths").getFullList(Object.assign({},t,{filter:this.client.filter("recordRef = {:id}",{id:e})}))}async unlinkExternalAuth(e,t,s){const i=await this.client.collection("_externalAuths").getFirstListItem(this.client.filter("recordRef = {:recordId} && provider = {:provider}",{recordId:e,provider:t}));return this.client.collection("_externalAuths").delete(i.id,s).then((()=>!0))}async requestOTP(e,t){return t=Object.assign({method:"POST",body:{email:e}},t),this.client.send(this.baseCollectionPath+"/request-otp",t)}async authWithOTP(e,t,s){return s=Object.assign({method:"POST",body:{otpId:e,password:t}},s),this.client.send(this.baseCollectionPath+"/auth-with-otp",s).then((e=>this.authResponse(e)))}async impersonate(e,t,s){(s=Object.assign({method:"POST",body:{duration:t}},s)).headers=s.headers||{},s.headers.Authorization||(s.headers.Authorization=this.client.authStore.token);const i=new Client(this.client.baseURL,new BaseAuthStore,this.client.lang),n=await i.send(this.baseCollectionPath+"/impersonate/"+encodeURIComponent(e),s);return i.authStore.save(n?.token,this.decode(n?.record||{})),i}_replaceQueryParams(e,t={}){let s=e,i="";e.indexOf("?")>=0&&(s=e.substring(0,e.indexOf("?")),i=e.substring(e.indexOf("?")+1));const n={},r=i.split("&");for(const e of r){if(""==e)continue;const t=e.split("=");n[decodeURIComponent(t[0].replace(/\+/g," "))]=decodeURIComponent((t[1]||"").replace(/\+/g," "))}for(let e in t)t.hasOwnProperty(e)&&(null==t[e]?delete n[e]:n[e]=t[e]);i="";for(let e in n)n.hasOwnProperty(e)&&(""!=i&&(i+="&"),i+=encodeURIComponent(e.replace(/%20/g,"+"))+"="+encodeURIComponent(n[e].replace(/%20/g,"+")));return""!=i?s+"?"+i:s}}function openBrowserPopup(e){if("undefined"==typeof window||!window?.open)throw new ClientResponseError(new Error("Not in a browser context - please pass a custom urlCallback function."));let t=1024,s=768,i=window.innerWidth,n=window.innerHeight;t=t>i?i:t,s=s>n?n:s;let r=i/2-t/2,o=n/2-s/2;return window.open(e,"popup_window","width="+t+",height="+s+",top="+o+",left="+r+",resizable,menubar=no")}class CollectionService extends CrudService{get baseCrudPath(){return"/api/collections"}async import(e,t=!1,s){return s=Object.assign({method:"PUT",body:{collections:e,deleteMissing:t}},s),this.client.send(this.baseCrudPath+"/import",s).then((()=>!0))}async getScaffolds(e){return e=Object.assign({method:"GET"},e),this.client.send(this.baseCrudPath+"/meta/scaffolds",e)}async truncate(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e)+"/truncate",t).then((()=>!0))}}class LogService extends BaseService{async getList(e=1,t=30,s){return(s=Object.assign({method:"GET"},s)).query=Object.assign({page:e,perPage:t},s.query),this.client.send("/api/logs",s)}async getOne(e,t){if(!e)throw new ClientResponseError({url:this.client.buildURL("/api/logs/"),status:404,response:{code:404,message:"Missing required log id.",data:{}}});return t=Object.assign({method:"GET"},t),this.client.send("/api/logs/"+encodeURIComponent(e),t)}async getStats(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/logs/stats",e)}}class HealthService extends BaseService{async check(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/health",e)}}class FileService extends BaseService{getUrl(e,t,s={}){return console.warn("Please replace pb.files.getUrl() with pb.files.getURL()"),this.getURL(e,t,s)}getURL(e,t,s={}){if(!t||!e?.id||!e?.collectionId&&!e?.collectionName)return"";const i=[];i.push("api"),i.push("files"),i.push(encodeURIComponent(e.collectionId||e.collectionName)),i.push(encodeURIComponent(e.id)),i.push(encodeURIComponent(t));let n=this.client.buildURL(i.join("/"));!1===s.download&&delete s.download;const r=serializeQueryParams(s);return r&&(n+=(n.includes("?")?"&":"?")+r),n}async getToken(e){return e=Object.assign({method:"POST"},e),this.client.send("/api/files/token",e).then((e=>e?.token||""))}}class BackupService extends BaseService{async getFullList(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/backups",e)}async create(e,t){return t=Object.assign({method:"POST",body:{name:e}},t),this.client.send("/api/backups",t).then((()=>!0))}async upload(e,t){return t=Object.assign({method:"POST",body:e},t),this.client.send("/api/backups/upload",t).then((()=>!0))}async delete(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(`/api/backups/${encodeURIComponent(e)}`,t).then((()=>!0))}async restore(e,t){return t=Object.assign({method:"POST"},t),this.client.send(`/api/backups/${encodeURIComponent(e)}/restore`,t).then((()=>!0))}getDownloadUrl(e,t){return console.warn("Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()"),this.getDownloadURL(e,t)}getDownloadURL(e,t){return this.client.buildURL(`/api/backups/${encodeURIComponent(t)}?token=${encodeURIComponent(e)}`)}}class CronService extends BaseService{async getFullList(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/crons",e)}async run(e,t){return t=Object.assign({method:"POST"},t),this.client.send(`/api/crons/${encodeURIComponent(e)}`,t).then((()=>!0))}}function isFile(e){return"undefined"!=typeof Blob&&e instanceof Blob||"undefined"!=typeof File&&e instanceof File||null!==e&&"object"==typeof e&&e.uri&&("undefined"!=typeof navigator&&"ReactNative"===navigator.product||"undefined"!=typeof global&&global.HermesInternal)}function isFormData(e){return e&&("FormData"===e.constructor?.name||"undefined"!=typeof FormData&&e instanceof FormData)}function hasFileField(e){for(const t in e){const s=Array.isArray(e[t])?e[t]:[e[t]];for(const e of s)if(isFile(e))return!0}return!1}const r=/^[\-\.\d]+$/;function inferFormDataValue(e){if("string"!=typeof e)return e;if("true"==e)return!0;if("false"==e)return!1;if(("-"===e[0]||e[0]>="0"&&e[0]<="9")&&r.test(e)){let t=+e;if(""+t===e)return t}return e}class BatchService extends BaseService{constructor(){super(...arguments),this.requests=[],this.subs={}}collection(e){return this.subs[e]||(this.subs[e]=new SubBatchService(this.requests,e)),this.subs[e]}async send(e){const t=new FormData,s=[];for(let e=0;e{if("@jsonPayload"===s&&"string"==typeof e)try{let s=JSON.parse(e);Object.assign(t,s)}catch(e){console.warn("@jsonPayload error:",e)}else void 0!==t[s]?(Array.isArray(t[s])||(t[s]=[t[s]]),t[s].push(inferFormDataValue(e))):t[s]=inferFormDataValue(e)})),t}(s));for(const t in s){const i=s[t];if(isFile(i))e.files[t]=e.files[t]||[],e.files[t].push(i);else if(Array.isArray(i)){const s=[],n=[];for(const e of i)isFile(e)?s.push(e):n.push(e);if(s.length>0&&s.length==i.length){e.files[t]=e.files[t]||[];for(let i of s)e.files[t].push(i)}else if(e.json[t]=n,s.length>0){let i=t;t.startsWith("+")||t.endsWith("+")||(i+="+"),e.files[i]=e.files[i]||[];for(let t of s)e.files[i].push(t)}}else e.json[t]=i}}}class Client{get baseUrl(){return this.baseURL}set baseUrl(e){this.baseURL=e}constructor(e="/",t,s="en-US"){this.cancelControllers={},this.recordServices={},this.enableAutoCancellation=!0,this.baseURL=e,this.lang=s,t?this.authStore=t:"undefined"!=typeof window&&window.Deno?this.authStore=new BaseAuthStore:this.authStore=new LocalAuthStore,this.collections=new CollectionService(this),this.files=new FileService(this),this.logs=new LogService(this),this.settings=new SettingsService(this),this.realtime=new RealtimeService(this),this.health=new HealthService(this),this.backups=new BackupService(this),this.crons=new CronService(this)}get admins(){return this.collection("_superusers")}createBatch(){return new BatchService(this)}collection(e){return this.recordServices[e]||(this.recordServices[e]=new RecordService(this,e)),this.recordServices[e]}autoCancellation(e){return this.enableAutoCancellation=!!e,this}cancelRequest(e){return this.cancelControllers[e]&&(this.cancelControllers[e].abort(),delete this.cancelControllers[e]),this}cancelAllRequests(){for(let e in this.cancelControllers)this.cancelControllers[e].abort();return this.cancelControllers={},this}filter(e,t){if(!t)return e;for(let s in t){let i=t[s];switch(typeof i){case"boolean":case"number":i=""+i;break;case"string":i="'"+i.replace(/'/g,"\\'")+"'";break;default:i=null===i?"null":i instanceof Date?"'"+i.toISOString().replace("T"," ")+"'":"'"+JSON.stringify(i).replace(/'/g,"\\'")+"'"}e=e.replaceAll("{:"+s+"}",i)}return e}getFileUrl(e,t,s={}){return console.warn("Please replace pb.getFileUrl() with pb.files.getURL()"),this.files.getURL(e,t,s)}buildUrl(e){return console.warn("Please replace pb.buildUrl() with pb.buildURL()"),this.buildURL(e)}buildURL(e){let t=this.baseURL;return"undefined"==typeof window||!window.location||t.startsWith("https://")||t.startsWith("http://")||(t=window.location.origin?.endsWith("/")?window.location.origin.substring(0,window.location.origin.length-1):window.location.origin||"",this.baseURL.startsWith("/")||(t+=window.location.pathname||"/",t+=t.endsWith("/")?"":"/"),t+=this.baseURL),e&&(t+=t.endsWith("/")?"":"/",t+=e.startsWith("/")?e.substring(1):e),t}async send(e,t){t=this.initSendOptions(e,t);let s=this.buildURL(e);if(this.beforeSend){const e=Object.assign({},await this.beforeSend(s,t));void 0!==e.url||void 0!==e.options?(s=e.url||s,t=e.options||t):Object.keys(e).length&&(t=e,console?.warn&&console.warn("Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`."))}if(void 0!==t.query){const e=serializeQueryParams(t.query);e&&(s+=(s.includes("?")?"&":"?")+e),delete t.query}"application/json"==this.getHeader(t.headers,"Content-Type")&&t.body&&"string"!=typeof t.body&&(t.body=JSON.stringify(t.body));return(t.fetch||fetch)(s,t).then((async e=>{let s={};try{s=await e.json()}catch(e){if(t.signal?.aborted||"AbortError"==e?.name||"Aborted"==e?.message)throw e}if(this.afterSend&&(s=await this.afterSend(e,s,t)),e.status>=400)throw new ClientResponseError({url:e.url,status:e.status,data:s});return s})).catch((e=>{throw new ClientResponseError(e)}))}initSendOptions(e,t){if((t=Object.assign({method:"GET"},t)).body=function convertToFormDataIfNeeded(e){if("undefined"==typeof FormData||void 0===e||"object"!=typeof e||null===e||isFormData(e)||!hasFileField(e))return e;const t=new FormData;for(const s in e){const i=e[s];if(void 0!==i)if("object"!=typeof i||hasFileField({data:i})){const e=Array.isArray(i)?i:[i];for(let i of e)t.append(s,i)}else{let e={};e[s]=i,t.append("@jsonPayload",JSON.stringify(e))}}return t}(t.body),normalizeUnknownQueryParams(t),t.query=Object.assign({},t.params,t.query),void 0===t.requestKey&&(!1===t.$autoCancel||!1===t.query.$autoCancel?t.requestKey=null:(t.$cancelKey||t.query.$cancelKey)&&(t.requestKey=t.$cancelKey||t.query.$cancelKey)),delete t.$autoCancel,delete t.query.$autoCancel,delete t.$cancelKey,delete t.query.$cancelKey,null!==this.getHeader(t.headers,"Content-Type")||isFormData(t.body)||(t.headers=Object.assign({},t.headers,{"Content-Type":"application/json"})),null===this.getHeader(t.headers,"Accept-Language")&&(t.headers=Object.assign({},t.headers,{"Accept-Language":this.lang})),this.authStore.token&&null===this.getHeader(t.headers,"Authorization")&&(t.headers=Object.assign({},t.headers,{Authorization:this.authStore.token})),this.enableAutoCancellation&&null!==t.requestKey){const s=t.requestKey||(t.method||"GET")+e;delete t.requestKey,this.cancelRequest(s);const i=new AbortController;this.cancelControllers[s]=i,t.signal=i.signal}return t}getHeader(e,t){e=e||{},t=t.toLowerCase();for(let s in e)if(s.toLowerCase()==t)return e[s];return null}}module.exports=Client; +//# sourceMappingURL=pocketbase.cjs.js.map diff --git a/script/node_modules/pocketbase/dist/pocketbase.cjs.js.map b/script/node_modules/pocketbase/dist/pocketbase.cjs.js.map new file mode 100644 index 0000000..fd49b39 --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.cjs.js.map @@ -0,0 +1 @@ +{"version":3,"file":"pocketbase.cjs.js","sources":["../src/ClientResponseError.ts","../src/tools/cookie.ts","../src/tools/jwt.ts","../src/stores/BaseAuthStore.ts","../src/stores/LocalAuthStore.ts","../src/services/BaseService.ts","../src/services/SettingsService.ts","../src/tools/options.ts","../src/services/RealtimeService.ts","../src/services/CrudService.ts","../src/tools/legacy.ts","../src/tools/refresh.ts","../src/services/RecordService.ts","../src/services/CollectionService.ts","../src/services/LogService.ts","../src/services/HealthService.ts","../src/services/FileService.ts","../src/services/BackupService.ts","../src/services/CronService.ts","../src/tools/formdata.ts","../src/services/BatchService.ts","../src/Client.ts"],"sourcesContent":["/**\n * ClientResponseError is a custom Error class that is intended to wrap\n * and normalize any error thrown by `Client.send()`.\n */\nexport class ClientResponseError extends Error {\n url: string = \"\";\n status: number = 0;\n response: { [key: string]: any } = {};\n isAbort: boolean = false;\n originalError: any = null;\n\n constructor(errData?: any) {\n super(\"ClientResponseError\");\n\n // Set the prototype explicitly.\n // https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, ClientResponseError.prototype);\n\n if (errData !== null && typeof errData === \"object\") {\n this.originalError = errData.originalError;\n this.url = typeof errData.url === \"string\" ? errData.url : \"\";\n this.status = typeof errData.status === \"number\" ? errData.status : 0;\n\n // note: DOMException is not implemented yet in React Native\n // and aborting a fetch throws a plain Error with message \"Aborted\".\n this.isAbort =\n !!errData.isAbort ||\n errData.name === \"AbortError\" ||\n errData.message === \"Aborted\";\n\n if (errData.response !== null && typeof errData.response === \"object\") {\n this.response = errData.response;\n } else if (errData.data !== null && typeof errData.data === \"object\") {\n this.response = errData.data;\n } else {\n this.response = {};\n }\n }\n\n if (!this.originalError && !(errData instanceof ClientResponseError)) {\n this.originalError = errData;\n }\n\n this.name = \"ClientResponseError \" + this.status;\n this.message = this.response?.message;\n if (!this.message) {\n if (this.isAbort) {\n this.message =\n \"The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).\";\n } else if (this.originalError?.cause?.message?.includes(\"ECONNREFUSED ::1\")) {\n this.message =\n \"Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).\";\n } else {\n this.message = \"Something went wrong.\";\n }\n }\n\n // set this.cause so that JS debugging tools can automatically connect\n // the dots between the original error and the wrapped one\n this.cause = this.originalError;\n }\n\n /**\n * Alias for `this.response` for backward compatibility.\n */\n get data() {\n return this.response;\n }\n\n /**\n * Make a POJO's copy of the current error class instance.\n * @see https://github.com/vuex-orm/vuex-orm/issues/255\n */\n toJSON() {\n return { ...this };\n }\n}\n","/**\n * -------------------------------------------------------------------\n * Simple cookie parse and serialize utilities mostly based on the\n * node module https://github.com/jshttp/cookie.\n * -------------------------------------------------------------------\n */\n\n/**\n * RegExp to match field-content in RFC 7230 sec 3.2\n *\n * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]\n * field-vchar = VCHAR / obs-text\n * obs-text = %x80-FF\n */\nconst fieldContentRegExp = /^[\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+$/;\n\nexport interface ParseOptions {\n decode?: (val: string) => string;\n}\n\n/**\n * Parses the given cookie header string into an object\n * The object has the various cookies as keys(names) => values\n */\nexport function cookieParse(str: string, options?: ParseOptions): { [key: string]: any } {\n const result: { [key: string]: any } = {};\n\n if (typeof str !== \"string\") {\n return result;\n }\n\n const opt = Object.assign({}, options || {});\n const decode = opt.decode || defaultDecode;\n\n let index = 0;\n while (index < str.length) {\n const eqIdx = str.indexOf(\"=\", index);\n\n // no more cookie pairs\n if (eqIdx === -1) {\n break;\n }\n\n let endIdx = str.indexOf(\";\", index);\n\n if (endIdx === -1) {\n endIdx = str.length;\n } else if (endIdx < eqIdx) {\n // backtrack on prior semicolon\n index = str.lastIndexOf(\";\", eqIdx - 1) + 1;\n continue;\n }\n\n const key = str.slice(index, eqIdx).trim();\n\n // only assign once\n if (undefined === result[key]) {\n let val = str.slice(eqIdx + 1, endIdx).trim();\n\n // quoted values\n if (val.charCodeAt(0) === 0x22) {\n val = val.slice(1, -1);\n }\n\n try {\n result[key] = decode(val);\n } catch (_) {\n result[key] = val; // no decoding\n }\n }\n\n index = endIdx + 1;\n }\n\n return result;\n}\n\nexport interface SerializeOptions {\n encode?: (val: string | number | boolean) => string;\n maxAge?: number;\n domain?: string;\n path?: string;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n priority?: string;\n sameSite?: boolean | string;\n}\n\n/**\n * Serialize data into a cookie header.\n *\n * Serialize the a name value pair into a cookie string suitable for\n * http headers. An optional options object specified cookie parameters.\n *\n * ```js\n * cookieSerialize('foo', 'bar', { httpOnly: true }) // \"foo=bar; httpOnly\"\n * ```\n */\nexport function cookieSerialize(\n name: string,\n val: string,\n options?: SerializeOptions,\n): string {\n const opt = Object.assign({}, options || {});\n const encode = opt.encode || defaultEncode;\n\n if (!fieldContentRegExp.test(name)) {\n throw new TypeError(\"argument name is invalid\");\n }\n\n const value = encode(val);\n\n if (value && !fieldContentRegExp.test(value)) {\n throw new TypeError(\"argument val is invalid\");\n }\n\n let result = name + \"=\" + value;\n\n if (opt.maxAge != null) {\n const maxAge = opt.maxAge - 0;\n\n if (isNaN(maxAge) || !isFinite(maxAge)) {\n throw new TypeError(\"option maxAge is invalid\");\n }\n\n result += \"; Max-Age=\" + Math.floor(maxAge);\n }\n\n if (opt.domain) {\n if (!fieldContentRegExp.test(opt.domain)) {\n throw new TypeError(\"option domain is invalid\");\n }\n\n result += \"; Domain=\" + opt.domain;\n }\n\n if (opt.path) {\n if (!fieldContentRegExp.test(opt.path)) {\n throw new TypeError(\"option path is invalid\");\n }\n\n result += \"; Path=\" + opt.path;\n }\n\n if (opt.expires) {\n if (!isDate(opt.expires) || isNaN(opt.expires.valueOf())) {\n throw new TypeError(\"option expires is invalid\");\n }\n\n result += \"; Expires=\" + opt.expires.toUTCString();\n }\n\n if (opt.httpOnly) {\n result += \"; HttpOnly\";\n }\n\n if (opt.secure) {\n result += \"; Secure\";\n }\n\n if (opt.priority) {\n const priority =\n typeof opt.priority === \"string\" ? opt.priority.toLowerCase() : opt.priority;\n\n switch (priority) {\n case \"low\":\n result += \"; Priority=Low\";\n break;\n case \"medium\":\n result += \"; Priority=Medium\";\n break;\n case \"high\":\n result += \"; Priority=High\";\n break;\n default:\n throw new TypeError(\"option priority is invalid\");\n }\n }\n\n if (opt.sameSite) {\n const sameSite =\n typeof opt.sameSite === \"string\" ? opt.sameSite.toLowerCase() : opt.sameSite;\n\n switch (sameSite) {\n case true:\n result += \"; SameSite=Strict\";\n break;\n case \"lax\":\n result += \"; SameSite=Lax\";\n break;\n case \"strict\":\n result += \"; SameSite=Strict\";\n break;\n case \"none\":\n result += \"; SameSite=None\";\n break;\n default:\n throw new TypeError(\"option sameSite is invalid\");\n }\n }\n\n return result;\n}\n\n/**\n * Default URL-decode string value function.\n * Optimized to skip native call when no `%`.\n */\nfunction defaultDecode(val: string): string {\n return val.indexOf(\"%\") !== -1 ? decodeURIComponent(val) : val;\n}\n\n/**\n * Default URL-encode value function.\n */\nfunction defaultEncode(val: string | number | boolean): string {\n return encodeURIComponent(val);\n}\n\n/**\n * Determines if value is a Date.\n */\nfunction isDate(val: any): boolean {\n return Object.prototype.toString.call(val) === \"[object Date]\" || val instanceof Date;\n}\n","// @todo remove after https://github.com/reactwg/react-native-releases/issues/287\nconst isReactNative =\n (typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\") ||\n (typeof global !== \"undefined\" && (global as any).HermesInternal);\n\nlet atobPolyfill: Function;\nif (typeof atob === \"function\" && !isReactNative) {\n atobPolyfill = atob;\n} else {\n /**\n * The code was extracted from:\n * https://github.com/davidchambers/Base64.js\n */\n atobPolyfill = (input: any) => {\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\";\n\n let str = String(input).replace(/=+$/, \"\");\n if (str.length % 4 == 1) {\n throw new Error(\n \"'atob' failed: The string to be decoded is not correctly encoded.\",\n );\n }\n\n for (\n // initialize result and counters\n var bc = 0, bs, buffer, idx = 0, output = \"\";\n // get next character\n (buffer = str.charAt(idx++));\n // character found in table? initialize bit storage and add its ascii value;\n ~buffer &&\n ((bs = bc % 4 ? (bs as any) * 64 + buffer : buffer),\n // and if not first of each 4 characters,\n // convert the first 8 bits to one ascii character\n bc++ % 4)\n ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))\n : 0\n ) {\n // try to find character in table (0-63, not found => -1)\n buffer = chars.indexOf(buffer);\n }\n\n return output;\n };\n}\n\n/**\n * Returns JWT token's payload data.\n */\nexport function getTokenPayload(token: string): { [key: string]: any } {\n if (token) {\n try {\n const encodedPayload = decodeURIComponent(\n atobPolyfill(token.split(\".\")[1])\n .split(\"\")\n .map(function (c: string) {\n return \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2);\n })\n .join(\"\"),\n );\n\n return JSON.parse(encodedPayload) || {};\n } catch (e) {}\n }\n\n return {};\n}\n\n/**\n * Checks whether a JWT token is expired or not.\n * Tokens without `exp` payload key are considered valid.\n * Tokens with empty payload (eg. invalid token strings) are considered expired.\n *\n * @param token The token to check.\n * @param [expirationThreshold] Time in seconds that will be subtracted from the token `exp` property.\n */\nexport function isTokenExpired(token: string, expirationThreshold = 0): boolean {\n let payload = getTokenPayload(token);\n\n if (\n Object.keys(payload).length > 0 &&\n (!payload.exp || payload.exp - expirationThreshold > Date.now() / 1000)\n ) {\n return false;\n }\n\n return true;\n}\n","import { cookieParse, cookieSerialize, SerializeOptions } from \"@/tools/cookie\";\nimport { isTokenExpired, getTokenPayload } from \"@/tools/jwt\";\nimport { RecordModel } from \"@/tools/dtos\";\n\nexport type AuthRecord = RecordModel | null;\n\nexport type AuthModel = AuthRecord; // for backward compatibility\n\nexport type OnStoreChangeFunc = (token: string, record: AuthRecord) => void;\n\nconst defaultCookieKey = \"pb_auth\";\n\n/**\n * Base AuthStore class that stores the auth state in runtime memory (aka. only for the duration of the store instane).\n *\n * Usually you wouldn't use it directly and instead use the builtin LocalAuthStore, AsyncAuthStore\n * or extend it with your own custom implementation.\n */\nexport class BaseAuthStore {\n protected baseToken: string = \"\";\n protected baseModel: AuthRecord = null;\n\n private _onChangeCallbacks: Array = [];\n\n /**\n * Retrieves the stored token (if any).\n */\n get token(): string {\n return this.baseToken;\n }\n\n /**\n * Retrieves the stored model data (if any).\n */\n get record(): AuthRecord {\n return this.baseModel;\n }\n\n /**\n * @deprecated use `record` instead.\n */\n get model(): AuthRecord {\n return this.baseModel;\n }\n\n /**\n * Loosely checks if the store has valid token (aka. existing and unexpired exp claim).\n */\n get isValid(): boolean {\n return !isTokenExpired(this.token);\n }\n\n /**\n * Loosely checks whether the currently loaded store state is for superuser.\n *\n * Alternatively you can also compare directly `pb.authStore.record?.collectionName`.\n */\n get isSuperuser(): boolean {\n let payload = getTokenPayload(this.token);\n\n return (\n payload.type == \"auth\" &&\n (this.record?.collectionName == \"_superusers\" ||\n // fallback in case the record field is not populated and assuming\n // that the collection crc32 checksum id wasn't manually changed\n (!this.record?.collectionName &&\n payload.collectionId == \"pbc_3142635823\"))\n );\n }\n\n /**\n * @deprecated use `isSuperuser` instead or simply check the record.collectionName property.\n */\n get isAdmin(): boolean {\n console.warn(\n \"Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName\",\n );\n return this.isSuperuser;\n }\n\n /**\n * @deprecated use `!isSuperuser` instead or simply check the record.collectionName property.\n */\n get isAuthRecord(): boolean {\n console.warn(\n \"Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName\",\n );\n return getTokenPayload(this.token).type == \"auth\" && !this.isSuperuser;\n }\n\n /**\n * Saves the provided new token and model data in the auth store.\n */\n save(token: string, record?: AuthRecord): void {\n this.baseToken = token || \"\";\n this.baseModel = record || null;\n\n this.triggerChange();\n }\n\n /**\n * Removes the stored token and model data form the auth store.\n */\n clear(): void {\n this.baseToken = \"\";\n this.baseModel = null;\n this.triggerChange();\n }\n\n /**\n * Parses the provided cookie string and updates the store state\n * with the cookie's token and model data.\n *\n * NB! This function doesn't validate the token or its data.\n * Usually this isn't a concern if you are interacting only with the\n * PocketBase API because it has the proper server-side security checks in place,\n * but if you are using the store `isValid` state for permission controls\n * in a node server (eg. SSR), then it is recommended to call `authRefresh()`\n * after loading the cookie to ensure an up-to-date token and model state.\n * For example:\n *\n * ```js\n * pb.authStore.loadFromCookie(\"cookie string...\");\n *\n * try {\n * // get an up-to-date auth store state by veryfing and refreshing the loaded auth model (if any)\n * pb.authStore.isValid && await pb.collection('users').authRefresh();\n * } catch (_) {\n * // clear the auth store on failed refresh\n * pb.authStore.clear();\n * }\n * ```\n */\n loadFromCookie(cookie: string, key = defaultCookieKey): void {\n const rawData = cookieParse(cookie || \"\")[key] || \"\";\n\n let data: { [key: string]: any } = {};\n try {\n data = JSON.parse(rawData);\n // normalize\n if (typeof data === null || typeof data !== \"object\" || Array.isArray(data)) {\n data = {};\n }\n } catch (_) {}\n\n this.save(data.token || \"\", data.record || data.model || null);\n }\n\n /**\n * Exports the current store state as cookie string.\n *\n * By default the following optional attributes are added:\n * - Secure\n * - HttpOnly\n * - SameSite=Strict\n * - Path=/\n * - Expires={the token expiration date}\n *\n * NB! If the generated cookie exceeds 4096 bytes, this method will\n * strip the model data to the bare minimum to try to fit within the\n * recommended size in https://www.rfc-editor.org/rfc/rfc6265#section-6.1.\n */\n exportToCookie(options?: SerializeOptions, key = defaultCookieKey): string {\n const defaultOptions: SerializeOptions = {\n secure: true,\n sameSite: true,\n httpOnly: true,\n path: \"/\",\n };\n\n // extract the token expiration date\n const payload = getTokenPayload(this.token);\n if (payload?.exp) {\n defaultOptions.expires = new Date(payload.exp * 1000);\n } else {\n defaultOptions.expires = new Date(\"1970-01-01\");\n }\n\n // merge with the user defined options\n options = Object.assign({}, defaultOptions, options);\n\n const rawData = {\n token: this.token,\n record: this.record ? JSON.parse(JSON.stringify(this.record)) : null,\n };\n\n let result = cookieSerialize(key, JSON.stringify(rawData), options);\n\n const resultLength =\n typeof Blob !== \"undefined\" ? new Blob([result]).size : result.length;\n\n // strip down the model data to the bare minimum\n if (rawData.record && resultLength > 4096) {\n rawData.record = { id: rawData.record?.id, email: rawData.record?.email };\n const extraProps = [\"collectionId\", \"collectionName\", \"verified\"];\n for (const prop in this.record) {\n if (extraProps.includes(prop)) {\n rawData.record[prop] = this.record[prop];\n }\n }\n result = cookieSerialize(key, JSON.stringify(rawData), options);\n }\n\n return result;\n }\n\n /**\n * Register a callback function that will be called on store change.\n *\n * You can set the `fireImmediately` argument to true in order to invoke\n * the provided callback right after registration.\n *\n * Returns a removal function that you could call to \"unsubscribe\" from the changes.\n */\n onChange(callback: OnStoreChangeFunc, fireImmediately = false): () => void {\n this._onChangeCallbacks.push(callback);\n\n if (fireImmediately) {\n callback(this.token, this.record);\n }\n\n return () => {\n for (let i = this._onChangeCallbacks.length - 1; i >= 0; i--) {\n if (this._onChangeCallbacks[i] == callback) {\n delete this._onChangeCallbacks[i]; // removes the function reference\n this._onChangeCallbacks.splice(i, 1); // reindex the array\n return;\n }\n }\n };\n }\n\n protected triggerChange(): void {\n for (const callback of this._onChangeCallbacks) {\n callback && callback(this.token, this.record);\n }\n }\n}\n","import { BaseAuthStore, AuthRecord } from \"@/stores/BaseAuthStore\";\n\n/**\n * The default token store for browsers with auto fallback\n * to runtime/memory if local storage is undefined (e.g. in node env).\n */\nexport class LocalAuthStore extends BaseAuthStore {\n private storageFallback: { [key: string]: any } = {};\n private storageKey: string;\n\n constructor(storageKey = \"pocketbase_auth\") {\n super();\n\n this.storageKey = storageKey;\n\n this._bindStorageEvent();\n }\n\n /**\n * @inheritdoc\n */\n get token(): string {\n const data = this._storageGet(this.storageKey) || {};\n\n return data.token || \"\";\n }\n\n /**\n * @inheritdoc\n */\n get record(): AuthRecord {\n const data = this._storageGet(this.storageKey) || {};\n\n return data.record || data.model || null;\n }\n\n /**\n * @deprecated use `record` instead.\n */\n get model(): AuthRecord {\n return this.record;\n }\n\n /**\n * @inheritdoc\n */\n save(token: string, record?: AuthRecord) {\n this._storageSet(this.storageKey, {\n token: token,\n record: record,\n });\n\n super.save(token, record);\n }\n\n /**\n * @inheritdoc\n */\n clear() {\n this._storageRemove(this.storageKey);\n\n super.clear();\n }\n\n // ---------------------------------------------------------------\n // Internal helpers:\n // ---------------------------------------------------------------\n\n /**\n * Retrieves `key` from the browser's local storage\n * (or runtime/memory if local storage is undefined).\n */\n private _storageGet(key: string): any {\n if (typeof window !== \"undefined\" && window?.localStorage) {\n const rawValue = window.localStorage.getItem(key) || \"\";\n try {\n return JSON.parse(rawValue);\n } catch (e) {\n // not a json\n return rawValue;\n }\n }\n\n // fallback\n return this.storageFallback[key];\n }\n\n /**\n * Stores a new data in the browser's local storage\n * (or runtime/memory if local storage is undefined).\n */\n private _storageSet(key: string, value: any) {\n if (typeof window !== \"undefined\" && window?.localStorage) {\n // store in local storage\n let normalizedVal = value;\n if (typeof value !== \"string\") {\n normalizedVal = JSON.stringify(value);\n }\n window.localStorage.setItem(key, normalizedVal);\n } else {\n // store in fallback\n this.storageFallback[key] = value;\n }\n }\n\n /**\n * Removes `key` from the browser's local storage and the runtime/memory.\n */\n private _storageRemove(key: string) {\n // delete from local storage\n if (typeof window !== \"undefined\" && window?.localStorage) {\n window.localStorage?.removeItem(key);\n }\n\n // delete from fallback\n delete this.storageFallback[key];\n }\n\n /**\n * Updates the current store state on localStorage change.\n */\n private _bindStorageEvent() {\n if (\n typeof window === \"undefined\" ||\n !window?.localStorage ||\n !window.addEventListener\n ) {\n return;\n }\n\n window.addEventListener(\"storage\", (e) => {\n if (e.key != this.storageKey) {\n return;\n }\n\n const data = this._storageGet(this.storageKey) || {};\n\n super.save(data.token || \"\", data.record || data.model || null);\n });\n }\n}\n","import Client from \"@/Client\";\n\n/**\n * BaseService class that should be inherited from all API services.\n */\nexport abstract class BaseService {\n readonly client: Client;\n\n constructor(client: Client) {\n this.client = client;\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\ninterface appleClientSecret {\n secret: string;\n}\n\nexport class SettingsService extends BaseService {\n /**\n * Fetch all available app settings.\n *\n * @throws {ClientResponseError}\n */\n async getAll(options?: CommonOptions): Promise<{ [key: string]: any }> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/settings\", options);\n }\n\n /**\n * Bulk updates app settings.\n *\n * @throws {ClientResponseError}\n */\n async update(\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise<{ [key: string]: any }> {\n options = Object.assign(\n {\n method: \"PATCH\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client.send(\"/api/settings\", options);\n }\n\n /**\n * Performs a S3 filesystem connection test.\n *\n * The currently supported `filesystem` are \"storage\" and \"backups\".\n *\n * @throws {ClientResponseError}\n */\n async testS3(\n filesystem: string = \"storage\",\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n filesystem: filesystem,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/test/s3\", options).then(() => true);\n }\n\n /**\n * Sends a test email.\n *\n * The possible `emailTemplate` values are:\n * - verification\n * - password-reset\n * - email-change\n *\n * @throws {ClientResponseError}\n */\n async testEmail(\n collectionIdOrName: string,\n toEmail: string,\n emailTemplate: string,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n email: toEmail,\n template: emailTemplate,\n collection: collectionIdOrName,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/test/email\", options).then(() => true);\n }\n\n /**\n * Generates a new Apple OAuth2 client secret.\n *\n * @throws {ClientResponseError}\n */\n async generateAppleClientSecret(\n clientId: string,\n teamId: string,\n keyId: string,\n privateKey: string,\n duration: number,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n clientId,\n teamId,\n keyId,\n privateKey,\n duration,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/apple/generate-client-secret\", options);\n }\n}\n","export interface SendOptions extends RequestInit {\n // for backward compatibility and to minimize the verbosity,\n // any top-level field that doesn't exist in RequestInit or the\n // fields below will be treated as query parameter.\n [key: string]: any;\n\n /**\n * Optional custom fetch function to use for sending the request.\n */\n fetch?: (url: RequestInfo | URL, config?: RequestInit) => Promise;\n\n /**\n * Custom headers to send with the requests.\n */\n headers?: { [key: string]: string };\n\n /**\n * The body of the request (serialized automatically for json requests).\n */\n body?: any;\n\n /**\n * Query parameters that will be appended to the request url.\n */\n query?: { [key: string]: any };\n\n /**\n * @deprecated use `query` instead\n *\n * for backward-compatibility `params` values are merged with `query`,\n * but this option may get removed in the final v1 release\n */\n params?: { [key: string]: any };\n\n /**\n * The request identifier that can be used to cancel pending requests.\n */\n requestKey?: string | null;\n\n /**\n * @deprecated use `requestKey:string` instead\n */\n $cancelKey?: string;\n\n /**\n * @deprecated use `requestKey:null` instead\n */\n $autoCancel?: boolean;\n}\n\nexport interface CommonOptions extends SendOptions {\n fields?: string;\n}\n\nexport interface ListOptions extends CommonOptions {\n page?: number;\n perPage?: number;\n sort?: string;\n filter?: string;\n skipTotal?: boolean;\n}\n\nexport interface FullListOptions extends ListOptions {\n batch?: number;\n}\n\nexport interface RecordOptions extends CommonOptions {\n expand?: string;\n}\n\nexport interface RecordListOptions extends ListOptions, RecordOptions {}\n\nexport interface RecordFullListOptions extends FullListOptions, RecordOptions {}\n\nexport interface RecordSubscribeOptions extends SendOptions {\n fields?: string;\n filter?: string;\n expand?: string;\n}\n\nexport interface LogStatsOptions extends CommonOptions {\n filter?: string;\n}\n\nexport interface FileOptions extends CommonOptions {\n thumb?: string;\n download?: boolean;\n}\n\nexport interface AuthOptions extends CommonOptions {\n /**\n * If autoRefreshThreshold is set it will take care to auto refresh\n * when necessary the auth data before each request to ensure that\n * the auth state is always valid.\n *\n * The value must be in seconds, aka. the amount of seconds\n * that will be subtracted from the current token `exp` claim in order\n * to determine whether it is going to expire within the specified time threshold.\n *\n * For example, if you want to auto refresh the token if it is\n * going to expire in the next 30mins (or already has expired),\n * it can be set to `1800`\n */\n autoRefreshThreshold?: number;\n}\n\n// -------------------------------------------------------------------\n\n// list of known SendOptions keys (everything else is treated as query param)\nconst knownSendOptionsKeys = [\n \"requestKey\",\n \"$cancelKey\",\n \"$autoCancel\",\n \"fetch\",\n \"headers\",\n \"body\",\n \"query\",\n \"params\",\n // ---,\n \"cache\",\n \"credentials\",\n \"headers\",\n \"integrity\",\n \"keepalive\",\n \"method\",\n \"mode\",\n \"redirect\",\n \"referrer\",\n \"referrerPolicy\",\n \"signal\",\n \"window\",\n];\n\n// modifies in place the provided options by moving unknown send options as query parameters.\nexport function normalizeUnknownQueryParams(options?: SendOptions): void {\n if (!options) {\n return;\n }\n\n options.query = options.query || {};\n for (let key in options) {\n if (knownSendOptionsKeys.includes(key)) {\n continue;\n }\n\n options.query[key] = options[key];\n delete options[key];\n }\n}\n\nexport function serializeQueryParams(params: { [key: string]: any }): string {\n const result: Array = [];\n\n for (const key in params) {\n const encodedKey = encodeURIComponent(key);\n const arrValue = Array.isArray(params[key]) ? params[key] : [params[key]];\n\n for (let v of arrValue) {\n v = prepareQueryParamValue(v);\n if (v === null) {\n continue;\n }\n result.push(encodedKey + \"=\" + v);\n }\n }\n\n return result.join(\"&\");\n}\n\n// encodes and normalizes the provided query param value.\nfunction prepareQueryParamValue(value: any): null | string {\n if (value === null || typeof value === \"undefined\") {\n return null;\n }\n\n if (value instanceof Date) {\n return encodeURIComponent(value.toISOString().replace(\"T\", \" \"));\n }\n\n if (typeof value === \"object\") {\n return encodeURIComponent(JSON.stringify(value));\n }\n\n return encodeURIComponent(value);\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseService } from \"@/services/BaseService\";\nimport { SendOptions, normalizeUnknownQueryParams } from \"@/tools/options\";\n\ninterface promiseCallbacks {\n resolve: Function;\n reject: Function;\n}\n\ntype Subscriptions = { [key: string]: Array };\n\nexport type UnsubscribeFunc = () => Promise;\n\nexport class RealtimeService extends BaseService {\n clientId: string = \"\";\n\n private eventSource: EventSource | null = null;\n private subscriptions: Subscriptions = {};\n private lastSentSubscriptions: Array = [];\n private connectTimeoutId: any;\n private maxConnectTimeout: number = 15000;\n private reconnectTimeoutId: any;\n private reconnectAttempts: number = 0;\n private maxReconnectAttempts: number = Infinity;\n private predefinedReconnectIntervals: Array = [\n 200, 300, 500, 1000, 1200, 1500, 2000,\n ];\n private pendingConnects: Array = [];\n\n /**\n * Returns whether the realtime connection has been established.\n */\n get isConnected(): boolean {\n return !!this.eventSource && !!this.clientId && !this.pendingConnects.length;\n }\n\n /**\n * An optional hook that is invoked when the realtime client disconnects\n * either when unsubscribing from all subscriptions or when the\n * connection was interrupted or closed by the server.\n *\n * The received argument could be used to determine whether the disconnect\n * is a result from unsubscribing (`activeSubscriptions.length == 0`)\n * or because of network/server error (`activeSubscriptions.length > 0`).\n *\n * If you want to listen for the opposite, aka. when the client connection is established,\n * subscribe to the `PB_CONNECT` event.\n */\n onDisconnect?: (activeSubscriptions: Array) => void;\n\n /**\n * Register the subscription listener.\n *\n * You can subscribe multiple times to the same topic.\n *\n * If the SSE connection is not started yet,\n * this method will also initialize it.\n */\n async subscribe(\n topic: string,\n callback: (data: any) => void,\n options?: SendOptions,\n ): Promise {\n if (!topic) {\n throw new Error(\"topic must be set.\");\n }\n\n let key = topic;\n\n // serialize and append the topic options (if any)\n if (options) {\n options = Object.assign({}, options); // shallow copy\n normalizeUnknownQueryParams(options);\n const serialized =\n \"options=\" +\n encodeURIComponent(\n JSON.stringify({ query: options.query, headers: options.headers }),\n );\n key += (key.includes(\"?\") ? \"&\" : \"?\") + serialized;\n }\n\n const listener = function (e: Event) {\n const msgEvent = e as MessageEvent;\n\n let data;\n try {\n data = JSON.parse(msgEvent?.data);\n } catch {}\n\n callback(data || {});\n };\n\n // store the listener\n if (!this.subscriptions[key]) {\n this.subscriptions[key] = [];\n }\n this.subscriptions[key].push(listener);\n\n if (!this.isConnected) {\n // initialize sse connection\n await this.connect();\n } else if (this.subscriptions[key].length === 1) {\n // send the updated subscriptions (if it is the first for the key)\n await this.submitSubscriptions();\n } else {\n // only register the listener\n this.eventSource?.addEventListener(key, listener);\n }\n\n return async (): Promise => {\n return this.unsubscribeByTopicAndListener(topic, listener);\n };\n }\n\n /**\n * Unsubscribe from all subscription listeners with the specified topic.\n *\n * If `topic` is not provided, then this method will unsubscribe\n * from all active subscriptions.\n *\n * This method is no-op if there are no active subscriptions.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribe(topic?: string): Promise {\n let needToSubmit = false;\n\n if (!topic) {\n // remove all subscriptions\n this.subscriptions = {};\n } else {\n // remove all listeners related to the topic\n const subs = this.getSubscriptionsByTopic(topic);\n for (let key in subs) {\n if (!this.hasSubscriptionListeners(key)) {\n continue; // already unsubscribed\n }\n\n for (let listener of this.subscriptions[key]) {\n this.eventSource?.removeEventListener(key, listener);\n }\n delete this.subscriptions[key];\n\n // mark for subscriptions change submit if there are no other listeners\n if (!needToSubmit) {\n needToSubmit = true;\n }\n }\n }\n\n if (!this.hasSubscriptionListeners()) {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n } else if (needToSubmit) {\n await this.submitSubscriptions();\n }\n }\n\n /**\n * Unsubscribe from all subscription listeners starting with the specified topic prefix.\n *\n * This method is no-op if there are no active subscriptions with the specified topic prefix.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribeByPrefix(keyPrefix: string): Promise {\n let hasAtleastOneTopic = false;\n for (let key in this.subscriptions) {\n // \"?\" so that it can be used as end delimiter for the prefix\n if (!(key + \"?\").startsWith(keyPrefix)) {\n continue;\n }\n\n hasAtleastOneTopic = true;\n for (let listener of this.subscriptions[key]) {\n this.eventSource?.removeEventListener(key, listener);\n }\n delete this.subscriptions[key];\n }\n\n if (!hasAtleastOneTopic) {\n return; // nothing to unsubscribe from\n }\n\n if (this.hasSubscriptionListeners()) {\n // submit the deleted subscriptions\n await this.submitSubscriptions();\n } else {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n }\n }\n\n /**\n * Unsubscribe from all subscriptions matching the specified topic and listener function.\n *\n * This method is no-op if there are no active subscription with\n * the specified topic and listener.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribeByTopicAndListener(\n topic: string,\n listener: EventListener,\n ): Promise {\n let needToSubmit = false;\n\n const subs = this.getSubscriptionsByTopic(topic);\n for (let key in subs) {\n if (\n !Array.isArray(this.subscriptions[key]) ||\n !this.subscriptions[key].length\n ) {\n continue; // already unsubscribed\n }\n\n let exist = false;\n for (let i = this.subscriptions[key].length - 1; i >= 0; i--) {\n if (this.subscriptions[key][i] !== listener) {\n continue;\n }\n\n exist = true; // has at least one matching listener\n delete this.subscriptions[key][i]; // removes the function reference\n this.subscriptions[key].splice(i, 1); // reindex the array\n this.eventSource?.removeEventListener(key, listener);\n }\n if (!exist) {\n continue;\n }\n\n // remove the key from the subscriptions list if there are no other listeners\n if (!this.subscriptions[key].length) {\n delete this.subscriptions[key];\n }\n\n // mark for subscriptions change submit if there are no other listeners\n if (!needToSubmit && !this.hasSubscriptionListeners(key)) {\n needToSubmit = true;\n }\n }\n\n if (!this.hasSubscriptionListeners()) {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n } else if (needToSubmit) {\n await this.submitSubscriptions();\n }\n }\n\n private hasSubscriptionListeners(keyToCheck?: string): boolean {\n this.subscriptions = this.subscriptions || {};\n\n // check the specified key\n if (keyToCheck) {\n return !!this.subscriptions[keyToCheck]?.length;\n }\n\n // check for at least one non-empty subscription\n for (let key in this.subscriptions) {\n if (!!this.subscriptions[key]?.length) {\n return true;\n }\n }\n\n return false;\n }\n\n private async submitSubscriptions(): Promise {\n if (!this.clientId) {\n return; // no client/subscriber\n }\n\n // optimistic update\n this.addAllSubscriptionListeners();\n\n this.lastSentSubscriptions = this.getNonEmptySubscriptionKeys();\n\n return this.client\n .send(\"/api/realtime\", {\n method: \"POST\",\n body: {\n clientId: this.clientId,\n subscriptions: this.lastSentSubscriptions,\n },\n requestKey: this.getSubscriptionsCancelKey(),\n })\n .catch((err) => {\n if (err?.isAbort) {\n return; // silently ignore aborted pending requests\n }\n throw err;\n });\n }\n\n private getSubscriptionsCancelKey(): string {\n return \"realtime_\" + this.clientId;\n }\n\n private getSubscriptionsByTopic(topic: string): Subscriptions {\n const result: Subscriptions = {};\n\n // \"?\" so that it can be used as end delimiter for the topic\n topic = topic.includes(\"?\") ? topic : topic + \"?\";\n\n for (let key in this.subscriptions) {\n if ((key + \"?\").startsWith(topic)) {\n result[key] = this.subscriptions[key];\n }\n }\n\n return result;\n }\n\n private getNonEmptySubscriptionKeys(): Array {\n const result: Array = [];\n\n for (let key in this.subscriptions) {\n if (this.subscriptions[key].length) {\n result.push(key);\n }\n }\n\n return result;\n }\n\n private addAllSubscriptionListeners(): void {\n if (!this.eventSource) {\n return;\n }\n\n this.removeAllSubscriptionListeners();\n\n for (let key in this.subscriptions) {\n for (let listener of this.subscriptions[key]) {\n this.eventSource.addEventListener(key, listener);\n }\n }\n }\n\n private removeAllSubscriptionListeners(): void {\n if (!this.eventSource) {\n return;\n }\n\n for (let key in this.subscriptions) {\n for (let listener of this.subscriptions[key]) {\n this.eventSource.removeEventListener(key, listener);\n }\n }\n }\n\n private async connect(): Promise {\n if (this.reconnectAttempts > 0) {\n // immediately resolve the promise to avoid indefinitely\n // blocking the client during reconnection\n return;\n }\n\n return new Promise((resolve, reject) => {\n this.pendingConnects.push({ resolve, reject });\n\n if (this.pendingConnects.length > 1) {\n // all promises will be resolved once the connection is established\n return;\n }\n\n this.initConnect();\n });\n }\n\n private initConnect() {\n this.disconnect(true);\n\n // wait up to 15s for connect\n clearTimeout(this.connectTimeoutId);\n this.connectTimeoutId = setTimeout(() => {\n this.connectErrorHandler(new Error(\"EventSource connect took too long.\"));\n }, this.maxConnectTimeout);\n\n this.eventSource = new EventSource(this.client.buildURL(\"/api/realtime\"));\n\n this.eventSource.onerror = (_) => {\n this.connectErrorHandler(\n new Error(\"Failed to establish realtime connection.\"),\n );\n };\n\n this.eventSource.addEventListener(\"PB_CONNECT\", (e) => {\n const msgEvent = e as MessageEvent;\n this.clientId = msgEvent?.lastEventId;\n\n this.submitSubscriptions()\n .then(async () => {\n let retries = 3;\n while (this.hasUnsentSubscriptions() && retries > 0) {\n retries--;\n // resubscribe to ensure that the latest topics are submitted\n //\n // This is needed because missed topics could happen on reconnect\n // if after the pending sent `submitSubscriptions()` call another `subscribe()`\n // was made before the submit was able to complete.\n await this.submitSubscriptions();\n }\n })\n .then(() => {\n for (let p of this.pendingConnects) {\n p.resolve();\n }\n\n // reset connect meta\n this.pendingConnects = [];\n this.reconnectAttempts = 0;\n clearTimeout(this.reconnectTimeoutId);\n clearTimeout(this.connectTimeoutId);\n\n // propagate the PB_CONNECT event\n const connectSubs = this.getSubscriptionsByTopic(\"PB_CONNECT\");\n for (let key in connectSubs) {\n for (let listener of connectSubs[key]) {\n listener(e);\n }\n }\n })\n .catch((err) => {\n this.clientId = \"\";\n this.connectErrorHandler(err);\n });\n });\n }\n\n private hasUnsentSubscriptions(): boolean {\n const latestTopics = this.getNonEmptySubscriptionKeys();\n if (latestTopics.length != this.lastSentSubscriptions.length) {\n return true;\n }\n\n for (const t of latestTopics) {\n if (!this.lastSentSubscriptions.includes(t)) {\n return true;\n }\n }\n\n return false;\n }\n\n private connectErrorHandler(err: any) {\n clearTimeout(this.connectTimeoutId);\n clearTimeout(this.reconnectTimeoutId);\n\n if (\n // wasn't previously connected -> direct reject\n (!this.clientId && !this.reconnectAttempts) ||\n // was previously connected but the max reconnection limit has been reached\n this.reconnectAttempts > this.maxReconnectAttempts\n ) {\n for (let p of this.pendingConnects) {\n p.reject(new ClientResponseError(err));\n }\n this.pendingConnects = [];\n this.disconnect();\n return;\n }\n\n // otherwise -> reconnect in the background\n this.disconnect(true);\n const timeout =\n this.predefinedReconnectIntervals[this.reconnectAttempts] ||\n this.predefinedReconnectIntervals[\n this.predefinedReconnectIntervals.length - 1\n ];\n this.reconnectAttempts++;\n this.reconnectTimeoutId = setTimeout(() => {\n this.initConnect();\n }, timeout);\n }\n\n private disconnect(fromReconnect = false): void {\n if (this.clientId && this.onDisconnect) {\n this.onDisconnect(Object.keys(this.subscriptions));\n }\n\n clearTimeout(this.connectTimeoutId);\n clearTimeout(this.reconnectTimeoutId);\n this.removeAllSubscriptionListeners();\n this.client.cancelRequest(this.getSubscriptionsCancelKey());\n this.eventSource?.close();\n this.eventSource = null;\n this.clientId = \"\";\n\n if (!fromReconnect) {\n this.reconnectAttempts = 0;\n\n // resolve any remaining connect promises\n //\n // this is done to avoid unnecessary throwing errors in case\n // unsubscribe is called before the pending connect promises complete\n // (see https://github.com/pocketbase/pocketbase/discussions/2897#discussioncomment-6423818)\n for (let p of this.pendingConnects) {\n p.resolve();\n }\n this.pendingConnects = [];\n }\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { ClientResponseError } from \"@/ClientResponseError\";\nimport { ListResult } from \"@/tools/dtos\";\nimport { CommonOptions, ListOptions, FullListOptions } from \"@/tools/options\";\n\nexport abstract class CrudService extends BaseService {\n /**\n * Base path for the crud actions (without trailing slash, eg. '/admins').\n */\n abstract get baseCrudPath(): string;\n\n /**\n * Response data decoder.\n */\n decode(data: { [key: string]: any }): T {\n return data as T;\n }\n\n /**\n * Returns a promise with all list items batch fetched at once\n * (by default 1000 items per request; to change it set the `batch` query param).\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: FullListOptions): Promise>;\n\n /**\n * Legacy version of getFullList with explicitly specified batch size.\n */\n async getFullList(batch?: number, options?: ListOptions): Promise>;\n\n async getFullList(\n batchOrqueryParams?: number | FullListOptions,\n options?: ListOptions,\n ): Promise> {\n if (typeof batchOrqueryParams == \"number\") {\n return this._getFullList(batchOrqueryParams, options);\n }\n\n options = Object.assign({}, batchOrqueryParams, options);\n\n let batch = 1000;\n if (options.batch) {\n batch = options.batch;\n delete options.batch;\n }\n\n return this._getFullList(batch, options);\n }\n\n /**\n * Returns paginated items list.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: ListOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n options.query = Object.assign(\n {\n page: page,\n perPage: perPage,\n },\n options.query,\n );\n\n return this.client.send(this.baseCrudPath, options).then((responseData: any) => {\n responseData.items =\n responseData.items?.map((item: any) => {\n return this.decode(item);\n }) || [];\n\n return responseData;\n });\n }\n\n /**\n * Returns the first found item by the specified filter.\n *\n * Internally it calls `getList(1, 1, { filter, skipTotal })` and\n * returns the first found item.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * For consistency with `getOne`, this method will throw a 404\n * ClientResponseError if no item was found.\n *\n * @throws {ClientResponseError}\n */\n async getFirstListItem(filter: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n requestKey: \"one_by_filter_\" + this.baseCrudPath + \"_\" + filter,\n },\n options,\n );\n\n options.query = Object.assign(\n {\n filter: filter,\n skipTotal: 1,\n },\n options.query,\n );\n\n return this.getList(1, 1, options).then((result) => {\n if (!result?.items?.length) {\n throw new ClientResponseError({\n status: 404,\n response: {\n code: 404,\n message: \"The requested resource wasn't found.\",\n data: {},\n },\n });\n }\n\n return result.items[0];\n });\n }\n\n /**\n * Returns single item by its id.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * If `id` is empty it will throw a 404 error.\n *\n * @throws {ClientResponseError}\n */\n async getOne(id: string, options?: CommonOptions): Promise {\n if (!id) {\n throw new ClientResponseError({\n url: this.client.buildURL(this.baseCrudPath + \"/\"),\n status: 404,\n response: {\n code: 404,\n message: \"Missing required record id.\",\n data: {},\n },\n });\n }\n\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Creates a new item.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath, options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Updates an existing item by its id.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"PATCH\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Deletes an existing item by its id.\n *\n * @throws {ClientResponseError}\n */\n async delete(id: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then(() => true);\n }\n\n /**\n * Returns a promise with all list items batch fetched at once.\n */\n protected _getFullList(\n batchSize = 1000,\n options?: ListOptions,\n ): Promise> {\n options = options || {};\n options.query = Object.assign(\n {\n skipTotal: 1,\n },\n options.query,\n );\n\n let result: Array = [];\n\n let request = async (page: number): Promise> => {\n return this.getList(page, batchSize || 1000, options).then((list) => {\n const castedList = list as any as ListResult;\n const items = castedList.items;\n\n result = result.concat(items);\n\n if (items.length == list.perPage) {\n return request(page + 1);\n }\n\n return result;\n });\n };\n\n return request(1);\n }\n}\n","import { SendOptions } from \"@/tools/options\";\n\nexport function normalizeLegacyOptionsArgs(\n legacyWarn: string,\n baseOptions: SendOptions,\n bodyOrOptions?: any,\n query?: any,\n): SendOptions {\n const hasBodyOrOptions = typeof bodyOrOptions !== \"undefined\";\n const hasQuery = typeof query !== \"undefined\";\n\n if (!hasQuery && !hasBodyOrOptions) {\n return baseOptions;\n }\n\n if (hasQuery) {\n console.warn(legacyWarn);\n baseOptions.body = Object.assign({}, baseOptions.body, bodyOrOptions);\n baseOptions.query = Object.assign({}, baseOptions.query, query);\n\n return baseOptions;\n }\n\n return Object.assign(baseOptions, bodyOrOptions);\n}\n","import Client from \"@/Client\";\nimport { isTokenExpired } from \"@/tools/jwt\";\n\n// reset previous auto refresh registrations\nexport function resetAutoRefresh(client: Client) {\n (client as any)._resetAutoRefresh?.();\n}\n\nexport function registerAutoRefresh(\n client: Client,\n threshold: number,\n refreshFunc: () => Promise,\n reauthenticateFunc: () => Promise,\n) {\n resetAutoRefresh(client);\n\n const oldBeforeSend = client.beforeSend;\n const oldModel = client.authStore.record;\n\n // unset the auto refresh in case the auth store was cleared\n // OR a new model was authenticated\n const unsubStoreChange = client.authStore.onChange((newToken, model) => {\n if (\n !newToken ||\n model?.id != oldModel?.id ||\n ((model?.collectionId || oldModel?.collectionId) &&\n model?.collectionId != oldModel?.collectionId)\n ) {\n resetAutoRefresh(client);\n }\n });\n\n // initialize a reset function and attach it dynamically to the client\n (client as any)._resetAutoRefresh = function () {\n unsubStoreChange();\n client.beforeSend = oldBeforeSend;\n delete (client as any)._resetAutoRefresh;\n };\n\n client.beforeSend = async (url, sendOptions) => {\n const oldToken = client.authStore.token;\n\n if (sendOptions.query?.autoRefresh) {\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n }\n\n let isValid = client.authStore.isValid;\n if (\n // is loosely valid\n isValid &&\n // but it is going to expire in the next \"threshold\" seconds\n isTokenExpired(client.authStore.token, threshold)\n ) {\n try {\n await refreshFunc();\n } catch (_) {\n isValid = false;\n }\n }\n\n // still invalid -> reauthenticate\n if (!isValid) {\n await reauthenticateFunc();\n }\n\n // the request wasn't sent with a custom token\n const headers = sendOptions.headers || {};\n for (let key in headers) {\n if (\n key.toLowerCase() == \"authorization\" &&\n // the request wasn't sent with a custom token\n oldToken == headers[key] &&\n client.authStore.token\n ) {\n // set the latest store token\n headers[key] = client.authStore.token;\n break;\n }\n }\n sendOptions.headers = headers;\n\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n };\n}\n","import Client from \"@/Client\";\nimport { ClientResponseError } from \"@/ClientResponseError\";\nimport { RealtimeService, UnsubscribeFunc } from \"@/services/RealtimeService\";\nimport { BaseAuthStore } from \"@/stores/BaseAuthStore\";\nimport { CrudService } from \"@/services/CrudService\";\nimport { ListResult, RecordModel } from \"@/tools/dtos\";\nimport { normalizeLegacyOptionsArgs } from \"@/tools/legacy\";\nimport {\n CommonOptions,\n RecordFullListOptions,\n RecordListOptions,\n RecordOptions,\n SendOptions,\n RecordSubscribeOptions,\n} from \"@/tools/options\";\nimport { getTokenPayload } from \"@/tools/jwt\";\nimport { registerAutoRefresh, resetAutoRefresh } from \"@/tools/refresh\";\n\nexport interface RecordAuthResponse {\n /**\n * The signed PocketBase auth record.\n */\n record: T;\n\n /**\n * The PocketBase record auth token.\n *\n * If you are looking for the OAuth2 access and refresh tokens\n * they are available under the `meta.accessToken` and `meta.refreshToken` props.\n */\n token: string;\n\n /**\n * Auth meta data usually filled when OAuth2 is used.\n */\n meta?: { [key: string]: any };\n}\n\nexport interface AuthProviderInfo {\n name: string;\n displayName: string;\n state: string;\n authURL: string;\n codeVerifier: string;\n codeChallenge: string;\n codeChallengeMethod: string;\n}\n\nexport interface AuthMethodsList {\n mfa: {\n enabled: boolean;\n duration: number;\n };\n otp: {\n enabled: boolean;\n duration: number;\n };\n password: {\n enabled: boolean;\n identityFields: Array;\n };\n oauth2: {\n enabled: boolean;\n providers: Array;\n };\n}\n\nexport interface RecordSubscription {\n action: string; // eg. create, update, delete\n record: T;\n}\n\nexport type OAuth2UrlCallback = (url: string) => void | Promise;\n\nexport interface OAuth2AuthConfig extends SendOptions {\n // the name of the OAuth2 provider (eg. \"google\")\n provider: string;\n\n // custom scopes to overwrite the default ones\n scopes?: Array;\n\n // optional record create data\n createData?: { [key: string]: any };\n\n // optional callback that is triggered after the OAuth2 sign-in/sign-up url generation\n urlCallback?: OAuth2UrlCallback;\n\n // optional query params to send with the PocketBase auth request (eg. fields, expand, etc.)\n query?: RecordOptions;\n}\n\nexport interface OTPResponse {\n otpId: string;\n}\n\nexport class RecordService extends CrudService {\n readonly collectionIdOrName: string;\n\n constructor(client: Client, collectionIdOrName: string) {\n super(client);\n\n this.collectionIdOrName = collectionIdOrName;\n }\n\n /**\n * @inheritdoc\n */\n get baseCrudPath(): string {\n return this.baseCollectionPath + \"/records\";\n }\n\n /**\n * Returns the current collection service base path.\n */\n get baseCollectionPath(): string {\n return \"/api/collections/\" + encodeURIComponent(this.collectionIdOrName);\n }\n\n /**\n * Returns whether the current service collection is superusers.\n */\n get isSuperusers(): boolean {\n return (\n this.collectionIdOrName == \"_superusers\" ||\n this.collectionIdOrName == \"_pbc_2773867675\"\n );\n }\n\n // ---------------------------------------------------------------\n // Realtime handlers\n // ---------------------------------------------------------------\n\n /**\n * Subscribe to realtime changes to the specified topic (\"*\" or record id).\n *\n * If `topic` is the wildcard \"*\", then this method will subscribe to\n * any record changes in the collection.\n *\n * If `topic` is a record id, then this method will subscribe only\n * to changes of the specified record id.\n *\n * It's OK to subscribe multiple times to the same topic.\n * You can use the returned `UnsubscribeFunc` to remove only a single subscription.\n * Or use `unsubscribe(topic)` if you want to remove all subscriptions attached to the topic.\n */\n async subscribe(\n topic: string,\n callback: (data: RecordSubscription) => void,\n options?: RecordSubscribeOptions,\n ): Promise {\n if (!topic) {\n throw new Error(\"Missing topic.\");\n }\n\n if (!callback) {\n throw new Error(\"Missing subscription callback.\");\n }\n\n return this.client.realtime.subscribe(\n this.collectionIdOrName + \"/\" + topic,\n callback,\n options,\n );\n }\n\n /**\n * Unsubscribe from all subscriptions of the specified topic\n * (\"*\" or record id).\n *\n * If `topic` is not set, then this method will unsubscribe from\n * all subscriptions associated to the current collection.\n */\n async unsubscribe(topic?: string): Promise {\n // unsubscribe from the specified topic\n if (topic) {\n return this.client.realtime.unsubscribe(\n this.collectionIdOrName + \"/\" + topic,\n );\n }\n\n // unsubscribe from everything related to the collection\n return this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName);\n }\n\n // ---------------------------------------------------------------\n // Crud handers\n // ---------------------------------------------------------------\n /**\n * @inheritdoc\n */\n async getFullList(options?: RecordFullListOptions): Promise>;\n\n /**\n * @inheritdoc\n */\n async getFullList(\n batch?: number,\n options?: RecordListOptions,\n ): Promise>;\n\n /**\n * @inheritdoc\n */\n async getFullList(\n batchOrOptions?: number | RecordFullListOptions,\n options?: RecordListOptions,\n ): Promise> {\n if (typeof batchOrOptions == \"number\") {\n return super.getFullList(batchOrOptions, options);\n }\n\n const params = Object.assign({}, batchOrOptions, options);\n\n return super.getFullList(params);\n }\n\n /**\n * @inheritdoc\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: RecordListOptions,\n ): Promise> {\n return super.getList(page, perPage, options);\n }\n\n /**\n * @inheritdoc\n */\n async getFirstListItem(\n filter: string,\n options?: RecordListOptions,\n ): Promise {\n return super.getFirstListItem(filter, options);\n }\n\n /**\n * @inheritdoc\n */\n async getOne(id: string, options?: RecordOptions): Promise {\n return super.getOne(id, options);\n }\n\n /**\n * @inheritdoc\n */\n async create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): Promise {\n return super.create(bodyParams, options);\n }\n\n /**\n * @inheritdoc\n *\n * If the current `client.authStore.record` matches with the updated id, then\n * on success the `client.authStore.record` will be updated with the new response record fields.\n */\n async update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): Promise {\n return super.update(id, bodyParams, options).then((item) => {\n if (\n // is record auth\n this.client.authStore.record?.id === item?.id &&\n (this.client.authStore.record?.collectionId === this.collectionIdOrName ||\n this.client.authStore.record?.collectionName ===\n this.collectionIdOrName)\n ) {\n let authExpand = Object.assign({}, this.client.authStore.record.expand);\n let authRecord = Object.assign({}, this.client.authStore.record, item);\n if (authExpand) {\n // for now \"merge\" only top-level expand\n authRecord.expand = Object.assign(authExpand, item.expand);\n }\n\n this.client.authStore.save(this.client.authStore.token, authRecord);\n }\n\n return item as any as T;\n });\n }\n\n /**\n * @inheritdoc\n *\n * If the current `client.authStore.record` matches with the deleted id,\n * then on success the `client.authStore` will be cleared.\n */\n async delete(id: string, options?: CommonOptions): Promise {\n return super.delete(id, options).then((success) => {\n if (\n success &&\n // is record auth\n this.client.authStore.record?.id === id &&\n (this.client.authStore.record?.collectionId === this.collectionIdOrName ||\n this.client.authStore.record?.collectionName ===\n this.collectionIdOrName)\n ) {\n this.client.authStore.clear();\n }\n\n return success;\n });\n }\n\n // ---------------------------------------------------------------\n // Auth handlers\n // ---------------------------------------------------------------\n\n /**\n * Prepare successful collection authorization response.\n */\n protected authResponse(responseData: any): RecordAuthResponse {\n const record = this.decode(responseData?.record || {});\n\n this.client.authStore.save(responseData?.token, record as any);\n\n return Object.assign({}, responseData, {\n // normalize common fields\n token: responseData?.token || \"\",\n record: record as any as T,\n });\n }\n\n /**\n * Returns all available collection auth methods.\n *\n * @throws {ClientResponseError}\n */\n async listAuthMethods(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"GET\",\n // @todo remove after deleting the pre v0.23 API response fields\n fields: \"mfa,otp,password,oauth2\",\n },\n options,\n );\n\n return this.client.send(this.baseCollectionPath + \"/auth-methods\", options);\n }\n\n /**\n * Authenticate a single auth collection record via its username/email and password.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n *\n * @throws {ClientResponseError}\n */\n async authWithPassword(\n usernameOrEmail: string,\n password: string,\n options?: RecordOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n identity: usernameOrEmail,\n password: password,\n },\n },\n options,\n );\n\n // note: consider to deprecate\n let autoRefreshThreshold;\n if (this.isSuperusers) {\n autoRefreshThreshold = options.autoRefreshThreshold;\n delete options.autoRefreshThreshold;\n if (!options.autoRefresh) {\n resetAutoRefresh(this.client);\n }\n }\n\n let authData = await this.client.send(\n this.baseCollectionPath + \"/auth-with-password\",\n options,\n );\n\n authData = this.authResponse(authData);\n\n if (autoRefreshThreshold && this.isSuperusers) {\n registerAutoRefresh(\n this.client,\n autoRefreshThreshold,\n () => this.authRefresh({ autoRefresh: true }),\n () =>\n this.authWithPassword(\n usernameOrEmail,\n password,\n Object.assign({ autoRefresh: true }, options),\n ),\n );\n }\n\n return authData;\n }\n\n /**\n * Authenticate a single auth collection record with OAuth2 code.\n *\n * If you don't have an OAuth2 code you may also want to check `authWithOAuth2` method.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n * - the OAuth2 account data (eg. name, email, avatar, etc.)\n *\n * @throws {ClientResponseError}\n */\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n options?: RecordOptions,\n ): Promise>;\n\n /**\n * @deprecated\n * Consider using authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createdData, options?).\n */\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n body?: any,\n query?: any,\n ): Promise>;\n\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n bodyOrOptions?: any,\n query?: any,\n ): Promise> {\n let options: any = {\n method: \"POST\",\n body: {\n provider: provider,\n code: code,\n codeVerifier: codeVerifier,\n redirectURL: redirectURL,\n createData: createData,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-with-oauth2\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * @deprecated This form of authWithOAuth2 is deprecated.\n *\n * Please use `authWithOAuth2Code()` OR its simplified realtime version\n * as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.\n */\n async authWithOAuth2(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n bodyParams?: { [key: string]: any },\n queryParams?: RecordOptions,\n ): Promise>;\n\n /**\n * Authenticate a single auth collection record with OAuth2\n * **without custom redirects, deeplinks or even page reload**.\n *\n * This method initializes a one-off realtime subscription and will\n * open a popup window with the OAuth2 vendor page to authenticate.\n * Once the external OAuth2 sign-in/sign-up flow is completed, the popup\n * window will be automatically closed and the OAuth2 data sent back\n * to the user through the previously established realtime connection.\n *\n * You can specify an optional `urlCallback` prop to customize\n * the default url `window.open` behavior.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n * - the OAuth2 account data (eg. name, email, avatar, etc.)\n *\n * Example:\n *\n * ```js\n * const authData = await pb.collection(\"users\").authWithOAuth2({\n * provider: \"google\",\n * })\n * ```\n *\n * Note1: When creating the OAuth2 app in the provider dashboard\n * you have to configure `https://yourdomain.com/api/oauth2-redirect`\n * as redirect URL.\n *\n * Note2: Safari may block the default `urlCallback` popup because\n * it doesn't allow `window.open` calls as part of an `async` click functions.\n * To workaround this you can either change your click handler to not be marked as `async`\n * OR manually call `window.open` before your `async` function and use the\n * window reference in your own custom `urlCallback` (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061).\n * For example:\n * ```js\n * \n * ...\n * document.getElementById(\"btn\").addEventListener(\"click\", () => {\n * pb.collection(\"users\").authWithOAuth2({\n * provider: \"gitlab\",\n * }).then((authData) => {\n * console.log(authData)\n * }).catch((err) => {\n * console.log(err, err.originalError);\n * });\n * })\n * ```\n *\n * @throws {ClientResponseError}\n */\n async authWithOAuth2(\n options: OAuth2AuthConfig,\n ): Promise>;\n\n authWithOAuth2(...args: any): Promise> {\n // fallback to legacy format\n if (args.length > 1 || typeof args?.[0] === \"string\") {\n console.warn(\n \"PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.\",\n );\n return this.authWithOAuth2Code(\n args?.[0] || \"\",\n args?.[1] || \"\",\n args?.[2] || \"\",\n args?.[3] || \"\",\n args?.[4] || {},\n args?.[5] || {},\n args?.[6] || {},\n );\n }\n\n const config = args?.[0] || {};\n\n // open a new popup window in case config.urlCallback is not set\n //\n // note: it is opened before any async calls due to Safari restrictions\n // (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061)\n let eagerDefaultPopup: Window | null = null;\n if (!config.urlCallback) {\n eagerDefaultPopup = openBrowserPopup(undefined);\n }\n\n // initialize a one-off realtime service\n const realtime = new RealtimeService(this.client);\n\n function cleanup() {\n eagerDefaultPopup?.close();\n realtime.unsubscribe();\n }\n\n const requestKeyOptions: SendOptions = {};\n const requestKey = config.requestKey;\n if (requestKey) {\n requestKeyOptions.requestKey = requestKey;\n }\n\n return this.listAuthMethods(requestKeyOptions)\n .then((authMethods) => {\n const provider = authMethods.oauth2.providers.find(\n (p) => p.name === config.provider,\n );\n if (!provider) {\n throw new ClientResponseError(\n new Error(`Missing or invalid provider \"${config.provider}\".`),\n );\n }\n\n const redirectURL = this.client.buildURL(\"/api/oauth2-redirect\");\n\n return new Promise(async (resolve, reject) => {\n // find the AbortController associated with the current request key (if any)\n const cancelController = requestKey\n ? this.client[\"cancelControllers\"]?.[requestKey]\n : undefined;\n if (cancelController) {\n cancelController.signal.onabort = () => {\n cleanup();\n reject(\n new ClientResponseError({\n isAbort: true,\n message: \"manually cancelled\",\n }),\n );\n };\n }\n\n // disconnected due to network/server error\n realtime.onDisconnect = (activeSubscriptions: Array) => {\n if (activeSubscriptions.length && reject) {\n cleanup();\n reject(\n new ClientResponseError(\n new Error(\"realtime connection interrupted\"),\n ),\n );\n }\n };\n\n try {\n await realtime.subscribe(\"@oauth2\", async (e) => {\n const oldState = realtime.clientId;\n\n try {\n if (!e.state || oldState !== e.state) {\n throw new Error(\"State parameters don't match.\");\n }\n\n if (e.error || !e.code) {\n throw new Error(\n \"OAuth2 redirect error or missing code: \" +\n e.error,\n );\n }\n\n // clear the non SendOptions props\n const options = Object.assign({}, config);\n delete options.provider;\n delete options.scopes;\n delete options.createData;\n delete options.urlCallback;\n\n // reset the cancelController listener as it will be triggered by the next api call\n if (cancelController?.signal?.onabort) {\n cancelController.signal.onabort = null;\n }\n\n const authData = await this.authWithOAuth2Code(\n provider.name,\n e.code,\n provider.codeVerifier,\n redirectURL,\n config.createData,\n options,\n );\n\n resolve(authData);\n } catch (err) {\n reject(new ClientResponseError(err));\n }\n\n cleanup();\n });\n\n const replacements: { [key: string]: any } = {\n state: realtime.clientId,\n };\n if (config.scopes?.length) {\n replacements[\"scope\"] = config.scopes.join(\" \");\n }\n\n const url = this._replaceQueryParams(\n provider.authURL + redirectURL,\n replacements,\n );\n\n let urlCallback =\n config.urlCallback ||\n function (url: string) {\n if (eagerDefaultPopup) {\n eagerDefaultPopup.location.href = url;\n } else {\n // it could have been blocked due to its empty initial url,\n // try again...\n eagerDefaultPopup = openBrowserPopup(url);\n }\n };\n\n await urlCallback(url);\n } catch (err) {\n // reset the cancelController listener in case the request key is reused\n if (cancelController?.signal?.onabort) {\n cancelController.signal.onabort = null;\n }\n\n cleanup();\n reject(new ClientResponseError(err));\n }\n });\n })\n .catch((err) => {\n cleanup();\n throw err; // rethrow\n }) as Promise>;\n }\n\n /**\n * Refreshes the current authenticated record instance and\n * returns a new token and record data.\n *\n * On success this method also automatically updates the client's AuthStore.\n *\n * @throws {ClientResponseError}\n */\n async authRefresh(options?: RecordOptions): Promise>;\n\n /**\n * @deprecated\n * Consider using authRefresh(options?).\n */\n async authRefresh(body?: any, query?: any): Promise>;\n\n async authRefresh(\n bodyOrOptions?: any,\n query?: any,\n ): Promise> {\n let options: any = {\n method: \"POST\",\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-refresh\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * Sends auth record password reset request.\n *\n * @throws {ClientResponseError}\n */\n async requestPasswordReset(email: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestPasswordReset(email, options?).\n */\n async requestPasswordReset(email: string, body?: any, query?: any): Promise;\n\n async requestPasswordReset(\n email: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n email: email,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-password-reset\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record password reset request.\n *\n * @throws {ClientResponseError}\n */\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmPasswordReset(passwordResetToken, password, passwordConfirm, options?).\n */\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: passwordResetToken,\n password: password,\n passwordConfirm: passwordConfirm,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-password-reset\", options)\n .then(() => true);\n }\n\n /**\n * Sends auth record verification email request.\n *\n * @throws {ClientResponseError}\n */\n async requestVerification(email: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestVerification(email, options?).\n */\n async requestVerification(email: string, body?: any, query?: any): Promise;\n\n async requestVerification(\n email: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n email: email,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-verification\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record email verification request.\n *\n * If the current `client.authStore.record` matches with the auth record from the token,\n * then on success the `client.authStore.record.verified` will be updated to `true`.\n *\n * @throws {ClientResponseError}\n */\n async confirmVerification(\n verificationToken: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmVerification(verificationToken, options?).\n */\n async confirmVerification(\n verificationToken: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmVerification(\n verificationToken: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: verificationToken,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-verification\", options)\n .then(() => {\n // on success manually update the current auth record verified state\n const payload = getTokenPayload(verificationToken);\n const model = this.client.authStore.record;\n if (\n model &&\n !model.verified &&\n model.id === payload.id &&\n model.collectionId === payload.collectionId\n ) {\n model.verified = true;\n this.client.authStore.save(this.client.authStore.token, model);\n }\n\n return true;\n });\n }\n\n /**\n * Sends an email change request to the authenticated record model.\n *\n * @throws {ClientResponseError}\n */\n async requestEmailChange(newEmail: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestEmailChange(newEmail, options?).\n */\n async requestEmailChange(newEmail: string, body?: any, query?: any): Promise;\n\n async requestEmailChange(\n newEmail: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n newEmail: newEmail,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-email-change\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record's new email address.\n *\n * If the current `client.authStore.record` matches with the auth record from the token,\n * then on success the `client.authStore` will be cleared.\n *\n * @throws {ClientResponseError}\n */\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmEmailChange(emailChangeToken, password, options?).\n */\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: emailChangeToken,\n password: password,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-email-change\", options)\n .then(() => {\n const payload = getTokenPayload(emailChangeToken);\n const model = this.client.authStore.record;\n if (\n model &&\n model.id === payload.id &&\n model.collectionId === payload.collectionId\n ) {\n this.client.authStore.clear();\n }\n\n return true;\n });\n }\n\n /**\n * @deprecated use collection(\"_externalAuths\").*\n *\n * Lists all linked external auth providers for the specified auth record.\n *\n * @throws {ClientResponseError}\n */\n async listExternalAuths(\n recordId: string,\n options?: CommonOptions,\n ): Promise> {\n return this.client.collection(\"_externalAuths\").getFullList(\n Object.assign({}, options, {\n filter: this.client.filter(\"recordRef = {:id}\", { id: recordId }),\n }),\n );\n }\n\n /**\n * @deprecated use collection(\"_externalAuths\").*\n *\n * Unlink a single external auth provider from the specified auth record.\n *\n * @throws {ClientResponseError}\n */\n async unlinkExternalAuth(\n recordId: string,\n provider: string,\n options?: CommonOptions,\n ): Promise {\n const ea = await this.client.collection(\"_externalAuths\").getFirstListItem(\n this.client.filter(\"recordRef = {:recordId} && provider = {:provider}\", {\n recordId,\n provider,\n }),\n );\n\n return this.client\n .collection(\"_externalAuths\")\n .delete(ea.id, options)\n .then(() => true);\n }\n\n /**\n * Sends auth record OTP to the provided email.\n *\n * @throws {ClientResponseError}\n */\n async requestOTP(email: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: { email: email },\n },\n options,\n );\n\n return this.client.send(this.baseCollectionPath + \"/request-otp\", options);\n }\n\n /**\n * Authenticate a single auth collection record via OTP.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n *\n * @throws {ClientResponseError}\n */\n async authWithOTP(\n otpId: string,\n password: string,\n options?: CommonOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"POST\",\n body: { otpId, password },\n },\n options,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-with-otp\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * Impersonate authenticates with the specified recordId and\n * returns a new client with the received auth token in a memory store.\n *\n * If `duration` is 0 the generated auth token will fallback\n * to the default collection auth token duration.\n *\n * This action currently requires superusers privileges.\n *\n * @throws {ClientResponseError}\n */\n async impersonate(\n recordId: string,\n duration: number,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: { duration: duration },\n },\n options,\n );\n options.headers = options.headers || {};\n if (!options.headers.Authorization) {\n options.headers.Authorization = this.client.authStore.token;\n }\n\n // create a new client loaded with the impersonated auth state\n // ---\n const client = new Client(\n this.client.baseURL,\n new BaseAuthStore(),\n this.client.lang,\n );\n\n const authData = await client.send(\n this.baseCollectionPath + \"/impersonate/\" + encodeURIComponent(recordId),\n options,\n );\n\n client.authStore.save(authData?.token, this.decode(authData?.record || {}));\n // ---\n\n return client;\n }\n\n // ---------------------------------------------------------------\n\n // very rudimentary url query params replacement because at the moment\n // URL (and URLSearchParams) doesn't seem to be fully supported in React Native\n //\n // note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html\n private _replaceQueryParams(\n url: string,\n replacements: { [key: string]: any } = {},\n ): string {\n let urlPath = url;\n let query = \"\";\n\n const queryIndex = url.indexOf(\"?\");\n if (queryIndex >= 0) {\n urlPath = url.substring(0, url.indexOf(\"?\"));\n query = url.substring(url.indexOf(\"?\") + 1);\n }\n\n const parsedParams: { [key: string]: string } = {};\n\n // parse the query parameters\n const rawParams = query.split(\"&\");\n for (const param of rawParams) {\n if (param == \"\") {\n continue;\n }\n\n const pair = param.split(\"=\");\n parsedParams[decodeURIComponent(pair[0].replace(/\\+/g, \" \"))] =\n decodeURIComponent((pair[1] || \"\").replace(/\\+/g, \" \"));\n }\n\n // apply the replacements\n for (let key in replacements) {\n if (!replacements.hasOwnProperty(key)) {\n continue;\n }\n\n if (replacements[key] == null) {\n delete parsedParams[key];\n } else {\n parsedParams[key] = replacements[key];\n }\n }\n\n // construct back the full query string\n query = \"\";\n for (let key in parsedParams) {\n if (!parsedParams.hasOwnProperty(key)) {\n continue;\n }\n\n if (query != \"\") {\n query += \"&\";\n }\n\n query +=\n encodeURIComponent(key.replace(/%20/g, \"+\")) +\n \"=\" +\n encodeURIComponent(parsedParams[key].replace(/%20/g, \"+\"));\n }\n\n return query != \"\" ? urlPath + \"?\" + query : urlPath;\n }\n}\n\nfunction openBrowserPopup(url?: string): Window | null {\n if (typeof window === \"undefined\" || !window?.open) {\n throw new ClientResponseError(\n new Error(\n `Not in a browser context - please pass a custom urlCallback function.`,\n ),\n );\n }\n\n let width = 1024;\n let height = 768;\n\n let windowWidth = window.innerWidth;\n let windowHeight = window.innerHeight;\n\n // normalize window size\n width = width > windowWidth ? windowWidth : width;\n height = height > windowHeight ? windowHeight : height;\n\n let left = windowWidth / 2 - width / 2;\n let top = windowHeight / 2 - height / 2;\n\n // note: we don't use the noopener and noreferrer attributes since\n // for some reason browser blocks such windows then url is undefined/blank\n return window.open(\n url,\n \"popup_window\",\n \"width=\" +\n width +\n \",height=\" +\n height +\n \",top=\" +\n top +\n \",left=\" +\n left +\n \",resizable,menubar=no\",\n );\n}\n","import { CrudService } from \"@/services/CrudService\";\nimport { CollectionModel } from \"@/tools/dtos\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport class CollectionService extends CrudService {\n /**\n * @inheritdoc\n */\n get baseCrudPath(): string {\n return \"/api/collections\";\n }\n\n /**\n * Imports the provided collections.\n *\n * If `deleteMissing` is `true`, all local collections and their fields,\n * that are not present in the imported configuration, WILL BE DELETED\n * (including their related records data)!\n *\n * @throws {ClientResponseError}\n */\n async import(\n collections: Array,\n deleteMissing: boolean = false,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"PUT\",\n body: {\n collections: collections,\n deleteMissing: deleteMissing,\n },\n },\n options,\n );\n\n return this.client.send(this.baseCrudPath + \"/import\", options).then(() => true);\n }\n\n /**\n * Returns type indexed map with scaffolded collection models\n * populated with their default field values.\n *\n * @throws {ClientResponseError}\n */\n async getScaffolds(\n options?: CommonOptions,\n ): Promise<{ [key: string]: CollectionModel }> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(this.baseCrudPath + \"/meta/scaffolds\", options);\n }\n\n /**\n * Deletes all records associated with the specified collection.\n *\n * @throws {ClientResponseError}\n */\n async truncate(collectionIdOrName: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(\n this.baseCrudPath +\n \"/\" +\n encodeURIComponent(collectionIdOrName) +\n \"/truncate\",\n options,\n )\n .then(() => true);\n }\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseService } from \"@/services/BaseService\";\nimport { ListResult, LogModel } from \"@/tools/dtos\";\nimport { CommonOptions, ListOptions, LogStatsOptions } from \"@/tools/options\";\n\nexport interface HourlyStats {\n total: number;\n date: string;\n}\n\nexport class LogService extends BaseService {\n /**\n * Returns paginated logs list.\n *\n * @throws {ClientResponseError}\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: ListOptions,\n ): Promise> {\n options = Object.assign({ method: \"GET\" }, options);\n\n options.query = Object.assign(\n {\n page: page,\n perPage: perPage,\n },\n options.query,\n );\n\n return this.client.send(\"/api/logs\", options);\n }\n\n /**\n * Returns a single log by its id.\n *\n * If `id` is empty it will throw a 404 error.\n *\n * @throws {ClientResponseError}\n */\n async getOne(id: string, options?: CommonOptions): Promise {\n if (!id) {\n throw new ClientResponseError({\n url: this.client.buildURL(\"/api/logs/\"),\n status: 404,\n response: {\n code: 404,\n message: \"Missing required log id.\",\n data: {},\n },\n });\n }\n\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/logs/\" + encodeURIComponent(id), options);\n }\n\n /**\n * Returns logs statistics.\n *\n * @throws {ClientResponseError}\n */\n async getStats(options?: LogStatsOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/logs/stats\", options);\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface HealthCheckResponse {\n code: number;\n message: string;\n data: { [key: string]: any };\n}\n\nexport class HealthService extends BaseService {\n /**\n * Checks the health status of the api.\n *\n * @throws {ClientResponseError}\n */\n async check(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/health\", options);\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions, FileOptions, serializeQueryParams } from \"@/tools/options\";\n\nexport class FileService extends BaseService {\n /**\n * @deprecated Please replace with `pb.files.getURL()`.\n */\n getUrl(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n console.warn(\"Please replace pb.files.getUrl() with pb.files.getURL()\");\n return this.getURL(record, filename, queryParams);\n }\n\n /**\n * Builds and returns an absolute record file url for the provided filename.\n */\n getURL(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n if (\n !filename ||\n !record?.id ||\n !(record?.collectionId || record?.collectionName)\n ) {\n return \"\";\n }\n\n const parts = [];\n parts.push(\"api\");\n parts.push(\"files\");\n parts.push(encodeURIComponent(record.collectionId || record.collectionName));\n parts.push(encodeURIComponent(record.id));\n parts.push(encodeURIComponent(filename));\n\n let result = this.client.buildURL(parts.join(\"/\"));\n\n // normalize the download query param for consistency with the Dart sdk\n if (queryParams.download === false) {\n delete queryParams.download;\n }\n\n const params = serializeQueryParams(queryParams);\n if (params) {\n result += (result.includes(\"?\") ? \"&\" : \"?\") + params;\n }\n\n return result;\n }\n\n /**\n * Requests a new private file access token for the current auth model.\n *\n * @throws {ClientResponseError}\n */\n async getToken(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(\"/api/files/token\", options)\n .then((data) => data?.token || \"\");\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface BackupFileInfo {\n key: string;\n size: number;\n modified: string;\n}\n\nexport class BackupService extends BaseService {\n /**\n * Returns list with all available backup files.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: CommonOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/backups\", options);\n }\n\n /**\n * Initializes a new backup.\n *\n * @throws {ClientResponseError}\n */\n async create(basename: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n name: basename,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/backups\", options).then(() => true);\n }\n\n /**\n * Uploads an existing backup file.\n *\n * Example:\n *\n * ```js\n * await pb.backups.upload({\n * file: new Blob([...]),\n * });\n * ```\n *\n * @throws {ClientResponseError}\n */\n async upload(\n bodyParams: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client.send(\"/api/backups/upload\", options).then(() => true);\n }\n\n /**\n * Deletes a single backup file.\n *\n * @throws {ClientResponseError}\n */\n async delete(key: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(`/api/backups/${encodeURIComponent(key)}`, options)\n .then(() => true);\n }\n\n /**\n * Initializes an app data restore from an existing backup.\n *\n * @throws {ClientResponseError}\n */\n async restore(key: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(`/api/backups/${encodeURIComponent(key)}/restore`, options)\n .then(() => true);\n }\n\n /**\n * @deprecated Please use `getDownloadURL()`.\n */\n getDownloadUrl(token: string, key: string): string {\n console.warn(\n \"Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()\",\n );\n return this.getDownloadURL(token, key);\n }\n\n /**\n * Builds a download url for a single existing backup using a\n * superuser file token and the backup file key.\n *\n * The file token can be generated via `pb.files.getToken()`.\n */\n getDownloadURL(token: string, key: string): string {\n return this.client.buildURL(\n `/api/backups/${encodeURIComponent(key)}?token=${encodeURIComponent(token)}`,\n );\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface CronJob {\n id: string;\n expression: string;\n}\n\nexport class CronService extends BaseService {\n /**\n * Returns list with all registered cron jobs.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: CommonOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/crons\", options);\n }\n\n /**\n * Runs the specified cron job.\n *\n * @throws {ClientResponseError}\n */\n async run(jobId: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(`/api/crons/${encodeURIComponent(jobId)}`, options)\n .then(() => true);\n }\n}\n","/**\n * Checks if the specified value is a file (aka. File, Blob, RN file object).\n */\nexport function isFile(val: any): boolean {\n return (\n (typeof Blob !== \"undefined\" && val instanceof Blob) ||\n (typeof File !== \"undefined\" && val instanceof File) ||\n // check for React Native file object format\n // (see https://github.com/pocketbase/pocketbase/discussions/2002#discussioncomment-5254168)\n (val !== null &&\n typeof val === \"object\" &&\n val.uri &&\n ((typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\") ||\n (typeof global !== \"undefined\" && (global as any).HermesInternal)))\n );\n}\n\n/**\n * Loosely checks if the specified body is a FormData instance.\n */\nexport function isFormData(body: any): boolean {\n return (\n body &&\n // we are checking the constructor name because FormData\n // is not available natively in some environments and the\n // polyfill(s) may not be globally accessible\n (body.constructor?.name === \"FormData\" ||\n // fallback to global FormData instance check\n // note: this is needed because the constructor.name could be different in case of\n // custom global FormData implementation, eg. React Native on Android/iOS\n (typeof FormData !== \"undefined\" && body instanceof FormData))\n );\n}\n\n/**\n * Checks if the submitted body object has at least one Blob/File field value.\n */\nexport function hasFileField(body: { [key: string]: any }): boolean {\n for (const key in body) {\n const values = Array.isArray(body[key]) ? body[key] : [body[key]];\n for (const v of values) {\n if (isFile(v)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Converts analyzes the provided body and converts it to FormData\n * in case a plain object with File/Blob values is used.\n */\nexport function convertToFormDataIfNeeded(body: any): any {\n if (\n typeof FormData === \"undefined\" ||\n typeof body === \"undefined\" ||\n typeof body !== \"object\" ||\n body === null ||\n isFormData(body) ||\n !hasFileField(body)\n ) {\n return body;\n }\n\n const form = new FormData();\n\n for (const key in body) {\n const val = body[key];\n\n // skip undefined values for consistency with JSON.stringify\n // (see https://github.com/pocketbase/pocketbase/issues/6731#issuecomment-2812382827)\n if (typeof val === \"undefined\") {\n continue;\n }\n\n if (typeof val === \"object\" && !hasFileField({ data: val })) {\n // send json-like values as jsonPayload to avoid the implicit string value normalization\n let payload: { [key: string]: any } = {};\n payload[key] = val;\n form.append(\"@jsonPayload\", JSON.stringify(payload));\n } else {\n // in case of mixed string and file/blob\n const normalizedVal = Array.isArray(val) ? val : [val];\n for (let v of normalizedVal) {\n form.append(key, v);\n }\n }\n }\n\n return form;\n}\n\n/**\n * Converts the provided FormData instance into a plain object.\n *\n * For consistency with the server multipart/form-data inferring,\n * the following normalization rules are applied for plain multipart string values:\n * - \"true\" is converted to the json \"true\"\n * - \"false\" is converted to the json \"false\"\n * - numeric strings are converted to json number ONLY if the resulted\n * minimal number string representation is the same as the provided raw string\n * (aka. scientific notations, \"Infinity\", \"0.0\", \"0001\", etc. are kept as string)\n * - any other string (empty string too) is left as it is\n */\nexport function convertFormDataToObject(formData: FormData): { [key: string]: any } {\n let result: { [key: string]: any } = {};\n\n formData.forEach((v, k) => {\n if (k === \"@jsonPayload\" && typeof v == \"string\") {\n try {\n let parsed = JSON.parse(v);\n Object.assign(result, parsed);\n } catch (err) {\n console.warn(\"@jsonPayload error:\", err);\n }\n } else {\n if (typeof result[k] !== \"undefined\") {\n if (!Array.isArray(result[k])) {\n result[k] = [result[k]];\n }\n result[k].push(inferFormDataValue(v));\n } else {\n result[k] = inferFormDataValue(v);\n }\n }\n });\n\n return result;\n}\n\nconst inferNumberCharsRegex = /^[\\-\\.\\d]+$/;\n\nfunction inferFormDataValue(value: any): any {\n if (typeof value != \"string\") {\n return value;\n }\n\n if (value == \"true\") {\n return true;\n }\n\n if (value == \"false\") {\n return false;\n }\n\n // note: expects the provided raw string to match exactly with the minimal string representation of the parsed number\n if (\n (value[0] === \"-\" || (value[0] >= \"0\" && value[0] <= \"9\")) &&\n inferNumberCharsRegex.test(value)\n ) {\n let num = +value;\n if (\"\" + num === value) {\n return num;\n }\n }\n\n return value;\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { isFile, isFormData, convertFormDataToObject } from \"@/tools/formdata\";\nimport {\n SendOptions,\n RecordOptions,\n normalizeUnknownQueryParams,\n serializeQueryParams,\n} from \"@/tools/options\";\n\nexport interface BatchRequest {\n method: string;\n url: string;\n json?: { [key: string]: any };\n files?: { [key: string]: Array };\n headers?: { [key: string]: string };\n}\n\nexport interface BatchRequestResult {\n status: number;\n body: any;\n}\n\nexport class BatchService extends BaseService {\n private requests: Array = [];\n private subs: { [key: string]: SubBatchService } = {};\n\n /**\n * Starts constructing a batch request entry for the specified collection.\n */\n collection(collectionIdOrName: string): SubBatchService {\n if (!this.subs[collectionIdOrName]) {\n this.subs[collectionIdOrName] = new SubBatchService(\n this.requests,\n collectionIdOrName,\n );\n }\n\n return this.subs[collectionIdOrName];\n }\n\n /**\n * Sends the batch requests.\n *\n * @throws {ClientResponseError}\n */\n async send(options?: SendOptions): Promise> {\n const formData = new FormData();\n\n const jsonData = [];\n\n for (let i = 0; i < this.requests.length; i++) {\n const req = this.requests[i];\n\n jsonData.push({\n method: req.method,\n url: req.url,\n headers: req.headers,\n body: req.json,\n });\n\n if (req.files) {\n for (let key in req.files) {\n const files = req.files[key] || [];\n for (let file of files) {\n formData.append(\"requests.\" + i + \".\" + key, file);\n }\n }\n }\n }\n\n formData.append(\"@jsonPayload\", JSON.stringify({ requests: jsonData }));\n\n options = Object.assign(\n {\n method: \"POST\",\n body: formData,\n },\n options,\n );\n\n return this.client.send(\"/api/batch\", options);\n }\n}\n\nexport class SubBatchService {\n private requests: Array = [];\n private readonly collectionIdOrName: string;\n\n constructor(requests: Array, collectionIdOrName: string) {\n this.requests = requests;\n this.collectionIdOrName = collectionIdOrName;\n }\n\n /**\n * Registers a record upsert request into the current batch queue.\n *\n * The request will be executed as update if `bodyParams` have a valid existing record `id` value, otherwise - create.\n */\n upsert(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"PUT\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records\",\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record create request into the current batch queue.\n */\n create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"POST\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records\",\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record update request into the current batch queue.\n */\n update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"PATCH\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records/\" +\n encodeURIComponent(id),\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record delete request into the current batch queue.\n */\n delete(id: string, options?: SendOptions): void {\n options = Object.assign({}, options);\n\n const request: BatchRequest = {\n method: \"DELETE\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records/\" +\n encodeURIComponent(id),\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n private prepareRequest(request: BatchRequest, options: SendOptions) {\n normalizeUnknownQueryParams(options);\n\n request.headers = options.headers;\n request.json = {};\n request.files = {};\n\n // serialize query parameters\n // -----------------------------------------------------------\n if (typeof options.query !== \"undefined\") {\n const query = serializeQueryParams(options.query);\n if (query) {\n request.url += (request.url.includes(\"?\") ? \"&\" : \"?\") + query;\n }\n }\n\n // extract json and files body data\n // -----------------------------------------------------------\n let body = options.body;\n if (isFormData(body)) {\n body = convertFormDataToObject(body);\n }\n\n for (const key in body) {\n const val = body[key];\n\n if (isFile(val)) {\n request.files[key] = request.files[key] || [];\n request.files[key].push(val);\n } else if (Array.isArray(val)) {\n const foundFiles = [];\n const foundRegular = [];\n for (const v of val) {\n if (isFile(v)) {\n foundFiles.push(v);\n } else {\n foundRegular.push(v);\n }\n }\n\n if (foundFiles.length > 0 && foundFiles.length == val.length) {\n // only files\n // ---\n request.files[key] = request.files[key] || [];\n for (let file of foundFiles) {\n request.files[key].push(file);\n }\n } else {\n // empty or mixed array (both regular and File/Blob values)\n // ---\n request.json[key] = foundRegular;\n\n if (foundFiles.length > 0) {\n // add \"+\" to append if not already since otherwise\n // the existing regular files will be deleted\n // (the mixed values order is preserved only within their corresponding groups)\n let fileKey = key;\n if (!key.startsWith(\"+\") && !key.endsWith(\"+\")) {\n fileKey += \"+\";\n }\n\n request.files[fileKey] = request.files[fileKey] || [];\n for (let file of foundFiles) {\n request.files[fileKey].push(file);\n }\n }\n }\n } else {\n request.json[key] = val;\n }\n }\n }\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseAuthStore } from \"@/stores/BaseAuthStore\";\nimport { LocalAuthStore } from \"@/stores/LocalAuthStore\";\nimport { SettingsService } from \"@/services/SettingsService\";\nimport { RecordService } from \"@/services/RecordService\";\nimport { CollectionService } from \"@/services/CollectionService\";\nimport { LogService } from \"@/services/LogService\";\nimport { RealtimeService } from \"@/services/RealtimeService\";\nimport { HealthService } from \"@/services/HealthService\";\nimport { FileService } from \"@/services/FileService\";\nimport { BackupService } from \"@/services/BackupService\";\nimport { CronService } from \"@/services/CronService\";\nimport { BatchService } from \"@/services/BatchService\";\nimport { RecordModel } from \"@/tools/dtos\";\nimport {\n SendOptions,\n FileOptions,\n normalizeUnknownQueryParams,\n serializeQueryParams,\n} from \"@/tools/options\";\nimport { isFormData, convertToFormDataIfNeeded } from \"@/tools/formdata\";\n\nexport interface BeforeSendResult {\n [key: string]: any; // for backward compatibility\n url?: string;\n options?: { [key: string]: any };\n}\n\n/**\n * PocketBase JS Client.\n */\nexport default class Client {\n /**\n * The base PocketBase backend url address (eg. 'http://127.0.0.1.8090').\n */\n baseURL: string;\n\n /**\n * Legacy getter alias for baseURL.\n * @deprecated Please replace with baseURL.\n */\n get baseUrl(): string {\n return this.baseURL;\n }\n\n /**\n * Legacy setter alias for baseURL.\n * @deprecated Please replace with baseURL.\n */\n set baseUrl(v: string) {\n this.baseURL = v;\n }\n\n /**\n * Hook that get triggered right before sending the fetch request,\n * allowing you to inspect and modify the url and request options.\n *\n * For list of the possible options check https://developer.mozilla.org/en-US/docs/Web/API/fetch#options\n *\n * You can return a non-empty result object `{ url, options }` to replace the url and request options entirely.\n *\n * Example:\n * ```js\n * const pb = new PocketBase(\"https://example.com\")\n *\n * pb.beforeSend = function (url, options) {\n * options.headers = Object.assign({}, options.headers, {\n * 'X-Custom-Header': 'example',\n * })\n *\n * return { url, options }\n * }\n *\n * // use the created client as usual...\n * ```\n */\n beforeSend?: (\n url: string,\n options: SendOptions,\n ) => BeforeSendResult | Promise;\n\n /**\n * Hook that get triggered after successfully sending the fetch request,\n * allowing you to inspect/modify the response object and its parsed data.\n *\n * Returns the new Promise resolved `data` that will be returned to the client.\n *\n * Example:\n * ```js\n * const pb = new PocketBase(\"https://example.com\")\n *\n * pb.afterSend = function (response, data, options) {\n * if (response.status != 200) {\n * throw new ClientResponseError({\n * url: response.url,\n * status: response.status,\n * response: { ... },\n * })\n * }\n *\n * return data;\n * }\n *\n * // use the created client as usual...\n * ```\n */\n afterSend?: ((response: Response, data: any) => any) &\n ((response: Response, data: any, options: SendOptions) => any);\n\n /**\n * Optional language code (default to `en-US`) that will be sent\n * with the requests to the server as `Accept-Language` header.\n */\n lang: string;\n\n /**\n * A replaceable instance of the local auth store service.\n */\n authStore: BaseAuthStore;\n\n /**\n * An instance of the service that handles the **Settings APIs**.\n */\n readonly settings: SettingsService;\n\n /**\n * An instance of the service that handles the **Collection APIs**.\n */\n readonly collections: CollectionService;\n\n /**\n * An instance of the service that handles the **File APIs**.\n */\n readonly files: FileService;\n\n /**\n * An instance of the service that handles the **Log APIs**.\n */\n readonly logs: LogService;\n\n /**\n * An instance of the service that handles the **Realtime APIs**.\n */\n readonly realtime: RealtimeService;\n\n /**\n * An instance of the service that handles the **Health APIs**.\n */\n readonly health: HealthService;\n\n /**\n * An instance of the service that handles the **Backup APIs**.\n */\n readonly backups: BackupService;\n\n /**\n * An instance of the service that handles the **Cron APIs**.\n */\n readonly crons: CronService;\n\n private cancelControllers: { [key: string]: AbortController } = {};\n private recordServices: { [key: string]: RecordService } = {};\n private enableAutoCancellation: boolean = true;\n\n constructor(baseURL = \"/\", authStore?: BaseAuthStore | null, lang = \"en-US\") {\n this.baseURL = baseURL;\n this.lang = lang;\n\n if (authStore) {\n this.authStore = authStore;\n } else if (typeof window != \"undefined\" && !!(window as any).Deno) {\n // note: to avoid common security issues we fallback to runtime/memory store in case the code is running in Deno env\n this.authStore = new BaseAuthStore();\n } else {\n this.authStore = new LocalAuthStore();\n }\n\n // common services\n this.collections = new CollectionService(this);\n this.files = new FileService(this);\n this.logs = new LogService(this);\n this.settings = new SettingsService(this);\n this.realtime = new RealtimeService(this);\n this.health = new HealthService(this);\n this.backups = new BackupService(this);\n this.crons = new CronService(this);\n }\n\n /**\n * @deprecated\n * With PocketBase v0.23.0 admins are converted to a regular auth\n * collection named \"_superusers\", aka. you can use directly collection(\"_superusers\").\n */\n get admins(): RecordService {\n return this.collection(\"_superusers\");\n }\n\n /**\n * Creates a new batch handler for sending multiple transactional\n * create/update/upsert/delete collection requests in one network call.\n *\n * Example:\n * ```js\n * const batch = pb.createBatch();\n *\n * batch.collection(\"example1\").create({ ... })\n * batch.collection(\"example2\").update(\"RECORD_ID\", { ... })\n * batch.collection(\"example3\").delete(\"RECORD_ID\")\n * batch.collection(\"example4\").upsert({ ... })\n *\n * await batch.send()\n * ```\n */\n createBatch(): BatchService {\n return new BatchService(this);\n }\n\n /**\n * Returns the RecordService associated to the specified collection.\n */\n collection(idOrName: string): RecordService {\n if (!this.recordServices[idOrName]) {\n this.recordServices[idOrName] = new RecordService(this, idOrName);\n }\n\n return this.recordServices[idOrName];\n }\n\n /**\n * Globally enable or disable auto cancellation for pending duplicated requests.\n */\n autoCancellation(enable: boolean): Client {\n this.enableAutoCancellation = !!enable;\n\n return this;\n }\n\n /**\n * Cancels single request by its cancellation key.\n */\n cancelRequest(requestKey: string): Client {\n if (this.cancelControllers[requestKey]) {\n this.cancelControllers[requestKey].abort();\n delete this.cancelControllers[requestKey];\n }\n\n return this;\n }\n\n /**\n * Cancels all pending requests.\n */\n cancelAllRequests(): Client {\n for (let k in this.cancelControllers) {\n this.cancelControllers[k].abort();\n }\n\n this.cancelControllers = {};\n\n return this;\n }\n\n /**\n * Constructs a filter expression with placeholders populated from a parameters object.\n *\n * Placeholder parameters are defined with the `{:paramName}` notation.\n *\n * The following parameter values are supported:\n *\n * - `string` (_single quotes are autoescaped_)\n * - `number`\n * - `boolean`\n * - `Date` object (_stringified into the PocketBase datetime format_)\n * - `null`\n * - everything else is converted to a string using `JSON.stringify()`\n *\n * Example:\n *\n * ```js\n * pb.collection(\"example\").getFirstListItem(pb.filter(\n * 'title ~ {:title} && created >= {:created}',\n * { title: \"example\", created: new Date()}\n * ))\n * ```\n */\n filter(raw: string, params?: { [key: string]: any }): string {\n if (!params) {\n return raw;\n }\n\n for (let key in params) {\n let val = params[key];\n switch (typeof val) {\n case \"boolean\":\n case \"number\":\n val = \"\" + val;\n break;\n case \"string\":\n val = \"'\" + val.replace(/'/g, \"\\\\'\") + \"'\";\n break;\n default:\n if (val === null) {\n val = \"null\";\n } else if (val instanceof Date) {\n val = \"'\" + val.toISOString().replace(\"T\", \" \") + \"'\";\n } else {\n val = \"'\" + JSON.stringify(val).replace(/'/g, \"\\\\'\") + \"'\";\n }\n }\n raw = raw.replaceAll(\"{:\" + key + \"}\", val);\n }\n\n return raw;\n }\n\n /**\n * @deprecated Please use `pb.files.getURL()`.\n */\n getFileUrl(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n console.warn(\"Please replace pb.getFileUrl() with pb.files.getURL()\");\n return this.files.getURL(record, filename, queryParams);\n }\n\n /**\n * @deprecated Please use `pb.buildURL()`.\n */\n buildUrl(path: string): string {\n console.warn(\"Please replace pb.buildUrl() with pb.buildURL()\");\n return this.buildURL(path);\n }\n\n /**\n * Builds a full client url by safely concatenating the provided path.\n */\n buildURL(path: string): string {\n let url = this.baseURL;\n\n // construct an absolute base url if in a browser environment\n if (\n typeof window !== \"undefined\" &&\n !!window.location &&\n !url.startsWith(\"https://\") &&\n !url.startsWith(\"http://\")\n ) {\n url = window.location.origin?.endsWith(\"/\")\n ? window.location.origin.substring(0, window.location.origin.length - 1)\n : window.location.origin || \"\";\n\n if (!this.baseURL.startsWith(\"/\")) {\n url += window.location.pathname || \"/\";\n url += url.endsWith(\"/\") ? \"\" : \"/\";\n }\n\n url += this.baseURL;\n }\n\n // concatenate the path\n if (path) {\n url += url.endsWith(\"/\") ? \"\" : \"/\"; // append trailing slash if missing\n url += path.startsWith(\"/\") ? path.substring(1) : path;\n }\n\n return url;\n }\n\n /**\n * Sends an api http request.\n *\n * @throws {ClientResponseError}\n */\n async send(path: string, options: SendOptions): Promise {\n options = this.initSendOptions(path, options);\n\n // build url + path\n let url = this.buildURL(path);\n\n if (this.beforeSend) {\n const result = Object.assign({}, await this.beforeSend(url, options));\n if (\n typeof result.url !== \"undefined\" ||\n typeof result.options !== \"undefined\"\n ) {\n url = result.url || url;\n options = result.options || options;\n } else if (Object.keys(result).length) {\n // legacy behavior\n options = result as SendOptions;\n console?.warn &&\n console.warn(\n \"Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`.\",\n );\n }\n }\n\n // serialize the query parameters\n if (typeof options.query !== \"undefined\") {\n const query = serializeQueryParams(options.query);\n if (query) {\n url += (url.includes(\"?\") ? \"&\" : \"?\") + query;\n }\n delete options.query;\n }\n\n // ensures that the json body is serialized\n if (\n this.getHeader(options.headers, \"Content-Type\") == \"application/json\" &&\n options.body &&\n typeof options.body !== \"string\"\n ) {\n options.body = JSON.stringify(options.body);\n }\n\n // early throw an abort error in case the request was already cancelled\n const fetchFunc = options.fetch || fetch;\n\n // send the request\n return fetchFunc(url, options)\n .then(async (response) => {\n let data: any = {};\n\n try {\n data = await response.json();\n } catch (err: any) {\n // @todo map against the response content type\n // all api responses are expected to return json\n // with exception of the realtime events and 204\n if (\n options.signal?.aborted ||\n err?.name == \"AbortError\" ||\n err?.message == \"Aborted\"\n ) {\n throw err;\n }\n }\n\n if (this.afterSend) {\n data = await this.afterSend(response, data, options);\n }\n\n if (response.status >= 400) {\n throw new ClientResponseError({\n url: response.url,\n status: response.status,\n data: data,\n });\n }\n\n return data as T;\n })\n .catch((err) => {\n // wrap to normalize all errors\n throw new ClientResponseError(err);\n });\n }\n\n /**\n * Shallow copy the provided object and takes care to initialize\n * any options required to preserve the backward compatability.\n *\n * @param {SendOptions} options\n * @return {SendOptions}\n */\n private initSendOptions(path: string, options: SendOptions): SendOptions {\n options = Object.assign({ method: \"GET\" } as SendOptions, options);\n\n // auto convert the body to FormData, if needed\n options.body = convertToFormDataIfNeeded(options.body);\n\n // move unknown send options as query parameters\n normalizeUnknownQueryParams(options);\n\n // requestKey normalizations for backward-compatibility\n // ---\n options.query = Object.assign({}, options.params, options.query);\n if (typeof options.requestKey === \"undefined\") {\n if (options.$autoCancel === false || options.query.$autoCancel === false) {\n options.requestKey = null;\n } else if (options.$cancelKey || options.query.$cancelKey) {\n options.requestKey = options.$cancelKey || options.query.$cancelKey;\n }\n }\n // remove the deprecated special cancellation params from the other query params\n delete options.$autoCancel;\n delete options.query.$autoCancel;\n delete options.$cancelKey;\n delete options.query.$cancelKey;\n // ---\n\n // add the json header, if not explicitly set\n // (for FormData body the Content-Type header should be skipped since the boundary is autogenerated)\n if (\n this.getHeader(options.headers, \"Content-Type\") === null &&\n !isFormData(options.body)\n ) {\n options.headers = Object.assign({}, options.headers, {\n \"Content-Type\": \"application/json\",\n });\n }\n\n // add Accept-Language header, if not explicitly set\n if (this.getHeader(options.headers, \"Accept-Language\") === null) {\n options.headers = Object.assign({}, options.headers, {\n \"Accept-Language\": this.lang,\n });\n }\n\n // check if Authorization header can be added\n if (\n // has valid token\n this.authStore.token &&\n // auth header is not explicitly set\n this.getHeader(options.headers, \"Authorization\") === null\n ) {\n options.headers = Object.assign({}, options.headers, {\n Authorization: this.authStore.token,\n });\n }\n\n // handle auto cancellation for duplicated pending request\n if (this.enableAutoCancellation && options.requestKey !== null) {\n const requestKey = options.requestKey || (options.method || \"GET\") + path;\n\n delete options.requestKey;\n\n // cancel previous pending requests\n this.cancelRequest(requestKey);\n\n // @todo evaluate if a cleanup after the request is necessary\n // (check also authWithOAuth2 as it currently relies on the controller)\n const controller = new AbortController();\n this.cancelControllers[requestKey] = controller;\n options.signal = controller.signal;\n }\n\n return options;\n }\n\n /**\n * Extracts the header with the provided name in case-insensitive manner.\n * Returns `null` if no header matching the name is found.\n */\n private getHeader(\n headers: { [key: string]: string } | undefined,\n name: string,\n ): string | null {\n headers = headers || {};\n name = name.toLowerCase();\n\n for (let key in headers) {\n if (key.toLowerCase() == name) {\n return headers[key];\n }\n }\n\n return null;\n }\n}\n"],"names":["ClientResponseError","Error","constructor","errData","super","this","url","status","response","isAbort","originalError","Object","setPrototypeOf","prototype","name","message","data","cause","includes","toJSON","fieldContentRegExp","cookieSerialize","val","options","opt","assign","encode","defaultEncode","test","TypeError","value","result","maxAge","isNaN","isFinite","Math","floor","domain","path","expires","isDate","toString","call","Date","valueOf","toUTCString","httpOnly","secure","priority","toLowerCase","sameSite","defaultDecode","indexOf","decodeURIComponent","encodeURIComponent","isReactNative","navigator","product","global","HermesInternal","atobPolyfill","getTokenPayload","token","encodedPayload","split","map","c","charCodeAt","slice","join","JSON","parse","e","isTokenExpired","expirationThreshold","payload","keys","length","exp","now","atob","input","str","String","replace","bs","buffer","bc","idx","output","charAt","fromCharCode","defaultCookieKey","BaseAuthStore","baseToken","baseModel","_onChangeCallbacks","record","model","isValid","isSuperuser","type","collectionName","collectionId","isAdmin","console","warn","isAuthRecord","save","triggerChange","clear","loadFromCookie","cookie","key","rawData","cookieParse","decode","index","eqIdx","endIdx","lastIndexOf","trim","undefined","_","Array","isArray","exportToCookie","defaultOptions","stringify","resultLength","Blob","size","id","email","extraProps","prop","onChange","callback","fireImmediately","push","i","splice","LocalAuthStore","storageKey","storageFallback","_bindStorageEvent","_storageGet","_storageSet","_storageRemove","window","localStorage","rawValue","getItem","normalizedVal","setItem","removeItem","addEventListener","BaseService","client","SettingsService","getAll","method","send","update","bodyParams","body","testS3","filesystem","then","testEmail","collectionIdOrName","toEmail","emailTemplate","template","collection","generateAppleClientSecret","clientId","teamId","keyId","privateKey","duration","knownSendOptionsKeys","normalizeUnknownQueryParams","query","serializeQueryParams","params","encodedKey","arrValue","v","prepareQueryParamValue","toISOString","RealtimeService","eventSource","subscriptions","lastSentSubscriptions","maxConnectTimeout","reconnectAttempts","maxReconnectAttempts","Infinity","predefinedReconnectIntervals","pendingConnects","isConnected","subscribe","topic","serialized","headers","listener","msgEvent","submitSubscriptions","connect","async","unsubscribeByTopicAndListener","unsubscribe","needToSubmit","subs","getSubscriptionsByTopic","hasSubscriptionListeners","removeEventListener","disconnect","unsubscribeByPrefix","keyPrefix","hasAtleastOneTopic","startsWith","exist","keyToCheck","addAllSubscriptionListeners","getNonEmptySubscriptionKeys","requestKey","getSubscriptionsCancelKey","catch","err","removeAllSubscriptionListeners","Promise","resolve","reject","initConnect","clearTimeout","connectTimeoutId","setTimeout","connectErrorHandler","EventSource","buildURL","onerror","lastEventId","retries","hasUnsentSubscriptions","p","reconnectTimeoutId","connectSubs","latestTopics","t","timeout","fromReconnect","onDisconnect","cancelRequest","close","CrudService","getFullList","batchOrqueryParams","_getFullList","batch","getList","page","perPage","baseCrudPath","responseData","items","item","getFirstListItem","filter","skipTotal","code","getOne","create","batchSize","request","list","concat","normalizeLegacyOptionsArgs","legacyWarn","baseOptions","bodyOrOptions","hasQuery","resetAutoRefresh","_resetAutoRefresh","RecordService","baseCollectionPath","isSuperusers","realtime","batchOrOptions","authStore","authExpand","expand","authRecord","delete","success","authResponse","listAuthMethods","fields","authWithPassword","usernameOrEmail","password","autoRefreshThreshold","identity","autoRefresh","authData","registerAutoRefresh","threshold","refreshFunc","reauthenticateFunc","oldBeforeSend","beforeSend","oldModel","unsubStoreChange","newToken","sendOptions","oldToken","authRefresh","authWithOAuth2Code","provider","codeVerifier","redirectURL","createData","authWithOAuth2","args","config","eagerDefaultPopup","urlCallback","openBrowserPopup","cleanup","requestKeyOptions","authMethods","oauth2","providers","find","cancelController","signal","onabort","activeSubscriptions","oldState","state","error","scopes","replacements","_replaceQueryParams","authURL","location","href","requestPasswordReset","confirmPasswordReset","passwordResetToken","passwordConfirm","requestVerification","confirmVerification","verificationToken","verified","requestEmailChange","newEmail","confirmEmailChange","emailChangeToken","listExternalAuths","recordId","unlinkExternalAuth","ea","requestOTP","authWithOTP","otpId","impersonate","Authorization","Client","baseURL","lang","urlPath","substring","parsedParams","rawParams","param","pair","hasOwnProperty","open","width","height","windowWidth","innerWidth","windowHeight","innerHeight","left","top","CollectionService","import","collections","deleteMissing","getScaffolds","truncate","LogService","getStats","HealthService","check","FileService","getUrl","filename","queryParams","getURL","parts","download","getToken","BackupService","basename","upload","restore","getDownloadUrl","getDownloadURL","CronService","run","jobId","isFile","File","uri","isFormData","FormData","hasFileField","values","inferNumberCharsRegex","inferFormDataValue","num","BatchService","requests","SubBatchService","formData","jsonData","req","json","files","file","append","upsert","prepareRequest","convertFormDataToObject","forEach","k","parsed","foundFiles","foundRegular","fileKey","endsWith","baseUrl","cancelControllers","recordServices","enableAutoCancellation","Deno","logs","settings","health","backups","crons","admins","createBatch","idOrName","autoCancellation","enable","abort","cancelAllRequests","raw","replaceAll","getFileUrl","buildUrl","origin","pathname","initSendOptions","getHeader","fetch","aborted","afterSend","convertToFormDataIfNeeded","form","$autoCancel","$cancelKey","controller","AbortController"],"mappings":"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"} \ No newline at end of file diff --git a/script/node_modules/pocketbase/dist/pocketbase.es.d.mts b/script/node_modules/pocketbase/dist/pocketbase.es.d.mts new file mode 100644 index 0000000..94c0a53 --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.es.d.mts @@ -0,0 +1,1583 @@ +interface ParseOptions { + decode?: (val: string) => string; +} +/** + * Parses the given cookie header string into an object + * The object has the various cookies as keys(names) => values + */ +declare function cookieParse(str: string, options?: ParseOptions): { + [key: string]: any; +}; +interface SerializeOptions { + encode?: (val: string | number | boolean) => string; + maxAge?: number; + domain?: string; + path?: string; + expires?: Date; + httpOnly?: boolean; + secure?: boolean; + priority?: string; + sameSite?: boolean | string; +} +/** + * Serialize data into a cookie header. + * + * Serialize the a name value pair into a cookie string suitable for + * http headers. An optional options object specified cookie parameters. + * + * ```js + * cookieSerialize('foo', 'bar', { httpOnly: true }) // "foo=bar; httpOnly" + * ``` + */ +declare function cookieSerialize(name: string, val: string, options?: SerializeOptions): string; +interface ListResult { + page: number; + perPage: number; + totalItems: number; + totalPages: number; + items: Array; +} +interface BaseModel { + [key: string]: any; + id: string; +} +interface LogModel extends BaseModel { + level: string; + message: string; + created: string; + updated: string; + data: { + [key: string]: any; + }; +} +interface RecordModel extends BaseModel { + collectionId: string; + collectionName: string; + expand?: { + [key: string]: any; + }; +} +// ------------------------------------------------------------------- +// Collection types +// ------------------------------------------------------------------- +interface CollectionField { + [key: string]: any; + id: string; + name: string; + type: string; + system: boolean; + hidden: boolean; + presentable: boolean; +} +interface TokenConfig { + duration: number; + secret?: string; +} +interface AuthAlertConfig { + enabled: boolean; + emailTemplate: EmailTemplate; +} +interface OTPConfig { + enabled: boolean; + duration: number; + length: number; + emailTemplate: EmailTemplate; +} +interface MFAConfig { + enabled: boolean; + duration: number; + rule: string; +} +interface PasswordAuthConfig { + enabled: boolean; + identityFields: Array; +} +interface OAuth2Provider { + pkce?: boolean; + clientId: string; + name: string; + clientSecret: string; + authURL: string; + tokenURL: string; + userInfoURL: string; + displayName: string; + extra?: { + [key: string]: any; + }; +} +interface OAuth2Config { + enabled: boolean; + mappedFields: { + [key: string]: string; + }; + providers: Array; +} +interface EmailTemplate { + subject: string; + body: string; +} +interface collection extends BaseModel { + name: string; + fields: Array; + indexes: Array; + system: boolean; + listRule?: string; + viewRule?: string; + createRule?: string; + updateRule?: string; + deleteRule?: string; +} +interface BaseCollectionModel extends collection { + type: "base"; +} +interface ViewCollectionModel extends collection { + type: "view"; + viewQuery: string; +} +interface AuthCollectionModel extends collection { + type: "auth"; + authRule?: string; + manageRule?: string; + authAlert: AuthAlertConfig; + oauth2: OAuth2Config; + passwordAuth: PasswordAuthConfig; + mfa: MFAConfig; + otp: OTPConfig; + authToken: TokenConfig; + passwordResetToken: TokenConfig; + emailChangeToken: TokenConfig; + verificationToken: TokenConfig; + fileToken: TokenConfig; + verificationTemplate: EmailTemplate; + resetPasswordTemplate: EmailTemplate; + confirmEmailChangeTemplate: EmailTemplate; +} +type CollectionModel = BaseCollectionModel | ViewCollectionModel | AuthCollectionModel; +type AuthRecord = RecordModel | null; +type AuthModel = AuthRecord; // for backward compatibility +// for backward compatibility +type OnStoreChangeFunc = (token: string, record: AuthRecord) => void; +/** + * Base AuthStore class that stores the auth state in runtime memory (aka. only for the duration of the store instane). + * + * Usually you wouldn't use it directly and instead use the builtin LocalAuthStore, AsyncAuthStore + * or extend it with your own custom implementation. + */ +declare class BaseAuthStore { + protected baseToken: string; + protected baseModel: AuthRecord; + private _onChangeCallbacks; + /** + * Retrieves the stored token (if any). + */ + get token(): string; + /** + * Retrieves the stored model data (if any). + */ + get record(): AuthRecord; + /** + * @deprecated use `record` instead. + */ + get model(): AuthRecord; + /** + * Loosely checks if the store has valid token (aka. existing and unexpired exp claim). + */ + get isValid(): boolean; + /** + * Loosely checks whether the currently loaded store state is for superuser. + * + * Alternatively you can also compare directly `pb.authStore.record?.collectionName`. + */ + get isSuperuser(): boolean; + /** + * @deprecated use `isSuperuser` instead or simply check the record.collectionName property. + */ + get isAdmin(): boolean; + /** + * @deprecated use `!isSuperuser` instead or simply check the record.collectionName property. + */ + get isAuthRecord(): boolean; + /** + * Saves the provided new token and model data in the auth store. + */ + save(token: string, record?: AuthRecord): void; + /** + * Removes the stored token and model data form the auth store. + */ + clear(): void; + /** + * Parses the provided cookie string and updates the store state + * with the cookie's token and model data. + * + * NB! This function doesn't validate the token or its data. + * Usually this isn't a concern if you are interacting only with the + * PocketBase API because it has the proper server-side security checks in place, + * but if you are using the store `isValid` state for permission controls + * in a node server (eg. SSR), then it is recommended to call `authRefresh()` + * after loading the cookie to ensure an up-to-date token and model state. + * For example: + * + * ```js + * pb.authStore.loadFromCookie("cookie string..."); + * + * try { + * // get an up-to-date auth store state by veryfing and refreshing the loaded auth model (if any) + * pb.authStore.isValid && await pb.collection('users').authRefresh(); + * } catch (_) { + * // clear the auth store on failed refresh + * pb.authStore.clear(); + * } + * ``` + */ + loadFromCookie(cookie: string, key?: string): void; + /** + * Exports the current store state as cookie string. + * + * By default the following optional attributes are added: + * - Secure + * - HttpOnly + * - SameSite=Strict + * - Path=/ + * - Expires={the token expiration date} + * + * NB! If the generated cookie exceeds 4096 bytes, this method will + * strip the model data to the bare minimum to try to fit within the + * recommended size in https://www.rfc-editor.org/rfc/rfc6265#section-6.1. + */ + exportToCookie(options?: SerializeOptions, key?: string): string; + /** + * Register a callback function that will be called on store change. + * + * You can set the `fireImmediately` argument to true in order to invoke + * the provided callback right after registration. + * + * Returns a removal function that you could call to "unsubscribe" from the changes. + */ + onChange(callback: OnStoreChangeFunc, fireImmediately?: boolean): () => void; + protected triggerChange(): void; +} +/** + * BaseService class that should be inherited from all API services. + */ +declare abstract class BaseService { + readonly client: Client; + constructor(client: Client); +} +interface SendOptions extends RequestInit { + // for backward compatibility and to minimize the verbosity, + // any top-level field that doesn't exist in RequestInit or the + // fields below will be treated as query parameter. + [key: string]: any; + /** + * Optional custom fetch function to use for sending the request. + */ + fetch?: (url: RequestInfo | URL, config?: RequestInit) => Promise; + /** + * Custom headers to send with the requests. + */ + headers?: { + [key: string]: string; + }; + /** + * The body of the request (serialized automatically for json requests). + */ + body?: any; + /** + * Query parameters that will be appended to the request url. + */ + query?: { + [key: string]: any; + }; + /** + * @deprecated use `query` instead + * + * for backward-compatibility `params` values are merged with `query`, + * but this option may get removed in the final v1 release + */ + params?: { + [key: string]: any; + }; + /** + * The request identifier that can be used to cancel pending requests. + */ + requestKey?: string | null; + /** + * @deprecated use `requestKey:string` instead + */ + $cancelKey?: string; + /** + * @deprecated use `requestKey:null` instead + */ + $autoCancel?: boolean; +} +interface CommonOptions extends SendOptions { + fields?: string; +} +interface ListOptions extends CommonOptions { + page?: number; + perPage?: number; + sort?: string; + filter?: string; + skipTotal?: boolean; +} +interface FullListOptions extends ListOptions { + batch?: number; +} +interface RecordOptions extends CommonOptions { + expand?: string; +} +interface RecordListOptions extends ListOptions, RecordOptions { +} +interface RecordFullListOptions extends FullListOptions, RecordOptions { +} +interface RecordSubscribeOptions extends SendOptions { + fields?: string; + filter?: string; + expand?: string; +} +interface LogStatsOptions extends CommonOptions { + filter?: string; +} +interface FileOptions extends CommonOptions { + thumb?: string; + download?: boolean; +} +interface AuthOptions extends CommonOptions { + /** + * If autoRefreshThreshold is set it will take care to auto refresh + * when necessary the auth data before each request to ensure that + * the auth state is always valid. + * + * The value must be in seconds, aka. the amount of seconds + * that will be subtracted from the current token `exp` claim in order + * to determine whether it is going to expire within the specified time threshold. + * + * For example, if you want to auto refresh the token if it is + * going to expire in the next 30mins (or already has expired), + * it can be set to `1800` + */ + autoRefreshThreshold?: number; +} +// modifies in place the provided options by moving unknown send options as query parameters. +declare function normalizeUnknownQueryParams(options?: SendOptions): void; +declare function serializeQueryParams(params: { + [key: string]: any; +}): string; +interface appleClientSecret { + secret: string; +} +declare class SettingsService extends BaseService { + /** + * Fetch all available app settings. + * + * @throws {ClientResponseError} + */ + getAll(options?: CommonOptions): Promise<{ + [key: string]: any; + }>; + /** + * Bulk updates app settings. + * + * @throws {ClientResponseError} + */ + update(bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise<{ + [key: string]: any; + }>; + /** + * Performs a S3 filesystem connection test. + * + * The currently supported `filesystem` are "storage" and "backups". + * + * @throws {ClientResponseError} + */ + testS3(filesystem?: string, options?: CommonOptions): Promise; + /** + * Sends a test email. + * + * The possible `emailTemplate` values are: + * - verification + * - password-reset + * - email-change + * + * @throws {ClientResponseError} + */ + testEmail(collectionIdOrName: string, toEmail: string, emailTemplate: string, options?: CommonOptions): Promise; + /** + * Generates a new Apple OAuth2 client secret. + * + * @throws {ClientResponseError} + */ + generateAppleClientSecret(clientId: string, teamId: string, keyId: string, privateKey: string, duration: number, options?: CommonOptions): Promise; +} +type UnsubscribeFunc = () => Promise; +declare class RealtimeService extends BaseService { + clientId: string; + private eventSource; + private subscriptions; + private lastSentSubscriptions; + private connectTimeoutId; + private maxConnectTimeout; + private reconnectTimeoutId; + private reconnectAttempts; + private maxReconnectAttempts; + private predefinedReconnectIntervals; + private pendingConnects; + /** + * Returns whether the realtime connection has been established. + */ + get isConnected(): boolean; + /** + * An optional hook that is invoked when the realtime client disconnects + * either when unsubscribing from all subscriptions or when the + * connection was interrupted or closed by the server. + * + * The received argument could be used to determine whether the disconnect + * is a result from unsubscribing (`activeSubscriptions.length == 0`) + * or because of network/server error (`activeSubscriptions.length > 0`). + * + * If you want to listen for the opposite, aka. when the client connection is established, + * subscribe to the `PB_CONNECT` event. + */ + onDisconnect?: (activeSubscriptions: Array) => void; + /** + * Register the subscription listener. + * + * You can subscribe multiple times to the same topic. + * + * If the SSE connection is not started yet, + * this method will also initialize it. + */ + subscribe(topic: string, callback: (data: any) => void, options?: SendOptions): Promise; + /** + * Unsubscribe from all subscription listeners with the specified topic. + * + * If `topic` is not provided, then this method will unsubscribe + * from all active subscriptions. + * + * This method is no-op if there are no active subscriptions. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribe(topic?: string): Promise; + /** + * Unsubscribe from all subscription listeners starting with the specified topic prefix. + * + * This method is no-op if there are no active subscriptions with the specified topic prefix. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribeByPrefix(keyPrefix: string): Promise; + /** + * Unsubscribe from all subscriptions matching the specified topic and listener function. + * + * This method is no-op if there are no active subscription with + * the specified topic and listener. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribeByTopicAndListener(topic: string, listener: EventListener): Promise; + private hasSubscriptionListeners; + private submitSubscriptions; + private getSubscriptionsCancelKey; + private getSubscriptionsByTopic; + private getNonEmptySubscriptionKeys; + private addAllSubscriptionListeners; + private removeAllSubscriptionListeners; + private connect; + private initConnect; + private hasUnsentSubscriptions; + private connectErrorHandler; + private disconnect; +} +declare abstract class CrudService extends BaseService { + /** + * Base path for the crud actions (without trailing slash, eg. '/admins'). + */ + abstract get baseCrudPath(): string; + /** + * Response data decoder. + */ + decode(data: { + [key: string]: any; + }): T; + /** + * Returns a promise with all list items batch fetched at once + * (by default 1000 items per request; to change it set the `batch` query param). + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + getFullList(options?: FullListOptions): Promise>; + /** + * Legacy version of getFullList with explicitly specified batch size. + */ + getFullList(batch?: number, options?: ListOptions): Promise>; + /** + * Returns paginated items list. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + getList(page?: number, perPage?: number, options?: ListOptions): Promise>; + /** + * Returns the first found item by the specified filter. + * + * Internally it calls `getList(1, 1, { filter, skipTotal })` and + * returns the first found item. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * For consistency with `getOne`, this method will throw a 404 + * ClientResponseError if no item was found. + * + * @throws {ClientResponseError} + */ + getFirstListItem(filter: string, options?: CommonOptions): Promise; + /** + * Returns single item by its id. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * If `id` is empty it will throw a 404 error. + * + * @throws {ClientResponseError} + */ + getOne(id: string, options?: CommonOptions): Promise; + /** + * Creates a new item. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Updates an existing item by its id. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Deletes an existing item by its id. + * + * @throws {ClientResponseError} + */ + delete(id: string, options?: CommonOptions): Promise; + /** + * Returns a promise with all list items batch fetched at once. + */ + protected _getFullList(batchSize?: number, options?: ListOptions): Promise>; +} +interface RecordAuthResponse { + /** + * The signed PocketBase auth record. + */ + record: T; + /** + * The PocketBase record auth token. + * + * If you are looking for the OAuth2 access and refresh tokens + * they are available under the `meta.accessToken` and `meta.refreshToken` props. + */ + token: string; + /** + * Auth meta data usually filled when OAuth2 is used. + */ + meta?: { + [key: string]: any; + }; +} +interface AuthProviderInfo { + name: string; + displayName: string; + state: string; + authURL: string; + codeVerifier: string; + codeChallenge: string; + codeChallengeMethod: string; +} +interface AuthMethodsList { + mfa: { + enabled: boolean; + duration: number; + }; + otp: { + enabled: boolean; + duration: number; + }; + password: { + enabled: boolean; + identityFields: Array; + }; + oauth2: { + enabled: boolean; + providers: Array; + }; +} +interface RecordSubscription { + action: string; // eg. create, update, delete + record: T; +} +type OAuth2UrlCallback = (url: string) => void | Promise; +interface OAuth2AuthConfig extends SendOptions { + // the name of the OAuth2 provider (eg. "google") + provider: string; + // custom scopes to overwrite the default ones + scopes?: Array; + // optional record create data + createData?: { + [key: string]: any; + }; + // optional callback that is triggered after the OAuth2 sign-in/sign-up url generation + urlCallback?: OAuth2UrlCallback; + // optional query params to send with the PocketBase auth request (eg. fields, expand, etc.) + query?: RecordOptions; +} +interface OTPResponse { + otpId: string; +} +declare class RecordService extends CrudService { + readonly collectionIdOrName: string; + constructor(client: Client, collectionIdOrName: string); + /** + * @inheritdoc + */ + get baseCrudPath(): string; + /** + * Returns the current collection service base path. + */ + get baseCollectionPath(): string; + /** + * Returns whether the current service collection is superusers. + */ + get isSuperusers(): boolean; + // --------------------------------------------------------------- + // Realtime handlers + // --------------------------------------------------------------- + /** + * Subscribe to realtime changes to the specified topic ("*" or record id). + * + * If `topic` is the wildcard "*", then this method will subscribe to + * any record changes in the collection. + * + * If `topic` is a record id, then this method will subscribe only + * to changes of the specified record id. + * + * It's OK to subscribe multiple times to the same topic. + * You can use the returned `UnsubscribeFunc` to remove only a single subscription. + * Or use `unsubscribe(topic)` if you want to remove all subscriptions attached to the topic. + */ + subscribe(topic: string, callback: (data: RecordSubscription) => void, options?: RecordSubscribeOptions): Promise; + /** + * Unsubscribe from all subscriptions of the specified topic + * ("*" or record id). + * + * If `topic` is not set, then this method will unsubscribe from + * all subscriptions associated to the current collection. + */ + unsubscribe(topic?: string): Promise; + // --------------------------------------------------------------- + // Crud handers + // --------------------------------------------------------------- + /** + * @inheritdoc + */ + getFullList(options?: RecordFullListOptions): Promise>; + /** + * @inheritdoc + */ + getFullList(batch?: number, options?: RecordListOptions): Promise>; + /** + * @inheritdoc + */ + getList(page?: number, perPage?: number, options?: RecordListOptions): Promise>; + /** + * @inheritdoc + */ + getFirstListItem(filter: string, options?: RecordListOptions): Promise; + /** + * @inheritdoc + */ + getOne(id: string, options?: RecordOptions): Promise; + /** + * @inheritdoc + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): Promise; + /** + * @inheritdoc + * + * If the current `client.authStore.record` matches with the updated id, then + * on success the `client.authStore.record` will be updated with the new response record fields. + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): Promise; + /** + * @inheritdoc + * + * If the current `client.authStore.record` matches with the deleted id, + * then on success the `client.authStore` will be cleared. + */ + delete(id: string, options?: CommonOptions): Promise; + // --------------------------------------------------------------- + // Auth handlers + // --------------------------------------------------------------- + /** + * Prepare successful collection authorization response. + */ + protected authResponse(responseData: any): RecordAuthResponse; + /** + * Returns all available collection auth methods. + * + * @throws {ClientResponseError} + */ + listAuthMethods(options?: CommonOptions): Promise; + /** + * Authenticate a single auth collection record via its username/email and password. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * + * @throws {ClientResponseError} + */ + authWithPassword(usernameOrEmail: string, password: string, options?: RecordOptions): Promise>; + /** + * Authenticate a single auth collection record with OAuth2 code. + * + * If you don't have an OAuth2 code you may also want to check `authWithOAuth2` method. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * - the OAuth2 account data (eg. name, email, avatar, etc.) + * + * @throws {ClientResponseError} + */ + authWithOAuth2Code(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, options?: RecordOptions): Promise>; + /** + * @deprecated + * Consider using authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createdData, options?). + */ + authWithOAuth2Code(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, body?: any, query?: any): Promise>; + /** + * @deprecated This form of authWithOAuth2 is deprecated. + * + * Please use `authWithOAuth2Code()` OR its simplified realtime version + * as shown in https://pocketbase.io/docs/authentication/#oauth2-integration. + */ + authWithOAuth2(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, bodyParams?: { + [key: string]: any; + }, queryParams?: RecordOptions): Promise>; + /** + * Authenticate a single auth collection record with OAuth2 + * **without custom redirects, deeplinks or even page reload**. + * + * This method initializes a one-off realtime subscription and will + * open a popup window with the OAuth2 vendor page to authenticate. + * Once the external OAuth2 sign-in/sign-up flow is completed, the popup + * window will be automatically closed and the OAuth2 data sent back + * to the user through the previously established realtime connection. + * + * You can specify an optional `urlCallback` prop to customize + * the default url `window.open` behavior. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * - the OAuth2 account data (eg. name, email, avatar, etc.) + * + * Example: + * + * ```js + * const authData = await pb.collection("users").authWithOAuth2({ + * provider: "google", + * }) + * ``` + * + * Note1: When creating the OAuth2 app in the provider dashboard + * you have to configure `https://yourdomain.com/api/oauth2-redirect` + * as redirect URL. + * + * Note2: Safari may block the default `urlCallback` popup because + * it doesn't allow `window.open` calls as part of an `async` click functions. + * To workaround this you can either change your click handler to not be marked as `async` + * OR manually call `window.open` before your `async` function and use the + * window reference in your own custom `urlCallback` (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061). + * For example: + * ```js + * + * ... + * document.getElementById("btn").addEventListener("click", () => { + * pb.collection("users").authWithOAuth2({ + * provider: "gitlab", + * }).then((authData) => { + * console.log(authData) + * }).catch((err) => { + * console.log(err, err.originalError); + * }); + * }) + * ``` + * + * @throws {ClientResponseError} + */ + authWithOAuth2(options: OAuth2AuthConfig): Promise>; + /** + * Refreshes the current authenticated record instance and + * returns a new token and record data. + * + * On success this method also automatically updates the client's AuthStore. + * + * @throws {ClientResponseError} + */ + authRefresh(options?: RecordOptions): Promise>; + /** + * @deprecated + * Consider using authRefresh(options?). + */ + authRefresh(body?: any, query?: any): Promise>; + /** + * Sends auth record password reset request. + * + * @throws {ClientResponseError} + */ + requestPasswordReset(email: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestPasswordReset(email, options?). + */ + requestPasswordReset(email: string, body?: any, query?: any): Promise; + /** + * Confirms auth record password reset request. + * + * @throws {ClientResponseError} + */ + confirmPasswordReset(passwordResetToken: string, password: string, passwordConfirm: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmPasswordReset(passwordResetToken, password, passwordConfirm, options?). + */ + confirmPasswordReset(passwordResetToken: string, password: string, passwordConfirm: string, body?: any, query?: any): Promise; + /** + * Sends auth record verification email request. + * + * @throws {ClientResponseError} + */ + requestVerification(email: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestVerification(email, options?). + */ + requestVerification(email: string, body?: any, query?: any): Promise; + /** + * Confirms auth record email verification request. + * + * If the current `client.authStore.record` matches with the auth record from the token, + * then on success the `client.authStore.record.verified` will be updated to `true`. + * + * @throws {ClientResponseError} + */ + confirmVerification(verificationToken: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmVerification(verificationToken, options?). + */ + confirmVerification(verificationToken: string, body?: any, query?: any): Promise; + /** + * Sends an email change request to the authenticated record model. + * + * @throws {ClientResponseError} + */ + requestEmailChange(newEmail: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestEmailChange(newEmail, options?). + */ + requestEmailChange(newEmail: string, body?: any, query?: any): Promise; + /** + * Confirms auth record's new email address. + * + * If the current `client.authStore.record` matches with the auth record from the token, + * then on success the `client.authStore` will be cleared. + * + * @throws {ClientResponseError} + */ + confirmEmailChange(emailChangeToken: string, password: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmEmailChange(emailChangeToken, password, options?). + */ + confirmEmailChange(emailChangeToken: string, password: string, body?: any, query?: any): Promise; + /** + * @deprecated use collection("_externalAuths").* + * + * Lists all linked external auth providers for the specified auth record. + * + * @throws {ClientResponseError} + */ + listExternalAuths(recordId: string, options?: CommonOptions): Promise>; + /** + * @deprecated use collection("_externalAuths").* + * + * Unlink a single external auth provider from the specified auth record. + * + * @throws {ClientResponseError} + */ + unlinkExternalAuth(recordId: string, provider: string, options?: CommonOptions): Promise; + /** + * Sends auth record OTP to the provided email. + * + * @throws {ClientResponseError} + */ + requestOTP(email: string, options?: CommonOptions): Promise; + /** + * Authenticate a single auth collection record via OTP. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * + * @throws {ClientResponseError} + */ + authWithOTP(otpId: string, password: string, options?: CommonOptions): Promise>; + /** + * Impersonate authenticates with the specified recordId and + * returns a new client with the received auth token in a memory store. + * + * If `duration` is 0 the generated auth token will fallback + * to the default collection auth token duration. + * + * This action currently requires superusers privileges. + * + * @throws {ClientResponseError} + */ + impersonate(recordId: string, duration: number, options?: CommonOptions): Promise; + // --------------------------------------------------------------- + // very rudimentary url query params replacement because at the moment + // URL (and URLSearchParams) doesn't seem to be fully supported in React Native + // + // note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html + private _replaceQueryParams; +} +declare class CollectionService extends CrudService { + /** + * @inheritdoc + */ + get baseCrudPath(): string; + /** + * Imports the provided collections. + * + * If `deleteMissing` is `true`, all local collections and their fields, + * that are not present in the imported configuration, WILL BE DELETED + * (including their related records data)! + * + * @throws {ClientResponseError} + */ + import(collections: Array, deleteMissing?: boolean, options?: CommonOptions): Promise; + /** + * Returns type indexed map with scaffolded collection models + * populated with their default field values. + * + * @throws {ClientResponseError} + */ + getScaffolds(options?: CommonOptions): Promise<{ + [key: string]: CollectionModel; + }>; + /** + * Deletes all records associated with the specified collection. + * + * @throws {ClientResponseError} + */ + truncate(collectionIdOrName: string, options?: CommonOptions): Promise; +} +interface HourlyStats { + total: number; + date: string; +} +declare class LogService extends BaseService { + /** + * Returns paginated logs list. + * + * @throws {ClientResponseError} + */ + getList(page?: number, perPage?: number, options?: ListOptions): Promise>; + /** + * Returns a single log by its id. + * + * If `id` is empty it will throw a 404 error. + * + * @throws {ClientResponseError} + */ + getOne(id: string, options?: CommonOptions): Promise; + /** + * Returns logs statistics. + * + * @throws {ClientResponseError} + */ + getStats(options?: LogStatsOptions): Promise>; +} +interface HealthCheckResponse { + code: number; + message: string; + data: { + [key: string]: any; + }; +} +declare class HealthService extends BaseService { + /** + * Checks the health status of the api. + * + * @throws {ClientResponseError} + */ + check(options?: CommonOptions): Promise; +} +declare class FileService extends BaseService { + /** + * @deprecated Please replace with `pb.files.getURL()`. + */ + getUrl(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * Builds and returns an absolute record file url for the provided filename. + */ + getURL(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * Requests a new private file access token for the current auth model. + * + * @throws {ClientResponseError} + */ + getToken(options?: CommonOptions): Promise; +} +interface BackupFileInfo { + key: string; + size: number; + modified: string; +} +declare class BackupService extends BaseService { + /** + * Returns list with all available backup files. + * + * @throws {ClientResponseError} + */ + getFullList(options?: CommonOptions): Promise>; + /** + * Initializes a new backup. + * + * @throws {ClientResponseError} + */ + create(basename: string, options?: CommonOptions): Promise; + /** + * Uploads an existing backup file. + * + * Example: + * + * ```js + * await pb.backups.upload({ + * file: new Blob([...]), + * }); + * ``` + * + * @throws {ClientResponseError} + */ + upload(bodyParams: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Deletes a single backup file. + * + * @throws {ClientResponseError} + */ + delete(key: string, options?: CommonOptions): Promise; + /** + * Initializes an app data restore from an existing backup. + * + * @throws {ClientResponseError} + */ + restore(key: string, options?: CommonOptions): Promise; + /** + * @deprecated Please use `getDownloadURL()`. + */ + getDownloadUrl(token: string, key: string): string; + /** + * Builds a download url for a single existing backup using a + * superuser file token and the backup file key. + * + * The file token can be generated via `pb.files.getToken()`. + */ + getDownloadURL(token: string, key: string): string; +} +interface CronJob { + id: string; + expression: string; +} +declare class CronService extends BaseService { + /** + * Returns list with all registered cron jobs. + * + * @throws {ClientResponseError} + */ + getFullList(options?: CommonOptions): Promise>; + /** + * Runs the specified cron job. + * + * @throws {ClientResponseError} + */ + run(jobId: string, options?: CommonOptions): Promise; +} +interface BatchRequest { + method: string; + url: string; + json?: { + [key: string]: any; + }; + files?: { + [key: string]: Array; + }; + headers?: { + [key: string]: string; + }; +} +interface BatchRequestResult { + status: number; + body: any; +} +declare class BatchService extends BaseService { + private requests; + private subs; + /** + * Starts constructing a batch request entry for the specified collection. + */ + collection(collectionIdOrName: string): SubBatchService; + /** + * Sends the batch requests. + * + * @throws {ClientResponseError} + */ + send(options?: SendOptions): Promise>; +} +declare class SubBatchService { + private requests; + private readonly collectionIdOrName; + constructor(requests: Array, collectionIdOrName: string); + /** + * Registers a record upsert request into the current batch queue. + * + * The request will be executed as update if `bodyParams` have a valid existing record `id` value, otherwise - create. + */ + upsert(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record create request into the current batch queue. + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record update request into the current batch queue. + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record delete request into the current batch queue. + */ + delete(id: string, options?: SendOptions): void; + private prepareRequest; +} +interface BeforeSendResult { + [key: string]: any; // for backward compatibility + url?: string; + options?: { + [key: string]: any; + }; +} +/** + * PocketBase JS Client. + */ +declare class Client { + /** + * The base PocketBase backend url address (eg. 'http://127.0.0.1.8090'). + */ + baseURL: string; + /** + * Legacy getter alias for baseURL. + * @deprecated Please replace with baseURL. + */ + get baseUrl(): string; + /** + * Legacy setter alias for baseURL. + * @deprecated Please replace with baseURL. + */ + set baseUrl(v: string); + /** + * Hook that get triggered right before sending the fetch request, + * allowing you to inspect and modify the url and request options. + * + * For list of the possible options check https://developer.mozilla.org/en-US/docs/Web/API/fetch#options + * + * You can return a non-empty result object `{ url, options }` to replace the url and request options entirely. + * + * Example: + * ```js + * const pb = new PocketBase("https://example.com") + * + * pb.beforeSend = function (url, options) { + * options.headers = Object.assign({}, options.headers, { + * 'X-Custom-Header': 'example', + * }) + * + * return { url, options } + * } + * + * // use the created client as usual... + * ``` + */ + beforeSend?: (url: string, options: SendOptions) => BeforeSendResult | Promise; + /** + * Hook that get triggered after successfully sending the fetch request, + * allowing you to inspect/modify the response object and its parsed data. + * + * Returns the new Promise resolved `data` that will be returned to the client. + * + * Example: + * ```js + * const pb = new PocketBase("https://example.com") + * + * pb.afterSend = function (response, data, options) { + * if (response.status != 200) { + * throw new ClientResponseError({ + * url: response.url, + * status: response.status, + * response: { ... }, + * }) + * } + * + * return data; + * } + * + * // use the created client as usual... + * ``` + */ + afterSend?: ((response: Response, data: any) => any) & ((response: Response, data: any, options: SendOptions) => any); + /** + * Optional language code (default to `en-US`) that will be sent + * with the requests to the server as `Accept-Language` header. + */ + lang: string; + /** + * A replaceable instance of the local auth store service. + */ + authStore: BaseAuthStore; + /** + * An instance of the service that handles the **Settings APIs**. + */ + readonly settings: SettingsService; + /** + * An instance of the service that handles the **Collection APIs**. + */ + readonly collections: CollectionService; + /** + * An instance of the service that handles the **File APIs**. + */ + readonly files: FileService; + /** + * An instance of the service that handles the **Log APIs**. + */ + readonly logs: LogService; + /** + * An instance of the service that handles the **Realtime APIs**. + */ + readonly realtime: RealtimeService; + /** + * An instance of the service that handles the **Health APIs**. + */ + readonly health: HealthService; + /** + * An instance of the service that handles the **Backup APIs**. + */ + readonly backups: BackupService; + /** + * An instance of the service that handles the **Cron APIs**. + */ + readonly crons: CronService; + private cancelControllers; + private recordServices; + private enableAutoCancellation; + constructor(baseURL?: string, authStore?: BaseAuthStore | null, lang?: string); + /** + * @deprecated + * With PocketBase v0.23.0 admins are converted to a regular auth + * collection named "_superusers", aka. you can use directly collection("_superusers"). + */ + get admins(): RecordService; + /** + * Creates a new batch handler for sending multiple transactional + * create/update/upsert/delete collection requests in one network call. + * + * Example: + * ```js + * const batch = pb.createBatch(); + * + * batch.collection("example1").create({ ... }) + * batch.collection("example2").update("RECORD_ID", { ... }) + * batch.collection("example3").delete("RECORD_ID") + * batch.collection("example4").upsert({ ... }) + * + * await batch.send() + * ``` + */ + createBatch(): BatchService; + /** + * Returns the RecordService associated to the specified collection. + */ + collection(idOrName: string): RecordService; + /** + * Globally enable or disable auto cancellation for pending duplicated requests. + */ + autoCancellation(enable: boolean): Client; + /** + * Cancels single request by its cancellation key. + */ + cancelRequest(requestKey: string): Client; + /** + * Cancels all pending requests. + */ + cancelAllRequests(): Client; + /** + * Constructs a filter expression with placeholders populated from a parameters object. + * + * Placeholder parameters are defined with the `{:paramName}` notation. + * + * The following parameter values are supported: + * + * - `string` (_single quotes are autoescaped_) + * - `number` + * - `boolean` + * - `Date` object (_stringified into the PocketBase datetime format_) + * - `null` + * - everything else is converted to a string using `JSON.stringify()` + * + * Example: + * + * ```js + * pb.collection("example").getFirstListItem(pb.filter( + * 'title ~ {:title} && created >= {:created}', + * { title: "example", created: new Date()} + * )) + * ``` + */ + filter(raw: string, params?: { + [key: string]: any; + }): string; + /** + * @deprecated Please use `pb.files.getURL()`. + */ + getFileUrl(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * @deprecated Please use `pb.buildURL()`. + */ + buildUrl(path: string): string; + /** + * Builds a full client url by safely concatenating the provided path. + */ + buildURL(path: string): string; + /** + * Sends an api http request. + * + * @throws {ClientResponseError} + */ + send(path: string, options: SendOptions): Promise; + /** + * Shallow copy the provided object and takes care to initialize + * any options required to preserve the backward compatability. + * + * @param {SendOptions} options + * @return {SendOptions} + */ + private initSendOptions; + /** + * Extracts the header with the provided name in case-insensitive manner. + * Returns `null` if no header matching the name is found. + */ + private getHeader; +} +/** + * ClientResponseError is a custom Error class that is intended to wrap + * and normalize any error thrown by `Client.send()`. + */ +declare class ClientResponseError extends Error { + url: string; + status: number; + response: { + [key: string]: any; + }; + isAbort: boolean; + originalError: any; + constructor(errData?: any); + /** + * Alias for `this.response` for backward compatibility. + */ + get data(): { + [key: string]: any; + }; + /** + * Make a POJO's copy of the current error class instance. + * @see https://github.com/vuex-orm/vuex-orm/issues/255 + */ + toJSON(): this; +} +type AsyncSaveFunc = (serializedPayload: string) => Promise; +type AsyncClearFunc = () => Promise; +/** + * AsyncAuthStore is a helper auth store implementation + * that could be used with any external async persistent layer + * (key-value db, local file, etc.). + * + * Here is an example with the React Native AsyncStorage package: + * + * ``` + * import AsyncStorage from "@react-native-async-storage/async-storage"; + * import PocketBase, { AsyncAuthStore } from "pocketbase"; + * + * const store = new AsyncAuthStore({ + * save: async (serialized) => AsyncStorage.setItem("pb_auth", serialized), + * initial: AsyncStorage.getItem("pb_auth"), + * }); + * + * const pb = new PocketBase("https://example.com", store) + * ``` + */ +declare class AsyncAuthStore extends BaseAuthStore { + private saveFunc; + private clearFunc?; + private queue; + constructor(config: { + // The async function that is called every time + // when the auth store state needs to be persisted. + save: AsyncSaveFunc; + /// An *optional* async function that is called every time + /// when the auth store needs to be cleared. + /// + /// If not explicitly set, `saveFunc` with empty data will be used. + clear?: AsyncClearFunc; + // An *optional* initial data to load into the store. + initial?: string | Promise; + }); + /** + * @inheritdoc + */ + save(token: string, record?: AuthRecord): void; + /** + * @inheritdoc + */ + clear(): void; + /** + * Initializes the auth store state. + */ + private _loadInitial; + /** + * Appends an async function to the queue. + */ + private _enqueue; + /** + * Starts the queue processing. + */ + private _dequeue; +} +/** + * The default token store for browsers with auto fallback + * to runtime/memory if local storage is undefined (e.g. in node env). + */ +declare class LocalAuthStore extends BaseAuthStore { + private storageFallback; + private storageKey; + constructor(storageKey?: string); + /** + * @inheritdoc + */ + get token(): string; + /** + * @inheritdoc + */ + get record(): AuthRecord; + /** + * @deprecated use `record` instead. + */ + get model(): AuthRecord; + /** + * @inheritdoc + */ + save(token: string, record?: AuthRecord): void; + /** + * @inheritdoc + */ + clear(): void; + // --------------------------------------------------------------- + // Internal helpers: + // --------------------------------------------------------------- + /** + * Retrieves `key` from the browser's local storage + * (or runtime/memory if local storage is undefined). + */ + private _storageGet; + /** + * Stores a new data in the browser's local storage + * (or runtime/memory if local storage is undefined). + */ + private _storageSet; + /** + * Removes `key` from the browser's local storage and the runtime/memory. + */ + private _storageRemove; + /** + * Updates the current store state on localStorage change. + */ + private _bindStorageEvent; +} +/** + * Returns JWT token's payload data. + */ +declare function getTokenPayload(token: string): { + [key: string]: any; +}; +/** + * Checks whether a JWT token is expired or not. + * Tokens without `exp` payload key are considered valid. + * Tokens with empty payload (eg. invalid token strings) are considered expired. + * + * @param token The token to check. + * @param [expirationThreshold] Time in seconds that will be subtracted from the token `exp` property. + */ +declare function isTokenExpired(token: string, expirationThreshold?: number): boolean; +export { Client as default, BeforeSendResult, ClientResponseError, CollectionService, HealthCheckResponse, HealthService, HourlyStats, LogService, UnsubscribeFunc, RealtimeService, RecordAuthResponse, AuthProviderInfo, AuthMethodsList, RecordSubscription, OAuth2UrlCallback, OAuth2AuthConfig, OTPResponse, RecordService, CrudService, BatchRequest, BatchRequestResult, BatchService, SubBatchService, AsyncSaveFunc, AsyncClearFunc, AsyncAuthStore, AuthRecord, AuthModel, OnStoreChangeFunc, BaseAuthStore, LocalAuthStore, ListResult, BaseModel, LogModel, RecordModel, CollectionField, TokenConfig, AuthAlertConfig, OTPConfig, MFAConfig, PasswordAuthConfig, OAuth2Provider, OAuth2Config, EmailTemplate, BaseCollectionModel, ViewCollectionModel, AuthCollectionModel, CollectionModel, SendOptions, CommonOptions, ListOptions, FullListOptions, RecordOptions, RecordListOptions, RecordFullListOptions, RecordSubscribeOptions, LogStatsOptions, FileOptions, AuthOptions, normalizeUnknownQueryParams, serializeQueryParams, ParseOptions, cookieParse, SerializeOptions, cookieSerialize, getTokenPayload, isTokenExpired }; diff --git a/script/node_modules/pocketbase/dist/pocketbase.es.d.ts b/script/node_modules/pocketbase/dist/pocketbase.es.d.ts new file mode 100644 index 0000000..94c0a53 --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.es.d.ts @@ -0,0 +1,1583 @@ +interface ParseOptions { + decode?: (val: string) => string; +} +/** + * Parses the given cookie header string into an object + * The object has the various cookies as keys(names) => values + */ +declare function cookieParse(str: string, options?: ParseOptions): { + [key: string]: any; +}; +interface SerializeOptions { + encode?: (val: string | number | boolean) => string; + maxAge?: number; + domain?: string; + path?: string; + expires?: Date; + httpOnly?: boolean; + secure?: boolean; + priority?: string; + sameSite?: boolean | string; +} +/** + * Serialize data into a cookie header. + * + * Serialize the a name value pair into a cookie string suitable for + * http headers. An optional options object specified cookie parameters. + * + * ```js + * cookieSerialize('foo', 'bar', { httpOnly: true }) // "foo=bar; httpOnly" + * ``` + */ +declare function cookieSerialize(name: string, val: string, options?: SerializeOptions): string; +interface ListResult { + page: number; + perPage: number; + totalItems: number; + totalPages: number; + items: Array; +} +interface BaseModel { + [key: string]: any; + id: string; +} +interface LogModel extends BaseModel { + level: string; + message: string; + created: string; + updated: string; + data: { + [key: string]: any; + }; +} +interface RecordModel extends BaseModel { + collectionId: string; + collectionName: string; + expand?: { + [key: string]: any; + }; +} +// ------------------------------------------------------------------- +// Collection types +// ------------------------------------------------------------------- +interface CollectionField { + [key: string]: any; + id: string; + name: string; + type: string; + system: boolean; + hidden: boolean; + presentable: boolean; +} +interface TokenConfig { + duration: number; + secret?: string; +} +interface AuthAlertConfig { + enabled: boolean; + emailTemplate: EmailTemplate; +} +interface OTPConfig { + enabled: boolean; + duration: number; + length: number; + emailTemplate: EmailTemplate; +} +interface MFAConfig { + enabled: boolean; + duration: number; + rule: string; +} +interface PasswordAuthConfig { + enabled: boolean; + identityFields: Array; +} +interface OAuth2Provider { + pkce?: boolean; + clientId: string; + name: string; + clientSecret: string; + authURL: string; + tokenURL: string; + userInfoURL: string; + displayName: string; + extra?: { + [key: string]: any; + }; +} +interface OAuth2Config { + enabled: boolean; + mappedFields: { + [key: string]: string; + }; + providers: Array; +} +interface EmailTemplate { + subject: string; + body: string; +} +interface collection extends BaseModel { + name: string; + fields: Array; + indexes: Array; + system: boolean; + listRule?: string; + viewRule?: string; + createRule?: string; + updateRule?: string; + deleteRule?: string; +} +interface BaseCollectionModel extends collection { + type: "base"; +} +interface ViewCollectionModel extends collection { + type: "view"; + viewQuery: string; +} +interface AuthCollectionModel extends collection { + type: "auth"; + authRule?: string; + manageRule?: string; + authAlert: AuthAlertConfig; + oauth2: OAuth2Config; + passwordAuth: PasswordAuthConfig; + mfa: MFAConfig; + otp: OTPConfig; + authToken: TokenConfig; + passwordResetToken: TokenConfig; + emailChangeToken: TokenConfig; + verificationToken: TokenConfig; + fileToken: TokenConfig; + verificationTemplate: EmailTemplate; + resetPasswordTemplate: EmailTemplate; + confirmEmailChangeTemplate: EmailTemplate; +} +type CollectionModel = BaseCollectionModel | ViewCollectionModel | AuthCollectionModel; +type AuthRecord = RecordModel | null; +type AuthModel = AuthRecord; // for backward compatibility +// for backward compatibility +type OnStoreChangeFunc = (token: string, record: AuthRecord) => void; +/** + * Base AuthStore class that stores the auth state in runtime memory (aka. only for the duration of the store instane). + * + * Usually you wouldn't use it directly and instead use the builtin LocalAuthStore, AsyncAuthStore + * or extend it with your own custom implementation. + */ +declare class BaseAuthStore { + protected baseToken: string; + protected baseModel: AuthRecord; + private _onChangeCallbacks; + /** + * Retrieves the stored token (if any). + */ + get token(): string; + /** + * Retrieves the stored model data (if any). + */ + get record(): AuthRecord; + /** + * @deprecated use `record` instead. + */ + get model(): AuthRecord; + /** + * Loosely checks if the store has valid token (aka. existing and unexpired exp claim). + */ + get isValid(): boolean; + /** + * Loosely checks whether the currently loaded store state is for superuser. + * + * Alternatively you can also compare directly `pb.authStore.record?.collectionName`. + */ + get isSuperuser(): boolean; + /** + * @deprecated use `isSuperuser` instead or simply check the record.collectionName property. + */ + get isAdmin(): boolean; + /** + * @deprecated use `!isSuperuser` instead or simply check the record.collectionName property. + */ + get isAuthRecord(): boolean; + /** + * Saves the provided new token and model data in the auth store. + */ + save(token: string, record?: AuthRecord): void; + /** + * Removes the stored token and model data form the auth store. + */ + clear(): void; + /** + * Parses the provided cookie string and updates the store state + * with the cookie's token and model data. + * + * NB! This function doesn't validate the token or its data. + * Usually this isn't a concern if you are interacting only with the + * PocketBase API because it has the proper server-side security checks in place, + * but if you are using the store `isValid` state for permission controls + * in a node server (eg. SSR), then it is recommended to call `authRefresh()` + * after loading the cookie to ensure an up-to-date token and model state. + * For example: + * + * ```js + * pb.authStore.loadFromCookie("cookie string..."); + * + * try { + * // get an up-to-date auth store state by veryfing and refreshing the loaded auth model (if any) + * pb.authStore.isValid && await pb.collection('users').authRefresh(); + * } catch (_) { + * // clear the auth store on failed refresh + * pb.authStore.clear(); + * } + * ``` + */ + loadFromCookie(cookie: string, key?: string): void; + /** + * Exports the current store state as cookie string. + * + * By default the following optional attributes are added: + * - Secure + * - HttpOnly + * - SameSite=Strict + * - Path=/ + * - Expires={the token expiration date} + * + * NB! If the generated cookie exceeds 4096 bytes, this method will + * strip the model data to the bare minimum to try to fit within the + * recommended size in https://www.rfc-editor.org/rfc/rfc6265#section-6.1. + */ + exportToCookie(options?: SerializeOptions, key?: string): string; + /** + * Register a callback function that will be called on store change. + * + * You can set the `fireImmediately` argument to true in order to invoke + * the provided callback right after registration. + * + * Returns a removal function that you could call to "unsubscribe" from the changes. + */ + onChange(callback: OnStoreChangeFunc, fireImmediately?: boolean): () => void; + protected triggerChange(): void; +} +/** + * BaseService class that should be inherited from all API services. + */ +declare abstract class BaseService { + readonly client: Client; + constructor(client: Client); +} +interface SendOptions extends RequestInit { + // for backward compatibility and to minimize the verbosity, + // any top-level field that doesn't exist in RequestInit or the + // fields below will be treated as query parameter. + [key: string]: any; + /** + * Optional custom fetch function to use for sending the request. + */ + fetch?: (url: RequestInfo | URL, config?: RequestInit) => Promise; + /** + * Custom headers to send with the requests. + */ + headers?: { + [key: string]: string; + }; + /** + * The body of the request (serialized automatically for json requests). + */ + body?: any; + /** + * Query parameters that will be appended to the request url. + */ + query?: { + [key: string]: any; + }; + /** + * @deprecated use `query` instead + * + * for backward-compatibility `params` values are merged with `query`, + * but this option may get removed in the final v1 release + */ + params?: { + [key: string]: any; + }; + /** + * The request identifier that can be used to cancel pending requests. + */ + requestKey?: string | null; + /** + * @deprecated use `requestKey:string` instead + */ + $cancelKey?: string; + /** + * @deprecated use `requestKey:null` instead + */ + $autoCancel?: boolean; +} +interface CommonOptions extends SendOptions { + fields?: string; +} +interface ListOptions extends CommonOptions { + page?: number; + perPage?: number; + sort?: string; + filter?: string; + skipTotal?: boolean; +} +interface FullListOptions extends ListOptions { + batch?: number; +} +interface RecordOptions extends CommonOptions { + expand?: string; +} +interface RecordListOptions extends ListOptions, RecordOptions { +} +interface RecordFullListOptions extends FullListOptions, RecordOptions { +} +interface RecordSubscribeOptions extends SendOptions { + fields?: string; + filter?: string; + expand?: string; +} +interface LogStatsOptions extends CommonOptions { + filter?: string; +} +interface FileOptions extends CommonOptions { + thumb?: string; + download?: boolean; +} +interface AuthOptions extends CommonOptions { + /** + * If autoRefreshThreshold is set it will take care to auto refresh + * when necessary the auth data before each request to ensure that + * the auth state is always valid. + * + * The value must be in seconds, aka. the amount of seconds + * that will be subtracted from the current token `exp` claim in order + * to determine whether it is going to expire within the specified time threshold. + * + * For example, if you want to auto refresh the token if it is + * going to expire in the next 30mins (or already has expired), + * it can be set to `1800` + */ + autoRefreshThreshold?: number; +} +// modifies in place the provided options by moving unknown send options as query parameters. +declare function normalizeUnknownQueryParams(options?: SendOptions): void; +declare function serializeQueryParams(params: { + [key: string]: any; +}): string; +interface appleClientSecret { + secret: string; +} +declare class SettingsService extends BaseService { + /** + * Fetch all available app settings. + * + * @throws {ClientResponseError} + */ + getAll(options?: CommonOptions): Promise<{ + [key: string]: any; + }>; + /** + * Bulk updates app settings. + * + * @throws {ClientResponseError} + */ + update(bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise<{ + [key: string]: any; + }>; + /** + * Performs a S3 filesystem connection test. + * + * The currently supported `filesystem` are "storage" and "backups". + * + * @throws {ClientResponseError} + */ + testS3(filesystem?: string, options?: CommonOptions): Promise; + /** + * Sends a test email. + * + * The possible `emailTemplate` values are: + * - verification + * - password-reset + * - email-change + * + * @throws {ClientResponseError} + */ + testEmail(collectionIdOrName: string, toEmail: string, emailTemplate: string, options?: CommonOptions): Promise; + /** + * Generates a new Apple OAuth2 client secret. + * + * @throws {ClientResponseError} + */ + generateAppleClientSecret(clientId: string, teamId: string, keyId: string, privateKey: string, duration: number, options?: CommonOptions): Promise; +} +type UnsubscribeFunc = () => Promise; +declare class RealtimeService extends BaseService { + clientId: string; + private eventSource; + private subscriptions; + private lastSentSubscriptions; + private connectTimeoutId; + private maxConnectTimeout; + private reconnectTimeoutId; + private reconnectAttempts; + private maxReconnectAttempts; + private predefinedReconnectIntervals; + private pendingConnects; + /** + * Returns whether the realtime connection has been established. + */ + get isConnected(): boolean; + /** + * An optional hook that is invoked when the realtime client disconnects + * either when unsubscribing from all subscriptions or when the + * connection was interrupted or closed by the server. + * + * The received argument could be used to determine whether the disconnect + * is a result from unsubscribing (`activeSubscriptions.length == 0`) + * or because of network/server error (`activeSubscriptions.length > 0`). + * + * If you want to listen for the opposite, aka. when the client connection is established, + * subscribe to the `PB_CONNECT` event. + */ + onDisconnect?: (activeSubscriptions: Array) => void; + /** + * Register the subscription listener. + * + * You can subscribe multiple times to the same topic. + * + * If the SSE connection is not started yet, + * this method will also initialize it. + */ + subscribe(topic: string, callback: (data: any) => void, options?: SendOptions): Promise; + /** + * Unsubscribe from all subscription listeners with the specified topic. + * + * If `topic` is not provided, then this method will unsubscribe + * from all active subscriptions. + * + * This method is no-op if there are no active subscriptions. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribe(topic?: string): Promise; + /** + * Unsubscribe from all subscription listeners starting with the specified topic prefix. + * + * This method is no-op if there are no active subscriptions with the specified topic prefix. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribeByPrefix(keyPrefix: string): Promise; + /** + * Unsubscribe from all subscriptions matching the specified topic and listener function. + * + * This method is no-op if there are no active subscription with + * the specified topic and listener. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribeByTopicAndListener(topic: string, listener: EventListener): Promise; + private hasSubscriptionListeners; + private submitSubscriptions; + private getSubscriptionsCancelKey; + private getSubscriptionsByTopic; + private getNonEmptySubscriptionKeys; + private addAllSubscriptionListeners; + private removeAllSubscriptionListeners; + private connect; + private initConnect; + private hasUnsentSubscriptions; + private connectErrorHandler; + private disconnect; +} +declare abstract class CrudService extends BaseService { + /** + * Base path for the crud actions (without trailing slash, eg. '/admins'). + */ + abstract get baseCrudPath(): string; + /** + * Response data decoder. + */ + decode(data: { + [key: string]: any; + }): T; + /** + * Returns a promise with all list items batch fetched at once + * (by default 1000 items per request; to change it set the `batch` query param). + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + getFullList(options?: FullListOptions): Promise>; + /** + * Legacy version of getFullList with explicitly specified batch size. + */ + getFullList(batch?: number, options?: ListOptions): Promise>; + /** + * Returns paginated items list. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + getList(page?: number, perPage?: number, options?: ListOptions): Promise>; + /** + * Returns the first found item by the specified filter. + * + * Internally it calls `getList(1, 1, { filter, skipTotal })` and + * returns the first found item. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * For consistency with `getOne`, this method will throw a 404 + * ClientResponseError if no item was found. + * + * @throws {ClientResponseError} + */ + getFirstListItem(filter: string, options?: CommonOptions): Promise; + /** + * Returns single item by its id. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * If `id` is empty it will throw a 404 error. + * + * @throws {ClientResponseError} + */ + getOne(id: string, options?: CommonOptions): Promise; + /** + * Creates a new item. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Updates an existing item by its id. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Deletes an existing item by its id. + * + * @throws {ClientResponseError} + */ + delete(id: string, options?: CommonOptions): Promise; + /** + * Returns a promise with all list items batch fetched at once. + */ + protected _getFullList(batchSize?: number, options?: ListOptions): Promise>; +} +interface RecordAuthResponse { + /** + * The signed PocketBase auth record. + */ + record: T; + /** + * The PocketBase record auth token. + * + * If you are looking for the OAuth2 access and refresh tokens + * they are available under the `meta.accessToken` and `meta.refreshToken` props. + */ + token: string; + /** + * Auth meta data usually filled when OAuth2 is used. + */ + meta?: { + [key: string]: any; + }; +} +interface AuthProviderInfo { + name: string; + displayName: string; + state: string; + authURL: string; + codeVerifier: string; + codeChallenge: string; + codeChallengeMethod: string; +} +interface AuthMethodsList { + mfa: { + enabled: boolean; + duration: number; + }; + otp: { + enabled: boolean; + duration: number; + }; + password: { + enabled: boolean; + identityFields: Array; + }; + oauth2: { + enabled: boolean; + providers: Array; + }; +} +interface RecordSubscription { + action: string; // eg. create, update, delete + record: T; +} +type OAuth2UrlCallback = (url: string) => void | Promise; +interface OAuth2AuthConfig extends SendOptions { + // the name of the OAuth2 provider (eg. "google") + provider: string; + // custom scopes to overwrite the default ones + scopes?: Array; + // optional record create data + createData?: { + [key: string]: any; + }; + // optional callback that is triggered after the OAuth2 sign-in/sign-up url generation + urlCallback?: OAuth2UrlCallback; + // optional query params to send with the PocketBase auth request (eg. fields, expand, etc.) + query?: RecordOptions; +} +interface OTPResponse { + otpId: string; +} +declare class RecordService extends CrudService { + readonly collectionIdOrName: string; + constructor(client: Client, collectionIdOrName: string); + /** + * @inheritdoc + */ + get baseCrudPath(): string; + /** + * Returns the current collection service base path. + */ + get baseCollectionPath(): string; + /** + * Returns whether the current service collection is superusers. + */ + get isSuperusers(): boolean; + // --------------------------------------------------------------- + // Realtime handlers + // --------------------------------------------------------------- + /** + * Subscribe to realtime changes to the specified topic ("*" or record id). + * + * If `topic` is the wildcard "*", then this method will subscribe to + * any record changes in the collection. + * + * If `topic` is a record id, then this method will subscribe only + * to changes of the specified record id. + * + * It's OK to subscribe multiple times to the same topic. + * You can use the returned `UnsubscribeFunc` to remove only a single subscription. + * Or use `unsubscribe(topic)` if you want to remove all subscriptions attached to the topic. + */ + subscribe(topic: string, callback: (data: RecordSubscription) => void, options?: RecordSubscribeOptions): Promise; + /** + * Unsubscribe from all subscriptions of the specified topic + * ("*" or record id). + * + * If `topic` is not set, then this method will unsubscribe from + * all subscriptions associated to the current collection. + */ + unsubscribe(topic?: string): Promise; + // --------------------------------------------------------------- + // Crud handers + // --------------------------------------------------------------- + /** + * @inheritdoc + */ + getFullList(options?: RecordFullListOptions): Promise>; + /** + * @inheritdoc + */ + getFullList(batch?: number, options?: RecordListOptions): Promise>; + /** + * @inheritdoc + */ + getList(page?: number, perPage?: number, options?: RecordListOptions): Promise>; + /** + * @inheritdoc + */ + getFirstListItem(filter: string, options?: RecordListOptions): Promise; + /** + * @inheritdoc + */ + getOne(id: string, options?: RecordOptions): Promise; + /** + * @inheritdoc + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): Promise; + /** + * @inheritdoc + * + * If the current `client.authStore.record` matches with the updated id, then + * on success the `client.authStore.record` will be updated with the new response record fields. + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): Promise; + /** + * @inheritdoc + * + * If the current `client.authStore.record` matches with the deleted id, + * then on success the `client.authStore` will be cleared. + */ + delete(id: string, options?: CommonOptions): Promise; + // --------------------------------------------------------------- + // Auth handlers + // --------------------------------------------------------------- + /** + * Prepare successful collection authorization response. + */ + protected authResponse(responseData: any): RecordAuthResponse; + /** + * Returns all available collection auth methods. + * + * @throws {ClientResponseError} + */ + listAuthMethods(options?: CommonOptions): Promise; + /** + * Authenticate a single auth collection record via its username/email and password. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * + * @throws {ClientResponseError} + */ + authWithPassword(usernameOrEmail: string, password: string, options?: RecordOptions): Promise>; + /** + * Authenticate a single auth collection record with OAuth2 code. + * + * If you don't have an OAuth2 code you may also want to check `authWithOAuth2` method. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * - the OAuth2 account data (eg. name, email, avatar, etc.) + * + * @throws {ClientResponseError} + */ + authWithOAuth2Code(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, options?: RecordOptions): Promise>; + /** + * @deprecated + * Consider using authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createdData, options?). + */ + authWithOAuth2Code(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, body?: any, query?: any): Promise>; + /** + * @deprecated This form of authWithOAuth2 is deprecated. + * + * Please use `authWithOAuth2Code()` OR its simplified realtime version + * as shown in https://pocketbase.io/docs/authentication/#oauth2-integration. + */ + authWithOAuth2(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, bodyParams?: { + [key: string]: any; + }, queryParams?: RecordOptions): Promise>; + /** + * Authenticate a single auth collection record with OAuth2 + * **without custom redirects, deeplinks or even page reload**. + * + * This method initializes a one-off realtime subscription and will + * open a popup window with the OAuth2 vendor page to authenticate. + * Once the external OAuth2 sign-in/sign-up flow is completed, the popup + * window will be automatically closed and the OAuth2 data sent back + * to the user through the previously established realtime connection. + * + * You can specify an optional `urlCallback` prop to customize + * the default url `window.open` behavior. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * - the OAuth2 account data (eg. name, email, avatar, etc.) + * + * Example: + * + * ```js + * const authData = await pb.collection("users").authWithOAuth2({ + * provider: "google", + * }) + * ``` + * + * Note1: When creating the OAuth2 app in the provider dashboard + * you have to configure `https://yourdomain.com/api/oauth2-redirect` + * as redirect URL. + * + * Note2: Safari may block the default `urlCallback` popup because + * it doesn't allow `window.open` calls as part of an `async` click functions. + * To workaround this you can either change your click handler to not be marked as `async` + * OR manually call `window.open` before your `async` function and use the + * window reference in your own custom `urlCallback` (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061). + * For example: + * ```js + * + * ... + * document.getElementById("btn").addEventListener("click", () => { + * pb.collection("users").authWithOAuth2({ + * provider: "gitlab", + * }).then((authData) => { + * console.log(authData) + * }).catch((err) => { + * console.log(err, err.originalError); + * }); + * }) + * ``` + * + * @throws {ClientResponseError} + */ + authWithOAuth2(options: OAuth2AuthConfig): Promise>; + /** + * Refreshes the current authenticated record instance and + * returns a new token and record data. + * + * On success this method also automatically updates the client's AuthStore. + * + * @throws {ClientResponseError} + */ + authRefresh(options?: RecordOptions): Promise>; + /** + * @deprecated + * Consider using authRefresh(options?). + */ + authRefresh(body?: any, query?: any): Promise>; + /** + * Sends auth record password reset request. + * + * @throws {ClientResponseError} + */ + requestPasswordReset(email: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestPasswordReset(email, options?). + */ + requestPasswordReset(email: string, body?: any, query?: any): Promise; + /** + * Confirms auth record password reset request. + * + * @throws {ClientResponseError} + */ + confirmPasswordReset(passwordResetToken: string, password: string, passwordConfirm: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmPasswordReset(passwordResetToken, password, passwordConfirm, options?). + */ + confirmPasswordReset(passwordResetToken: string, password: string, passwordConfirm: string, body?: any, query?: any): Promise; + /** + * Sends auth record verification email request. + * + * @throws {ClientResponseError} + */ + requestVerification(email: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestVerification(email, options?). + */ + requestVerification(email: string, body?: any, query?: any): Promise; + /** + * Confirms auth record email verification request. + * + * If the current `client.authStore.record` matches with the auth record from the token, + * then on success the `client.authStore.record.verified` will be updated to `true`. + * + * @throws {ClientResponseError} + */ + confirmVerification(verificationToken: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmVerification(verificationToken, options?). + */ + confirmVerification(verificationToken: string, body?: any, query?: any): Promise; + /** + * Sends an email change request to the authenticated record model. + * + * @throws {ClientResponseError} + */ + requestEmailChange(newEmail: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestEmailChange(newEmail, options?). + */ + requestEmailChange(newEmail: string, body?: any, query?: any): Promise; + /** + * Confirms auth record's new email address. + * + * If the current `client.authStore.record` matches with the auth record from the token, + * then on success the `client.authStore` will be cleared. + * + * @throws {ClientResponseError} + */ + confirmEmailChange(emailChangeToken: string, password: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmEmailChange(emailChangeToken, password, options?). + */ + confirmEmailChange(emailChangeToken: string, password: string, body?: any, query?: any): Promise; + /** + * @deprecated use collection("_externalAuths").* + * + * Lists all linked external auth providers for the specified auth record. + * + * @throws {ClientResponseError} + */ + listExternalAuths(recordId: string, options?: CommonOptions): Promise>; + /** + * @deprecated use collection("_externalAuths").* + * + * Unlink a single external auth provider from the specified auth record. + * + * @throws {ClientResponseError} + */ + unlinkExternalAuth(recordId: string, provider: string, options?: CommonOptions): Promise; + /** + * Sends auth record OTP to the provided email. + * + * @throws {ClientResponseError} + */ + requestOTP(email: string, options?: CommonOptions): Promise; + /** + * Authenticate a single auth collection record via OTP. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * + * @throws {ClientResponseError} + */ + authWithOTP(otpId: string, password: string, options?: CommonOptions): Promise>; + /** + * Impersonate authenticates with the specified recordId and + * returns a new client with the received auth token in a memory store. + * + * If `duration` is 0 the generated auth token will fallback + * to the default collection auth token duration. + * + * This action currently requires superusers privileges. + * + * @throws {ClientResponseError} + */ + impersonate(recordId: string, duration: number, options?: CommonOptions): Promise; + // --------------------------------------------------------------- + // very rudimentary url query params replacement because at the moment + // URL (and URLSearchParams) doesn't seem to be fully supported in React Native + // + // note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html + private _replaceQueryParams; +} +declare class CollectionService extends CrudService { + /** + * @inheritdoc + */ + get baseCrudPath(): string; + /** + * Imports the provided collections. + * + * If `deleteMissing` is `true`, all local collections and their fields, + * that are not present in the imported configuration, WILL BE DELETED + * (including their related records data)! + * + * @throws {ClientResponseError} + */ + import(collections: Array, deleteMissing?: boolean, options?: CommonOptions): Promise; + /** + * Returns type indexed map with scaffolded collection models + * populated with their default field values. + * + * @throws {ClientResponseError} + */ + getScaffolds(options?: CommonOptions): Promise<{ + [key: string]: CollectionModel; + }>; + /** + * Deletes all records associated with the specified collection. + * + * @throws {ClientResponseError} + */ + truncate(collectionIdOrName: string, options?: CommonOptions): Promise; +} +interface HourlyStats { + total: number; + date: string; +} +declare class LogService extends BaseService { + /** + * Returns paginated logs list. + * + * @throws {ClientResponseError} + */ + getList(page?: number, perPage?: number, options?: ListOptions): Promise>; + /** + * Returns a single log by its id. + * + * If `id` is empty it will throw a 404 error. + * + * @throws {ClientResponseError} + */ + getOne(id: string, options?: CommonOptions): Promise; + /** + * Returns logs statistics. + * + * @throws {ClientResponseError} + */ + getStats(options?: LogStatsOptions): Promise>; +} +interface HealthCheckResponse { + code: number; + message: string; + data: { + [key: string]: any; + }; +} +declare class HealthService extends BaseService { + /** + * Checks the health status of the api. + * + * @throws {ClientResponseError} + */ + check(options?: CommonOptions): Promise; +} +declare class FileService extends BaseService { + /** + * @deprecated Please replace with `pb.files.getURL()`. + */ + getUrl(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * Builds and returns an absolute record file url for the provided filename. + */ + getURL(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * Requests a new private file access token for the current auth model. + * + * @throws {ClientResponseError} + */ + getToken(options?: CommonOptions): Promise; +} +interface BackupFileInfo { + key: string; + size: number; + modified: string; +} +declare class BackupService extends BaseService { + /** + * Returns list with all available backup files. + * + * @throws {ClientResponseError} + */ + getFullList(options?: CommonOptions): Promise>; + /** + * Initializes a new backup. + * + * @throws {ClientResponseError} + */ + create(basename: string, options?: CommonOptions): Promise; + /** + * Uploads an existing backup file. + * + * Example: + * + * ```js + * await pb.backups.upload({ + * file: new Blob([...]), + * }); + * ``` + * + * @throws {ClientResponseError} + */ + upload(bodyParams: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Deletes a single backup file. + * + * @throws {ClientResponseError} + */ + delete(key: string, options?: CommonOptions): Promise; + /** + * Initializes an app data restore from an existing backup. + * + * @throws {ClientResponseError} + */ + restore(key: string, options?: CommonOptions): Promise; + /** + * @deprecated Please use `getDownloadURL()`. + */ + getDownloadUrl(token: string, key: string): string; + /** + * Builds a download url for a single existing backup using a + * superuser file token and the backup file key. + * + * The file token can be generated via `pb.files.getToken()`. + */ + getDownloadURL(token: string, key: string): string; +} +interface CronJob { + id: string; + expression: string; +} +declare class CronService extends BaseService { + /** + * Returns list with all registered cron jobs. + * + * @throws {ClientResponseError} + */ + getFullList(options?: CommonOptions): Promise>; + /** + * Runs the specified cron job. + * + * @throws {ClientResponseError} + */ + run(jobId: string, options?: CommonOptions): Promise; +} +interface BatchRequest { + method: string; + url: string; + json?: { + [key: string]: any; + }; + files?: { + [key: string]: Array; + }; + headers?: { + [key: string]: string; + }; +} +interface BatchRequestResult { + status: number; + body: any; +} +declare class BatchService extends BaseService { + private requests; + private subs; + /** + * Starts constructing a batch request entry for the specified collection. + */ + collection(collectionIdOrName: string): SubBatchService; + /** + * Sends the batch requests. + * + * @throws {ClientResponseError} + */ + send(options?: SendOptions): Promise>; +} +declare class SubBatchService { + private requests; + private readonly collectionIdOrName; + constructor(requests: Array, collectionIdOrName: string); + /** + * Registers a record upsert request into the current batch queue. + * + * The request will be executed as update if `bodyParams` have a valid existing record `id` value, otherwise - create. + */ + upsert(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record create request into the current batch queue. + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record update request into the current batch queue. + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record delete request into the current batch queue. + */ + delete(id: string, options?: SendOptions): void; + private prepareRequest; +} +interface BeforeSendResult { + [key: string]: any; // for backward compatibility + url?: string; + options?: { + [key: string]: any; + }; +} +/** + * PocketBase JS Client. + */ +declare class Client { + /** + * The base PocketBase backend url address (eg. 'http://127.0.0.1.8090'). + */ + baseURL: string; + /** + * Legacy getter alias for baseURL. + * @deprecated Please replace with baseURL. + */ + get baseUrl(): string; + /** + * Legacy setter alias for baseURL. + * @deprecated Please replace with baseURL. + */ + set baseUrl(v: string); + /** + * Hook that get triggered right before sending the fetch request, + * allowing you to inspect and modify the url and request options. + * + * For list of the possible options check https://developer.mozilla.org/en-US/docs/Web/API/fetch#options + * + * You can return a non-empty result object `{ url, options }` to replace the url and request options entirely. + * + * Example: + * ```js + * const pb = new PocketBase("https://example.com") + * + * pb.beforeSend = function (url, options) { + * options.headers = Object.assign({}, options.headers, { + * 'X-Custom-Header': 'example', + * }) + * + * return { url, options } + * } + * + * // use the created client as usual... + * ``` + */ + beforeSend?: (url: string, options: SendOptions) => BeforeSendResult | Promise; + /** + * Hook that get triggered after successfully sending the fetch request, + * allowing you to inspect/modify the response object and its parsed data. + * + * Returns the new Promise resolved `data` that will be returned to the client. + * + * Example: + * ```js + * const pb = new PocketBase("https://example.com") + * + * pb.afterSend = function (response, data, options) { + * if (response.status != 200) { + * throw new ClientResponseError({ + * url: response.url, + * status: response.status, + * response: { ... }, + * }) + * } + * + * return data; + * } + * + * // use the created client as usual... + * ``` + */ + afterSend?: ((response: Response, data: any) => any) & ((response: Response, data: any, options: SendOptions) => any); + /** + * Optional language code (default to `en-US`) that will be sent + * with the requests to the server as `Accept-Language` header. + */ + lang: string; + /** + * A replaceable instance of the local auth store service. + */ + authStore: BaseAuthStore; + /** + * An instance of the service that handles the **Settings APIs**. + */ + readonly settings: SettingsService; + /** + * An instance of the service that handles the **Collection APIs**. + */ + readonly collections: CollectionService; + /** + * An instance of the service that handles the **File APIs**. + */ + readonly files: FileService; + /** + * An instance of the service that handles the **Log APIs**. + */ + readonly logs: LogService; + /** + * An instance of the service that handles the **Realtime APIs**. + */ + readonly realtime: RealtimeService; + /** + * An instance of the service that handles the **Health APIs**. + */ + readonly health: HealthService; + /** + * An instance of the service that handles the **Backup APIs**. + */ + readonly backups: BackupService; + /** + * An instance of the service that handles the **Cron APIs**. + */ + readonly crons: CronService; + private cancelControllers; + private recordServices; + private enableAutoCancellation; + constructor(baseURL?: string, authStore?: BaseAuthStore | null, lang?: string); + /** + * @deprecated + * With PocketBase v0.23.0 admins are converted to a regular auth + * collection named "_superusers", aka. you can use directly collection("_superusers"). + */ + get admins(): RecordService; + /** + * Creates a new batch handler for sending multiple transactional + * create/update/upsert/delete collection requests in one network call. + * + * Example: + * ```js + * const batch = pb.createBatch(); + * + * batch.collection("example1").create({ ... }) + * batch.collection("example2").update("RECORD_ID", { ... }) + * batch.collection("example3").delete("RECORD_ID") + * batch.collection("example4").upsert({ ... }) + * + * await batch.send() + * ``` + */ + createBatch(): BatchService; + /** + * Returns the RecordService associated to the specified collection. + */ + collection(idOrName: string): RecordService; + /** + * Globally enable or disable auto cancellation for pending duplicated requests. + */ + autoCancellation(enable: boolean): Client; + /** + * Cancels single request by its cancellation key. + */ + cancelRequest(requestKey: string): Client; + /** + * Cancels all pending requests. + */ + cancelAllRequests(): Client; + /** + * Constructs a filter expression with placeholders populated from a parameters object. + * + * Placeholder parameters are defined with the `{:paramName}` notation. + * + * The following parameter values are supported: + * + * - `string` (_single quotes are autoescaped_) + * - `number` + * - `boolean` + * - `Date` object (_stringified into the PocketBase datetime format_) + * - `null` + * - everything else is converted to a string using `JSON.stringify()` + * + * Example: + * + * ```js + * pb.collection("example").getFirstListItem(pb.filter( + * 'title ~ {:title} && created >= {:created}', + * { title: "example", created: new Date()} + * )) + * ``` + */ + filter(raw: string, params?: { + [key: string]: any; + }): string; + /** + * @deprecated Please use `pb.files.getURL()`. + */ + getFileUrl(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * @deprecated Please use `pb.buildURL()`. + */ + buildUrl(path: string): string; + /** + * Builds a full client url by safely concatenating the provided path. + */ + buildURL(path: string): string; + /** + * Sends an api http request. + * + * @throws {ClientResponseError} + */ + send(path: string, options: SendOptions): Promise; + /** + * Shallow copy the provided object and takes care to initialize + * any options required to preserve the backward compatability. + * + * @param {SendOptions} options + * @return {SendOptions} + */ + private initSendOptions; + /** + * Extracts the header with the provided name in case-insensitive manner. + * Returns `null` if no header matching the name is found. + */ + private getHeader; +} +/** + * ClientResponseError is a custom Error class that is intended to wrap + * and normalize any error thrown by `Client.send()`. + */ +declare class ClientResponseError extends Error { + url: string; + status: number; + response: { + [key: string]: any; + }; + isAbort: boolean; + originalError: any; + constructor(errData?: any); + /** + * Alias for `this.response` for backward compatibility. + */ + get data(): { + [key: string]: any; + }; + /** + * Make a POJO's copy of the current error class instance. + * @see https://github.com/vuex-orm/vuex-orm/issues/255 + */ + toJSON(): this; +} +type AsyncSaveFunc = (serializedPayload: string) => Promise; +type AsyncClearFunc = () => Promise; +/** + * AsyncAuthStore is a helper auth store implementation + * that could be used with any external async persistent layer + * (key-value db, local file, etc.). + * + * Here is an example with the React Native AsyncStorage package: + * + * ``` + * import AsyncStorage from "@react-native-async-storage/async-storage"; + * import PocketBase, { AsyncAuthStore } from "pocketbase"; + * + * const store = new AsyncAuthStore({ + * save: async (serialized) => AsyncStorage.setItem("pb_auth", serialized), + * initial: AsyncStorage.getItem("pb_auth"), + * }); + * + * const pb = new PocketBase("https://example.com", store) + * ``` + */ +declare class AsyncAuthStore extends BaseAuthStore { + private saveFunc; + private clearFunc?; + private queue; + constructor(config: { + // The async function that is called every time + // when the auth store state needs to be persisted. + save: AsyncSaveFunc; + /// An *optional* async function that is called every time + /// when the auth store needs to be cleared. + /// + /// If not explicitly set, `saveFunc` with empty data will be used. + clear?: AsyncClearFunc; + // An *optional* initial data to load into the store. + initial?: string | Promise; + }); + /** + * @inheritdoc + */ + save(token: string, record?: AuthRecord): void; + /** + * @inheritdoc + */ + clear(): void; + /** + * Initializes the auth store state. + */ + private _loadInitial; + /** + * Appends an async function to the queue. + */ + private _enqueue; + /** + * Starts the queue processing. + */ + private _dequeue; +} +/** + * The default token store for browsers with auto fallback + * to runtime/memory if local storage is undefined (e.g. in node env). + */ +declare class LocalAuthStore extends BaseAuthStore { + private storageFallback; + private storageKey; + constructor(storageKey?: string); + /** + * @inheritdoc + */ + get token(): string; + /** + * @inheritdoc + */ + get record(): AuthRecord; + /** + * @deprecated use `record` instead. + */ + get model(): AuthRecord; + /** + * @inheritdoc + */ + save(token: string, record?: AuthRecord): void; + /** + * @inheritdoc + */ + clear(): void; + // --------------------------------------------------------------- + // Internal helpers: + // --------------------------------------------------------------- + /** + * Retrieves `key` from the browser's local storage + * (or runtime/memory if local storage is undefined). + */ + private _storageGet; + /** + * Stores a new data in the browser's local storage + * (or runtime/memory if local storage is undefined). + */ + private _storageSet; + /** + * Removes `key` from the browser's local storage and the runtime/memory. + */ + private _storageRemove; + /** + * Updates the current store state on localStorage change. + */ + private _bindStorageEvent; +} +/** + * Returns JWT token's payload data. + */ +declare function getTokenPayload(token: string): { + [key: string]: any; +}; +/** + * Checks whether a JWT token is expired or not. + * Tokens without `exp` payload key are considered valid. + * Tokens with empty payload (eg. invalid token strings) are considered expired. + * + * @param token The token to check. + * @param [expirationThreshold] Time in seconds that will be subtracted from the token `exp` property. + */ +declare function isTokenExpired(token: string, expirationThreshold?: number): boolean; +export { Client as default, BeforeSendResult, ClientResponseError, CollectionService, HealthCheckResponse, HealthService, HourlyStats, LogService, UnsubscribeFunc, RealtimeService, RecordAuthResponse, AuthProviderInfo, AuthMethodsList, RecordSubscription, OAuth2UrlCallback, OAuth2AuthConfig, OTPResponse, RecordService, CrudService, BatchRequest, BatchRequestResult, BatchService, SubBatchService, AsyncSaveFunc, AsyncClearFunc, AsyncAuthStore, AuthRecord, AuthModel, OnStoreChangeFunc, BaseAuthStore, LocalAuthStore, ListResult, BaseModel, LogModel, RecordModel, CollectionField, TokenConfig, AuthAlertConfig, OTPConfig, MFAConfig, PasswordAuthConfig, OAuth2Provider, OAuth2Config, EmailTemplate, BaseCollectionModel, ViewCollectionModel, AuthCollectionModel, CollectionModel, SendOptions, CommonOptions, ListOptions, FullListOptions, RecordOptions, RecordListOptions, RecordFullListOptions, RecordSubscribeOptions, LogStatsOptions, FileOptions, AuthOptions, normalizeUnknownQueryParams, serializeQueryParams, ParseOptions, cookieParse, SerializeOptions, cookieSerialize, getTokenPayload, isTokenExpired }; diff --git a/script/node_modules/pocketbase/dist/pocketbase.es.js b/script/node_modules/pocketbase/dist/pocketbase.es.js new file mode 100644 index 0000000..0768024 --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.es.js @@ -0,0 +1,2 @@ +class ClientResponseError extends Error{constructor(e){super("ClientResponseError"),this.url="",this.status=0,this.response={},this.isAbort=!1,this.originalError=null,Object.setPrototypeOf(this,ClientResponseError.prototype),null!==e&&"object"==typeof e&&(this.originalError=e.originalError,this.url="string"==typeof e.url?e.url:"",this.status="number"==typeof e.status?e.status:0,this.isAbort=!!e.isAbort||"AbortError"===e.name||"Aborted"===e.message,null!==e.response&&"object"==typeof e.response?this.response=e.response:null!==e.data&&"object"==typeof e.data?this.response=e.data:this.response={}),this.originalError||e instanceof ClientResponseError||(this.originalError=e),this.name="ClientResponseError "+this.status,this.message=this.response?.message,this.message||(this.isAbort?this.message="The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).":this.originalError?.cause?.message?.includes("ECONNREFUSED ::1")?this.message="Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).":this.message="Something went wrong."),this.cause=this.originalError}get data(){return this.response}toJSON(){return{...this}}}const e=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function cookieParse(e,t){const s={};if("string"!=typeof e)return s;const i=Object.assign({},t||{}).decode||defaultDecode;let n=0;for(;n0&&(!s.exp||s.exp-t>Date.now()/1e3))}s="function"!=typeof atob||t?e=>{let t=String(e).replace(/=+$/,"");if(t.length%4==1)throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");for(var s,i,n=0,r=0,o="";i=t.charAt(r++);~i&&(s=n%4?64*s+i:i,n++%4)?o+=String.fromCharCode(255&s>>(-2*n&6)):0)i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(i);return o}:atob;const i="pb_auth";class BaseAuthStore{constructor(){this.baseToken="",this.baseModel=null,this._onChangeCallbacks=[]}get token(){return this.baseToken}get record(){return this.baseModel}get model(){return this.baseModel}get isValid(){return!isTokenExpired(this.token)}get isSuperuser(){let e=getTokenPayload(this.token);return"auth"==e.type&&("_superusers"==this.record?.collectionName||!this.record?.collectionName&&"pbc_3142635823"==e.collectionId)}get isAdmin(){return console.warn("Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName"),this.isSuperuser}get isAuthRecord(){return console.warn("Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName"),"auth"==getTokenPayload(this.token).type&&!this.isSuperuser}save(e,t){this.baseToken=e||"",this.baseModel=t||null,this.triggerChange()}clear(){this.baseToken="",this.baseModel=null,this.triggerChange()}loadFromCookie(e,t=i){const s=cookieParse(e||"")[t]||"";let n={};try{n=JSON.parse(s),(null===typeof n||"object"!=typeof n||Array.isArray(n))&&(n={})}catch(e){}this.save(n.token||"",n.record||n.model||null)}exportToCookie(e,t=i){const s={secure:!0,sameSite:!0,httpOnly:!0,path:"/"},n=getTokenPayload(this.token);s.expires=n?.exp?new Date(1e3*n.exp):new Date("1970-01-01"),e=Object.assign({},s,e);const r={token:this.token,record:this.record?JSON.parse(JSON.stringify(this.record)):null};let o=cookieSerialize(t,JSON.stringify(r),e);const a="undefined"!=typeof Blob?new Blob([o]).size:o.length;if(r.record&&a>4096){r.record={id:r.record?.id,email:r.record?.email};const s=["collectionId","collectionName","verified"];for(const e in this.record)s.includes(e)&&(r.record[e]=this.record[e]);o=cookieSerialize(t,JSON.stringify(r),e)}return o}onChange(e,t=!1){return this._onChangeCallbacks.push(e),t&&e(this.token,this.record),()=>{for(let t=this._onChangeCallbacks.length-1;t>=0;t--)if(this._onChangeCallbacks[t]==e)return delete this._onChangeCallbacks[t],void this._onChangeCallbacks.splice(t,1)}}triggerChange(){for(const e of this._onChangeCallbacks)e&&e(this.token,this.record)}}class LocalAuthStore extends BaseAuthStore{constructor(e="pocketbase_auth"){super(),this.storageFallback={},this.storageKey=e,this._bindStorageEvent()}get token(){return(this._storageGet(this.storageKey)||{}).token||""}get record(){const e=this._storageGet(this.storageKey)||{};return e.record||e.model||null}get model(){return this.record}save(e,t){this._storageSet(this.storageKey,{token:e,record:t}),super.save(e,t)}clear(){this._storageRemove(this.storageKey),super.clear()}_storageGet(e){if("undefined"!=typeof window&&window?.localStorage){const t=window.localStorage.getItem(e)||"";try{return JSON.parse(t)}catch(e){return t}}return this.storageFallback[e]}_storageSet(e,t){if("undefined"!=typeof window&&window?.localStorage){let s=t;"string"!=typeof t&&(s=JSON.stringify(t)),window.localStorage.setItem(e,s)}else this.storageFallback[e]=t}_storageRemove(e){"undefined"!=typeof window&&window?.localStorage&&window.localStorage?.removeItem(e),delete this.storageFallback[e]}_bindStorageEvent(){"undefined"!=typeof window&&window?.localStorage&&window.addEventListener&&window.addEventListener("storage",(e=>{if(e.key!=this.storageKey)return;const t=this._storageGet(this.storageKey)||{};super.save(t.token||"",t.record||t.model||null)}))}}class BaseService{constructor(e){this.client=e}}class SettingsService extends BaseService{async getAll(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/settings",e)}async update(e,t){return t=Object.assign({method:"PATCH",body:e},t),this.client.send("/api/settings",t)}async testS3(e="storage",t){return t=Object.assign({method:"POST",body:{filesystem:e}},t),this.client.send("/api/settings/test/s3",t).then((()=>!0))}async testEmail(e,t,s,i){return i=Object.assign({method:"POST",body:{email:t,template:s,collection:e}},i),this.client.send("/api/settings/test/email",i).then((()=>!0))}async generateAppleClientSecret(e,t,s,i,n,r){return r=Object.assign({method:"POST",body:{clientId:e,teamId:t,keyId:s,privateKey:i,duration:n}},r),this.client.send("/api/settings/apple/generate-client-secret",r)}}const n=["requestKey","$cancelKey","$autoCancel","fetch","headers","body","query","params","cache","credentials","headers","integrity","keepalive","method","mode","redirect","referrer","referrerPolicy","signal","window"];function normalizeUnknownQueryParams(e){if(e){e.query=e.query||{};for(let t in e)n.includes(t)||(e.query[t]=e[t],delete e[t])}}function serializeQueryParams(e){const t=[];for(const s in e){const i=encodeURIComponent(s),n=Array.isArray(e[s])?e[s]:[e[s]];for(let e of n)e=prepareQueryParamValue(e),null!==e&&t.push(i+"="+e)}return t.join("&")}function prepareQueryParamValue(e){return null==e?null:e instanceof Date?encodeURIComponent(e.toISOString().replace("T"," ")):"object"==typeof e?encodeURIComponent(JSON.stringify(e)):encodeURIComponent(e)}class RealtimeService extends BaseService{constructor(){super(...arguments),this.clientId="",this.eventSource=null,this.subscriptions={},this.lastSentSubscriptions=[],this.maxConnectTimeout=15e3,this.reconnectAttempts=0,this.maxReconnectAttempts=1/0,this.predefinedReconnectIntervals=[200,300,500,1e3,1200,1500,2e3],this.pendingConnects=[]}get isConnected(){return!!this.eventSource&&!!this.clientId&&!this.pendingConnects.length}async subscribe(e,t,s){if(!e)throw new Error("topic must be set.");let i=e;if(s){normalizeUnknownQueryParams(s=Object.assign({},s));const e="options="+encodeURIComponent(JSON.stringify({query:s.query,headers:s.headers}));i+=(i.includes("?")?"&":"?")+e}const listener=function(e){const s=e;let i;try{i=JSON.parse(s?.data)}catch{}t(i||{})};return this.subscriptions[i]||(this.subscriptions[i]=[]),this.subscriptions[i].push(listener),this.isConnected?1===this.subscriptions[i].length?await this.submitSubscriptions():this.eventSource?.addEventListener(i,listener):await this.connect(),async()=>this.unsubscribeByTopicAndListener(e,listener)}async unsubscribe(e){let t=!1;if(e){const s=this.getSubscriptionsByTopic(e);for(let e in s)if(this.hasSubscriptionListeners(e)){for(let t of this.subscriptions[e])this.eventSource?.removeEventListener(e,t);delete this.subscriptions[e],t||(t=!0)}}else this.subscriptions={};this.hasSubscriptionListeners()?t&&await this.submitSubscriptions():this.disconnect()}async unsubscribeByPrefix(e){let t=!1;for(let s in this.subscriptions)if((s+"?").startsWith(e)){t=!0;for(let e of this.subscriptions[s])this.eventSource?.removeEventListener(s,e);delete this.subscriptions[s]}t&&(this.hasSubscriptionListeners()?await this.submitSubscriptions():this.disconnect())}async unsubscribeByTopicAndListener(e,t){let s=!1;const i=this.getSubscriptionsByTopic(e);for(let e in i){if(!Array.isArray(this.subscriptions[e])||!this.subscriptions[e].length)continue;let i=!1;for(let s=this.subscriptions[e].length-1;s>=0;s--)this.subscriptions[e][s]===t&&(i=!0,delete this.subscriptions[e][s],this.subscriptions[e].splice(s,1),this.eventSource?.removeEventListener(e,t));i&&(this.subscriptions[e].length||delete this.subscriptions[e],s||this.hasSubscriptionListeners(e)||(s=!0))}this.hasSubscriptionListeners()?s&&await this.submitSubscriptions():this.disconnect()}hasSubscriptionListeners(e){if(this.subscriptions=this.subscriptions||{},e)return!!this.subscriptions[e]?.length;for(let e in this.subscriptions)if(this.subscriptions[e]?.length)return!0;return!1}async submitSubscriptions(){if(this.clientId)return this.addAllSubscriptionListeners(),this.lastSentSubscriptions=this.getNonEmptySubscriptionKeys(),this.client.send("/api/realtime",{method:"POST",body:{clientId:this.clientId,subscriptions:this.lastSentSubscriptions},requestKey:this.getSubscriptionsCancelKey()}).catch((e=>{if(!e?.isAbort)throw e}))}getSubscriptionsCancelKey(){return"realtime_"+this.clientId}getSubscriptionsByTopic(e){const t={};e=e.includes("?")?e:e+"?";for(let s in this.subscriptions)(s+"?").startsWith(e)&&(t[s]=this.subscriptions[s]);return t}getNonEmptySubscriptionKeys(){const e=[];for(let t in this.subscriptions)this.subscriptions[t].length&&e.push(t);return e}addAllSubscriptionListeners(){if(this.eventSource){this.removeAllSubscriptionListeners();for(let e in this.subscriptions)for(let t of this.subscriptions[e])this.eventSource.addEventListener(e,t)}}removeAllSubscriptionListeners(){if(this.eventSource)for(let e in this.subscriptions)for(let t of this.subscriptions[e])this.eventSource.removeEventListener(e,t)}async connect(){if(!(this.reconnectAttempts>0))return new Promise(((e,t)=>{this.pendingConnects.push({resolve:e,reject:t}),this.pendingConnects.length>1||this.initConnect()}))}initConnect(){this.disconnect(!0),clearTimeout(this.connectTimeoutId),this.connectTimeoutId=setTimeout((()=>{this.connectErrorHandler(new Error("EventSource connect took too long."))}),this.maxConnectTimeout),this.eventSource=new EventSource(this.client.buildURL("/api/realtime")),this.eventSource.onerror=e=>{this.connectErrorHandler(new Error("Failed to establish realtime connection."))},this.eventSource.addEventListener("PB_CONNECT",(e=>{const t=e;this.clientId=t?.lastEventId,this.submitSubscriptions().then((async()=>{let e=3;for(;this.hasUnsentSubscriptions()&&e>0;)e--,await this.submitSubscriptions()})).then((()=>{for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[],this.reconnectAttempts=0,clearTimeout(this.reconnectTimeoutId),clearTimeout(this.connectTimeoutId);const t=this.getSubscriptionsByTopic("PB_CONNECT");for(let s in t)for(let i of t[s])i(e)})).catch((e=>{this.clientId="",this.connectErrorHandler(e)}))}))}hasUnsentSubscriptions(){const e=this.getNonEmptySubscriptionKeys();if(e.length!=this.lastSentSubscriptions.length)return!0;for(const t of e)if(!this.lastSentSubscriptions.includes(t))return!0;return!1}connectErrorHandler(e){if(clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),!this.clientId&&!this.reconnectAttempts||this.reconnectAttempts>this.maxReconnectAttempts){for(let t of this.pendingConnects)t.reject(new ClientResponseError(e));return this.pendingConnects=[],void this.disconnect()}this.disconnect(!0);const t=this.predefinedReconnectIntervals[this.reconnectAttempts]||this.predefinedReconnectIntervals[this.predefinedReconnectIntervals.length-1];this.reconnectAttempts++,this.reconnectTimeoutId=setTimeout((()=>{this.initConnect()}),t)}disconnect(e=!1){if(this.clientId&&this.onDisconnect&&this.onDisconnect(Object.keys(this.subscriptions)),clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),this.removeAllSubscriptionListeners(),this.client.cancelRequest(this.getSubscriptionsCancelKey()),this.eventSource?.close(),this.eventSource=null,this.clientId="",!e){this.reconnectAttempts=0;for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[]}}}class CrudService extends BaseService{decode(e){return e}async getFullList(e,t){if("number"==typeof e)return this._getFullList(e,t);let s=1e3;return(t=Object.assign({},e,t)).batch&&(s=t.batch,delete t.batch),this._getFullList(s,t)}async getList(e=1,t=30,s){return(s=Object.assign({method:"GET"},s)).query=Object.assign({page:e,perPage:t},s.query),this.client.send(this.baseCrudPath,s).then((e=>(e.items=e.items?.map((e=>this.decode(e)))||[],e)))}async getFirstListItem(e,t){return(t=Object.assign({requestKey:"one_by_filter_"+this.baseCrudPath+"_"+e},t)).query=Object.assign({filter:e,skipTotal:1},t.query),this.getList(1,1,t).then((e=>{if(!e?.items?.length)throw new ClientResponseError({status:404,response:{code:404,message:"The requested resource wasn't found.",data:{}}});return e.items[0]}))}async getOne(e,t){if(!e)throw new ClientResponseError({url:this.client.buildURL(this.baseCrudPath+"/"),status:404,response:{code:404,message:"Missing required record id.",data:{}}});return t=Object.assign({method:"GET"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),t).then((e=>this.decode(e)))}async create(e,t){return t=Object.assign({method:"POST",body:e},t),this.client.send(this.baseCrudPath,t).then((e=>this.decode(e)))}async update(e,t,s){return s=Object.assign({method:"PATCH",body:t},s),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),s).then((e=>this.decode(e)))}async delete(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),t).then((()=>!0))}_getFullList(e=1e3,t){(t=t||{}).query=Object.assign({skipTotal:1},t.query);let s=[],request=async i=>this.getList(i,e||1e3,t).then((e=>{const t=e.items;return s=s.concat(t),t.length==e.perPage?request(i+1):s}));return request(1)}}function normalizeLegacyOptionsArgs(e,t,s,i){const n=void 0!==i;return n||void 0!==s?n?(console.warn(e),t.body=Object.assign({},t.body,s),t.query=Object.assign({},t.query,i),t):Object.assign(t,s):t}function resetAutoRefresh(e){e._resetAutoRefresh?.()}class RecordService extends CrudService{constructor(e,t){super(e),this.collectionIdOrName=t}get baseCrudPath(){return this.baseCollectionPath+"/records"}get baseCollectionPath(){return"/api/collections/"+encodeURIComponent(this.collectionIdOrName)}get isSuperusers(){return"_superusers"==this.collectionIdOrName||"_pbc_2773867675"==this.collectionIdOrName}async subscribe(e,t,s){if(!e)throw new Error("Missing topic.");if(!t)throw new Error("Missing subscription callback.");return this.client.realtime.subscribe(this.collectionIdOrName+"/"+e,t,s)}async unsubscribe(e){return e?this.client.realtime.unsubscribe(this.collectionIdOrName+"/"+e):this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName)}async getFullList(e,t){if("number"==typeof e)return super.getFullList(e,t);const s=Object.assign({},e,t);return super.getFullList(s)}async getList(e=1,t=30,s){return super.getList(e,t,s)}async getFirstListItem(e,t){return super.getFirstListItem(e,t)}async getOne(e,t){return super.getOne(e,t)}async create(e,t){return super.create(e,t)}async update(e,t,s){return super.update(e,t,s).then((e=>{if(this.client.authStore.record?.id===e?.id&&(this.client.authStore.record?.collectionId===this.collectionIdOrName||this.client.authStore.record?.collectionName===this.collectionIdOrName)){let t=Object.assign({},this.client.authStore.record.expand),s=Object.assign({},this.client.authStore.record,e);t&&(s.expand=Object.assign(t,e.expand)),this.client.authStore.save(this.client.authStore.token,s)}return e}))}async delete(e,t){return super.delete(e,t).then((t=>(!t||this.client.authStore.record?.id!==e||this.client.authStore.record?.collectionId!==this.collectionIdOrName&&this.client.authStore.record?.collectionName!==this.collectionIdOrName||this.client.authStore.clear(),t)))}authResponse(e){const t=this.decode(e?.record||{});return this.client.authStore.save(e?.token,t),Object.assign({},e,{token:e?.token||"",record:t})}async listAuthMethods(e){return e=Object.assign({method:"GET",fields:"mfa,otp,password,oauth2"},e),this.client.send(this.baseCollectionPath+"/auth-methods",e)}async authWithPassword(e,t,s){let i;s=Object.assign({method:"POST",body:{identity:e,password:t}},s),this.isSuperusers&&(i=s.autoRefreshThreshold,delete s.autoRefreshThreshold,s.autoRefresh||resetAutoRefresh(this.client));let n=await this.client.send(this.baseCollectionPath+"/auth-with-password",s);return n=this.authResponse(n),i&&this.isSuperusers&&function registerAutoRefresh(e,t,s,i){resetAutoRefresh(e);const n=e.beforeSend,r=e.authStore.record,o=e.authStore.onChange(((t,s)=>{(!t||s?.id!=r?.id||(s?.collectionId||r?.collectionId)&&s?.collectionId!=r?.collectionId)&&resetAutoRefresh(e)}));e._resetAutoRefresh=function(){o(),e.beforeSend=n,delete e._resetAutoRefresh},e.beforeSend=async(r,o)=>{const a=e.authStore.token;if(o.query?.autoRefresh)return n?n(r,o):{url:r,sendOptions:o};let c=e.authStore.isValid;if(c&&isTokenExpired(e.authStore.token,t))try{await s()}catch(e){c=!1}c||await i();const l=o.headers||{};for(let t in l)if("authorization"==t.toLowerCase()&&a==l[t]&&e.authStore.token){l[t]=e.authStore.token;break}return o.headers=l,n?n(r,o):{url:r,sendOptions:o}}}(this.client,i,(()=>this.authRefresh({autoRefresh:!0})),(()=>this.authWithPassword(e,t,Object.assign({autoRefresh:!0},s)))),n}async authWithOAuth2Code(e,t,s,i,n,r,o){let a={method:"POST",body:{provider:e,code:t,codeVerifier:s,redirectURL:i,createData:n}};return a=normalizeLegacyOptionsArgs("This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).",a,r,o),this.client.send(this.baseCollectionPath+"/auth-with-oauth2",a).then((e=>this.authResponse(e)))}authWithOAuth2(...e){if(e.length>1||"string"==typeof e?.[0])return console.warn("PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration."),this.authWithOAuth2Code(e?.[0]||"",e?.[1]||"",e?.[2]||"",e?.[3]||"",e?.[4]||{},e?.[5]||{},e?.[6]||{});const t=e?.[0]||{};let s=null;t.urlCallback||(s=openBrowserPopup(void 0));const i=new RealtimeService(this.client);function cleanup(){s?.close(),i.unsubscribe()}const n={},r=t.requestKey;return r&&(n.requestKey=r),this.listAuthMethods(n).then((e=>{const n=e.oauth2.providers.find((e=>e.name===t.provider));if(!n)throw new ClientResponseError(new Error(`Missing or invalid provider "${t.provider}".`));const o=this.client.buildURL("/api/oauth2-redirect");return new Promise((async(e,a)=>{const c=r?this.client.cancelControllers?.[r]:void 0;c&&(c.signal.onabort=()=>{cleanup(),a(new ClientResponseError({isAbort:!0,message:"manually cancelled"}))}),i.onDisconnect=e=>{e.length&&a&&(cleanup(),a(new ClientResponseError(new Error("realtime connection interrupted"))))};try{await i.subscribe("@oauth2",(async s=>{const r=i.clientId;try{if(!s.state||r!==s.state)throw new Error("State parameters don't match.");if(s.error||!s.code)throw new Error("OAuth2 redirect error or missing code: "+s.error);const i=Object.assign({},t);delete i.provider,delete i.scopes,delete i.createData,delete i.urlCallback,c?.signal?.onabort&&(c.signal.onabort=null);const a=await this.authWithOAuth2Code(n.name,s.code,n.codeVerifier,o,t.createData,i);e(a)}catch(e){a(new ClientResponseError(e))}cleanup()}));const r={state:i.clientId};t.scopes?.length&&(r.scope=t.scopes.join(" "));const l=this._replaceQueryParams(n.authURL+o,r);let h=t.urlCallback||function(e){s?s.location.href=e:s=openBrowserPopup(e)};await h(l)}catch(e){c?.signal?.onabort&&(c.signal.onabort=null),cleanup(),a(new ClientResponseError(e))}}))})).catch((e=>{throw cleanup(),e}))}async authRefresh(e,t){let s={method:"POST"};return s=normalizeLegacyOptionsArgs("This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).",s,e,t),this.client.send(this.baseCollectionPath+"/auth-refresh",s).then((e=>this.authResponse(e)))}async requestPasswordReset(e,t,s){let i={method:"POST",body:{email:e}};return i=normalizeLegacyOptionsArgs("This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-password-reset",i).then((()=>!0))}async confirmPasswordReset(e,t,s,i,n){let r={method:"POST",body:{token:e,password:t,passwordConfirm:s}};return r=normalizeLegacyOptionsArgs("This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).",r,i,n),this.client.send(this.baseCollectionPath+"/confirm-password-reset",r).then((()=>!0))}async requestVerification(e,t,s){let i={method:"POST",body:{email:e}};return i=normalizeLegacyOptionsArgs("This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-verification",i).then((()=>!0))}async confirmVerification(e,t,s){let i={method:"POST",body:{token:e}};return i=normalizeLegacyOptionsArgs("This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/confirm-verification",i).then((()=>{const t=getTokenPayload(e),s=this.client.authStore.record;return s&&!s.verified&&s.id===t.id&&s.collectionId===t.collectionId&&(s.verified=!0,this.client.authStore.save(this.client.authStore.token,s)),!0}))}async requestEmailChange(e,t,s){let i={method:"POST",body:{newEmail:e}};return i=normalizeLegacyOptionsArgs("This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-email-change",i).then((()=>!0))}async confirmEmailChange(e,t,s,i){let n={method:"POST",body:{token:e,password:t}};return n=normalizeLegacyOptionsArgs("This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).",n,s,i),this.client.send(this.baseCollectionPath+"/confirm-email-change",n).then((()=>{const t=getTokenPayload(e),s=this.client.authStore.record;return s&&s.id===t.id&&s.collectionId===t.collectionId&&this.client.authStore.clear(),!0}))}async listExternalAuths(e,t){return this.client.collection("_externalAuths").getFullList(Object.assign({},t,{filter:this.client.filter("recordRef = {:id}",{id:e})}))}async unlinkExternalAuth(e,t,s){const i=await this.client.collection("_externalAuths").getFirstListItem(this.client.filter("recordRef = {:recordId} && provider = {:provider}",{recordId:e,provider:t}));return this.client.collection("_externalAuths").delete(i.id,s).then((()=>!0))}async requestOTP(e,t){return t=Object.assign({method:"POST",body:{email:e}},t),this.client.send(this.baseCollectionPath+"/request-otp",t)}async authWithOTP(e,t,s){return s=Object.assign({method:"POST",body:{otpId:e,password:t}},s),this.client.send(this.baseCollectionPath+"/auth-with-otp",s).then((e=>this.authResponse(e)))}async impersonate(e,t,s){(s=Object.assign({method:"POST",body:{duration:t}},s)).headers=s.headers||{},s.headers.Authorization||(s.headers.Authorization=this.client.authStore.token);const i=new Client(this.client.baseURL,new BaseAuthStore,this.client.lang),n=await i.send(this.baseCollectionPath+"/impersonate/"+encodeURIComponent(e),s);return i.authStore.save(n?.token,this.decode(n?.record||{})),i}_replaceQueryParams(e,t={}){let s=e,i="";e.indexOf("?")>=0&&(s=e.substring(0,e.indexOf("?")),i=e.substring(e.indexOf("?")+1));const n={},r=i.split("&");for(const e of r){if(""==e)continue;const t=e.split("=");n[decodeURIComponent(t[0].replace(/\+/g," "))]=decodeURIComponent((t[1]||"").replace(/\+/g," "))}for(let e in t)t.hasOwnProperty(e)&&(null==t[e]?delete n[e]:n[e]=t[e]);i="";for(let e in n)n.hasOwnProperty(e)&&(""!=i&&(i+="&"),i+=encodeURIComponent(e.replace(/%20/g,"+"))+"="+encodeURIComponent(n[e].replace(/%20/g,"+")));return""!=i?s+"?"+i:s}}function openBrowserPopup(e){if("undefined"==typeof window||!window?.open)throw new ClientResponseError(new Error("Not in a browser context - please pass a custom urlCallback function."));let t=1024,s=768,i=window.innerWidth,n=window.innerHeight;t=t>i?i:t,s=s>n?n:s;let r=i/2-t/2,o=n/2-s/2;return window.open(e,"popup_window","width="+t+",height="+s+",top="+o+",left="+r+",resizable,menubar=no")}class CollectionService extends CrudService{get baseCrudPath(){return"/api/collections"}async import(e,t=!1,s){return s=Object.assign({method:"PUT",body:{collections:e,deleteMissing:t}},s),this.client.send(this.baseCrudPath+"/import",s).then((()=>!0))}async getScaffolds(e){return e=Object.assign({method:"GET"},e),this.client.send(this.baseCrudPath+"/meta/scaffolds",e)}async truncate(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e)+"/truncate",t).then((()=>!0))}}class LogService extends BaseService{async getList(e=1,t=30,s){return(s=Object.assign({method:"GET"},s)).query=Object.assign({page:e,perPage:t},s.query),this.client.send("/api/logs",s)}async getOne(e,t){if(!e)throw new ClientResponseError({url:this.client.buildURL("/api/logs/"),status:404,response:{code:404,message:"Missing required log id.",data:{}}});return t=Object.assign({method:"GET"},t),this.client.send("/api/logs/"+encodeURIComponent(e),t)}async getStats(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/logs/stats",e)}}class HealthService extends BaseService{async check(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/health",e)}}class FileService extends BaseService{getUrl(e,t,s={}){return console.warn("Please replace pb.files.getUrl() with pb.files.getURL()"),this.getURL(e,t,s)}getURL(e,t,s={}){if(!t||!e?.id||!e?.collectionId&&!e?.collectionName)return"";const i=[];i.push("api"),i.push("files"),i.push(encodeURIComponent(e.collectionId||e.collectionName)),i.push(encodeURIComponent(e.id)),i.push(encodeURIComponent(t));let n=this.client.buildURL(i.join("/"));!1===s.download&&delete s.download;const r=serializeQueryParams(s);return r&&(n+=(n.includes("?")?"&":"?")+r),n}async getToken(e){return e=Object.assign({method:"POST"},e),this.client.send("/api/files/token",e).then((e=>e?.token||""))}}class BackupService extends BaseService{async getFullList(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/backups",e)}async create(e,t){return t=Object.assign({method:"POST",body:{name:e}},t),this.client.send("/api/backups",t).then((()=>!0))}async upload(e,t){return t=Object.assign({method:"POST",body:e},t),this.client.send("/api/backups/upload",t).then((()=>!0))}async delete(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(`/api/backups/${encodeURIComponent(e)}`,t).then((()=>!0))}async restore(e,t){return t=Object.assign({method:"POST"},t),this.client.send(`/api/backups/${encodeURIComponent(e)}/restore`,t).then((()=>!0))}getDownloadUrl(e,t){return console.warn("Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()"),this.getDownloadURL(e,t)}getDownloadURL(e,t){return this.client.buildURL(`/api/backups/${encodeURIComponent(t)}?token=${encodeURIComponent(e)}`)}}class CronService extends BaseService{async getFullList(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/crons",e)}async run(e,t){return t=Object.assign({method:"POST"},t),this.client.send(`/api/crons/${encodeURIComponent(e)}`,t).then((()=>!0))}}function isFile(e){return"undefined"!=typeof Blob&&e instanceof Blob||"undefined"!=typeof File&&e instanceof File||null!==e&&"object"==typeof e&&e.uri&&("undefined"!=typeof navigator&&"ReactNative"===navigator.product||"undefined"!=typeof global&&global.HermesInternal)}function isFormData(e){return e&&("FormData"===e.constructor?.name||"undefined"!=typeof FormData&&e instanceof FormData)}function hasFileField(e){for(const t in e){const s=Array.isArray(e[t])?e[t]:[e[t]];for(const e of s)if(isFile(e))return!0}return!1}const r=/^[\-\.\d]+$/;function inferFormDataValue(e){if("string"!=typeof e)return e;if("true"==e)return!0;if("false"==e)return!1;if(("-"===e[0]||e[0]>="0"&&e[0]<="9")&&r.test(e)){let t=+e;if(""+t===e)return t}return e}class BatchService extends BaseService{constructor(){super(...arguments),this.requests=[],this.subs={}}collection(e){return this.subs[e]||(this.subs[e]=new SubBatchService(this.requests,e)),this.subs[e]}async send(e){const t=new FormData,s=[];for(let e=0;e{if("@jsonPayload"===s&&"string"==typeof e)try{let s=JSON.parse(e);Object.assign(t,s)}catch(e){console.warn("@jsonPayload error:",e)}else void 0!==t[s]?(Array.isArray(t[s])||(t[s]=[t[s]]),t[s].push(inferFormDataValue(e))):t[s]=inferFormDataValue(e)})),t}(s));for(const t in s){const i=s[t];if(isFile(i))e.files[t]=e.files[t]||[],e.files[t].push(i);else if(Array.isArray(i)){const s=[],n=[];for(const e of i)isFile(e)?s.push(e):n.push(e);if(s.length>0&&s.length==i.length){e.files[t]=e.files[t]||[];for(let i of s)e.files[t].push(i)}else if(e.json[t]=n,s.length>0){let i=t;t.startsWith("+")||t.endsWith("+")||(i+="+"),e.files[i]=e.files[i]||[];for(let t of s)e.files[i].push(t)}}else e.json[t]=i}}}class Client{get baseUrl(){return this.baseURL}set baseUrl(e){this.baseURL=e}constructor(e="/",t,s="en-US"){this.cancelControllers={},this.recordServices={},this.enableAutoCancellation=!0,this.baseURL=e,this.lang=s,t?this.authStore=t:"undefined"!=typeof window&&window.Deno?this.authStore=new BaseAuthStore:this.authStore=new LocalAuthStore,this.collections=new CollectionService(this),this.files=new FileService(this),this.logs=new LogService(this),this.settings=new SettingsService(this),this.realtime=new RealtimeService(this),this.health=new HealthService(this),this.backups=new BackupService(this),this.crons=new CronService(this)}get admins(){return this.collection("_superusers")}createBatch(){return new BatchService(this)}collection(e){return this.recordServices[e]||(this.recordServices[e]=new RecordService(this,e)),this.recordServices[e]}autoCancellation(e){return this.enableAutoCancellation=!!e,this}cancelRequest(e){return this.cancelControllers[e]&&(this.cancelControllers[e].abort(),delete this.cancelControllers[e]),this}cancelAllRequests(){for(let e in this.cancelControllers)this.cancelControllers[e].abort();return this.cancelControllers={},this}filter(e,t){if(!t)return e;for(let s in t){let i=t[s];switch(typeof i){case"boolean":case"number":i=""+i;break;case"string":i="'"+i.replace(/'/g,"\\'")+"'";break;default:i=null===i?"null":i instanceof Date?"'"+i.toISOString().replace("T"," ")+"'":"'"+JSON.stringify(i).replace(/'/g,"\\'")+"'"}e=e.replaceAll("{:"+s+"}",i)}return e}getFileUrl(e,t,s={}){return console.warn("Please replace pb.getFileUrl() with pb.files.getURL()"),this.files.getURL(e,t,s)}buildUrl(e){return console.warn("Please replace pb.buildUrl() with pb.buildURL()"),this.buildURL(e)}buildURL(e){let t=this.baseURL;return"undefined"==typeof window||!window.location||t.startsWith("https://")||t.startsWith("http://")||(t=window.location.origin?.endsWith("/")?window.location.origin.substring(0,window.location.origin.length-1):window.location.origin||"",this.baseURL.startsWith("/")||(t+=window.location.pathname||"/",t+=t.endsWith("/")?"":"/"),t+=this.baseURL),e&&(t+=t.endsWith("/")?"":"/",t+=e.startsWith("/")?e.substring(1):e),t}async send(e,t){t=this.initSendOptions(e,t);let s=this.buildURL(e);if(this.beforeSend){const e=Object.assign({},await this.beforeSend(s,t));void 0!==e.url||void 0!==e.options?(s=e.url||s,t=e.options||t):Object.keys(e).length&&(t=e,console?.warn&&console.warn("Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`."))}if(void 0!==t.query){const e=serializeQueryParams(t.query);e&&(s+=(s.includes("?")?"&":"?")+e),delete t.query}"application/json"==this.getHeader(t.headers,"Content-Type")&&t.body&&"string"!=typeof t.body&&(t.body=JSON.stringify(t.body));return(t.fetch||fetch)(s,t).then((async e=>{let s={};try{s=await e.json()}catch(e){if(t.signal?.aborted||"AbortError"==e?.name||"Aborted"==e?.message)throw e}if(this.afterSend&&(s=await this.afterSend(e,s,t)),e.status>=400)throw new ClientResponseError({url:e.url,status:e.status,data:s});return s})).catch((e=>{throw new ClientResponseError(e)}))}initSendOptions(e,t){if((t=Object.assign({method:"GET"},t)).body=function convertToFormDataIfNeeded(e){if("undefined"==typeof FormData||void 0===e||"object"!=typeof e||null===e||isFormData(e)||!hasFileField(e))return e;const t=new FormData;for(const s in e){const i=e[s];if(void 0!==i)if("object"!=typeof i||hasFileField({data:i})){const e=Array.isArray(i)?i:[i];for(let i of e)t.append(s,i)}else{let e={};e[s]=i,t.append("@jsonPayload",JSON.stringify(e))}}return t}(t.body),normalizeUnknownQueryParams(t),t.query=Object.assign({},t.params,t.query),void 0===t.requestKey&&(!1===t.$autoCancel||!1===t.query.$autoCancel?t.requestKey=null:(t.$cancelKey||t.query.$cancelKey)&&(t.requestKey=t.$cancelKey||t.query.$cancelKey)),delete t.$autoCancel,delete t.query.$autoCancel,delete t.$cancelKey,delete t.query.$cancelKey,null!==this.getHeader(t.headers,"Content-Type")||isFormData(t.body)||(t.headers=Object.assign({},t.headers,{"Content-Type":"application/json"})),null===this.getHeader(t.headers,"Accept-Language")&&(t.headers=Object.assign({},t.headers,{"Accept-Language":this.lang})),this.authStore.token&&null===this.getHeader(t.headers,"Authorization")&&(t.headers=Object.assign({},t.headers,{Authorization:this.authStore.token})),this.enableAutoCancellation&&null!==t.requestKey){const s=t.requestKey||(t.method||"GET")+e;delete t.requestKey,this.cancelRequest(s);const i=new AbortController;this.cancelControllers[s]=i,t.signal=i.signal}return t}getHeader(e,t){e=e||{},t=t.toLowerCase();for(let s in e)if(s.toLowerCase()==t)return e[s];return null}}class AsyncAuthStore extends BaseAuthStore{constructor(e){super(),this.queue=[],this.saveFunc=e.save,this.clearFunc=e.clear,this._enqueue((()=>this._loadInitial(e.initial)))}save(e,t){super.save(e,t);let s="";try{s=JSON.stringify({token:e,record:t})}catch(e){console.warn("AsyncAuthStore: failed to stringify the new state")}this._enqueue((()=>this.saveFunc(s)))}clear(){super.clear(),this.clearFunc?this._enqueue((()=>this.clearFunc())):this._enqueue((()=>this.saveFunc("")))}async _loadInitial(e){try{if(e=await e){let t;"string"==typeof e?t=JSON.parse(e)||{}:"object"==typeof e&&(t=e),this.save(t.token||"",t.record||t.model||null)}}catch(e){}}_enqueue(e){this.queue.push(e),1==this.queue.length&&this._dequeue()}_dequeue(){this.queue.length&&this.queue[0]().finally((()=>{this.queue.shift(),this.queue.length&&this._dequeue()}))}}export{AsyncAuthStore,BaseAuthStore,BatchService,ClientResponseError,CollectionService,CrudService,HealthService,LocalAuthStore,LogService,RealtimeService,RecordService,SubBatchService,cookieParse,cookieSerialize,Client as default,getTokenPayload,isTokenExpired,normalizeUnknownQueryParams,serializeQueryParams}; +//# sourceMappingURL=pocketbase.es.js.map diff --git a/script/node_modules/pocketbase/dist/pocketbase.es.js.map b/script/node_modules/pocketbase/dist/pocketbase.es.js.map new file mode 100644 index 0000000..e812277 --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.es.js.map @@ -0,0 +1 @@ +{"version":3,"file":"pocketbase.es.js","sources":["../src/ClientResponseError.ts","../src/tools/cookie.ts","../src/tools/jwt.ts","../src/stores/BaseAuthStore.ts","../src/stores/LocalAuthStore.ts","../src/services/BaseService.ts","../src/services/SettingsService.ts","../src/tools/options.ts","../src/services/RealtimeService.ts","../src/services/CrudService.ts","../src/tools/legacy.ts","../src/tools/refresh.ts","../src/services/RecordService.ts","../src/services/CollectionService.ts","../src/services/LogService.ts","../src/services/HealthService.ts","../src/services/FileService.ts","../src/services/BackupService.ts","../src/services/CronService.ts","../src/tools/formdata.ts","../src/services/BatchService.ts","../src/Client.ts","../src/stores/AsyncAuthStore.ts"],"sourcesContent":["/**\n * ClientResponseError is a custom Error class that is intended to wrap\n * and normalize any error thrown by `Client.send()`.\n */\nexport class ClientResponseError extends Error {\n url: string = \"\";\n status: number = 0;\n response: { [key: string]: any } = {};\n isAbort: boolean = false;\n originalError: any = null;\n\n constructor(errData?: any) {\n super(\"ClientResponseError\");\n\n // Set the prototype explicitly.\n // https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, ClientResponseError.prototype);\n\n if (errData !== null && typeof errData === \"object\") {\n this.originalError = errData.originalError;\n this.url = typeof errData.url === \"string\" ? errData.url : \"\";\n this.status = typeof errData.status === \"number\" ? errData.status : 0;\n\n // note: DOMException is not implemented yet in React Native\n // and aborting a fetch throws a plain Error with message \"Aborted\".\n this.isAbort =\n !!errData.isAbort ||\n errData.name === \"AbortError\" ||\n errData.message === \"Aborted\";\n\n if (errData.response !== null && typeof errData.response === \"object\") {\n this.response = errData.response;\n } else if (errData.data !== null && typeof errData.data === \"object\") {\n this.response = errData.data;\n } else {\n this.response = {};\n }\n }\n\n if (!this.originalError && !(errData instanceof ClientResponseError)) {\n this.originalError = errData;\n }\n\n this.name = \"ClientResponseError \" + this.status;\n this.message = this.response?.message;\n if (!this.message) {\n if (this.isAbort) {\n this.message =\n \"The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).\";\n } else if (this.originalError?.cause?.message?.includes(\"ECONNREFUSED ::1\")) {\n this.message =\n \"Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).\";\n } else {\n this.message = \"Something went wrong.\";\n }\n }\n\n // set this.cause so that JS debugging tools can automatically connect\n // the dots between the original error and the wrapped one\n this.cause = this.originalError;\n }\n\n /**\n * Alias for `this.response` for backward compatibility.\n */\n get data() {\n return this.response;\n }\n\n /**\n * Make a POJO's copy of the current error class instance.\n * @see https://github.com/vuex-orm/vuex-orm/issues/255\n */\n toJSON() {\n return { ...this };\n }\n}\n","/**\n * -------------------------------------------------------------------\n * Simple cookie parse and serialize utilities mostly based on the\n * node module https://github.com/jshttp/cookie.\n * -------------------------------------------------------------------\n */\n\n/**\n * RegExp to match field-content in RFC 7230 sec 3.2\n *\n * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]\n * field-vchar = VCHAR / obs-text\n * obs-text = %x80-FF\n */\nconst fieldContentRegExp = /^[\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+$/;\n\nexport interface ParseOptions {\n decode?: (val: string) => string;\n}\n\n/**\n * Parses the given cookie header string into an object\n * The object has the various cookies as keys(names) => values\n */\nexport function cookieParse(str: string, options?: ParseOptions): { [key: string]: any } {\n const result: { [key: string]: any } = {};\n\n if (typeof str !== \"string\") {\n return result;\n }\n\n const opt = Object.assign({}, options || {});\n const decode = opt.decode || defaultDecode;\n\n let index = 0;\n while (index < str.length) {\n const eqIdx = str.indexOf(\"=\", index);\n\n // no more cookie pairs\n if (eqIdx === -1) {\n break;\n }\n\n let endIdx = str.indexOf(\";\", index);\n\n if (endIdx === -1) {\n endIdx = str.length;\n } else if (endIdx < eqIdx) {\n // backtrack on prior semicolon\n index = str.lastIndexOf(\";\", eqIdx - 1) + 1;\n continue;\n }\n\n const key = str.slice(index, eqIdx).trim();\n\n // only assign once\n if (undefined === result[key]) {\n let val = str.slice(eqIdx + 1, endIdx).trim();\n\n // quoted values\n if (val.charCodeAt(0) === 0x22) {\n val = val.slice(1, -1);\n }\n\n try {\n result[key] = decode(val);\n } catch (_) {\n result[key] = val; // no decoding\n }\n }\n\n index = endIdx + 1;\n }\n\n return result;\n}\n\nexport interface SerializeOptions {\n encode?: (val: string | number | boolean) => string;\n maxAge?: number;\n domain?: string;\n path?: string;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n priority?: string;\n sameSite?: boolean | string;\n}\n\n/**\n * Serialize data into a cookie header.\n *\n * Serialize the a name value pair into a cookie string suitable for\n * http headers. An optional options object specified cookie parameters.\n *\n * ```js\n * cookieSerialize('foo', 'bar', { httpOnly: true }) // \"foo=bar; httpOnly\"\n * ```\n */\nexport function cookieSerialize(\n name: string,\n val: string,\n options?: SerializeOptions,\n): string {\n const opt = Object.assign({}, options || {});\n const encode = opt.encode || defaultEncode;\n\n if (!fieldContentRegExp.test(name)) {\n throw new TypeError(\"argument name is invalid\");\n }\n\n const value = encode(val);\n\n if (value && !fieldContentRegExp.test(value)) {\n throw new TypeError(\"argument val is invalid\");\n }\n\n let result = name + \"=\" + value;\n\n if (opt.maxAge != null) {\n const maxAge = opt.maxAge - 0;\n\n if (isNaN(maxAge) || !isFinite(maxAge)) {\n throw new TypeError(\"option maxAge is invalid\");\n }\n\n result += \"; Max-Age=\" + Math.floor(maxAge);\n }\n\n if (opt.domain) {\n if (!fieldContentRegExp.test(opt.domain)) {\n throw new TypeError(\"option domain is invalid\");\n }\n\n result += \"; Domain=\" + opt.domain;\n }\n\n if (opt.path) {\n if (!fieldContentRegExp.test(opt.path)) {\n throw new TypeError(\"option path is invalid\");\n }\n\n result += \"; Path=\" + opt.path;\n }\n\n if (opt.expires) {\n if (!isDate(opt.expires) || isNaN(opt.expires.valueOf())) {\n throw new TypeError(\"option expires is invalid\");\n }\n\n result += \"; Expires=\" + opt.expires.toUTCString();\n }\n\n if (opt.httpOnly) {\n result += \"; HttpOnly\";\n }\n\n if (opt.secure) {\n result += \"; Secure\";\n }\n\n if (opt.priority) {\n const priority =\n typeof opt.priority === \"string\" ? opt.priority.toLowerCase() : opt.priority;\n\n switch (priority) {\n case \"low\":\n result += \"; Priority=Low\";\n break;\n case \"medium\":\n result += \"; Priority=Medium\";\n break;\n case \"high\":\n result += \"; Priority=High\";\n break;\n default:\n throw new TypeError(\"option priority is invalid\");\n }\n }\n\n if (opt.sameSite) {\n const sameSite =\n typeof opt.sameSite === \"string\" ? opt.sameSite.toLowerCase() : opt.sameSite;\n\n switch (sameSite) {\n case true:\n result += \"; SameSite=Strict\";\n break;\n case \"lax\":\n result += \"; SameSite=Lax\";\n break;\n case \"strict\":\n result += \"; SameSite=Strict\";\n break;\n case \"none\":\n result += \"; SameSite=None\";\n break;\n default:\n throw new TypeError(\"option sameSite is invalid\");\n }\n }\n\n return result;\n}\n\n/**\n * Default URL-decode string value function.\n * Optimized to skip native call when no `%`.\n */\nfunction defaultDecode(val: string): string {\n return val.indexOf(\"%\") !== -1 ? decodeURIComponent(val) : val;\n}\n\n/**\n * Default URL-encode value function.\n */\nfunction defaultEncode(val: string | number | boolean): string {\n return encodeURIComponent(val);\n}\n\n/**\n * Determines if value is a Date.\n */\nfunction isDate(val: any): boolean {\n return Object.prototype.toString.call(val) === \"[object Date]\" || val instanceof Date;\n}\n","// @todo remove after https://github.com/reactwg/react-native-releases/issues/287\nconst isReactNative =\n (typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\") ||\n (typeof global !== \"undefined\" && (global as any).HermesInternal);\n\nlet atobPolyfill: Function;\nif (typeof atob === \"function\" && !isReactNative) {\n atobPolyfill = atob;\n} else {\n /**\n * The code was extracted from:\n * https://github.com/davidchambers/Base64.js\n */\n atobPolyfill = (input: any) => {\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\";\n\n let str = String(input).replace(/=+$/, \"\");\n if (str.length % 4 == 1) {\n throw new Error(\n \"'atob' failed: The string to be decoded is not correctly encoded.\",\n );\n }\n\n for (\n // initialize result and counters\n var bc = 0, bs, buffer, idx = 0, output = \"\";\n // get next character\n (buffer = str.charAt(idx++));\n // character found in table? initialize bit storage and add its ascii value;\n ~buffer &&\n ((bs = bc % 4 ? (bs as any) * 64 + buffer : buffer),\n // and if not first of each 4 characters,\n // convert the first 8 bits to one ascii character\n bc++ % 4)\n ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))\n : 0\n ) {\n // try to find character in table (0-63, not found => -1)\n buffer = chars.indexOf(buffer);\n }\n\n return output;\n };\n}\n\n/**\n * Returns JWT token's payload data.\n */\nexport function getTokenPayload(token: string): { [key: string]: any } {\n if (token) {\n try {\n const encodedPayload = decodeURIComponent(\n atobPolyfill(token.split(\".\")[1])\n .split(\"\")\n .map(function (c: string) {\n return \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2);\n })\n .join(\"\"),\n );\n\n return JSON.parse(encodedPayload) || {};\n } catch (e) {}\n }\n\n return {};\n}\n\n/**\n * Checks whether a JWT token is expired or not.\n * Tokens without `exp` payload key are considered valid.\n * Tokens with empty payload (eg. invalid token strings) are considered expired.\n *\n * @param token The token to check.\n * @param [expirationThreshold] Time in seconds that will be subtracted from the token `exp` property.\n */\nexport function isTokenExpired(token: string, expirationThreshold = 0): boolean {\n let payload = getTokenPayload(token);\n\n if (\n Object.keys(payload).length > 0 &&\n (!payload.exp || payload.exp - expirationThreshold > Date.now() / 1000)\n ) {\n return false;\n }\n\n return true;\n}\n","import { cookieParse, cookieSerialize, SerializeOptions } from \"@/tools/cookie\";\nimport { isTokenExpired, getTokenPayload } from \"@/tools/jwt\";\nimport { RecordModel } from \"@/tools/dtos\";\n\nexport type AuthRecord = RecordModel | null;\n\nexport type AuthModel = AuthRecord; // for backward compatibility\n\nexport type OnStoreChangeFunc = (token: string, record: AuthRecord) => void;\n\nconst defaultCookieKey = \"pb_auth\";\n\n/**\n * Base AuthStore class that stores the auth state in runtime memory (aka. only for the duration of the store instane).\n *\n * Usually you wouldn't use it directly and instead use the builtin LocalAuthStore, AsyncAuthStore\n * or extend it with your own custom implementation.\n */\nexport class BaseAuthStore {\n protected baseToken: string = \"\";\n protected baseModel: AuthRecord = null;\n\n private _onChangeCallbacks: Array = [];\n\n /**\n * Retrieves the stored token (if any).\n */\n get token(): string {\n return this.baseToken;\n }\n\n /**\n * Retrieves the stored model data (if any).\n */\n get record(): AuthRecord {\n return this.baseModel;\n }\n\n /**\n * @deprecated use `record` instead.\n */\n get model(): AuthRecord {\n return this.baseModel;\n }\n\n /**\n * Loosely checks if the store has valid token (aka. existing and unexpired exp claim).\n */\n get isValid(): boolean {\n return !isTokenExpired(this.token);\n }\n\n /**\n * Loosely checks whether the currently loaded store state is for superuser.\n *\n * Alternatively you can also compare directly `pb.authStore.record?.collectionName`.\n */\n get isSuperuser(): boolean {\n let payload = getTokenPayload(this.token);\n\n return (\n payload.type == \"auth\" &&\n (this.record?.collectionName == \"_superusers\" ||\n // fallback in case the record field is not populated and assuming\n // that the collection crc32 checksum id wasn't manually changed\n (!this.record?.collectionName &&\n payload.collectionId == \"pbc_3142635823\"))\n );\n }\n\n /**\n * @deprecated use `isSuperuser` instead or simply check the record.collectionName property.\n */\n get isAdmin(): boolean {\n console.warn(\n \"Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName\",\n );\n return this.isSuperuser;\n }\n\n /**\n * @deprecated use `!isSuperuser` instead or simply check the record.collectionName property.\n */\n get isAuthRecord(): boolean {\n console.warn(\n \"Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName\",\n );\n return getTokenPayload(this.token).type == \"auth\" && !this.isSuperuser;\n }\n\n /**\n * Saves the provided new token and model data in the auth store.\n */\n save(token: string, record?: AuthRecord): void {\n this.baseToken = token || \"\";\n this.baseModel = record || null;\n\n this.triggerChange();\n }\n\n /**\n * Removes the stored token and model data form the auth store.\n */\n clear(): void {\n this.baseToken = \"\";\n this.baseModel = null;\n this.triggerChange();\n }\n\n /**\n * Parses the provided cookie string and updates the store state\n * with the cookie's token and model data.\n *\n * NB! This function doesn't validate the token or its data.\n * Usually this isn't a concern if you are interacting only with the\n * PocketBase API because it has the proper server-side security checks in place,\n * but if you are using the store `isValid` state for permission controls\n * in a node server (eg. SSR), then it is recommended to call `authRefresh()`\n * after loading the cookie to ensure an up-to-date token and model state.\n * For example:\n *\n * ```js\n * pb.authStore.loadFromCookie(\"cookie string...\");\n *\n * try {\n * // get an up-to-date auth store state by veryfing and refreshing the loaded auth model (if any)\n * pb.authStore.isValid && await pb.collection('users').authRefresh();\n * } catch (_) {\n * // clear the auth store on failed refresh\n * pb.authStore.clear();\n * }\n * ```\n */\n loadFromCookie(cookie: string, key = defaultCookieKey): void {\n const rawData = cookieParse(cookie || \"\")[key] || \"\";\n\n let data: { [key: string]: any } = {};\n try {\n data = JSON.parse(rawData);\n // normalize\n if (typeof data === null || typeof data !== \"object\" || Array.isArray(data)) {\n data = {};\n }\n } catch (_) {}\n\n this.save(data.token || \"\", data.record || data.model || null);\n }\n\n /**\n * Exports the current store state as cookie string.\n *\n * By default the following optional attributes are added:\n * - Secure\n * - HttpOnly\n * - SameSite=Strict\n * - Path=/\n * - Expires={the token expiration date}\n *\n * NB! If the generated cookie exceeds 4096 bytes, this method will\n * strip the model data to the bare minimum to try to fit within the\n * recommended size in https://www.rfc-editor.org/rfc/rfc6265#section-6.1.\n */\n exportToCookie(options?: SerializeOptions, key = defaultCookieKey): string {\n const defaultOptions: SerializeOptions = {\n secure: true,\n sameSite: true,\n httpOnly: true,\n path: \"/\",\n };\n\n // extract the token expiration date\n const payload = getTokenPayload(this.token);\n if (payload?.exp) {\n defaultOptions.expires = new Date(payload.exp * 1000);\n } else {\n defaultOptions.expires = new Date(\"1970-01-01\");\n }\n\n // merge with the user defined options\n options = Object.assign({}, defaultOptions, options);\n\n const rawData = {\n token: this.token,\n record: this.record ? JSON.parse(JSON.stringify(this.record)) : null,\n };\n\n let result = cookieSerialize(key, JSON.stringify(rawData), options);\n\n const resultLength =\n typeof Blob !== \"undefined\" ? new Blob([result]).size : result.length;\n\n // strip down the model data to the bare minimum\n if (rawData.record && resultLength > 4096) {\n rawData.record = { id: rawData.record?.id, email: rawData.record?.email };\n const extraProps = [\"collectionId\", \"collectionName\", \"verified\"];\n for (const prop in this.record) {\n if (extraProps.includes(prop)) {\n rawData.record[prop] = this.record[prop];\n }\n }\n result = cookieSerialize(key, JSON.stringify(rawData), options);\n }\n\n return result;\n }\n\n /**\n * Register a callback function that will be called on store change.\n *\n * You can set the `fireImmediately` argument to true in order to invoke\n * the provided callback right after registration.\n *\n * Returns a removal function that you could call to \"unsubscribe\" from the changes.\n */\n onChange(callback: OnStoreChangeFunc, fireImmediately = false): () => void {\n this._onChangeCallbacks.push(callback);\n\n if (fireImmediately) {\n callback(this.token, this.record);\n }\n\n return () => {\n for (let i = this._onChangeCallbacks.length - 1; i >= 0; i--) {\n if (this._onChangeCallbacks[i] == callback) {\n delete this._onChangeCallbacks[i]; // removes the function reference\n this._onChangeCallbacks.splice(i, 1); // reindex the array\n return;\n }\n }\n };\n }\n\n protected triggerChange(): void {\n for (const callback of this._onChangeCallbacks) {\n callback && callback(this.token, this.record);\n }\n }\n}\n","import { BaseAuthStore, AuthRecord } from \"@/stores/BaseAuthStore\";\n\n/**\n * The default token store for browsers with auto fallback\n * to runtime/memory if local storage is undefined (e.g. in node env).\n */\nexport class LocalAuthStore extends BaseAuthStore {\n private storageFallback: { [key: string]: any } = {};\n private storageKey: string;\n\n constructor(storageKey = \"pocketbase_auth\") {\n super();\n\n this.storageKey = storageKey;\n\n this._bindStorageEvent();\n }\n\n /**\n * @inheritdoc\n */\n get token(): string {\n const data = this._storageGet(this.storageKey) || {};\n\n return data.token || \"\";\n }\n\n /**\n * @inheritdoc\n */\n get record(): AuthRecord {\n const data = this._storageGet(this.storageKey) || {};\n\n return data.record || data.model || null;\n }\n\n /**\n * @deprecated use `record` instead.\n */\n get model(): AuthRecord {\n return this.record;\n }\n\n /**\n * @inheritdoc\n */\n save(token: string, record?: AuthRecord) {\n this._storageSet(this.storageKey, {\n token: token,\n record: record,\n });\n\n super.save(token, record);\n }\n\n /**\n * @inheritdoc\n */\n clear() {\n this._storageRemove(this.storageKey);\n\n super.clear();\n }\n\n // ---------------------------------------------------------------\n // Internal helpers:\n // ---------------------------------------------------------------\n\n /**\n * Retrieves `key` from the browser's local storage\n * (or runtime/memory if local storage is undefined).\n */\n private _storageGet(key: string): any {\n if (typeof window !== \"undefined\" && window?.localStorage) {\n const rawValue = window.localStorage.getItem(key) || \"\";\n try {\n return JSON.parse(rawValue);\n } catch (e) {\n // not a json\n return rawValue;\n }\n }\n\n // fallback\n return this.storageFallback[key];\n }\n\n /**\n * Stores a new data in the browser's local storage\n * (or runtime/memory if local storage is undefined).\n */\n private _storageSet(key: string, value: any) {\n if (typeof window !== \"undefined\" && window?.localStorage) {\n // store in local storage\n let normalizedVal = value;\n if (typeof value !== \"string\") {\n normalizedVal = JSON.stringify(value);\n }\n window.localStorage.setItem(key, normalizedVal);\n } else {\n // store in fallback\n this.storageFallback[key] = value;\n }\n }\n\n /**\n * Removes `key` from the browser's local storage and the runtime/memory.\n */\n private _storageRemove(key: string) {\n // delete from local storage\n if (typeof window !== \"undefined\" && window?.localStorage) {\n window.localStorage?.removeItem(key);\n }\n\n // delete from fallback\n delete this.storageFallback[key];\n }\n\n /**\n * Updates the current store state on localStorage change.\n */\n private _bindStorageEvent() {\n if (\n typeof window === \"undefined\" ||\n !window?.localStorage ||\n !window.addEventListener\n ) {\n return;\n }\n\n window.addEventListener(\"storage\", (e) => {\n if (e.key != this.storageKey) {\n return;\n }\n\n const data = this._storageGet(this.storageKey) || {};\n\n super.save(data.token || \"\", data.record || data.model || null);\n });\n }\n}\n","import Client from \"@/Client\";\n\n/**\n * BaseService class that should be inherited from all API services.\n */\nexport abstract class BaseService {\n readonly client: Client;\n\n constructor(client: Client) {\n this.client = client;\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\ninterface appleClientSecret {\n secret: string;\n}\n\nexport class SettingsService extends BaseService {\n /**\n * Fetch all available app settings.\n *\n * @throws {ClientResponseError}\n */\n async getAll(options?: CommonOptions): Promise<{ [key: string]: any }> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/settings\", options);\n }\n\n /**\n * Bulk updates app settings.\n *\n * @throws {ClientResponseError}\n */\n async update(\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise<{ [key: string]: any }> {\n options = Object.assign(\n {\n method: \"PATCH\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client.send(\"/api/settings\", options);\n }\n\n /**\n * Performs a S3 filesystem connection test.\n *\n * The currently supported `filesystem` are \"storage\" and \"backups\".\n *\n * @throws {ClientResponseError}\n */\n async testS3(\n filesystem: string = \"storage\",\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n filesystem: filesystem,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/test/s3\", options).then(() => true);\n }\n\n /**\n * Sends a test email.\n *\n * The possible `emailTemplate` values are:\n * - verification\n * - password-reset\n * - email-change\n *\n * @throws {ClientResponseError}\n */\n async testEmail(\n collectionIdOrName: string,\n toEmail: string,\n emailTemplate: string,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n email: toEmail,\n template: emailTemplate,\n collection: collectionIdOrName,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/test/email\", options).then(() => true);\n }\n\n /**\n * Generates a new Apple OAuth2 client secret.\n *\n * @throws {ClientResponseError}\n */\n async generateAppleClientSecret(\n clientId: string,\n teamId: string,\n keyId: string,\n privateKey: string,\n duration: number,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n clientId,\n teamId,\n keyId,\n privateKey,\n duration,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/apple/generate-client-secret\", options);\n }\n}\n","export interface SendOptions extends RequestInit {\n // for backward compatibility and to minimize the verbosity,\n // any top-level field that doesn't exist in RequestInit or the\n // fields below will be treated as query parameter.\n [key: string]: any;\n\n /**\n * Optional custom fetch function to use for sending the request.\n */\n fetch?: (url: RequestInfo | URL, config?: RequestInit) => Promise;\n\n /**\n * Custom headers to send with the requests.\n */\n headers?: { [key: string]: string };\n\n /**\n * The body of the request (serialized automatically for json requests).\n */\n body?: any;\n\n /**\n * Query parameters that will be appended to the request url.\n */\n query?: { [key: string]: any };\n\n /**\n * @deprecated use `query` instead\n *\n * for backward-compatibility `params` values are merged with `query`,\n * but this option may get removed in the final v1 release\n */\n params?: { [key: string]: any };\n\n /**\n * The request identifier that can be used to cancel pending requests.\n */\n requestKey?: string | null;\n\n /**\n * @deprecated use `requestKey:string` instead\n */\n $cancelKey?: string;\n\n /**\n * @deprecated use `requestKey:null` instead\n */\n $autoCancel?: boolean;\n}\n\nexport interface CommonOptions extends SendOptions {\n fields?: string;\n}\n\nexport interface ListOptions extends CommonOptions {\n page?: number;\n perPage?: number;\n sort?: string;\n filter?: string;\n skipTotal?: boolean;\n}\n\nexport interface FullListOptions extends ListOptions {\n batch?: number;\n}\n\nexport interface RecordOptions extends CommonOptions {\n expand?: string;\n}\n\nexport interface RecordListOptions extends ListOptions, RecordOptions {}\n\nexport interface RecordFullListOptions extends FullListOptions, RecordOptions {}\n\nexport interface RecordSubscribeOptions extends SendOptions {\n fields?: string;\n filter?: string;\n expand?: string;\n}\n\nexport interface LogStatsOptions extends CommonOptions {\n filter?: string;\n}\n\nexport interface FileOptions extends CommonOptions {\n thumb?: string;\n download?: boolean;\n}\n\nexport interface AuthOptions extends CommonOptions {\n /**\n * If autoRefreshThreshold is set it will take care to auto refresh\n * when necessary the auth data before each request to ensure that\n * the auth state is always valid.\n *\n * The value must be in seconds, aka. the amount of seconds\n * that will be subtracted from the current token `exp` claim in order\n * to determine whether it is going to expire within the specified time threshold.\n *\n * For example, if you want to auto refresh the token if it is\n * going to expire in the next 30mins (or already has expired),\n * it can be set to `1800`\n */\n autoRefreshThreshold?: number;\n}\n\n// -------------------------------------------------------------------\n\n// list of known SendOptions keys (everything else is treated as query param)\nconst knownSendOptionsKeys = [\n \"requestKey\",\n \"$cancelKey\",\n \"$autoCancel\",\n \"fetch\",\n \"headers\",\n \"body\",\n \"query\",\n \"params\",\n // ---,\n \"cache\",\n \"credentials\",\n \"headers\",\n \"integrity\",\n \"keepalive\",\n \"method\",\n \"mode\",\n \"redirect\",\n \"referrer\",\n \"referrerPolicy\",\n \"signal\",\n \"window\",\n];\n\n// modifies in place the provided options by moving unknown send options as query parameters.\nexport function normalizeUnknownQueryParams(options?: SendOptions): void {\n if (!options) {\n return;\n }\n\n options.query = options.query || {};\n for (let key in options) {\n if (knownSendOptionsKeys.includes(key)) {\n continue;\n }\n\n options.query[key] = options[key];\n delete options[key];\n }\n}\n\nexport function serializeQueryParams(params: { [key: string]: any }): string {\n const result: Array = [];\n\n for (const key in params) {\n const encodedKey = encodeURIComponent(key);\n const arrValue = Array.isArray(params[key]) ? params[key] : [params[key]];\n\n for (let v of arrValue) {\n v = prepareQueryParamValue(v);\n if (v === null) {\n continue;\n }\n result.push(encodedKey + \"=\" + v);\n }\n }\n\n return result.join(\"&\");\n}\n\n// encodes and normalizes the provided query param value.\nfunction prepareQueryParamValue(value: any): null | string {\n if (value === null || typeof value === \"undefined\") {\n return null;\n }\n\n if (value instanceof Date) {\n return encodeURIComponent(value.toISOString().replace(\"T\", \" \"));\n }\n\n if (typeof value === \"object\") {\n return encodeURIComponent(JSON.stringify(value));\n }\n\n return encodeURIComponent(value);\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseService } from \"@/services/BaseService\";\nimport { SendOptions, normalizeUnknownQueryParams } from \"@/tools/options\";\n\ninterface promiseCallbacks {\n resolve: Function;\n reject: Function;\n}\n\ntype Subscriptions = { [key: string]: Array };\n\nexport type UnsubscribeFunc = () => Promise;\n\nexport class RealtimeService extends BaseService {\n clientId: string = \"\";\n\n private eventSource: EventSource | null = null;\n private subscriptions: Subscriptions = {};\n private lastSentSubscriptions: Array = [];\n private connectTimeoutId: any;\n private maxConnectTimeout: number = 15000;\n private reconnectTimeoutId: any;\n private reconnectAttempts: number = 0;\n private maxReconnectAttempts: number = Infinity;\n private predefinedReconnectIntervals: Array = [\n 200, 300, 500, 1000, 1200, 1500, 2000,\n ];\n private pendingConnects: Array = [];\n\n /**\n * Returns whether the realtime connection has been established.\n */\n get isConnected(): boolean {\n return !!this.eventSource && !!this.clientId && !this.pendingConnects.length;\n }\n\n /**\n * An optional hook that is invoked when the realtime client disconnects\n * either when unsubscribing from all subscriptions or when the\n * connection was interrupted or closed by the server.\n *\n * The received argument could be used to determine whether the disconnect\n * is a result from unsubscribing (`activeSubscriptions.length == 0`)\n * or because of network/server error (`activeSubscriptions.length > 0`).\n *\n * If you want to listen for the opposite, aka. when the client connection is established,\n * subscribe to the `PB_CONNECT` event.\n */\n onDisconnect?: (activeSubscriptions: Array) => void;\n\n /**\n * Register the subscription listener.\n *\n * You can subscribe multiple times to the same topic.\n *\n * If the SSE connection is not started yet,\n * this method will also initialize it.\n */\n async subscribe(\n topic: string,\n callback: (data: any) => void,\n options?: SendOptions,\n ): Promise {\n if (!topic) {\n throw new Error(\"topic must be set.\");\n }\n\n let key = topic;\n\n // serialize and append the topic options (if any)\n if (options) {\n options = Object.assign({}, options); // shallow copy\n normalizeUnknownQueryParams(options);\n const serialized =\n \"options=\" +\n encodeURIComponent(\n JSON.stringify({ query: options.query, headers: options.headers }),\n );\n key += (key.includes(\"?\") ? \"&\" : \"?\") + serialized;\n }\n\n const listener = function (e: Event) {\n const msgEvent = e as MessageEvent;\n\n let data;\n try {\n data = JSON.parse(msgEvent?.data);\n } catch {}\n\n callback(data || {});\n };\n\n // store the listener\n if (!this.subscriptions[key]) {\n this.subscriptions[key] = [];\n }\n this.subscriptions[key].push(listener);\n\n if (!this.isConnected) {\n // initialize sse connection\n await this.connect();\n } else if (this.subscriptions[key].length === 1) {\n // send the updated subscriptions (if it is the first for the key)\n await this.submitSubscriptions();\n } else {\n // only register the listener\n this.eventSource?.addEventListener(key, listener);\n }\n\n return async (): Promise => {\n return this.unsubscribeByTopicAndListener(topic, listener);\n };\n }\n\n /**\n * Unsubscribe from all subscription listeners with the specified topic.\n *\n * If `topic` is not provided, then this method will unsubscribe\n * from all active subscriptions.\n *\n * This method is no-op if there are no active subscriptions.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribe(topic?: string): Promise {\n let needToSubmit = false;\n\n if (!topic) {\n // remove all subscriptions\n this.subscriptions = {};\n } else {\n // remove all listeners related to the topic\n const subs = this.getSubscriptionsByTopic(topic);\n for (let key in subs) {\n if (!this.hasSubscriptionListeners(key)) {\n continue; // already unsubscribed\n }\n\n for (let listener of this.subscriptions[key]) {\n this.eventSource?.removeEventListener(key, listener);\n }\n delete this.subscriptions[key];\n\n // mark for subscriptions change submit if there are no other listeners\n if (!needToSubmit) {\n needToSubmit = true;\n }\n }\n }\n\n if (!this.hasSubscriptionListeners()) {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n } else if (needToSubmit) {\n await this.submitSubscriptions();\n }\n }\n\n /**\n * Unsubscribe from all subscription listeners starting with the specified topic prefix.\n *\n * This method is no-op if there are no active subscriptions with the specified topic prefix.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribeByPrefix(keyPrefix: string): Promise {\n let hasAtleastOneTopic = false;\n for (let key in this.subscriptions) {\n // \"?\" so that it can be used as end delimiter for the prefix\n if (!(key + \"?\").startsWith(keyPrefix)) {\n continue;\n }\n\n hasAtleastOneTopic = true;\n for (let listener of this.subscriptions[key]) {\n this.eventSource?.removeEventListener(key, listener);\n }\n delete this.subscriptions[key];\n }\n\n if (!hasAtleastOneTopic) {\n return; // nothing to unsubscribe from\n }\n\n if (this.hasSubscriptionListeners()) {\n // submit the deleted subscriptions\n await this.submitSubscriptions();\n } else {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n }\n }\n\n /**\n * Unsubscribe from all subscriptions matching the specified topic and listener function.\n *\n * This method is no-op if there are no active subscription with\n * the specified topic and listener.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribeByTopicAndListener(\n topic: string,\n listener: EventListener,\n ): Promise {\n let needToSubmit = false;\n\n const subs = this.getSubscriptionsByTopic(topic);\n for (let key in subs) {\n if (\n !Array.isArray(this.subscriptions[key]) ||\n !this.subscriptions[key].length\n ) {\n continue; // already unsubscribed\n }\n\n let exist = false;\n for (let i = this.subscriptions[key].length - 1; i >= 0; i--) {\n if (this.subscriptions[key][i] !== listener) {\n continue;\n }\n\n exist = true; // has at least one matching listener\n delete this.subscriptions[key][i]; // removes the function reference\n this.subscriptions[key].splice(i, 1); // reindex the array\n this.eventSource?.removeEventListener(key, listener);\n }\n if (!exist) {\n continue;\n }\n\n // remove the key from the subscriptions list if there are no other listeners\n if (!this.subscriptions[key].length) {\n delete this.subscriptions[key];\n }\n\n // mark for subscriptions change submit if there are no other listeners\n if (!needToSubmit && !this.hasSubscriptionListeners(key)) {\n needToSubmit = true;\n }\n }\n\n if (!this.hasSubscriptionListeners()) {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n } else if (needToSubmit) {\n await this.submitSubscriptions();\n }\n }\n\n private hasSubscriptionListeners(keyToCheck?: string): boolean {\n this.subscriptions = this.subscriptions || {};\n\n // check the specified key\n if (keyToCheck) {\n return !!this.subscriptions[keyToCheck]?.length;\n }\n\n // check for at least one non-empty subscription\n for (let key in this.subscriptions) {\n if (!!this.subscriptions[key]?.length) {\n return true;\n }\n }\n\n return false;\n }\n\n private async submitSubscriptions(): Promise {\n if (!this.clientId) {\n return; // no client/subscriber\n }\n\n // optimistic update\n this.addAllSubscriptionListeners();\n\n this.lastSentSubscriptions = this.getNonEmptySubscriptionKeys();\n\n return this.client\n .send(\"/api/realtime\", {\n method: \"POST\",\n body: {\n clientId: this.clientId,\n subscriptions: this.lastSentSubscriptions,\n },\n requestKey: this.getSubscriptionsCancelKey(),\n })\n .catch((err) => {\n if (err?.isAbort) {\n return; // silently ignore aborted pending requests\n }\n throw err;\n });\n }\n\n private getSubscriptionsCancelKey(): string {\n return \"realtime_\" + this.clientId;\n }\n\n private getSubscriptionsByTopic(topic: string): Subscriptions {\n const result: Subscriptions = {};\n\n // \"?\" so that it can be used as end delimiter for the topic\n topic = topic.includes(\"?\") ? topic : topic + \"?\";\n\n for (let key in this.subscriptions) {\n if ((key + \"?\").startsWith(topic)) {\n result[key] = this.subscriptions[key];\n }\n }\n\n return result;\n }\n\n private getNonEmptySubscriptionKeys(): Array {\n const result: Array = [];\n\n for (let key in this.subscriptions) {\n if (this.subscriptions[key].length) {\n result.push(key);\n }\n }\n\n return result;\n }\n\n private addAllSubscriptionListeners(): void {\n if (!this.eventSource) {\n return;\n }\n\n this.removeAllSubscriptionListeners();\n\n for (let key in this.subscriptions) {\n for (let listener of this.subscriptions[key]) {\n this.eventSource.addEventListener(key, listener);\n }\n }\n }\n\n private removeAllSubscriptionListeners(): void {\n if (!this.eventSource) {\n return;\n }\n\n for (let key in this.subscriptions) {\n for (let listener of this.subscriptions[key]) {\n this.eventSource.removeEventListener(key, listener);\n }\n }\n }\n\n private async connect(): Promise {\n if (this.reconnectAttempts > 0) {\n // immediately resolve the promise to avoid indefinitely\n // blocking the client during reconnection\n return;\n }\n\n return new Promise((resolve, reject) => {\n this.pendingConnects.push({ resolve, reject });\n\n if (this.pendingConnects.length > 1) {\n // all promises will be resolved once the connection is established\n return;\n }\n\n this.initConnect();\n });\n }\n\n private initConnect() {\n this.disconnect(true);\n\n // wait up to 15s for connect\n clearTimeout(this.connectTimeoutId);\n this.connectTimeoutId = setTimeout(() => {\n this.connectErrorHandler(new Error(\"EventSource connect took too long.\"));\n }, this.maxConnectTimeout);\n\n this.eventSource = new EventSource(this.client.buildURL(\"/api/realtime\"));\n\n this.eventSource.onerror = (_) => {\n this.connectErrorHandler(\n new Error(\"Failed to establish realtime connection.\"),\n );\n };\n\n this.eventSource.addEventListener(\"PB_CONNECT\", (e) => {\n const msgEvent = e as MessageEvent;\n this.clientId = msgEvent?.lastEventId;\n\n this.submitSubscriptions()\n .then(async () => {\n let retries = 3;\n while (this.hasUnsentSubscriptions() && retries > 0) {\n retries--;\n // resubscribe to ensure that the latest topics are submitted\n //\n // This is needed because missed topics could happen on reconnect\n // if after the pending sent `submitSubscriptions()` call another `subscribe()`\n // was made before the submit was able to complete.\n await this.submitSubscriptions();\n }\n })\n .then(() => {\n for (let p of this.pendingConnects) {\n p.resolve();\n }\n\n // reset connect meta\n this.pendingConnects = [];\n this.reconnectAttempts = 0;\n clearTimeout(this.reconnectTimeoutId);\n clearTimeout(this.connectTimeoutId);\n\n // propagate the PB_CONNECT event\n const connectSubs = this.getSubscriptionsByTopic(\"PB_CONNECT\");\n for (let key in connectSubs) {\n for (let listener of connectSubs[key]) {\n listener(e);\n }\n }\n })\n .catch((err) => {\n this.clientId = \"\";\n this.connectErrorHandler(err);\n });\n });\n }\n\n private hasUnsentSubscriptions(): boolean {\n const latestTopics = this.getNonEmptySubscriptionKeys();\n if (latestTopics.length != this.lastSentSubscriptions.length) {\n return true;\n }\n\n for (const t of latestTopics) {\n if (!this.lastSentSubscriptions.includes(t)) {\n return true;\n }\n }\n\n return false;\n }\n\n private connectErrorHandler(err: any) {\n clearTimeout(this.connectTimeoutId);\n clearTimeout(this.reconnectTimeoutId);\n\n if (\n // wasn't previously connected -> direct reject\n (!this.clientId && !this.reconnectAttempts) ||\n // was previously connected but the max reconnection limit has been reached\n this.reconnectAttempts > this.maxReconnectAttempts\n ) {\n for (let p of this.pendingConnects) {\n p.reject(new ClientResponseError(err));\n }\n this.pendingConnects = [];\n this.disconnect();\n return;\n }\n\n // otherwise -> reconnect in the background\n this.disconnect(true);\n const timeout =\n this.predefinedReconnectIntervals[this.reconnectAttempts] ||\n this.predefinedReconnectIntervals[\n this.predefinedReconnectIntervals.length - 1\n ];\n this.reconnectAttempts++;\n this.reconnectTimeoutId = setTimeout(() => {\n this.initConnect();\n }, timeout);\n }\n\n private disconnect(fromReconnect = false): void {\n if (this.clientId && this.onDisconnect) {\n this.onDisconnect(Object.keys(this.subscriptions));\n }\n\n clearTimeout(this.connectTimeoutId);\n clearTimeout(this.reconnectTimeoutId);\n this.removeAllSubscriptionListeners();\n this.client.cancelRequest(this.getSubscriptionsCancelKey());\n this.eventSource?.close();\n this.eventSource = null;\n this.clientId = \"\";\n\n if (!fromReconnect) {\n this.reconnectAttempts = 0;\n\n // resolve any remaining connect promises\n //\n // this is done to avoid unnecessary throwing errors in case\n // unsubscribe is called before the pending connect promises complete\n // (see https://github.com/pocketbase/pocketbase/discussions/2897#discussioncomment-6423818)\n for (let p of this.pendingConnects) {\n p.resolve();\n }\n this.pendingConnects = [];\n }\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { ClientResponseError } from \"@/ClientResponseError\";\nimport { ListResult } from \"@/tools/dtos\";\nimport { CommonOptions, ListOptions, FullListOptions } from \"@/tools/options\";\n\nexport abstract class CrudService extends BaseService {\n /**\n * Base path for the crud actions (without trailing slash, eg. '/admins').\n */\n abstract get baseCrudPath(): string;\n\n /**\n * Response data decoder.\n */\n decode(data: { [key: string]: any }): T {\n return data as T;\n }\n\n /**\n * Returns a promise with all list items batch fetched at once\n * (by default 1000 items per request; to change it set the `batch` query param).\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: FullListOptions): Promise>;\n\n /**\n * Legacy version of getFullList with explicitly specified batch size.\n */\n async getFullList(batch?: number, options?: ListOptions): Promise>;\n\n async getFullList(\n batchOrqueryParams?: number | FullListOptions,\n options?: ListOptions,\n ): Promise> {\n if (typeof batchOrqueryParams == \"number\") {\n return this._getFullList(batchOrqueryParams, options);\n }\n\n options = Object.assign({}, batchOrqueryParams, options);\n\n let batch = 1000;\n if (options.batch) {\n batch = options.batch;\n delete options.batch;\n }\n\n return this._getFullList(batch, options);\n }\n\n /**\n * Returns paginated items list.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: ListOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n options.query = Object.assign(\n {\n page: page,\n perPage: perPage,\n },\n options.query,\n );\n\n return this.client.send(this.baseCrudPath, options).then((responseData: any) => {\n responseData.items =\n responseData.items?.map((item: any) => {\n return this.decode(item);\n }) || [];\n\n return responseData;\n });\n }\n\n /**\n * Returns the first found item by the specified filter.\n *\n * Internally it calls `getList(1, 1, { filter, skipTotal })` and\n * returns the first found item.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * For consistency with `getOne`, this method will throw a 404\n * ClientResponseError if no item was found.\n *\n * @throws {ClientResponseError}\n */\n async getFirstListItem(filter: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n requestKey: \"one_by_filter_\" + this.baseCrudPath + \"_\" + filter,\n },\n options,\n );\n\n options.query = Object.assign(\n {\n filter: filter,\n skipTotal: 1,\n },\n options.query,\n );\n\n return this.getList(1, 1, options).then((result) => {\n if (!result?.items?.length) {\n throw new ClientResponseError({\n status: 404,\n response: {\n code: 404,\n message: \"The requested resource wasn't found.\",\n data: {},\n },\n });\n }\n\n return result.items[0];\n });\n }\n\n /**\n * Returns single item by its id.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * If `id` is empty it will throw a 404 error.\n *\n * @throws {ClientResponseError}\n */\n async getOne(id: string, options?: CommonOptions): Promise {\n if (!id) {\n throw new ClientResponseError({\n url: this.client.buildURL(this.baseCrudPath + \"/\"),\n status: 404,\n response: {\n code: 404,\n message: \"Missing required record id.\",\n data: {},\n },\n });\n }\n\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Creates a new item.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath, options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Updates an existing item by its id.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"PATCH\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Deletes an existing item by its id.\n *\n * @throws {ClientResponseError}\n */\n async delete(id: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then(() => true);\n }\n\n /**\n * Returns a promise with all list items batch fetched at once.\n */\n protected _getFullList(\n batchSize = 1000,\n options?: ListOptions,\n ): Promise> {\n options = options || {};\n options.query = Object.assign(\n {\n skipTotal: 1,\n },\n options.query,\n );\n\n let result: Array = [];\n\n let request = async (page: number): Promise> => {\n return this.getList(page, batchSize || 1000, options).then((list) => {\n const castedList = list as any as ListResult;\n const items = castedList.items;\n\n result = result.concat(items);\n\n if (items.length == list.perPage) {\n return request(page + 1);\n }\n\n return result;\n });\n };\n\n return request(1);\n }\n}\n","import { SendOptions } from \"@/tools/options\";\n\nexport function normalizeLegacyOptionsArgs(\n legacyWarn: string,\n baseOptions: SendOptions,\n bodyOrOptions?: any,\n query?: any,\n): SendOptions {\n const hasBodyOrOptions = typeof bodyOrOptions !== \"undefined\";\n const hasQuery = typeof query !== \"undefined\";\n\n if (!hasQuery && !hasBodyOrOptions) {\n return baseOptions;\n }\n\n if (hasQuery) {\n console.warn(legacyWarn);\n baseOptions.body = Object.assign({}, baseOptions.body, bodyOrOptions);\n baseOptions.query = Object.assign({}, baseOptions.query, query);\n\n return baseOptions;\n }\n\n return Object.assign(baseOptions, bodyOrOptions);\n}\n","import Client from \"@/Client\";\nimport { isTokenExpired } from \"@/tools/jwt\";\n\n// reset previous auto refresh registrations\nexport function resetAutoRefresh(client: Client) {\n (client as any)._resetAutoRefresh?.();\n}\n\nexport function registerAutoRefresh(\n client: Client,\n threshold: number,\n refreshFunc: () => Promise,\n reauthenticateFunc: () => Promise,\n) {\n resetAutoRefresh(client);\n\n const oldBeforeSend = client.beforeSend;\n const oldModel = client.authStore.record;\n\n // unset the auto refresh in case the auth store was cleared\n // OR a new model was authenticated\n const unsubStoreChange = client.authStore.onChange((newToken, model) => {\n if (\n !newToken ||\n model?.id != oldModel?.id ||\n ((model?.collectionId || oldModel?.collectionId) &&\n model?.collectionId != oldModel?.collectionId)\n ) {\n resetAutoRefresh(client);\n }\n });\n\n // initialize a reset function and attach it dynamically to the client\n (client as any)._resetAutoRefresh = function () {\n unsubStoreChange();\n client.beforeSend = oldBeforeSend;\n delete (client as any)._resetAutoRefresh;\n };\n\n client.beforeSend = async (url, sendOptions) => {\n const oldToken = client.authStore.token;\n\n if (sendOptions.query?.autoRefresh) {\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n }\n\n let isValid = client.authStore.isValid;\n if (\n // is loosely valid\n isValid &&\n // but it is going to expire in the next \"threshold\" seconds\n isTokenExpired(client.authStore.token, threshold)\n ) {\n try {\n await refreshFunc();\n } catch (_) {\n isValid = false;\n }\n }\n\n // still invalid -> reauthenticate\n if (!isValid) {\n await reauthenticateFunc();\n }\n\n // the request wasn't sent with a custom token\n const headers = sendOptions.headers || {};\n for (let key in headers) {\n if (\n key.toLowerCase() == \"authorization\" &&\n // the request wasn't sent with a custom token\n oldToken == headers[key] &&\n client.authStore.token\n ) {\n // set the latest store token\n headers[key] = client.authStore.token;\n break;\n }\n }\n sendOptions.headers = headers;\n\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n };\n}\n","import Client from \"@/Client\";\nimport { ClientResponseError } from \"@/ClientResponseError\";\nimport { RealtimeService, UnsubscribeFunc } from \"@/services/RealtimeService\";\nimport { BaseAuthStore } from \"@/stores/BaseAuthStore\";\nimport { CrudService } from \"@/services/CrudService\";\nimport { ListResult, RecordModel } from \"@/tools/dtos\";\nimport { normalizeLegacyOptionsArgs } from \"@/tools/legacy\";\nimport {\n CommonOptions,\n RecordFullListOptions,\n RecordListOptions,\n RecordOptions,\n SendOptions,\n RecordSubscribeOptions,\n} from \"@/tools/options\";\nimport { getTokenPayload } from \"@/tools/jwt\";\nimport { registerAutoRefresh, resetAutoRefresh } from \"@/tools/refresh\";\n\nexport interface RecordAuthResponse {\n /**\n * The signed PocketBase auth record.\n */\n record: T;\n\n /**\n * The PocketBase record auth token.\n *\n * If you are looking for the OAuth2 access and refresh tokens\n * they are available under the `meta.accessToken` and `meta.refreshToken` props.\n */\n token: string;\n\n /**\n * Auth meta data usually filled when OAuth2 is used.\n */\n meta?: { [key: string]: any };\n}\n\nexport interface AuthProviderInfo {\n name: string;\n displayName: string;\n state: string;\n authURL: string;\n codeVerifier: string;\n codeChallenge: string;\n codeChallengeMethod: string;\n}\n\nexport interface AuthMethodsList {\n mfa: {\n enabled: boolean;\n duration: number;\n };\n otp: {\n enabled: boolean;\n duration: number;\n };\n password: {\n enabled: boolean;\n identityFields: Array;\n };\n oauth2: {\n enabled: boolean;\n providers: Array;\n };\n}\n\nexport interface RecordSubscription {\n action: string; // eg. create, update, delete\n record: T;\n}\n\nexport type OAuth2UrlCallback = (url: string) => void | Promise;\n\nexport interface OAuth2AuthConfig extends SendOptions {\n // the name of the OAuth2 provider (eg. \"google\")\n provider: string;\n\n // custom scopes to overwrite the default ones\n scopes?: Array;\n\n // optional record create data\n createData?: { [key: string]: any };\n\n // optional callback that is triggered after the OAuth2 sign-in/sign-up url generation\n urlCallback?: OAuth2UrlCallback;\n\n // optional query params to send with the PocketBase auth request (eg. fields, expand, etc.)\n query?: RecordOptions;\n}\n\nexport interface OTPResponse {\n otpId: string;\n}\n\nexport class RecordService extends CrudService {\n readonly collectionIdOrName: string;\n\n constructor(client: Client, collectionIdOrName: string) {\n super(client);\n\n this.collectionIdOrName = collectionIdOrName;\n }\n\n /**\n * @inheritdoc\n */\n get baseCrudPath(): string {\n return this.baseCollectionPath + \"/records\";\n }\n\n /**\n * Returns the current collection service base path.\n */\n get baseCollectionPath(): string {\n return \"/api/collections/\" + encodeURIComponent(this.collectionIdOrName);\n }\n\n /**\n * Returns whether the current service collection is superusers.\n */\n get isSuperusers(): boolean {\n return (\n this.collectionIdOrName == \"_superusers\" ||\n this.collectionIdOrName == \"_pbc_2773867675\"\n );\n }\n\n // ---------------------------------------------------------------\n // Realtime handlers\n // ---------------------------------------------------------------\n\n /**\n * Subscribe to realtime changes to the specified topic (\"*\" or record id).\n *\n * If `topic` is the wildcard \"*\", then this method will subscribe to\n * any record changes in the collection.\n *\n * If `topic` is a record id, then this method will subscribe only\n * to changes of the specified record id.\n *\n * It's OK to subscribe multiple times to the same topic.\n * You can use the returned `UnsubscribeFunc` to remove only a single subscription.\n * Or use `unsubscribe(topic)` if you want to remove all subscriptions attached to the topic.\n */\n async subscribe(\n topic: string,\n callback: (data: RecordSubscription) => void,\n options?: RecordSubscribeOptions,\n ): Promise {\n if (!topic) {\n throw new Error(\"Missing topic.\");\n }\n\n if (!callback) {\n throw new Error(\"Missing subscription callback.\");\n }\n\n return this.client.realtime.subscribe(\n this.collectionIdOrName + \"/\" + topic,\n callback,\n options,\n );\n }\n\n /**\n * Unsubscribe from all subscriptions of the specified topic\n * (\"*\" or record id).\n *\n * If `topic` is not set, then this method will unsubscribe from\n * all subscriptions associated to the current collection.\n */\n async unsubscribe(topic?: string): Promise {\n // unsubscribe from the specified topic\n if (topic) {\n return this.client.realtime.unsubscribe(\n this.collectionIdOrName + \"/\" + topic,\n );\n }\n\n // unsubscribe from everything related to the collection\n return this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName);\n }\n\n // ---------------------------------------------------------------\n // Crud handers\n // ---------------------------------------------------------------\n /**\n * @inheritdoc\n */\n async getFullList(options?: RecordFullListOptions): Promise>;\n\n /**\n * @inheritdoc\n */\n async getFullList(\n batch?: number,\n options?: RecordListOptions,\n ): Promise>;\n\n /**\n * @inheritdoc\n */\n async getFullList(\n batchOrOptions?: number | RecordFullListOptions,\n options?: RecordListOptions,\n ): Promise> {\n if (typeof batchOrOptions == \"number\") {\n return super.getFullList(batchOrOptions, options);\n }\n\n const params = Object.assign({}, batchOrOptions, options);\n\n return super.getFullList(params);\n }\n\n /**\n * @inheritdoc\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: RecordListOptions,\n ): Promise> {\n return super.getList(page, perPage, options);\n }\n\n /**\n * @inheritdoc\n */\n async getFirstListItem(\n filter: string,\n options?: RecordListOptions,\n ): Promise {\n return super.getFirstListItem(filter, options);\n }\n\n /**\n * @inheritdoc\n */\n async getOne(id: string, options?: RecordOptions): Promise {\n return super.getOne(id, options);\n }\n\n /**\n * @inheritdoc\n */\n async create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): Promise {\n return super.create(bodyParams, options);\n }\n\n /**\n * @inheritdoc\n *\n * If the current `client.authStore.record` matches with the updated id, then\n * on success the `client.authStore.record` will be updated with the new response record fields.\n */\n async update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): Promise {\n return super.update(id, bodyParams, options).then((item) => {\n if (\n // is record auth\n this.client.authStore.record?.id === item?.id &&\n (this.client.authStore.record?.collectionId === this.collectionIdOrName ||\n this.client.authStore.record?.collectionName ===\n this.collectionIdOrName)\n ) {\n let authExpand = Object.assign({}, this.client.authStore.record.expand);\n let authRecord = Object.assign({}, this.client.authStore.record, item);\n if (authExpand) {\n // for now \"merge\" only top-level expand\n authRecord.expand = Object.assign(authExpand, item.expand);\n }\n\n this.client.authStore.save(this.client.authStore.token, authRecord);\n }\n\n return item as any as T;\n });\n }\n\n /**\n * @inheritdoc\n *\n * If the current `client.authStore.record` matches with the deleted id,\n * then on success the `client.authStore` will be cleared.\n */\n async delete(id: string, options?: CommonOptions): Promise {\n return super.delete(id, options).then((success) => {\n if (\n success &&\n // is record auth\n this.client.authStore.record?.id === id &&\n (this.client.authStore.record?.collectionId === this.collectionIdOrName ||\n this.client.authStore.record?.collectionName ===\n this.collectionIdOrName)\n ) {\n this.client.authStore.clear();\n }\n\n return success;\n });\n }\n\n // ---------------------------------------------------------------\n // Auth handlers\n // ---------------------------------------------------------------\n\n /**\n * Prepare successful collection authorization response.\n */\n protected authResponse(responseData: any): RecordAuthResponse {\n const record = this.decode(responseData?.record || {});\n\n this.client.authStore.save(responseData?.token, record as any);\n\n return Object.assign({}, responseData, {\n // normalize common fields\n token: responseData?.token || \"\",\n record: record as any as T,\n });\n }\n\n /**\n * Returns all available collection auth methods.\n *\n * @throws {ClientResponseError}\n */\n async listAuthMethods(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"GET\",\n // @todo remove after deleting the pre v0.23 API response fields\n fields: \"mfa,otp,password,oauth2\",\n },\n options,\n );\n\n return this.client.send(this.baseCollectionPath + \"/auth-methods\", options);\n }\n\n /**\n * Authenticate a single auth collection record via its username/email and password.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n *\n * @throws {ClientResponseError}\n */\n async authWithPassword(\n usernameOrEmail: string,\n password: string,\n options?: RecordOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n identity: usernameOrEmail,\n password: password,\n },\n },\n options,\n );\n\n // note: consider to deprecate\n let autoRefreshThreshold;\n if (this.isSuperusers) {\n autoRefreshThreshold = options.autoRefreshThreshold;\n delete options.autoRefreshThreshold;\n if (!options.autoRefresh) {\n resetAutoRefresh(this.client);\n }\n }\n\n let authData = await this.client.send(\n this.baseCollectionPath + \"/auth-with-password\",\n options,\n );\n\n authData = this.authResponse(authData);\n\n if (autoRefreshThreshold && this.isSuperusers) {\n registerAutoRefresh(\n this.client,\n autoRefreshThreshold,\n () => this.authRefresh({ autoRefresh: true }),\n () =>\n this.authWithPassword(\n usernameOrEmail,\n password,\n Object.assign({ autoRefresh: true }, options),\n ),\n );\n }\n\n return authData;\n }\n\n /**\n * Authenticate a single auth collection record with OAuth2 code.\n *\n * If you don't have an OAuth2 code you may also want to check `authWithOAuth2` method.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n * - the OAuth2 account data (eg. name, email, avatar, etc.)\n *\n * @throws {ClientResponseError}\n */\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n options?: RecordOptions,\n ): Promise>;\n\n /**\n * @deprecated\n * Consider using authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createdData, options?).\n */\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n body?: any,\n query?: any,\n ): Promise>;\n\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n bodyOrOptions?: any,\n query?: any,\n ): Promise> {\n let options: any = {\n method: \"POST\",\n body: {\n provider: provider,\n code: code,\n codeVerifier: codeVerifier,\n redirectURL: redirectURL,\n createData: createData,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-with-oauth2\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * @deprecated This form of authWithOAuth2 is deprecated.\n *\n * Please use `authWithOAuth2Code()` OR its simplified realtime version\n * as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.\n */\n async authWithOAuth2(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n bodyParams?: { [key: string]: any },\n queryParams?: RecordOptions,\n ): Promise>;\n\n /**\n * Authenticate a single auth collection record with OAuth2\n * **without custom redirects, deeplinks or even page reload**.\n *\n * This method initializes a one-off realtime subscription and will\n * open a popup window with the OAuth2 vendor page to authenticate.\n * Once the external OAuth2 sign-in/sign-up flow is completed, the popup\n * window will be automatically closed and the OAuth2 data sent back\n * to the user through the previously established realtime connection.\n *\n * You can specify an optional `urlCallback` prop to customize\n * the default url `window.open` behavior.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n * - the OAuth2 account data (eg. name, email, avatar, etc.)\n *\n * Example:\n *\n * ```js\n * const authData = await pb.collection(\"users\").authWithOAuth2({\n * provider: \"google\",\n * })\n * ```\n *\n * Note1: When creating the OAuth2 app in the provider dashboard\n * you have to configure `https://yourdomain.com/api/oauth2-redirect`\n * as redirect URL.\n *\n * Note2: Safari may block the default `urlCallback` popup because\n * it doesn't allow `window.open` calls as part of an `async` click functions.\n * To workaround this you can either change your click handler to not be marked as `async`\n * OR manually call `window.open` before your `async` function and use the\n * window reference in your own custom `urlCallback` (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061).\n * For example:\n * ```js\n * \n * ...\n * document.getElementById(\"btn\").addEventListener(\"click\", () => {\n * pb.collection(\"users\").authWithOAuth2({\n * provider: \"gitlab\",\n * }).then((authData) => {\n * console.log(authData)\n * }).catch((err) => {\n * console.log(err, err.originalError);\n * });\n * })\n * ```\n *\n * @throws {ClientResponseError}\n */\n async authWithOAuth2(\n options: OAuth2AuthConfig,\n ): Promise>;\n\n authWithOAuth2(...args: any): Promise> {\n // fallback to legacy format\n if (args.length > 1 || typeof args?.[0] === \"string\") {\n console.warn(\n \"PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.\",\n );\n return this.authWithOAuth2Code(\n args?.[0] || \"\",\n args?.[1] || \"\",\n args?.[2] || \"\",\n args?.[3] || \"\",\n args?.[4] || {},\n args?.[5] || {},\n args?.[6] || {},\n );\n }\n\n const config = args?.[0] || {};\n\n // open a new popup window in case config.urlCallback is not set\n //\n // note: it is opened before any async calls due to Safari restrictions\n // (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061)\n let eagerDefaultPopup: Window | null = null;\n if (!config.urlCallback) {\n eagerDefaultPopup = openBrowserPopup(undefined);\n }\n\n // initialize a one-off realtime service\n const realtime = new RealtimeService(this.client);\n\n function cleanup() {\n eagerDefaultPopup?.close();\n realtime.unsubscribe();\n }\n\n const requestKeyOptions: SendOptions = {};\n const requestKey = config.requestKey;\n if (requestKey) {\n requestKeyOptions.requestKey = requestKey;\n }\n\n return this.listAuthMethods(requestKeyOptions)\n .then((authMethods) => {\n const provider = authMethods.oauth2.providers.find(\n (p) => p.name === config.provider,\n );\n if (!provider) {\n throw new ClientResponseError(\n new Error(`Missing or invalid provider \"${config.provider}\".`),\n );\n }\n\n const redirectURL = this.client.buildURL(\"/api/oauth2-redirect\");\n\n return new Promise(async (resolve, reject) => {\n // find the AbortController associated with the current request key (if any)\n const cancelController = requestKey\n ? this.client[\"cancelControllers\"]?.[requestKey]\n : undefined;\n if (cancelController) {\n cancelController.signal.onabort = () => {\n cleanup();\n reject(\n new ClientResponseError({\n isAbort: true,\n message: \"manually cancelled\",\n }),\n );\n };\n }\n\n // disconnected due to network/server error\n realtime.onDisconnect = (activeSubscriptions: Array) => {\n if (activeSubscriptions.length && reject) {\n cleanup();\n reject(\n new ClientResponseError(\n new Error(\"realtime connection interrupted\"),\n ),\n );\n }\n };\n\n try {\n await realtime.subscribe(\"@oauth2\", async (e) => {\n const oldState = realtime.clientId;\n\n try {\n if (!e.state || oldState !== e.state) {\n throw new Error(\"State parameters don't match.\");\n }\n\n if (e.error || !e.code) {\n throw new Error(\n \"OAuth2 redirect error or missing code: \" +\n e.error,\n );\n }\n\n // clear the non SendOptions props\n const options = Object.assign({}, config);\n delete options.provider;\n delete options.scopes;\n delete options.createData;\n delete options.urlCallback;\n\n // reset the cancelController listener as it will be triggered by the next api call\n if (cancelController?.signal?.onabort) {\n cancelController.signal.onabort = null;\n }\n\n const authData = await this.authWithOAuth2Code(\n provider.name,\n e.code,\n provider.codeVerifier,\n redirectURL,\n config.createData,\n options,\n );\n\n resolve(authData);\n } catch (err) {\n reject(new ClientResponseError(err));\n }\n\n cleanup();\n });\n\n const replacements: { [key: string]: any } = {\n state: realtime.clientId,\n };\n if (config.scopes?.length) {\n replacements[\"scope\"] = config.scopes.join(\" \");\n }\n\n const url = this._replaceQueryParams(\n provider.authURL + redirectURL,\n replacements,\n );\n\n let urlCallback =\n config.urlCallback ||\n function (url: string) {\n if (eagerDefaultPopup) {\n eagerDefaultPopup.location.href = url;\n } else {\n // it could have been blocked due to its empty initial url,\n // try again...\n eagerDefaultPopup = openBrowserPopup(url);\n }\n };\n\n await urlCallback(url);\n } catch (err) {\n // reset the cancelController listener in case the request key is reused\n if (cancelController?.signal?.onabort) {\n cancelController.signal.onabort = null;\n }\n\n cleanup();\n reject(new ClientResponseError(err));\n }\n });\n })\n .catch((err) => {\n cleanup();\n throw err; // rethrow\n }) as Promise>;\n }\n\n /**\n * Refreshes the current authenticated record instance and\n * returns a new token and record data.\n *\n * On success this method also automatically updates the client's AuthStore.\n *\n * @throws {ClientResponseError}\n */\n async authRefresh(options?: RecordOptions): Promise>;\n\n /**\n * @deprecated\n * Consider using authRefresh(options?).\n */\n async authRefresh(body?: any, query?: any): Promise>;\n\n async authRefresh(\n bodyOrOptions?: any,\n query?: any,\n ): Promise> {\n let options: any = {\n method: \"POST\",\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-refresh\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * Sends auth record password reset request.\n *\n * @throws {ClientResponseError}\n */\n async requestPasswordReset(email: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestPasswordReset(email, options?).\n */\n async requestPasswordReset(email: string, body?: any, query?: any): Promise;\n\n async requestPasswordReset(\n email: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n email: email,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-password-reset\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record password reset request.\n *\n * @throws {ClientResponseError}\n */\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmPasswordReset(passwordResetToken, password, passwordConfirm, options?).\n */\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: passwordResetToken,\n password: password,\n passwordConfirm: passwordConfirm,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-password-reset\", options)\n .then(() => true);\n }\n\n /**\n * Sends auth record verification email request.\n *\n * @throws {ClientResponseError}\n */\n async requestVerification(email: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestVerification(email, options?).\n */\n async requestVerification(email: string, body?: any, query?: any): Promise;\n\n async requestVerification(\n email: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n email: email,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-verification\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record email verification request.\n *\n * If the current `client.authStore.record` matches with the auth record from the token,\n * then on success the `client.authStore.record.verified` will be updated to `true`.\n *\n * @throws {ClientResponseError}\n */\n async confirmVerification(\n verificationToken: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmVerification(verificationToken, options?).\n */\n async confirmVerification(\n verificationToken: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmVerification(\n verificationToken: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: verificationToken,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-verification\", options)\n .then(() => {\n // on success manually update the current auth record verified state\n const payload = getTokenPayload(verificationToken);\n const model = this.client.authStore.record;\n if (\n model &&\n !model.verified &&\n model.id === payload.id &&\n model.collectionId === payload.collectionId\n ) {\n model.verified = true;\n this.client.authStore.save(this.client.authStore.token, model);\n }\n\n return true;\n });\n }\n\n /**\n * Sends an email change request to the authenticated record model.\n *\n * @throws {ClientResponseError}\n */\n async requestEmailChange(newEmail: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestEmailChange(newEmail, options?).\n */\n async requestEmailChange(newEmail: string, body?: any, query?: any): Promise;\n\n async requestEmailChange(\n newEmail: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n newEmail: newEmail,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-email-change\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record's new email address.\n *\n * If the current `client.authStore.record` matches with the auth record from the token,\n * then on success the `client.authStore` will be cleared.\n *\n * @throws {ClientResponseError}\n */\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmEmailChange(emailChangeToken, password, options?).\n */\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: emailChangeToken,\n password: password,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-email-change\", options)\n .then(() => {\n const payload = getTokenPayload(emailChangeToken);\n const model = this.client.authStore.record;\n if (\n model &&\n model.id === payload.id &&\n model.collectionId === payload.collectionId\n ) {\n this.client.authStore.clear();\n }\n\n return true;\n });\n }\n\n /**\n * @deprecated use collection(\"_externalAuths\").*\n *\n * Lists all linked external auth providers for the specified auth record.\n *\n * @throws {ClientResponseError}\n */\n async listExternalAuths(\n recordId: string,\n options?: CommonOptions,\n ): Promise> {\n return this.client.collection(\"_externalAuths\").getFullList(\n Object.assign({}, options, {\n filter: this.client.filter(\"recordRef = {:id}\", { id: recordId }),\n }),\n );\n }\n\n /**\n * @deprecated use collection(\"_externalAuths\").*\n *\n * Unlink a single external auth provider from the specified auth record.\n *\n * @throws {ClientResponseError}\n */\n async unlinkExternalAuth(\n recordId: string,\n provider: string,\n options?: CommonOptions,\n ): Promise {\n const ea = await this.client.collection(\"_externalAuths\").getFirstListItem(\n this.client.filter(\"recordRef = {:recordId} && provider = {:provider}\", {\n recordId,\n provider,\n }),\n );\n\n return this.client\n .collection(\"_externalAuths\")\n .delete(ea.id, options)\n .then(() => true);\n }\n\n /**\n * Sends auth record OTP to the provided email.\n *\n * @throws {ClientResponseError}\n */\n async requestOTP(email: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: { email: email },\n },\n options,\n );\n\n return this.client.send(this.baseCollectionPath + \"/request-otp\", options);\n }\n\n /**\n * Authenticate a single auth collection record via OTP.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n *\n * @throws {ClientResponseError}\n */\n async authWithOTP(\n otpId: string,\n password: string,\n options?: CommonOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"POST\",\n body: { otpId, password },\n },\n options,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-with-otp\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * Impersonate authenticates with the specified recordId and\n * returns a new client with the received auth token in a memory store.\n *\n * If `duration` is 0 the generated auth token will fallback\n * to the default collection auth token duration.\n *\n * This action currently requires superusers privileges.\n *\n * @throws {ClientResponseError}\n */\n async impersonate(\n recordId: string,\n duration: number,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: { duration: duration },\n },\n options,\n );\n options.headers = options.headers || {};\n if (!options.headers.Authorization) {\n options.headers.Authorization = this.client.authStore.token;\n }\n\n // create a new client loaded with the impersonated auth state\n // ---\n const client = new Client(\n this.client.baseURL,\n new BaseAuthStore(),\n this.client.lang,\n );\n\n const authData = await client.send(\n this.baseCollectionPath + \"/impersonate/\" + encodeURIComponent(recordId),\n options,\n );\n\n client.authStore.save(authData?.token, this.decode(authData?.record || {}));\n // ---\n\n return client;\n }\n\n // ---------------------------------------------------------------\n\n // very rudimentary url query params replacement because at the moment\n // URL (and URLSearchParams) doesn't seem to be fully supported in React Native\n //\n // note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html\n private _replaceQueryParams(\n url: string,\n replacements: { [key: string]: any } = {},\n ): string {\n let urlPath = url;\n let query = \"\";\n\n const queryIndex = url.indexOf(\"?\");\n if (queryIndex >= 0) {\n urlPath = url.substring(0, url.indexOf(\"?\"));\n query = url.substring(url.indexOf(\"?\") + 1);\n }\n\n const parsedParams: { [key: string]: string } = {};\n\n // parse the query parameters\n const rawParams = query.split(\"&\");\n for (const param of rawParams) {\n if (param == \"\") {\n continue;\n }\n\n const pair = param.split(\"=\");\n parsedParams[decodeURIComponent(pair[0].replace(/\\+/g, \" \"))] =\n decodeURIComponent((pair[1] || \"\").replace(/\\+/g, \" \"));\n }\n\n // apply the replacements\n for (let key in replacements) {\n if (!replacements.hasOwnProperty(key)) {\n continue;\n }\n\n if (replacements[key] == null) {\n delete parsedParams[key];\n } else {\n parsedParams[key] = replacements[key];\n }\n }\n\n // construct back the full query string\n query = \"\";\n for (let key in parsedParams) {\n if (!parsedParams.hasOwnProperty(key)) {\n continue;\n }\n\n if (query != \"\") {\n query += \"&\";\n }\n\n query +=\n encodeURIComponent(key.replace(/%20/g, \"+\")) +\n \"=\" +\n encodeURIComponent(parsedParams[key].replace(/%20/g, \"+\"));\n }\n\n return query != \"\" ? urlPath + \"?\" + query : urlPath;\n }\n}\n\nfunction openBrowserPopup(url?: string): Window | null {\n if (typeof window === \"undefined\" || !window?.open) {\n throw new ClientResponseError(\n new Error(\n `Not in a browser context - please pass a custom urlCallback function.`,\n ),\n );\n }\n\n let width = 1024;\n let height = 768;\n\n let windowWidth = window.innerWidth;\n let windowHeight = window.innerHeight;\n\n // normalize window size\n width = width > windowWidth ? windowWidth : width;\n height = height > windowHeight ? windowHeight : height;\n\n let left = windowWidth / 2 - width / 2;\n let top = windowHeight / 2 - height / 2;\n\n // note: we don't use the noopener and noreferrer attributes since\n // for some reason browser blocks such windows then url is undefined/blank\n return window.open(\n url,\n \"popup_window\",\n \"width=\" +\n width +\n \",height=\" +\n height +\n \",top=\" +\n top +\n \",left=\" +\n left +\n \",resizable,menubar=no\",\n );\n}\n","import { CrudService } from \"@/services/CrudService\";\nimport { CollectionModel } from \"@/tools/dtos\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport class CollectionService extends CrudService {\n /**\n * @inheritdoc\n */\n get baseCrudPath(): string {\n return \"/api/collections\";\n }\n\n /**\n * Imports the provided collections.\n *\n * If `deleteMissing` is `true`, all local collections and their fields,\n * that are not present in the imported configuration, WILL BE DELETED\n * (including their related records data)!\n *\n * @throws {ClientResponseError}\n */\n async import(\n collections: Array,\n deleteMissing: boolean = false,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"PUT\",\n body: {\n collections: collections,\n deleteMissing: deleteMissing,\n },\n },\n options,\n );\n\n return this.client.send(this.baseCrudPath + \"/import\", options).then(() => true);\n }\n\n /**\n * Returns type indexed map with scaffolded collection models\n * populated with their default field values.\n *\n * @throws {ClientResponseError}\n */\n async getScaffolds(\n options?: CommonOptions,\n ): Promise<{ [key: string]: CollectionModel }> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(this.baseCrudPath + \"/meta/scaffolds\", options);\n }\n\n /**\n * Deletes all records associated with the specified collection.\n *\n * @throws {ClientResponseError}\n */\n async truncate(collectionIdOrName: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(\n this.baseCrudPath +\n \"/\" +\n encodeURIComponent(collectionIdOrName) +\n \"/truncate\",\n options,\n )\n .then(() => true);\n }\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseService } from \"@/services/BaseService\";\nimport { ListResult, LogModel } from \"@/tools/dtos\";\nimport { CommonOptions, ListOptions, LogStatsOptions } from \"@/tools/options\";\n\nexport interface HourlyStats {\n total: number;\n date: string;\n}\n\nexport class LogService extends BaseService {\n /**\n * Returns paginated logs list.\n *\n * @throws {ClientResponseError}\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: ListOptions,\n ): Promise> {\n options = Object.assign({ method: \"GET\" }, options);\n\n options.query = Object.assign(\n {\n page: page,\n perPage: perPage,\n },\n options.query,\n );\n\n return this.client.send(\"/api/logs\", options);\n }\n\n /**\n * Returns a single log by its id.\n *\n * If `id` is empty it will throw a 404 error.\n *\n * @throws {ClientResponseError}\n */\n async getOne(id: string, options?: CommonOptions): Promise {\n if (!id) {\n throw new ClientResponseError({\n url: this.client.buildURL(\"/api/logs/\"),\n status: 404,\n response: {\n code: 404,\n message: \"Missing required log id.\",\n data: {},\n },\n });\n }\n\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/logs/\" + encodeURIComponent(id), options);\n }\n\n /**\n * Returns logs statistics.\n *\n * @throws {ClientResponseError}\n */\n async getStats(options?: LogStatsOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/logs/stats\", options);\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface HealthCheckResponse {\n code: number;\n message: string;\n data: { [key: string]: any };\n}\n\nexport class HealthService extends BaseService {\n /**\n * Checks the health status of the api.\n *\n * @throws {ClientResponseError}\n */\n async check(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/health\", options);\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions, FileOptions, serializeQueryParams } from \"@/tools/options\";\n\nexport class FileService extends BaseService {\n /**\n * @deprecated Please replace with `pb.files.getURL()`.\n */\n getUrl(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n console.warn(\"Please replace pb.files.getUrl() with pb.files.getURL()\");\n return this.getURL(record, filename, queryParams);\n }\n\n /**\n * Builds and returns an absolute record file url for the provided filename.\n */\n getURL(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n if (\n !filename ||\n !record?.id ||\n !(record?.collectionId || record?.collectionName)\n ) {\n return \"\";\n }\n\n const parts = [];\n parts.push(\"api\");\n parts.push(\"files\");\n parts.push(encodeURIComponent(record.collectionId || record.collectionName));\n parts.push(encodeURIComponent(record.id));\n parts.push(encodeURIComponent(filename));\n\n let result = this.client.buildURL(parts.join(\"/\"));\n\n // normalize the download query param for consistency with the Dart sdk\n if (queryParams.download === false) {\n delete queryParams.download;\n }\n\n const params = serializeQueryParams(queryParams);\n if (params) {\n result += (result.includes(\"?\") ? \"&\" : \"?\") + params;\n }\n\n return result;\n }\n\n /**\n * Requests a new private file access token for the current auth model.\n *\n * @throws {ClientResponseError}\n */\n async getToken(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(\"/api/files/token\", options)\n .then((data) => data?.token || \"\");\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface BackupFileInfo {\n key: string;\n size: number;\n modified: string;\n}\n\nexport class BackupService extends BaseService {\n /**\n * Returns list with all available backup files.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: CommonOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/backups\", options);\n }\n\n /**\n * Initializes a new backup.\n *\n * @throws {ClientResponseError}\n */\n async create(basename: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n name: basename,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/backups\", options).then(() => true);\n }\n\n /**\n * Uploads an existing backup file.\n *\n * Example:\n *\n * ```js\n * await pb.backups.upload({\n * file: new Blob([...]),\n * });\n * ```\n *\n * @throws {ClientResponseError}\n */\n async upload(\n bodyParams: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client.send(\"/api/backups/upload\", options).then(() => true);\n }\n\n /**\n * Deletes a single backup file.\n *\n * @throws {ClientResponseError}\n */\n async delete(key: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(`/api/backups/${encodeURIComponent(key)}`, options)\n .then(() => true);\n }\n\n /**\n * Initializes an app data restore from an existing backup.\n *\n * @throws {ClientResponseError}\n */\n async restore(key: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(`/api/backups/${encodeURIComponent(key)}/restore`, options)\n .then(() => true);\n }\n\n /**\n * @deprecated Please use `getDownloadURL()`.\n */\n getDownloadUrl(token: string, key: string): string {\n console.warn(\n \"Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()\",\n );\n return this.getDownloadURL(token, key);\n }\n\n /**\n * Builds a download url for a single existing backup using a\n * superuser file token and the backup file key.\n *\n * The file token can be generated via `pb.files.getToken()`.\n */\n getDownloadURL(token: string, key: string): string {\n return this.client.buildURL(\n `/api/backups/${encodeURIComponent(key)}?token=${encodeURIComponent(token)}`,\n );\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface CronJob {\n id: string;\n expression: string;\n}\n\nexport class CronService extends BaseService {\n /**\n * Returns list with all registered cron jobs.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: CommonOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/crons\", options);\n }\n\n /**\n * Runs the specified cron job.\n *\n * @throws {ClientResponseError}\n */\n async run(jobId: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(`/api/crons/${encodeURIComponent(jobId)}`, options)\n .then(() => true);\n }\n}\n","/**\n * Checks if the specified value is a file (aka. File, Blob, RN file object).\n */\nexport function isFile(val: any): boolean {\n return (\n (typeof Blob !== \"undefined\" && val instanceof Blob) ||\n (typeof File !== \"undefined\" && val instanceof File) ||\n // check for React Native file object format\n // (see https://github.com/pocketbase/pocketbase/discussions/2002#discussioncomment-5254168)\n (val !== null &&\n typeof val === \"object\" &&\n val.uri &&\n ((typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\") ||\n (typeof global !== \"undefined\" && (global as any).HermesInternal)))\n );\n}\n\n/**\n * Loosely checks if the specified body is a FormData instance.\n */\nexport function isFormData(body: any): boolean {\n return (\n body &&\n // we are checking the constructor name because FormData\n // is not available natively in some environments and the\n // polyfill(s) may not be globally accessible\n (body.constructor?.name === \"FormData\" ||\n // fallback to global FormData instance check\n // note: this is needed because the constructor.name could be different in case of\n // custom global FormData implementation, eg. React Native on Android/iOS\n (typeof FormData !== \"undefined\" && body instanceof FormData))\n );\n}\n\n/**\n * Checks if the submitted body object has at least one Blob/File field value.\n */\nexport function hasFileField(body: { [key: string]: any }): boolean {\n for (const key in body) {\n const values = Array.isArray(body[key]) ? body[key] : [body[key]];\n for (const v of values) {\n if (isFile(v)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Converts analyzes the provided body and converts it to FormData\n * in case a plain object with File/Blob values is used.\n */\nexport function convertToFormDataIfNeeded(body: any): any {\n if (\n typeof FormData === \"undefined\" ||\n typeof body === \"undefined\" ||\n typeof body !== \"object\" ||\n body === null ||\n isFormData(body) ||\n !hasFileField(body)\n ) {\n return body;\n }\n\n const form = new FormData();\n\n for (const key in body) {\n const val = body[key];\n\n // skip undefined values for consistency with JSON.stringify\n // (see https://github.com/pocketbase/pocketbase/issues/6731#issuecomment-2812382827)\n if (typeof val === \"undefined\") {\n continue;\n }\n\n if (typeof val === \"object\" && !hasFileField({ data: val })) {\n // send json-like values as jsonPayload to avoid the implicit string value normalization\n let payload: { [key: string]: any } = {};\n payload[key] = val;\n form.append(\"@jsonPayload\", JSON.stringify(payload));\n } else {\n // in case of mixed string and file/blob\n const normalizedVal = Array.isArray(val) ? val : [val];\n for (let v of normalizedVal) {\n form.append(key, v);\n }\n }\n }\n\n return form;\n}\n\n/**\n * Converts the provided FormData instance into a plain object.\n *\n * For consistency with the server multipart/form-data inferring,\n * the following normalization rules are applied for plain multipart string values:\n * - \"true\" is converted to the json \"true\"\n * - \"false\" is converted to the json \"false\"\n * - numeric strings are converted to json number ONLY if the resulted\n * minimal number string representation is the same as the provided raw string\n * (aka. scientific notations, \"Infinity\", \"0.0\", \"0001\", etc. are kept as string)\n * - any other string (empty string too) is left as it is\n */\nexport function convertFormDataToObject(formData: FormData): { [key: string]: any } {\n let result: { [key: string]: any } = {};\n\n formData.forEach((v, k) => {\n if (k === \"@jsonPayload\" && typeof v == \"string\") {\n try {\n let parsed = JSON.parse(v);\n Object.assign(result, parsed);\n } catch (err) {\n console.warn(\"@jsonPayload error:\", err);\n }\n } else {\n if (typeof result[k] !== \"undefined\") {\n if (!Array.isArray(result[k])) {\n result[k] = [result[k]];\n }\n result[k].push(inferFormDataValue(v));\n } else {\n result[k] = inferFormDataValue(v);\n }\n }\n });\n\n return result;\n}\n\nconst inferNumberCharsRegex = /^[\\-\\.\\d]+$/;\n\nfunction inferFormDataValue(value: any): any {\n if (typeof value != \"string\") {\n return value;\n }\n\n if (value == \"true\") {\n return true;\n }\n\n if (value == \"false\") {\n return false;\n }\n\n // note: expects the provided raw string to match exactly with the minimal string representation of the parsed number\n if (\n (value[0] === \"-\" || (value[0] >= \"0\" && value[0] <= \"9\")) &&\n inferNumberCharsRegex.test(value)\n ) {\n let num = +value;\n if (\"\" + num === value) {\n return num;\n }\n }\n\n return value;\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { isFile, isFormData, convertFormDataToObject } from \"@/tools/formdata\";\nimport {\n SendOptions,\n RecordOptions,\n normalizeUnknownQueryParams,\n serializeQueryParams,\n} from \"@/tools/options\";\n\nexport interface BatchRequest {\n method: string;\n url: string;\n json?: { [key: string]: any };\n files?: { [key: string]: Array };\n headers?: { [key: string]: string };\n}\n\nexport interface BatchRequestResult {\n status: number;\n body: any;\n}\n\nexport class BatchService extends BaseService {\n private requests: Array = [];\n private subs: { [key: string]: SubBatchService } = {};\n\n /**\n * Starts constructing a batch request entry for the specified collection.\n */\n collection(collectionIdOrName: string): SubBatchService {\n if (!this.subs[collectionIdOrName]) {\n this.subs[collectionIdOrName] = new SubBatchService(\n this.requests,\n collectionIdOrName,\n );\n }\n\n return this.subs[collectionIdOrName];\n }\n\n /**\n * Sends the batch requests.\n *\n * @throws {ClientResponseError}\n */\n async send(options?: SendOptions): Promise> {\n const formData = new FormData();\n\n const jsonData = [];\n\n for (let i = 0; i < this.requests.length; i++) {\n const req = this.requests[i];\n\n jsonData.push({\n method: req.method,\n url: req.url,\n headers: req.headers,\n body: req.json,\n });\n\n if (req.files) {\n for (let key in req.files) {\n const files = req.files[key] || [];\n for (let file of files) {\n formData.append(\"requests.\" + i + \".\" + key, file);\n }\n }\n }\n }\n\n formData.append(\"@jsonPayload\", JSON.stringify({ requests: jsonData }));\n\n options = Object.assign(\n {\n method: \"POST\",\n body: formData,\n },\n options,\n );\n\n return this.client.send(\"/api/batch\", options);\n }\n}\n\nexport class SubBatchService {\n private requests: Array = [];\n private readonly collectionIdOrName: string;\n\n constructor(requests: Array, collectionIdOrName: string) {\n this.requests = requests;\n this.collectionIdOrName = collectionIdOrName;\n }\n\n /**\n * Registers a record upsert request into the current batch queue.\n *\n * The request will be executed as update if `bodyParams` have a valid existing record `id` value, otherwise - create.\n */\n upsert(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"PUT\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records\",\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record create request into the current batch queue.\n */\n create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"POST\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records\",\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record update request into the current batch queue.\n */\n update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"PATCH\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records/\" +\n encodeURIComponent(id),\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record delete request into the current batch queue.\n */\n delete(id: string, options?: SendOptions): void {\n options = Object.assign({}, options);\n\n const request: BatchRequest = {\n method: \"DELETE\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records/\" +\n encodeURIComponent(id),\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n private prepareRequest(request: BatchRequest, options: SendOptions) {\n normalizeUnknownQueryParams(options);\n\n request.headers = options.headers;\n request.json = {};\n request.files = {};\n\n // serialize query parameters\n // -----------------------------------------------------------\n if (typeof options.query !== \"undefined\") {\n const query = serializeQueryParams(options.query);\n if (query) {\n request.url += (request.url.includes(\"?\") ? \"&\" : \"?\") + query;\n }\n }\n\n // extract json and files body data\n // -----------------------------------------------------------\n let body = options.body;\n if (isFormData(body)) {\n body = convertFormDataToObject(body);\n }\n\n for (const key in body) {\n const val = body[key];\n\n if (isFile(val)) {\n request.files[key] = request.files[key] || [];\n request.files[key].push(val);\n } else if (Array.isArray(val)) {\n const foundFiles = [];\n const foundRegular = [];\n for (const v of val) {\n if (isFile(v)) {\n foundFiles.push(v);\n } else {\n foundRegular.push(v);\n }\n }\n\n if (foundFiles.length > 0 && foundFiles.length == val.length) {\n // only files\n // ---\n request.files[key] = request.files[key] || [];\n for (let file of foundFiles) {\n request.files[key].push(file);\n }\n } else {\n // empty or mixed array (both regular and File/Blob values)\n // ---\n request.json[key] = foundRegular;\n\n if (foundFiles.length > 0) {\n // add \"+\" to append if not already since otherwise\n // the existing regular files will be deleted\n // (the mixed values order is preserved only within their corresponding groups)\n let fileKey = key;\n if (!key.startsWith(\"+\") && !key.endsWith(\"+\")) {\n fileKey += \"+\";\n }\n\n request.files[fileKey] = request.files[fileKey] || [];\n for (let file of foundFiles) {\n request.files[fileKey].push(file);\n }\n }\n }\n } else {\n request.json[key] = val;\n }\n }\n }\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseAuthStore } from \"@/stores/BaseAuthStore\";\nimport { LocalAuthStore } from \"@/stores/LocalAuthStore\";\nimport { SettingsService } from \"@/services/SettingsService\";\nimport { RecordService } from \"@/services/RecordService\";\nimport { CollectionService } from \"@/services/CollectionService\";\nimport { LogService } from \"@/services/LogService\";\nimport { RealtimeService } from \"@/services/RealtimeService\";\nimport { HealthService } from \"@/services/HealthService\";\nimport { FileService } from \"@/services/FileService\";\nimport { BackupService } from \"@/services/BackupService\";\nimport { CronService } from \"@/services/CronService\";\nimport { BatchService } from \"@/services/BatchService\";\nimport { RecordModel } from \"@/tools/dtos\";\nimport {\n SendOptions,\n FileOptions,\n normalizeUnknownQueryParams,\n serializeQueryParams,\n} from \"@/tools/options\";\nimport { isFormData, convertToFormDataIfNeeded } from \"@/tools/formdata\";\n\nexport interface BeforeSendResult {\n [key: string]: any; // for backward compatibility\n url?: string;\n options?: { [key: string]: any };\n}\n\n/**\n * PocketBase JS Client.\n */\nexport default class Client {\n /**\n * The base PocketBase backend url address (eg. 'http://127.0.0.1.8090').\n */\n baseURL: string;\n\n /**\n * Legacy getter alias for baseURL.\n * @deprecated Please replace with baseURL.\n */\n get baseUrl(): string {\n return this.baseURL;\n }\n\n /**\n * Legacy setter alias for baseURL.\n * @deprecated Please replace with baseURL.\n */\n set baseUrl(v: string) {\n this.baseURL = v;\n }\n\n /**\n * Hook that get triggered right before sending the fetch request,\n * allowing you to inspect and modify the url and request options.\n *\n * For list of the possible options check https://developer.mozilla.org/en-US/docs/Web/API/fetch#options\n *\n * You can return a non-empty result object `{ url, options }` to replace the url and request options entirely.\n *\n * Example:\n * ```js\n * const pb = new PocketBase(\"https://example.com\")\n *\n * pb.beforeSend = function (url, options) {\n * options.headers = Object.assign({}, options.headers, {\n * 'X-Custom-Header': 'example',\n * })\n *\n * return { url, options }\n * }\n *\n * // use the created client as usual...\n * ```\n */\n beforeSend?: (\n url: string,\n options: SendOptions,\n ) => BeforeSendResult | Promise;\n\n /**\n * Hook that get triggered after successfully sending the fetch request,\n * allowing you to inspect/modify the response object and its parsed data.\n *\n * Returns the new Promise resolved `data` that will be returned to the client.\n *\n * Example:\n * ```js\n * const pb = new PocketBase(\"https://example.com\")\n *\n * pb.afterSend = function (response, data, options) {\n * if (response.status != 200) {\n * throw new ClientResponseError({\n * url: response.url,\n * status: response.status,\n * response: { ... },\n * })\n * }\n *\n * return data;\n * }\n *\n * // use the created client as usual...\n * ```\n */\n afterSend?: ((response: Response, data: any) => any) &\n ((response: Response, data: any, options: SendOptions) => any);\n\n /**\n * Optional language code (default to `en-US`) that will be sent\n * with the requests to the server as `Accept-Language` header.\n */\n lang: string;\n\n /**\n * A replaceable instance of the local auth store service.\n */\n authStore: BaseAuthStore;\n\n /**\n * An instance of the service that handles the **Settings APIs**.\n */\n readonly settings: SettingsService;\n\n /**\n * An instance of the service that handles the **Collection APIs**.\n */\n readonly collections: CollectionService;\n\n /**\n * An instance of the service that handles the **File APIs**.\n */\n readonly files: FileService;\n\n /**\n * An instance of the service that handles the **Log APIs**.\n */\n readonly logs: LogService;\n\n /**\n * An instance of the service that handles the **Realtime APIs**.\n */\n readonly realtime: RealtimeService;\n\n /**\n * An instance of the service that handles the **Health APIs**.\n */\n readonly health: HealthService;\n\n /**\n * An instance of the service that handles the **Backup APIs**.\n */\n readonly backups: BackupService;\n\n /**\n * An instance of the service that handles the **Cron APIs**.\n */\n readonly crons: CronService;\n\n private cancelControllers: { [key: string]: AbortController } = {};\n private recordServices: { [key: string]: RecordService } = {};\n private enableAutoCancellation: boolean = true;\n\n constructor(baseURL = \"/\", authStore?: BaseAuthStore | null, lang = \"en-US\") {\n this.baseURL = baseURL;\n this.lang = lang;\n\n if (authStore) {\n this.authStore = authStore;\n } else if (typeof window != \"undefined\" && !!(window as any).Deno) {\n // note: to avoid common security issues we fallback to runtime/memory store in case the code is running in Deno env\n this.authStore = new BaseAuthStore();\n } else {\n this.authStore = new LocalAuthStore();\n }\n\n // common services\n this.collections = new CollectionService(this);\n this.files = new FileService(this);\n this.logs = new LogService(this);\n this.settings = new SettingsService(this);\n this.realtime = new RealtimeService(this);\n this.health = new HealthService(this);\n this.backups = new BackupService(this);\n this.crons = new CronService(this);\n }\n\n /**\n * @deprecated\n * With PocketBase v0.23.0 admins are converted to a regular auth\n * collection named \"_superusers\", aka. you can use directly collection(\"_superusers\").\n */\n get admins(): RecordService {\n return this.collection(\"_superusers\");\n }\n\n /**\n * Creates a new batch handler for sending multiple transactional\n * create/update/upsert/delete collection requests in one network call.\n *\n * Example:\n * ```js\n * const batch = pb.createBatch();\n *\n * batch.collection(\"example1\").create({ ... })\n * batch.collection(\"example2\").update(\"RECORD_ID\", { ... })\n * batch.collection(\"example3\").delete(\"RECORD_ID\")\n * batch.collection(\"example4\").upsert({ ... })\n *\n * await batch.send()\n * ```\n */\n createBatch(): BatchService {\n return new BatchService(this);\n }\n\n /**\n * Returns the RecordService associated to the specified collection.\n */\n collection(idOrName: string): RecordService {\n if (!this.recordServices[idOrName]) {\n this.recordServices[idOrName] = new RecordService(this, idOrName);\n }\n\n return this.recordServices[idOrName];\n }\n\n /**\n * Globally enable or disable auto cancellation for pending duplicated requests.\n */\n autoCancellation(enable: boolean): Client {\n this.enableAutoCancellation = !!enable;\n\n return this;\n }\n\n /**\n * Cancels single request by its cancellation key.\n */\n cancelRequest(requestKey: string): Client {\n if (this.cancelControllers[requestKey]) {\n this.cancelControllers[requestKey].abort();\n delete this.cancelControllers[requestKey];\n }\n\n return this;\n }\n\n /**\n * Cancels all pending requests.\n */\n cancelAllRequests(): Client {\n for (let k in this.cancelControllers) {\n this.cancelControllers[k].abort();\n }\n\n this.cancelControllers = {};\n\n return this;\n }\n\n /**\n * Constructs a filter expression with placeholders populated from a parameters object.\n *\n * Placeholder parameters are defined with the `{:paramName}` notation.\n *\n * The following parameter values are supported:\n *\n * - `string` (_single quotes are autoescaped_)\n * - `number`\n * - `boolean`\n * - `Date` object (_stringified into the PocketBase datetime format_)\n * - `null`\n * - everything else is converted to a string using `JSON.stringify()`\n *\n * Example:\n *\n * ```js\n * pb.collection(\"example\").getFirstListItem(pb.filter(\n * 'title ~ {:title} && created >= {:created}',\n * { title: \"example\", created: new Date()}\n * ))\n * ```\n */\n filter(raw: string, params?: { [key: string]: any }): string {\n if (!params) {\n return raw;\n }\n\n for (let key in params) {\n let val = params[key];\n switch (typeof val) {\n case \"boolean\":\n case \"number\":\n val = \"\" + val;\n break;\n case \"string\":\n val = \"'\" + val.replace(/'/g, \"\\\\'\") + \"'\";\n break;\n default:\n if (val === null) {\n val = \"null\";\n } else if (val instanceof Date) {\n val = \"'\" + val.toISOString().replace(\"T\", \" \") + \"'\";\n } else {\n val = \"'\" + JSON.stringify(val).replace(/'/g, \"\\\\'\") + \"'\";\n }\n }\n raw = raw.replaceAll(\"{:\" + key + \"}\", val);\n }\n\n return raw;\n }\n\n /**\n * @deprecated Please use `pb.files.getURL()`.\n */\n getFileUrl(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n console.warn(\"Please replace pb.getFileUrl() with pb.files.getURL()\");\n return this.files.getURL(record, filename, queryParams);\n }\n\n /**\n * @deprecated Please use `pb.buildURL()`.\n */\n buildUrl(path: string): string {\n console.warn(\"Please replace pb.buildUrl() with pb.buildURL()\");\n return this.buildURL(path);\n }\n\n /**\n * Builds a full client url by safely concatenating the provided path.\n */\n buildURL(path: string): string {\n let url = this.baseURL;\n\n // construct an absolute base url if in a browser environment\n if (\n typeof window !== \"undefined\" &&\n !!window.location &&\n !url.startsWith(\"https://\") &&\n !url.startsWith(\"http://\")\n ) {\n url = window.location.origin?.endsWith(\"/\")\n ? window.location.origin.substring(0, window.location.origin.length - 1)\n : window.location.origin || \"\";\n\n if (!this.baseURL.startsWith(\"/\")) {\n url += window.location.pathname || \"/\";\n url += url.endsWith(\"/\") ? \"\" : \"/\";\n }\n\n url += this.baseURL;\n }\n\n // concatenate the path\n if (path) {\n url += url.endsWith(\"/\") ? \"\" : \"/\"; // append trailing slash if missing\n url += path.startsWith(\"/\") ? path.substring(1) : path;\n }\n\n return url;\n }\n\n /**\n * Sends an api http request.\n *\n * @throws {ClientResponseError}\n */\n async send(path: string, options: SendOptions): Promise {\n options = this.initSendOptions(path, options);\n\n // build url + path\n let url = this.buildURL(path);\n\n if (this.beforeSend) {\n const result = Object.assign({}, await this.beforeSend(url, options));\n if (\n typeof result.url !== \"undefined\" ||\n typeof result.options !== \"undefined\"\n ) {\n url = result.url || url;\n options = result.options || options;\n } else if (Object.keys(result).length) {\n // legacy behavior\n options = result as SendOptions;\n console?.warn &&\n console.warn(\n \"Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`.\",\n );\n }\n }\n\n // serialize the query parameters\n if (typeof options.query !== \"undefined\") {\n const query = serializeQueryParams(options.query);\n if (query) {\n url += (url.includes(\"?\") ? \"&\" : \"?\") + query;\n }\n delete options.query;\n }\n\n // ensures that the json body is serialized\n if (\n this.getHeader(options.headers, \"Content-Type\") == \"application/json\" &&\n options.body &&\n typeof options.body !== \"string\"\n ) {\n options.body = JSON.stringify(options.body);\n }\n\n // early throw an abort error in case the request was already cancelled\n const fetchFunc = options.fetch || fetch;\n\n // send the request\n return fetchFunc(url, options)\n .then(async (response) => {\n let data: any = {};\n\n try {\n data = await response.json();\n } catch (err: any) {\n // @todo map against the response content type\n // all api responses are expected to return json\n // with exception of the realtime events and 204\n if (\n options.signal?.aborted ||\n err?.name == \"AbortError\" ||\n err?.message == \"Aborted\"\n ) {\n throw err;\n }\n }\n\n if (this.afterSend) {\n data = await this.afterSend(response, data, options);\n }\n\n if (response.status >= 400) {\n throw new ClientResponseError({\n url: response.url,\n status: response.status,\n data: data,\n });\n }\n\n return data as T;\n })\n .catch((err) => {\n // wrap to normalize all errors\n throw new ClientResponseError(err);\n });\n }\n\n /**\n * Shallow copy the provided object and takes care to initialize\n * any options required to preserve the backward compatability.\n *\n * @param {SendOptions} options\n * @return {SendOptions}\n */\n private initSendOptions(path: string, options: SendOptions): SendOptions {\n options = Object.assign({ method: \"GET\" } as SendOptions, options);\n\n // auto convert the body to FormData, if needed\n options.body = convertToFormDataIfNeeded(options.body);\n\n // move unknown send options as query parameters\n normalizeUnknownQueryParams(options);\n\n // requestKey normalizations for backward-compatibility\n // ---\n options.query = Object.assign({}, options.params, options.query);\n if (typeof options.requestKey === \"undefined\") {\n if (options.$autoCancel === false || options.query.$autoCancel === false) {\n options.requestKey = null;\n } else if (options.$cancelKey || options.query.$cancelKey) {\n options.requestKey = options.$cancelKey || options.query.$cancelKey;\n }\n }\n // remove the deprecated special cancellation params from the other query params\n delete options.$autoCancel;\n delete options.query.$autoCancel;\n delete options.$cancelKey;\n delete options.query.$cancelKey;\n // ---\n\n // add the json header, if not explicitly set\n // (for FormData body the Content-Type header should be skipped since the boundary is autogenerated)\n if (\n this.getHeader(options.headers, \"Content-Type\") === null &&\n !isFormData(options.body)\n ) {\n options.headers = Object.assign({}, options.headers, {\n \"Content-Type\": \"application/json\",\n });\n }\n\n // add Accept-Language header, if not explicitly set\n if (this.getHeader(options.headers, \"Accept-Language\") === null) {\n options.headers = Object.assign({}, options.headers, {\n \"Accept-Language\": this.lang,\n });\n }\n\n // check if Authorization header can be added\n if (\n // has valid token\n this.authStore.token &&\n // auth header is not explicitly set\n this.getHeader(options.headers, \"Authorization\") === null\n ) {\n options.headers = Object.assign({}, options.headers, {\n Authorization: this.authStore.token,\n });\n }\n\n // handle auto cancellation for duplicated pending request\n if (this.enableAutoCancellation && options.requestKey !== null) {\n const requestKey = options.requestKey || (options.method || \"GET\") + path;\n\n delete options.requestKey;\n\n // cancel previous pending requests\n this.cancelRequest(requestKey);\n\n // @todo evaluate if a cleanup after the request is necessary\n // (check also authWithOAuth2 as it currently relies on the controller)\n const controller = new AbortController();\n this.cancelControllers[requestKey] = controller;\n options.signal = controller.signal;\n }\n\n return options;\n }\n\n /**\n * Extracts the header with the provided name in case-insensitive manner.\n * Returns `null` if no header matching the name is found.\n */\n private getHeader(\n headers: { [key: string]: string } | undefined,\n name: string,\n ): string | null {\n headers = headers || {};\n name = name.toLowerCase();\n\n for (let key in headers) {\n if (key.toLowerCase() == name) {\n return headers[key];\n }\n }\n\n return null;\n }\n}\n","import { BaseAuthStore, AuthRecord } from \"@/stores/BaseAuthStore\";\n\nexport type AsyncSaveFunc = (serializedPayload: string) => Promise;\n\nexport type AsyncClearFunc = () => Promise;\n\ntype queueFunc = () => Promise;\n\n/**\n * AsyncAuthStore is a helper auth store implementation\n * that could be used with any external async persistent layer\n * (key-value db, local file, etc.).\n *\n * Here is an example with the React Native AsyncStorage package:\n *\n * ```\n * import AsyncStorage from \"@react-native-async-storage/async-storage\";\n * import PocketBase, { AsyncAuthStore } from \"pocketbase\";\n *\n * const store = new AsyncAuthStore({\n * save: async (serialized) => AsyncStorage.setItem(\"pb_auth\", serialized),\n * initial: AsyncStorage.getItem(\"pb_auth\"),\n * });\n *\n * const pb = new PocketBase(\"https://example.com\", store)\n * ```\n */\nexport class AsyncAuthStore extends BaseAuthStore {\n private saveFunc: AsyncSaveFunc;\n private clearFunc?: AsyncClearFunc;\n private queue: Array = [];\n\n constructor(config: {\n // The async function that is called every time\n // when the auth store state needs to be persisted.\n save: AsyncSaveFunc;\n\n /// An *optional* async function that is called every time\n /// when the auth store needs to be cleared.\n ///\n /// If not explicitly set, `saveFunc` with empty data will be used.\n clear?: AsyncClearFunc;\n\n // An *optional* initial data to load into the store.\n initial?: string | Promise;\n }) {\n super();\n\n this.saveFunc = config.save;\n this.clearFunc = config.clear;\n\n this._enqueue(() => this._loadInitial(config.initial));\n }\n\n /**\n * @inheritdoc\n */\n save(token: string, record?: AuthRecord): void {\n super.save(token, record);\n\n let value = \"\";\n try {\n value = JSON.stringify({ token, record });\n } catch (err) {\n console.warn(\"AsyncAuthStore: failed to stringify the new state\");\n }\n\n this._enqueue(() => this.saveFunc(value));\n }\n\n /**\n * @inheritdoc\n */\n clear(): void {\n super.clear();\n\n if (this.clearFunc) {\n this._enqueue(() => this.clearFunc!());\n } else {\n this._enqueue(() => this.saveFunc(\"\"));\n }\n }\n\n /**\n * Initializes the auth store state.\n */\n private async _loadInitial(payload?: string | Promise) {\n try {\n payload = await payload;\n\n if (payload) {\n let parsed;\n if (typeof payload === \"string\") {\n parsed = JSON.parse(payload) || {};\n } else if (typeof payload === \"object\") {\n parsed = payload;\n }\n\n this.save(parsed.token || \"\", parsed.record || parsed.model || null);\n }\n } catch (_) {}\n }\n\n /**\n * Appends an async function to the queue.\n */\n private _enqueue(asyncCallback: () => Promise) {\n this.queue.push(asyncCallback);\n\n if (this.queue.length == 1) {\n this._dequeue();\n }\n }\n\n /**\n * Starts the queue processing.\n */\n private _dequeue() {\n if (!this.queue.length) {\n return;\n }\n\n this.queue[0]().finally(() => {\n this.queue.shift();\n\n if (!this.queue.length) {\n return;\n }\n\n this._dequeue();\n });\n }\n}\n"],"names":["ClientResponseError","Error","constructor","errData","super","this","url","status","response","isAbort","originalError","Object","setPrototypeOf","prototype","name","message","data","cause","includes","toJSON","fieldContentRegExp","cookieParse","str","options","result","decode","assign","defaultDecode","index","length","eqIdx","indexOf","endIdx","lastIndexOf","key","slice","trim","undefined","val","charCodeAt","_","cookieSerialize","opt","encode","defaultEncode","test","TypeError","value","maxAge","isNaN","isFinite","Math","floor","domain","path","expires","isDate","toString","call","Date","valueOf","toUTCString","httpOnly","secure","priority","toLowerCase","sameSite","decodeURIComponent","encodeURIComponent","isReactNative","navigator","product","global","HermesInternal","atobPolyfill","getTokenPayload","token","encodedPayload","split","map","c","join","JSON","parse","e","isTokenExpired","expirationThreshold","payload","keys","exp","now","atob","input","String","replace","bs","buffer","bc","idx","output","charAt","fromCharCode","defaultCookieKey","BaseAuthStore","baseToken","baseModel","_onChangeCallbacks","record","model","isValid","isSuperuser","type","collectionName","collectionId","isAdmin","console","warn","isAuthRecord","save","triggerChange","clear","loadFromCookie","cookie","rawData","Array","isArray","exportToCookie","defaultOptions","stringify","resultLength","Blob","size","id","email","extraProps","prop","onChange","callback","fireImmediately","push","i","splice","LocalAuthStore","storageKey","storageFallback","_bindStorageEvent","_storageGet","_storageSet","_storageRemove","window","localStorage","rawValue","getItem","normalizedVal","setItem","removeItem","addEventListener","BaseService","client","SettingsService","getAll","method","send","update","bodyParams","body","testS3","filesystem","then","testEmail","collectionIdOrName","toEmail","emailTemplate","template","collection","generateAppleClientSecret","clientId","teamId","keyId","privateKey","duration","knownSendOptionsKeys","normalizeUnknownQueryParams","query","serializeQueryParams","params","encodedKey","arrValue","v","prepareQueryParamValue","toISOString","RealtimeService","eventSource","subscriptions","lastSentSubscriptions","maxConnectTimeout","reconnectAttempts","maxReconnectAttempts","Infinity","predefinedReconnectIntervals","pendingConnects","isConnected","subscribe","topic","serialized","headers","listener","msgEvent","submitSubscriptions","connect","async","unsubscribeByTopicAndListener","unsubscribe","needToSubmit","subs","getSubscriptionsByTopic","hasSubscriptionListeners","removeEventListener","disconnect","unsubscribeByPrefix","keyPrefix","hasAtleastOneTopic","startsWith","exist","keyToCheck","addAllSubscriptionListeners","getNonEmptySubscriptionKeys","requestKey","getSubscriptionsCancelKey","catch","err","removeAllSubscriptionListeners","Promise","resolve","reject","initConnect","clearTimeout","connectTimeoutId","setTimeout","connectErrorHandler","EventSource","buildURL","onerror","lastEventId","retries","hasUnsentSubscriptions","p","reconnectTimeoutId","connectSubs","latestTopics","t","timeout","fromReconnect","onDisconnect","cancelRequest","close","CrudService","getFullList","batchOrqueryParams","_getFullList","batch","getList","page","perPage","baseCrudPath","responseData","items","item","getFirstListItem","filter","skipTotal","code","getOne","create","batchSize","request","list","concat","normalizeLegacyOptionsArgs","legacyWarn","baseOptions","bodyOrOptions","hasQuery","resetAutoRefresh","_resetAutoRefresh","RecordService","baseCollectionPath","isSuperusers","realtime","batchOrOptions","authStore","authExpand","expand","authRecord","delete","success","authResponse","listAuthMethods","fields","authWithPassword","usernameOrEmail","password","autoRefreshThreshold","identity","autoRefresh","authData","registerAutoRefresh","threshold","refreshFunc","reauthenticateFunc","oldBeforeSend","beforeSend","oldModel","unsubStoreChange","newToken","sendOptions","oldToken","authRefresh","authWithOAuth2Code","provider","codeVerifier","redirectURL","createData","authWithOAuth2","args","config","eagerDefaultPopup","urlCallback","openBrowserPopup","cleanup","requestKeyOptions","authMethods","oauth2","providers","find","cancelController","signal","onabort","activeSubscriptions","oldState","state","error","scopes","replacements","_replaceQueryParams","authURL","location","href","requestPasswordReset","confirmPasswordReset","passwordResetToken","passwordConfirm","requestVerification","confirmVerification","verificationToken","verified","requestEmailChange","newEmail","confirmEmailChange","emailChangeToken","listExternalAuths","recordId","unlinkExternalAuth","ea","requestOTP","authWithOTP","otpId","impersonate","Authorization","Client","baseURL","lang","urlPath","substring","parsedParams","rawParams","param","pair","hasOwnProperty","open","width","height","windowWidth","innerWidth","windowHeight","innerHeight","left","top","CollectionService","import","collections","deleteMissing","getScaffolds","truncate","LogService","getStats","HealthService","check","FileService","getUrl","filename","queryParams","getURL","parts","download","getToken","BackupService","basename","upload","restore","getDownloadUrl","getDownloadURL","CronService","run","jobId","isFile","File","uri","isFormData","FormData","hasFileField","values","inferNumberCharsRegex","inferFormDataValue","num","BatchService","requests","SubBatchService","formData","jsonData","req","json","files","file","append","upsert","prepareRequest","convertFormDataToObject","forEach","k","parsed","foundFiles","foundRegular","fileKey","endsWith","baseUrl","cancelControllers","recordServices","enableAutoCancellation","Deno","logs","settings","health","backups","crons","admins","createBatch","idOrName","autoCancellation","enable","abort","cancelAllRequests","raw","replaceAll","getFileUrl","buildUrl","origin","pathname","initSendOptions","getHeader","fetch","aborted","afterSend","convertToFormDataIfNeeded","form","$autoCancel","$cancelKey","controller","AbortController","AsyncAuthStore","queue","saveFunc","clearFunc","_enqueue","_loadInitial","initial","asyncCallback","_dequeue","finally","shift"],"mappings":"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"} \ No newline at end of file diff --git a/script/node_modules/pocketbase/dist/pocketbase.es.mjs b/script/node_modules/pocketbase/dist/pocketbase.es.mjs new file mode 100644 index 0000000..5f9e81f --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.es.mjs @@ -0,0 +1,2 @@ +class ClientResponseError extends Error{constructor(e){super("ClientResponseError"),this.url="",this.status=0,this.response={},this.isAbort=!1,this.originalError=null,Object.setPrototypeOf(this,ClientResponseError.prototype),null!==e&&"object"==typeof e&&(this.originalError=e.originalError,this.url="string"==typeof e.url?e.url:"",this.status="number"==typeof e.status?e.status:0,this.isAbort=!!e.isAbort||"AbortError"===e.name||"Aborted"===e.message,null!==e.response&&"object"==typeof e.response?this.response=e.response:null!==e.data&&"object"==typeof e.data?this.response=e.data:this.response={}),this.originalError||e instanceof ClientResponseError||(this.originalError=e),this.name="ClientResponseError "+this.status,this.message=this.response?.message,this.message||(this.isAbort?this.message="The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).":this.originalError?.cause?.message?.includes("ECONNREFUSED ::1")?this.message="Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).":this.message="Something went wrong."),this.cause=this.originalError}get data(){return this.response}toJSON(){return{...this}}}const e=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function cookieParse(e,t){const s={};if("string"!=typeof e)return s;const i=Object.assign({},t||{}).decode||defaultDecode;let n=0;for(;n0&&(!s.exp||s.exp-t>Date.now()/1e3))}s="function"!=typeof atob||t?e=>{let t=String(e).replace(/=+$/,"");if(t.length%4==1)throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");for(var s,i,n=0,r=0,o="";i=t.charAt(r++);~i&&(s=n%4?64*s+i:i,n++%4)?o+=String.fromCharCode(255&s>>(-2*n&6)):0)i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(i);return o}:atob;const i="pb_auth";class BaseAuthStore{constructor(){this.baseToken="",this.baseModel=null,this._onChangeCallbacks=[]}get token(){return this.baseToken}get record(){return this.baseModel}get model(){return this.baseModel}get isValid(){return!isTokenExpired(this.token)}get isSuperuser(){let e=getTokenPayload(this.token);return"auth"==e.type&&("_superusers"==this.record?.collectionName||!this.record?.collectionName&&"pbc_3142635823"==e.collectionId)}get isAdmin(){return console.warn("Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName"),this.isSuperuser}get isAuthRecord(){return console.warn("Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName"),"auth"==getTokenPayload(this.token).type&&!this.isSuperuser}save(e,t){this.baseToken=e||"",this.baseModel=t||null,this.triggerChange()}clear(){this.baseToken="",this.baseModel=null,this.triggerChange()}loadFromCookie(e,t=i){const s=cookieParse(e||"")[t]||"";let n={};try{n=JSON.parse(s),(null===typeof n||"object"!=typeof n||Array.isArray(n))&&(n={})}catch(e){}this.save(n.token||"",n.record||n.model||null)}exportToCookie(e,t=i){const s={secure:!0,sameSite:!0,httpOnly:!0,path:"/"},n=getTokenPayload(this.token);s.expires=n?.exp?new Date(1e3*n.exp):new Date("1970-01-01"),e=Object.assign({},s,e);const r={token:this.token,record:this.record?JSON.parse(JSON.stringify(this.record)):null};let o=cookieSerialize(t,JSON.stringify(r),e);const a="undefined"!=typeof Blob?new Blob([o]).size:o.length;if(r.record&&a>4096){r.record={id:r.record?.id,email:r.record?.email};const s=["collectionId","collectionName","verified"];for(const e in this.record)s.includes(e)&&(r.record[e]=this.record[e]);o=cookieSerialize(t,JSON.stringify(r),e)}return o}onChange(e,t=!1){return this._onChangeCallbacks.push(e),t&&e(this.token,this.record),()=>{for(let t=this._onChangeCallbacks.length-1;t>=0;t--)if(this._onChangeCallbacks[t]==e)return delete this._onChangeCallbacks[t],void this._onChangeCallbacks.splice(t,1)}}triggerChange(){for(const e of this._onChangeCallbacks)e&&e(this.token,this.record)}}class LocalAuthStore extends BaseAuthStore{constructor(e="pocketbase_auth"){super(),this.storageFallback={},this.storageKey=e,this._bindStorageEvent()}get token(){return(this._storageGet(this.storageKey)||{}).token||""}get record(){const e=this._storageGet(this.storageKey)||{};return e.record||e.model||null}get model(){return this.record}save(e,t){this._storageSet(this.storageKey,{token:e,record:t}),super.save(e,t)}clear(){this._storageRemove(this.storageKey),super.clear()}_storageGet(e){if("undefined"!=typeof window&&window?.localStorage){const t=window.localStorage.getItem(e)||"";try{return JSON.parse(t)}catch(e){return t}}return this.storageFallback[e]}_storageSet(e,t){if("undefined"!=typeof window&&window?.localStorage){let s=t;"string"!=typeof t&&(s=JSON.stringify(t)),window.localStorage.setItem(e,s)}else this.storageFallback[e]=t}_storageRemove(e){"undefined"!=typeof window&&window?.localStorage&&window.localStorage?.removeItem(e),delete this.storageFallback[e]}_bindStorageEvent(){"undefined"!=typeof window&&window?.localStorage&&window.addEventListener&&window.addEventListener("storage",(e=>{if(e.key!=this.storageKey)return;const t=this._storageGet(this.storageKey)||{};super.save(t.token||"",t.record||t.model||null)}))}}class BaseService{constructor(e){this.client=e}}class SettingsService extends BaseService{async getAll(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/settings",e)}async update(e,t){return t=Object.assign({method:"PATCH",body:e},t),this.client.send("/api/settings",t)}async testS3(e="storage",t){return t=Object.assign({method:"POST",body:{filesystem:e}},t),this.client.send("/api/settings/test/s3",t).then((()=>!0))}async testEmail(e,t,s,i){return i=Object.assign({method:"POST",body:{email:t,template:s,collection:e}},i),this.client.send("/api/settings/test/email",i).then((()=>!0))}async generateAppleClientSecret(e,t,s,i,n,r){return r=Object.assign({method:"POST",body:{clientId:e,teamId:t,keyId:s,privateKey:i,duration:n}},r),this.client.send("/api/settings/apple/generate-client-secret",r)}}const n=["requestKey","$cancelKey","$autoCancel","fetch","headers","body","query","params","cache","credentials","headers","integrity","keepalive","method","mode","redirect","referrer","referrerPolicy","signal","window"];function normalizeUnknownQueryParams(e){if(e){e.query=e.query||{};for(let t in e)n.includes(t)||(e.query[t]=e[t],delete e[t])}}function serializeQueryParams(e){const t=[];for(const s in e){const i=encodeURIComponent(s),n=Array.isArray(e[s])?e[s]:[e[s]];for(let e of n)e=prepareQueryParamValue(e),null!==e&&t.push(i+"="+e)}return t.join("&")}function prepareQueryParamValue(e){return null==e?null:e instanceof Date?encodeURIComponent(e.toISOString().replace("T"," ")):"object"==typeof e?encodeURIComponent(JSON.stringify(e)):encodeURIComponent(e)}class RealtimeService extends BaseService{constructor(){super(...arguments),this.clientId="",this.eventSource=null,this.subscriptions={},this.lastSentSubscriptions=[],this.maxConnectTimeout=15e3,this.reconnectAttempts=0,this.maxReconnectAttempts=1/0,this.predefinedReconnectIntervals=[200,300,500,1e3,1200,1500,2e3],this.pendingConnects=[]}get isConnected(){return!!this.eventSource&&!!this.clientId&&!this.pendingConnects.length}async subscribe(e,t,s){if(!e)throw new Error("topic must be set.");let i=e;if(s){normalizeUnknownQueryParams(s=Object.assign({},s));const e="options="+encodeURIComponent(JSON.stringify({query:s.query,headers:s.headers}));i+=(i.includes("?")?"&":"?")+e}const listener=function(e){const s=e;let i;try{i=JSON.parse(s?.data)}catch{}t(i||{})};return this.subscriptions[i]||(this.subscriptions[i]=[]),this.subscriptions[i].push(listener),this.isConnected?1===this.subscriptions[i].length?await this.submitSubscriptions():this.eventSource?.addEventListener(i,listener):await this.connect(),async()=>this.unsubscribeByTopicAndListener(e,listener)}async unsubscribe(e){let t=!1;if(e){const s=this.getSubscriptionsByTopic(e);for(let e in s)if(this.hasSubscriptionListeners(e)){for(let t of this.subscriptions[e])this.eventSource?.removeEventListener(e,t);delete this.subscriptions[e],t||(t=!0)}}else this.subscriptions={};this.hasSubscriptionListeners()?t&&await this.submitSubscriptions():this.disconnect()}async unsubscribeByPrefix(e){let t=!1;for(let s in this.subscriptions)if((s+"?").startsWith(e)){t=!0;for(let e of this.subscriptions[s])this.eventSource?.removeEventListener(s,e);delete this.subscriptions[s]}t&&(this.hasSubscriptionListeners()?await this.submitSubscriptions():this.disconnect())}async unsubscribeByTopicAndListener(e,t){let s=!1;const i=this.getSubscriptionsByTopic(e);for(let e in i){if(!Array.isArray(this.subscriptions[e])||!this.subscriptions[e].length)continue;let i=!1;for(let s=this.subscriptions[e].length-1;s>=0;s--)this.subscriptions[e][s]===t&&(i=!0,delete this.subscriptions[e][s],this.subscriptions[e].splice(s,1),this.eventSource?.removeEventListener(e,t));i&&(this.subscriptions[e].length||delete this.subscriptions[e],s||this.hasSubscriptionListeners(e)||(s=!0))}this.hasSubscriptionListeners()?s&&await this.submitSubscriptions():this.disconnect()}hasSubscriptionListeners(e){if(this.subscriptions=this.subscriptions||{},e)return!!this.subscriptions[e]?.length;for(let e in this.subscriptions)if(this.subscriptions[e]?.length)return!0;return!1}async submitSubscriptions(){if(this.clientId)return this.addAllSubscriptionListeners(),this.lastSentSubscriptions=this.getNonEmptySubscriptionKeys(),this.client.send("/api/realtime",{method:"POST",body:{clientId:this.clientId,subscriptions:this.lastSentSubscriptions},requestKey:this.getSubscriptionsCancelKey()}).catch((e=>{if(!e?.isAbort)throw e}))}getSubscriptionsCancelKey(){return"realtime_"+this.clientId}getSubscriptionsByTopic(e){const t={};e=e.includes("?")?e:e+"?";for(let s in this.subscriptions)(s+"?").startsWith(e)&&(t[s]=this.subscriptions[s]);return t}getNonEmptySubscriptionKeys(){const e=[];for(let t in this.subscriptions)this.subscriptions[t].length&&e.push(t);return e}addAllSubscriptionListeners(){if(this.eventSource){this.removeAllSubscriptionListeners();for(let e in this.subscriptions)for(let t of this.subscriptions[e])this.eventSource.addEventListener(e,t)}}removeAllSubscriptionListeners(){if(this.eventSource)for(let e in this.subscriptions)for(let t of this.subscriptions[e])this.eventSource.removeEventListener(e,t)}async connect(){if(!(this.reconnectAttempts>0))return new Promise(((e,t)=>{this.pendingConnects.push({resolve:e,reject:t}),this.pendingConnects.length>1||this.initConnect()}))}initConnect(){this.disconnect(!0),clearTimeout(this.connectTimeoutId),this.connectTimeoutId=setTimeout((()=>{this.connectErrorHandler(new Error("EventSource connect took too long."))}),this.maxConnectTimeout),this.eventSource=new EventSource(this.client.buildURL("/api/realtime")),this.eventSource.onerror=e=>{this.connectErrorHandler(new Error("Failed to establish realtime connection."))},this.eventSource.addEventListener("PB_CONNECT",(e=>{const t=e;this.clientId=t?.lastEventId,this.submitSubscriptions().then((async()=>{let e=3;for(;this.hasUnsentSubscriptions()&&e>0;)e--,await this.submitSubscriptions()})).then((()=>{for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[],this.reconnectAttempts=0,clearTimeout(this.reconnectTimeoutId),clearTimeout(this.connectTimeoutId);const t=this.getSubscriptionsByTopic("PB_CONNECT");for(let s in t)for(let i of t[s])i(e)})).catch((e=>{this.clientId="",this.connectErrorHandler(e)}))}))}hasUnsentSubscriptions(){const e=this.getNonEmptySubscriptionKeys();if(e.length!=this.lastSentSubscriptions.length)return!0;for(const t of e)if(!this.lastSentSubscriptions.includes(t))return!0;return!1}connectErrorHandler(e){if(clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),!this.clientId&&!this.reconnectAttempts||this.reconnectAttempts>this.maxReconnectAttempts){for(let t of this.pendingConnects)t.reject(new ClientResponseError(e));return this.pendingConnects=[],void this.disconnect()}this.disconnect(!0);const t=this.predefinedReconnectIntervals[this.reconnectAttempts]||this.predefinedReconnectIntervals[this.predefinedReconnectIntervals.length-1];this.reconnectAttempts++,this.reconnectTimeoutId=setTimeout((()=>{this.initConnect()}),t)}disconnect(e=!1){if(this.clientId&&this.onDisconnect&&this.onDisconnect(Object.keys(this.subscriptions)),clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),this.removeAllSubscriptionListeners(),this.client.cancelRequest(this.getSubscriptionsCancelKey()),this.eventSource?.close(),this.eventSource=null,this.clientId="",!e){this.reconnectAttempts=0;for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[]}}}class CrudService extends BaseService{decode(e){return e}async getFullList(e,t){if("number"==typeof e)return this._getFullList(e,t);let s=1e3;return(t=Object.assign({},e,t)).batch&&(s=t.batch,delete t.batch),this._getFullList(s,t)}async getList(e=1,t=30,s){return(s=Object.assign({method:"GET"},s)).query=Object.assign({page:e,perPage:t},s.query),this.client.send(this.baseCrudPath,s).then((e=>(e.items=e.items?.map((e=>this.decode(e)))||[],e)))}async getFirstListItem(e,t){return(t=Object.assign({requestKey:"one_by_filter_"+this.baseCrudPath+"_"+e},t)).query=Object.assign({filter:e,skipTotal:1},t.query),this.getList(1,1,t).then((e=>{if(!e?.items?.length)throw new ClientResponseError({status:404,response:{code:404,message:"The requested resource wasn't found.",data:{}}});return e.items[0]}))}async getOne(e,t){if(!e)throw new ClientResponseError({url:this.client.buildURL(this.baseCrudPath+"/"),status:404,response:{code:404,message:"Missing required record id.",data:{}}});return t=Object.assign({method:"GET"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),t).then((e=>this.decode(e)))}async create(e,t){return t=Object.assign({method:"POST",body:e},t),this.client.send(this.baseCrudPath,t).then((e=>this.decode(e)))}async update(e,t,s){return s=Object.assign({method:"PATCH",body:t},s),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),s).then((e=>this.decode(e)))}async delete(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),t).then((()=>!0))}_getFullList(e=1e3,t){(t=t||{}).query=Object.assign({skipTotal:1},t.query);let s=[],request=async i=>this.getList(i,e||1e3,t).then((e=>{const t=e.items;return s=s.concat(t),t.length==e.perPage?request(i+1):s}));return request(1)}}function normalizeLegacyOptionsArgs(e,t,s,i){const n=void 0!==i;return n||void 0!==s?n?(console.warn(e),t.body=Object.assign({},t.body,s),t.query=Object.assign({},t.query,i),t):Object.assign(t,s):t}function resetAutoRefresh(e){e._resetAutoRefresh?.()}class RecordService extends CrudService{constructor(e,t){super(e),this.collectionIdOrName=t}get baseCrudPath(){return this.baseCollectionPath+"/records"}get baseCollectionPath(){return"/api/collections/"+encodeURIComponent(this.collectionIdOrName)}get isSuperusers(){return"_superusers"==this.collectionIdOrName||"_pbc_2773867675"==this.collectionIdOrName}async subscribe(e,t,s){if(!e)throw new Error("Missing topic.");if(!t)throw new Error("Missing subscription callback.");return this.client.realtime.subscribe(this.collectionIdOrName+"/"+e,t,s)}async unsubscribe(e){return e?this.client.realtime.unsubscribe(this.collectionIdOrName+"/"+e):this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName)}async getFullList(e,t){if("number"==typeof e)return super.getFullList(e,t);const s=Object.assign({},e,t);return super.getFullList(s)}async getList(e=1,t=30,s){return super.getList(e,t,s)}async getFirstListItem(e,t){return super.getFirstListItem(e,t)}async getOne(e,t){return super.getOne(e,t)}async create(e,t){return super.create(e,t)}async update(e,t,s){return super.update(e,t,s).then((e=>{if(this.client.authStore.record?.id===e?.id&&(this.client.authStore.record?.collectionId===this.collectionIdOrName||this.client.authStore.record?.collectionName===this.collectionIdOrName)){let t=Object.assign({},this.client.authStore.record.expand),s=Object.assign({},this.client.authStore.record,e);t&&(s.expand=Object.assign(t,e.expand)),this.client.authStore.save(this.client.authStore.token,s)}return e}))}async delete(e,t){return super.delete(e,t).then((t=>(!t||this.client.authStore.record?.id!==e||this.client.authStore.record?.collectionId!==this.collectionIdOrName&&this.client.authStore.record?.collectionName!==this.collectionIdOrName||this.client.authStore.clear(),t)))}authResponse(e){const t=this.decode(e?.record||{});return this.client.authStore.save(e?.token,t),Object.assign({},e,{token:e?.token||"",record:t})}async listAuthMethods(e){return e=Object.assign({method:"GET",fields:"mfa,otp,password,oauth2"},e),this.client.send(this.baseCollectionPath+"/auth-methods",e)}async authWithPassword(e,t,s){let i;s=Object.assign({method:"POST",body:{identity:e,password:t}},s),this.isSuperusers&&(i=s.autoRefreshThreshold,delete s.autoRefreshThreshold,s.autoRefresh||resetAutoRefresh(this.client));let n=await this.client.send(this.baseCollectionPath+"/auth-with-password",s);return n=this.authResponse(n),i&&this.isSuperusers&&function registerAutoRefresh(e,t,s,i){resetAutoRefresh(e);const n=e.beforeSend,r=e.authStore.record,o=e.authStore.onChange(((t,s)=>{(!t||s?.id!=r?.id||(s?.collectionId||r?.collectionId)&&s?.collectionId!=r?.collectionId)&&resetAutoRefresh(e)}));e._resetAutoRefresh=function(){o(),e.beforeSend=n,delete e._resetAutoRefresh},e.beforeSend=async(r,o)=>{const a=e.authStore.token;if(o.query?.autoRefresh)return n?n(r,o):{url:r,sendOptions:o};let c=e.authStore.isValid;if(c&&isTokenExpired(e.authStore.token,t))try{await s()}catch(e){c=!1}c||await i();const l=o.headers||{};for(let t in l)if("authorization"==t.toLowerCase()&&a==l[t]&&e.authStore.token){l[t]=e.authStore.token;break}return o.headers=l,n?n(r,o):{url:r,sendOptions:o}}}(this.client,i,(()=>this.authRefresh({autoRefresh:!0})),(()=>this.authWithPassword(e,t,Object.assign({autoRefresh:!0},s)))),n}async authWithOAuth2Code(e,t,s,i,n,r,o){let a={method:"POST",body:{provider:e,code:t,codeVerifier:s,redirectURL:i,createData:n}};return a=normalizeLegacyOptionsArgs("This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).",a,r,o),this.client.send(this.baseCollectionPath+"/auth-with-oauth2",a).then((e=>this.authResponse(e)))}authWithOAuth2(...e){if(e.length>1||"string"==typeof e?.[0])return console.warn("PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration."),this.authWithOAuth2Code(e?.[0]||"",e?.[1]||"",e?.[2]||"",e?.[3]||"",e?.[4]||{},e?.[5]||{},e?.[6]||{});const t=e?.[0]||{};let s=null;t.urlCallback||(s=openBrowserPopup(void 0));const i=new RealtimeService(this.client);function cleanup(){s?.close(),i.unsubscribe()}const n={},r=t.requestKey;return r&&(n.requestKey=r),this.listAuthMethods(n).then((e=>{const n=e.oauth2.providers.find((e=>e.name===t.provider));if(!n)throw new ClientResponseError(new Error(`Missing or invalid provider "${t.provider}".`));const o=this.client.buildURL("/api/oauth2-redirect");return new Promise((async(e,a)=>{const c=r?this.client.cancelControllers?.[r]:void 0;c&&(c.signal.onabort=()=>{cleanup(),a(new ClientResponseError({isAbort:!0,message:"manually cancelled"}))}),i.onDisconnect=e=>{e.length&&a&&(cleanup(),a(new ClientResponseError(new Error("realtime connection interrupted"))))};try{await i.subscribe("@oauth2",(async s=>{const r=i.clientId;try{if(!s.state||r!==s.state)throw new Error("State parameters don't match.");if(s.error||!s.code)throw new Error("OAuth2 redirect error or missing code: "+s.error);const i=Object.assign({},t);delete i.provider,delete i.scopes,delete i.createData,delete i.urlCallback,c?.signal?.onabort&&(c.signal.onabort=null);const a=await this.authWithOAuth2Code(n.name,s.code,n.codeVerifier,o,t.createData,i);e(a)}catch(e){a(new ClientResponseError(e))}cleanup()}));const r={state:i.clientId};t.scopes?.length&&(r.scope=t.scopes.join(" "));const l=this._replaceQueryParams(n.authURL+o,r);let h=t.urlCallback||function(e){s?s.location.href=e:s=openBrowserPopup(e)};await h(l)}catch(e){c?.signal?.onabort&&(c.signal.onabort=null),cleanup(),a(new ClientResponseError(e))}}))})).catch((e=>{throw cleanup(),e}))}async authRefresh(e,t){let s={method:"POST"};return s=normalizeLegacyOptionsArgs("This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).",s,e,t),this.client.send(this.baseCollectionPath+"/auth-refresh",s).then((e=>this.authResponse(e)))}async requestPasswordReset(e,t,s){let i={method:"POST",body:{email:e}};return i=normalizeLegacyOptionsArgs("This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-password-reset",i).then((()=>!0))}async confirmPasswordReset(e,t,s,i,n){let r={method:"POST",body:{token:e,password:t,passwordConfirm:s}};return r=normalizeLegacyOptionsArgs("This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).",r,i,n),this.client.send(this.baseCollectionPath+"/confirm-password-reset",r).then((()=>!0))}async requestVerification(e,t,s){let i={method:"POST",body:{email:e}};return i=normalizeLegacyOptionsArgs("This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-verification",i).then((()=>!0))}async confirmVerification(e,t,s){let i={method:"POST",body:{token:e}};return i=normalizeLegacyOptionsArgs("This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/confirm-verification",i).then((()=>{const t=getTokenPayload(e),s=this.client.authStore.record;return s&&!s.verified&&s.id===t.id&&s.collectionId===t.collectionId&&(s.verified=!0,this.client.authStore.save(this.client.authStore.token,s)),!0}))}async requestEmailChange(e,t,s){let i={method:"POST",body:{newEmail:e}};return i=normalizeLegacyOptionsArgs("This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-email-change",i).then((()=>!0))}async confirmEmailChange(e,t,s,i){let n={method:"POST",body:{token:e,password:t}};return n=normalizeLegacyOptionsArgs("This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).",n,s,i),this.client.send(this.baseCollectionPath+"/confirm-email-change",n).then((()=>{const t=getTokenPayload(e),s=this.client.authStore.record;return s&&s.id===t.id&&s.collectionId===t.collectionId&&this.client.authStore.clear(),!0}))}async listExternalAuths(e,t){return this.client.collection("_externalAuths").getFullList(Object.assign({},t,{filter:this.client.filter("recordRef = {:id}",{id:e})}))}async unlinkExternalAuth(e,t,s){const i=await this.client.collection("_externalAuths").getFirstListItem(this.client.filter("recordRef = {:recordId} && provider = {:provider}",{recordId:e,provider:t}));return this.client.collection("_externalAuths").delete(i.id,s).then((()=>!0))}async requestOTP(e,t){return t=Object.assign({method:"POST",body:{email:e}},t),this.client.send(this.baseCollectionPath+"/request-otp",t)}async authWithOTP(e,t,s){return s=Object.assign({method:"POST",body:{otpId:e,password:t}},s),this.client.send(this.baseCollectionPath+"/auth-with-otp",s).then((e=>this.authResponse(e)))}async impersonate(e,t,s){(s=Object.assign({method:"POST",body:{duration:t}},s)).headers=s.headers||{},s.headers.Authorization||(s.headers.Authorization=this.client.authStore.token);const i=new Client(this.client.baseURL,new BaseAuthStore,this.client.lang),n=await i.send(this.baseCollectionPath+"/impersonate/"+encodeURIComponent(e),s);return i.authStore.save(n?.token,this.decode(n?.record||{})),i}_replaceQueryParams(e,t={}){let s=e,i="";e.indexOf("?")>=0&&(s=e.substring(0,e.indexOf("?")),i=e.substring(e.indexOf("?")+1));const n={},r=i.split("&");for(const e of r){if(""==e)continue;const t=e.split("=");n[decodeURIComponent(t[0].replace(/\+/g," "))]=decodeURIComponent((t[1]||"").replace(/\+/g," "))}for(let e in t)t.hasOwnProperty(e)&&(null==t[e]?delete n[e]:n[e]=t[e]);i="";for(let e in n)n.hasOwnProperty(e)&&(""!=i&&(i+="&"),i+=encodeURIComponent(e.replace(/%20/g,"+"))+"="+encodeURIComponent(n[e].replace(/%20/g,"+")));return""!=i?s+"?"+i:s}}function openBrowserPopup(e){if("undefined"==typeof window||!window?.open)throw new ClientResponseError(new Error("Not in a browser context - please pass a custom urlCallback function."));let t=1024,s=768,i=window.innerWidth,n=window.innerHeight;t=t>i?i:t,s=s>n?n:s;let r=i/2-t/2,o=n/2-s/2;return window.open(e,"popup_window","width="+t+",height="+s+",top="+o+",left="+r+",resizable,menubar=no")}class CollectionService extends CrudService{get baseCrudPath(){return"/api/collections"}async import(e,t=!1,s){return s=Object.assign({method:"PUT",body:{collections:e,deleteMissing:t}},s),this.client.send(this.baseCrudPath+"/import",s).then((()=>!0))}async getScaffolds(e){return e=Object.assign({method:"GET"},e),this.client.send(this.baseCrudPath+"/meta/scaffolds",e)}async truncate(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e)+"/truncate",t).then((()=>!0))}}class LogService extends BaseService{async getList(e=1,t=30,s){return(s=Object.assign({method:"GET"},s)).query=Object.assign({page:e,perPage:t},s.query),this.client.send("/api/logs",s)}async getOne(e,t){if(!e)throw new ClientResponseError({url:this.client.buildURL("/api/logs/"),status:404,response:{code:404,message:"Missing required log id.",data:{}}});return t=Object.assign({method:"GET"},t),this.client.send("/api/logs/"+encodeURIComponent(e),t)}async getStats(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/logs/stats",e)}}class HealthService extends BaseService{async check(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/health",e)}}class FileService extends BaseService{getUrl(e,t,s={}){return console.warn("Please replace pb.files.getUrl() with pb.files.getURL()"),this.getURL(e,t,s)}getURL(e,t,s={}){if(!t||!e?.id||!e?.collectionId&&!e?.collectionName)return"";const i=[];i.push("api"),i.push("files"),i.push(encodeURIComponent(e.collectionId||e.collectionName)),i.push(encodeURIComponent(e.id)),i.push(encodeURIComponent(t));let n=this.client.buildURL(i.join("/"));!1===s.download&&delete s.download;const r=serializeQueryParams(s);return r&&(n+=(n.includes("?")?"&":"?")+r),n}async getToken(e){return e=Object.assign({method:"POST"},e),this.client.send("/api/files/token",e).then((e=>e?.token||""))}}class BackupService extends BaseService{async getFullList(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/backups",e)}async create(e,t){return t=Object.assign({method:"POST",body:{name:e}},t),this.client.send("/api/backups",t).then((()=>!0))}async upload(e,t){return t=Object.assign({method:"POST",body:e},t),this.client.send("/api/backups/upload",t).then((()=>!0))}async delete(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(`/api/backups/${encodeURIComponent(e)}`,t).then((()=>!0))}async restore(e,t){return t=Object.assign({method:"POST"},t),this.client.send(`/api/backups/${encodeURIComponent(e)}/restore`,t).then((()=>!0))}getDownloadUrl(e,t){return console.warn("Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()"),this.getDownloadURL(e,t)}getDownloadURL(e,t){return this.client.buildURL(`/api/backups/${encodeURIComponent(t)}?token=${encodeURIComponent(e)}`)}}class CronService extends BaseService{async getFullList(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/crons",e)}async run(e,t){return t=Object.assign({method:"POST"},t),this.client.send(`/api/crons/${encodeURIComponent(e)}`,t).then((()=>!0))}}function isFile(e){return"undefined"!=typeof Blob&&e instanceof Blob||"undefined"!=typeof File&&e instanceof File||null!==e&&"object"==typeof e&&e.uri&&("undefined"!=typeof navigator&&"ReactNative"===navigator.product||"undefined"!=typeof global&&global.HermesInternal)}function isFormData(e){return e&&("FormData"===e.constructor?.name||"undefined"!=typeof FormData&&e instanceof FormData)}function hasFileField(e){for(const t in e){const s=Array.isArray(e[t])?e[t]:[e[t]];for(const e of s)if(isFile(e))return!0}return!1}const r=/^[\-\.\d]+$/;function inferFormDataValue(e){if("string"!=typeof e)return e;if("true"==e)return!0;if("false"==e)return!1;if(("-"===e[0]||e[0]>="0"&&e[0]<="9")&&r.test(e)){let t=+e;if(""+t===e)return t}return e}class BatchService extends BaseService{constructor(){super(...arguments),this.requests=[],this.subs={}}collection(e){return this.subs[e]||(this.subs[e]=new SubBatchService(this.requests,e)),this.subs[e]}async send(e){const t=new FormData,s=[];for(let e=0;e{if("@jsonPayload"===s&&"string"==typeof e)try{let s=JSON.parse(e);Object.assign(t,s)}catch(e){console.warn("@jsonPayload error:",e)}else void 0!==t[s]?(Array.isArray(t[s])||(t[s]=[t[s]]),t[s].push(inferFormDataValue(e))):t[s]=inferFormDataValue(e)})),t}(s));for(const t in s){const i=s[t];if(isFile(i))e.files[t]=e.files[t]||[],e.files[t].push(i);else if(Array.isArray(i)){const s=[],n=[];for(const e of i)isFile(e)?s.push(e):n.push(e);if(s.length>0&&s.length==i.length){e.files[t]=e.files[t]||[];for(let i of s)e.files[t].push(i)}else if(e.json[t]=n,s.length>0){let i=t;t.startsWith("+")||t.endsWith("+")||(i+="+"),e.files[i]=e.files[i]||[];for(let t of s)e.files[i].push(t)}}else e.json[t]=i}}}class Client{get baseUrl(){return this.baseURL}set baseUrl(e){this.baseURL=e}constructor(e="/",t,s="en-US"){this.cancelControllers={},this.recordServices={},this.enableAutoCancellation=!0,this.baseURL=e,this.lang=s,t?this.authStore=t:"undefined"!=typeof window&&window.Deno?this.authStore=new BaseAuthStore:this.authStore=new LocalAuthStore,this.collections=new CollectionService(this),this.files=new FileService(this),this.logs=new LogService(this),this.settings=new SettingsService(this),this.realtime=new RealtimeService(this),this.health=new HealthService(this),this.backups=new BackupService(this),this.crons=new CronService(this)}get admins(){return this.collection("_superusers")}createBatch(){return new BatchService(this)}collection(e){return this.recordServices[e]||(this.recordServices[e]=new RecordService(this,e)),this.recordServices[e]}autoCancellation(e){return this.enableAutoCancellation=!!e,this}cancelRequest(e){return this.cancelControllers[e]&&(this.cancelControllers[e].abort(),delete this.cancelControllers[e]),this}cancelAllRequests(){for(let e in this.cancelControllers)this.cancelControllers[e].abort();return this.cancelControllers={},this}filter(e,t){if(!t)return e;for(let s in t){let i=t[s];switch(typeof i){case"boolean":case"number":i=""+i;break;case"string":i="'"+i.replace(/'/g,"\\'")+"'";break;default:i=null===i?"null":i instanceof Date?"'"+i.toISOString().replace("T"," ")+"'":"'"+JSON.stringify(i).replace(/'/g,"\\'")+"'"}e=e.replaceAll("{:"+s+"}",i)}return e}getFileUrl(e,t,s={}){return console.warn("Please replace pb.getFileUrl() with pb.files.getURL()"),this.files.getURL(e,t,s)}buildUrl(e){return console.warn("Please replace pb.buildUrl() with pb.buildURL()"),this.buildURL(e)}buildURL(e){let t=this.baseURL;return"undefined"==typeof window||!window.location||t.startsWith("https://")||t.startsWith("http://")||(t=window.location.origin?.endsWith("/")?window.location.origin.substring(0,window.location.origin.length-1):window.location.origin||"",this.baseURL.startsWith("/")||(t+=window.location.pathname||"/",t+=t.endsWith("/")?"":"/"),t+=this.baseURL),e&&(t+=t.endsWith("/")?"":"/",t+=e.startsWith("/")?e.substring(1):e),t}async send(e,t){t=this.initSendOptions(e,t);let s=this.buildURL(e);if(this.beforeSend){const e=Object.assign({},await this.beforeSend(s,t));void 0!==e.url||void 0!==e.options?(s=e.url||s,t=e.options||t):Object.keys(e).length&&(t=e,console?.warn&&console.warn("Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`."))}if(void 0!==t.query){const e=serializeQueryParams(t.query);e&&(s+=(s.includes("?")?"&":"?")+e),delete t.query}"application/json"==this.getHeader(t.headers,"Content-Type")&&t.body&&"string"!=typeof t.body&&(t.body=JSON.stringify(t.body));return(t.fetch||fetch)(s,t).then((async e=>{let s={};try{s=await e.json()}catch(e){if(t.signal?.aborted||"AbortError"==e?.name||"Aborted"==e?.message)throw e}if(this.afterSend&&(s=await this.afterSend(e,s,t)),e.status>=400)throw new ClientResponseError({url:e.url,status:e.status,data:s});return s})).catch((e=>{throw new ClientResponseError(e)}))}initSendOptions(e,t){if((t=Object.assign({method:"GET"},t)).body=function convertToFormDataIfNeeded(e){if("undefined"==typeof FormData||void 0===e||"object"!=typeof e||null===e||isFormData(e)||!hasFileField(e))return e;const t=new FormData;for(const s in e){const i=e[s];if(void 0!==i)if("object"!=typeof i||hasFileField({data:i})){const e=Array.isArray(i)?i:[i];for(let i of e)t.append(s,i)}else{let e={};e[s]=i,t.append("@jsonPayload",JSON.stringify(e))}}return t}(t.body),normalizeUnknownQueryParams(t),t.query=Object.assign({},t.params,t.query),void 0===t.requestKey&&(!1===t.$autoCancel||!1===t.query.$autoCancel?t.requestKey=null:(t.$cancelKey||t.query.$cancelKey)&&(t.requestKey=t.$cancelKey||t.query.$cancelKey)),delete t.$autoCancel,delete t.query.$autoCancel,delete t.$cancelKey,delete t.query.$cancelKey,null!==this.getHeader(t.headers,"Content-Type")||isFormData(t.body)||(t.headers=Object.assign({},t.headers,{"Content-Type":"application/json"})),null===this.getHeader(t.headers,"Accept-Language")&&(t.headers=Object.assign({},t.headers,{"Accept-Language":this.lang})),this.authStore.token&&null===this.getHeader(t.headers,"Authorization")&&(t.headers=Object.assign({},t.headers,{Authorization:this.authStore.token})),this.enableAutoCancellation&&null!==t.requestKey){const s=t.requestKey||(t.method||"GET")+e;delete t.requestKey,this.cancelRequest(s);const i=new AbortController;this.cancelControllers[s]=i,t.signal=i.signal}return t}getHeader(e,t){e=e||{},t=t.toLowerCase();for(let s in e)if(s.toLowerCase()==t)return e[s];return null}}class AsyncAuthStore extends BaseAuthStore{constructor(e){super(),this.queue=[],this.saveFunc=e.save,this.clearFunc=e.clear,this._enqueue((()=>this._loadInitial(e.initial)))}save(e,t){super.save(e,t);let s="";try{s=JSON.stringify({token:e,record:t})}catch(e){console.warn("AsyncAuthStore: failed to stringify the new state")}this._enqueue((()=>this.saveFunc(s)))}clear(){super.clear(),this.clearFunc?this._enqueue((()=>this.clearFunc())):this._enqueue((()=>this.saveFunc("")))}async _loadInitial(e){try{if(e=await e){let t;"string"==typeof e?t=JSON.parse(e)||{}:"object"==typeof e&&(t=e),this.save(t.token||"",t.record||t.model||null)}}catch(e){}}_enqueue(e){this.queue.push(e),1==this.queue.length&&this._dequeue()}_dequeue(){this.queue.length&&this.queue[0]().finally((()=>{this.queue.shift(),this.queue.length&&this._dequeue()}))}}export{AsyncAuthStore,BaseAuthStore,BatchService,ClientResponseError,CollectionService,CrudService,HealthService,LocalAuthStore,LogService,RealtimeService,RecordService,SubBatchService,cookieParse,cookieSerialize,Client as default,getTokenPayload,isTokenExpired,normalizeUnknownQueryParams,serializeQueryParams}; +//# sourceMappingURL=pocketbase.es.mjs.map diff --git a/script/node_modules/pocketbase/dist/pocketbase.es.mjs.map b/script/node_modules/pocketbase/dist/pocketbase.es.mjs.map new file mode 100644 index 0000000..9cbe524 --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.es.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"pocketbase.es.mjs","sources":["../src/ClientResponseError.ts","../src/tools/cookie.ts","../src/tools/jwt.ts","../src/stores/BaseAuthStore.ts","../src/stores/LocalAuthStore.ts","../src/services/BaseService.ts","../src/services/SettingsService.ts","../src/tools/options.ts","../src/services/RealtimeService.ts","../src/services/CrudService.ts","../src/tools/legacy.ts","../src/tools/refresh.ts","../src/services/RecordService.ts","../src/services/CollectionService.ts","../src/services/LogService.ts","../src/services/HealthService.ts","../src/services/FileService.ts","../src/services/BackupService.ts","../src/services/CronService.ts","../src/tools/formdata.ts","../src/services/BatchService.ts","../src/Client.ts","../src/stores/AsyncAuthStore.ts"],"sourcesContent":["/**\n * ClientResponseError is a custom Error class that is intended to wrap\n * and normalize any error thrown by `Client.send()`.\n */\nexport class ClientResponseError extends Error {\n url: string = \"\";\n status: number = 0;\n response: { [key: string]: any } = {};\n isAbort: boolean = false;\n originalError: any = null;\n\n constructor(errData?: any) {\n super(\"ClientResponseError\");\n\n // Set the prototype explicitly.\n // https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, ClientResponseError.prototype);\n\n if (errData !== null && typeof errData === \"object\") {\n this.originalError = errData.originalError;\n this.url = typeof errData.url === \"string\" ? errData.url : \"\";\n this.status = typeof errData.status === \"number\" ? errData.status : 0;\n\n // note: DOMException is not implemented yet in React Native\n // and aborting a fetch throws a plain Error with message \"Aborted\".\n this.isAbort =\n !!errData.isAbort ||\n errData.name === \"AbortError\" ||\n errData.message === \"Aborted\";\n\n if (errData.response !== null && typeof errData.response === \"object\") {\n this.response = errData.response;\n } else if (errData.data !== null && typeof errData.data === \"object\") {\n this.response = errData.data;\n } else {\n this.response = {};\n }\n }\n\n if (!this.originalError && !(errData instanceof ClientResponseError)) {\n this.originalError = errData;\n }\n\n this.name = \"ClientResponseError \" + this.status;\n this.message = this.response?.message;\n if (!this.message) {\n if (this.isAbort) {\n this.message =\n \"The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).\";\n } else if (this.originalError?.cause?.message?.includes(\"ECONNREFUSED ::1\")) {\n this.message =\n \"Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).\";\n } else {\n this.message = \"Something went wrong.\";\n }\n }\n\n // set this.cause so that JS debugging tools can automatically connect\n // the dots between the original error and the wrapped one\n this.cause = this.originalError;\n }\n\n /**\n * Alias for `this.response` for backward compatibility.\n */\n get data() {\n return this.response;\n }\n\n /**\n * Make a POJO's copy of the current error class instance.\n * @see https://github.com/vuex-orm/vuex-orm/issues/255\n */\n toJSON() {\n return { ...this };\n }\n}\n","/**\n * -------------------------------------------------------------------\n * Simple cookie parse and serialize utilities mostly based on the\n * node module https://github.com/jshttp/cookie.\n * -------------------------------------------------------------------\n */\n\n/**\n * RegExp to match field-content in RFC 7230 sec 3.2\n *\n * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]\n * field-vchar = VCHAR / obs-text\n * obs-text = %x80-FF\n */\nconst fieldContentRegExp = /^[\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+$/;\n\nexport interface ParseOptions {\n decode?: (val: string) => string;\n}\n\n/**\n * Parses the given cookie header string into an object\n * The object has the various cookies as keys(names) => values\n */\nexport function cookieParse(str: string, options?: ParseOptions): { [key: string]: any } {\n const result: { [key: string]: any } = {};\n\n if (typeof str !== \"string\") {\n return result;\n }\n\n const opt = Object.assign({}, options || {});\n const decode = opt.decode || defaultDecode;\n\n let index = 0;\n while (index < str.length) {\n const eqIdx = str.indexOf(\"=\", index);\n\n // no more cookie pairs\n if (eqIdx === -1) {\n break;\n }\n\n let endIdx = str.indexOf(\";\", index);\n\n if (endIdx === -1) {\n endIdx = str.length;\n } else if (endIdx < eqIdx) {\n // backtrack on prior semicolon\n index = str.lastIndexOf(\";\", eqIdx - 1) + 1;\n continue;\n }\n\n const key = str.slice(index, eqIdx).trim();\n\n // only assign once\n if (undefined === result[key]) {\n let val = str.slice(eqIdx + 1, endIdx).trim();\n\n // quoted values\n if (val.charCodeAt(0) === 0x22) {\n val = val.slice(1, -1);\n }\n\n try {\n result[key] = decode(val);\n } catch (_) {\n result[key] = val; // no decoding\n }\n }\n\n index = endIdx + 1;\n }\n\n return result;\n}\n\nexport interface SerializeOptions {\n encode?: (val: string | number | boolean) => string;\n maxAge?: number;\n domain?: string;\n path?: string;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n priority?: string;\n sameSite?: boolean | string;\n}\n\n/**\n * Serialize data into a cookie header.\n *\n * Serialize the a name value pair into a cookie string suitable for\n * http headers. An optional options object specified cookie parameters.\n *\n * ```js\n * cookieSerialize('foo', 'bar', { httpOnly: true }) // \"foo=bar; httpOnly\"\n * ```\n */\nexport function cookieSerialize(\n name: string,\n val: string,\n options?: SerializeOptions,\n): string {\n const opt = Object.assign({}, options || {});\n const encode = opt.encode || defaultEncode;\n\n if (!fieldContentRegExp.test(name)) {\n throw new TypeError(\"argument name is invalid\");\n }\n\n const value = encode(val);\n\n if (value && !fieldContentRegExp.test(value)) {\n throw new TypeError(\"argument val is invalid\");\n }\n\n let result = name + \"=\" + value;\n\n if (opt.maxAge != null) {\n const maxAge = opt.maxAge - 0;\n\n if (isNaN(maxAge) || !isFinite(maxAge)) {\n throw new TypeError(\"option maxAge is invalid\");\n }\n\n result += \"; Max-Age=\" + Math.floor(maxAge);\n }\n\n if (opt.domain) {\n if (!fieldContentRegExp.test(opt.domain)) {\n throw new TypeError(\"option domain is invalid\");\n }\n\n result += \"; Domain=\" + opt.domain;\n }\n\n if (opt.path) {\n if (!fieldContentRegExp.test(opt.path)) {\n throw new TypeError(\"option path is invalid\");\n }\n\n result += \"; Path=\" + opt.path;\n }\n\n if (opt.expires) {\n if (!isDate(opt.expires) || isNaN(opt.expires.valueOf())) {\n throw new TypeError(\"option expires is invalid\");\n }\n\n result += \"; Expires=\" + opt.expires.toUTCString();\n }\n\n if (opt.httpOnly) {\n result += \"; HttpOnly\";\n }\n\n if (opt.secure) {\n result += \"; Secure\";\n }\n\n if (opt.priority) {\n const priority =\n typeof opt.priority === \"string\" ? opt.priority.toLowerCase() : opt.priority;\n\n switch (priority) {\n case \"low\":\n result += \"; Priority=Low\";\n break;\n case \"medium\":\n result += \"; Priority=Medium\";\n break;\n case \"high\":\n result += \"; Priority=High\";\n break;\n default:\n throw new TypeError(\"option priority is invalid\");\n }\n }\n\n if (opt.sameSite) {\n const sameSite =\n typeof opt.sameSite === \"string\" ? opt.sameSite.toLowerCase() : opt.sameSite;\n\n switch (sameSite) {\n case true:\n result += \"; SameSite=Strict\";\n break;\n case \"lax\":\n result += \"; SameSite=Lax\";\n break;\n case \"strict\":\n result += \"; SameSite=Strict\";\n break;\n case \"none\":\n result += \"; SameSite=None\";\n break;\n default:\n throw new TypeError(\"option sameSite is invalid\");\n }\n }\n\n return result;\n}\n\n/**\n * Default URL-decode string value function.\n * Optimized to skip native call when no `%`.\n */\nfunction defaultDecode(val: string): string {\n return val.indexOf(\"%\") !== -1 ? decodeURIComponent(val) : val;\n}\n\n/**\n * Default URL-encode value function.\n */\nfunction defaultEncode(val: string | number | boolean): string {\n return encodeURIComponent(val);\n}\n\n/**\n * Determines if value is a Date.\n */\nfunction isDate(val: any): boolean {\n return Object.prototype.toString.call(val) === \"[object Date]\" || val instanceof Date;\n}\n","// @todo remove after https://github.com/reactwg/react-native-releases/issues/287\nconst isReactNative =\n (typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\") ||\n (typeof global !== \"undefined\" && (global as any).HermesInternal);\n\nlet atobPolyfill: Function;\nif (typeof atob === \"function\" && !isReactNative) {\n atobPolyfill = atob;\n} else {\n /**\n * The code was extracted from:\n * https://github.com/davidchambers/Base64.js\n */\n atobPolyfill = (input: any) => {\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\";\n\n let str = String(input).replace(/=+$/, \"\");\n if (str.length % 4 == 1) {\n throw new Error(\n \"'atob' failed: The string to be decoded is not correctly encoded.\",\n );\n }\n\n for (\n // initialize result and counters\n var bc = 0, bs, buffer, idx = 0, output = \"\";\n // get next character\n (buffer = str.charAt(idx++));\n // character found in table? initialize bit storage and add its ascii value;\n ~buffer &&\n ((bs = bc % 4 ? (bs as any) * 64 + buffer : buffer),\n // and if not first of each 4 characters,\n // convert the first 8 bits to one ascii character\n bc++ % 4)\n ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))\n : 0\n ) {\n // try to find character in table (0-63, not found => -1)\n buffer = chars.indexOf(buffer);\n }\n\n return output;\n };\n}\n\n/**\n * Returns JWT token's payload data.\n */\nexport function getTokenPayload(token: string): { [key: string]: any } {\n if (token) {\n try {\n const encodedPayload = decodeURIComponent(\n atobPolyfill(token.split(\".\")[1])\n .split(\"\")\n .map(function (c: string) {\n return \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2);\n })\n .join(\"\"),\n );\n\n return JSON.parse(encodedPayload) || {};\n } catch (e) {}\n }\n\n return {};\n}\n\n/**\n * Checks whether a JWT token is expired or not.\n * Tokens without `exp` payload key are considered valid.\n * Tokens with empty payload (eg. invalid token strings) are considered expired.\n *\n * @param token The token to check.\n * @param [expirationThreshold] Time in seconds that will be subtracted from the token `exp` property.\n */\nexport function isTokenExpired(token: string, expirationThreshold = 0): boolean {\n let payload = getTokenPayload(token);\n\n if (\n Object.keys(payload).length > 0 &&\n (!payload.exp || payload.exp - expirationThreshold > Date.now() / 1000)\n ) {\n return false;\n }\n\n return true;\n}\n","import { cookieParse, cookieSerialize, SerializeOptions } from \"@/tools/cookie\";\nimport { isTokenExpired, getTokenPayload } from \"@/tools/jwt\";\nimport { RecordModel } from \"@/tools/dtos\";\n\nexport type AuthRecord = RecordModel | null;\n\nexport type AuthModel = AuthRecord; // for backward compatibility\n\nexport type OnStoreChangeFunc = (token: string, record: AuthRecord) => void;\n\nconst defaultCookieKey = \"pb_auth\";\n\n/**\n * Base AuthStore class that stores the auth state in runtime memory (aka. only for the duration of the store instane).\n *\n * Usually you wouldn't use it directly and instead use the builtin LocalAuthStore, AsyncAuthStore\n * or extend it with your own custom implementation.\n */\nexport class BaseAuthStore {\n protected baseToken: string = \"\";\n protected baseModel: AuthRecord = null;\n\n private _onChangeCallbacks: Array = [];\n\n /**\n * Retrieves the stored token (if any).\n */\n get token(): string {\n return this.baseToken;\n }\n\n /**\n * Retrieves the stored model data (if any).\n */\n get record(): AuthRecord {\n return this.baseModel;\n }\n\n /**\n * @deprecated use `record` instead.\n */\n get model(): AuthRecord {\n return this.baseModel;\n }\n\n /**\n * Loosely checks if the store has valid token (aka. existing and unexpired exp claim).\n */\n get isValid(): boolean {\n return !isTokenExpired(this.token);\n }\n\n /**\n * Loosely checks whether the currently loaded store state is for superuser.\n *\n * Alternatively you can also compare directly `pb.authStore.record?.collectionName`.\n */\n get isSuperuser(): boolean {\n let payload = getTokenPayload(this.token);\n\n return (\n payload.type == \"auth\" &&\n (this.record?.collectionName == \"_superusers\" ||\n // fallback in case the record field is not populated and assuming\n // that the collection crc32 checksum id wasn't manually changed\n (!this.record?.collectionName &&\n payload.collectionId == \"pbc_3142635823\"))\n );\n }\n\n /**\n * @deprecated use `isSuperuser` instead or simply check the record.collectionName property.\n */\n get isAdmin(): boolean {\n console.warn(\n \"Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName\",\n );\n return this.isSuperuser;\n }\n\n /**\n * @deprecated use `!isSuperuser` instead or simply check the record.collectionName property.\n */\n get isAuthRecord(): boolean {\n console.warn(\n \"Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName\",\n );\n return getTokenPayload(this.token).type == \"auth\" && !this.isSuperuser;\n }\n\n /**\n * Saves the provided new token and model data in the auth store.\n */\n save(token: string, record?: AuthRecord): void {\n this.baseToken = token || \"\";\n this.baseModel = record || null;\n\n this.triggerChange();\n }\n\n /**\n * Removes the stored token and model data form the auth store.\n */\n clear(): void {\n this.baseToken = \"\";\n this.baseModel = null;\n this.triggerChange();\n }\n\n /**\n * Parses the provided cookie string and updates the store state\n * with the cookie's token and model data.\n *\n * NB! This function doesn't validate the token or its data.\n * Usually this isn't a concern if you are interacting only with the\n * PocketBase API because it has the proper server-side security checks in place,\n * but if you are using the store `isValid` state for permission controls\n * in a node server (eg. SSR), then it is recommended to call `authRefresh()`\n * after loading the cookie to ensure an up-to-date token and model state.\n * For example:\n *\n * ```js\n * pb.authStore.loadFromCookie(\"cookie string...\");\n *\n * try {\n * // get an up-to-date auth store state by veryfing and refreshing the loaded auth model (if any)\n * pb.authStore.isValid && await pb.collection('users').authRefresh();\n * } catch (_) {\n * // clear the auth store on failed refresh\n * pb.authStore.clear();\n * }\n * ```\n */\n loadFromCookie(cookie: string, key = defaultCookieKey): void {\n const rawData = cookieParse(cookie || \"\")[key] || \"\";\n\n let data: { [key: string]: any } = {};\n try {\n data = JSON.parse(rawData);\n // normalize\n if (typeof data === null || typeof data !== \"object\" || Array.isArray(data)) {\n data = {};\n }\n } catch (_) {}\n\n this.save(data.token || \"\", data.record || data.model || null);\n }\n\n /**\n * Exports the current store state as cookie string.\n *\n * By default the following optional attributes are added:\n * - Secure\n * - HttpOnly\n * - SameSite=Strict\n * - Path=/\n * - Expires={the token expiration date}\n *\n * NB! If the generated cookie exceeds 4096 bytes, this method will\n * strip the model data to the bare minimum to try to fit within the\n * recommended size in https://www.rfc-editor.org/rfc/rfc6265#section-6.1.\n */\n exportToCookie(options?: SerializeOptions, key = defaultCookieKey): string {\n const defaultOptions: SerializeOptions = {\n secure: true,\n sameSite: true,\n httpOnly: true,\n path: \"/\",\n };\n\n // extract the token expiration date\n const payload = getTokenPayload(this.token);\n if (payload?.exp) {\n defaultOptions.expires = new Date(payload.exp * 1000);\n } else {\n defaultOptions.expires = new Date(\"1970-01-01\");\n }\n\n // merge with the user defined options\n options = Object.assign({}, defaultOptions, options);\n\n const rawData = {\n token: this.token,\n record: this.record ? JSON.parse(JSON.stringify(this.record)) : null,\n };\n\n let result = cookieSerialize(key, JSON.stringify(rawData), options);\n\n const resultLength =\n typeof Blob !== \"undefined\" ? new Blob([result]).size : result.length;\n\n // strip down the model data to the bare minimum\n if (rawData.record && resultLength > 4096) {\n rawData.record = { id: rawData.record?.id, email: rawData.record?.email };\n const extraProps = [\"collectionId\", \"collectionName\", \"verified\"];\n for (const prop in this.record) {\n if (extraProps.includes(prop)) {\n rawData.record[prop] = this.record[prop];\n }\n }\n result = cookieSerialize(key, JSON.stringify(rawData), options);\n }\n\n return result;\n }\n\n /**\n * Register a callback function that will be called on store change.\n *\n * You can set the `fireImmediately` argument to true in order to invoke\n * the provided callback right after registration.\n *\n * Returns a removal function that you could call to \"unsubscribe\" from the changes.\n */\n onChange(callback: OnStoreChangeFunc, fireImmediately = false): () => void {\n this._onChangeCallbacks.push(callback);\n\n if (fireImmediately) {\n callback(this.token, this.record);\n }\n\n return () => {\n for (let i = this._onChangeCallbacks.length - 1; i >= 0; i--) {\n if (this._onChangeCallbacks[i] == callback) {\n delete this._onChangeCallbacks[i]; // removes the function reference\n this._onChangeCallbacks.splice(i, 1); // reindex the array\n return;\n }\n }\n };\n }\n\n protected triggerChange(): void {\n for (const callback of this._onChangeCallbacks) {\n callback && callback(this.token, this.record);\n }\n }\n}\n","import { BaseAuthStore, AuthRecord } from \"@/stores/BaseAuthStore\";\n\n/**\n * The default token store for browsers with auto fallback\n * to runtime/memory if local storage is undefined (e.g. in node env).\n */\nexport class LocalAuthStore extends BaseAuthStore {\n private storageFallback: { [key: string]: any } = {};\n private storageKey: string;\n\n constructor(storageKey = \"pocketbase_auth\") {\n super();\n\n this.storageKey = storageKey;\n\n this._bindStorageEvent();\n }\n\n /**\n * @inheritdoc\n */\n get token(): string {\n const data = this._storageGet(this.storageKey) || {};\n\n return data.token || \"\";\n }\n\n /**\n * @inheritdoc\n */\n get record(): AuthRecord {\n const data = this._storageGet(this.storageKey) || {};\n\n return data.record || data.model || null;\n }\n\n /**\n * @deprecated use `record` instead.\n */\n get model(): AuthRecord {\n return this.record;\n }\n\n /**\n * @inheritdoc\n */\n save(token: string, record?: AuthRecord) {\n this._storageSet(this.storageKey, {\n token: token,\n record: record,\n });\n\n super.save(token, record);\n }\n\n /**\n * @inheritdoc\n */\n clear() {\n this._storageRemove(this.storageKey);\n\n super.clear();\n }\n\n // ---------------------------------------------------------------\n // Internal helpers:\n // ---------------------------------------------------------------\n\n /**\n * Retrieves `key` from the browser's local storage\n * (or runtime/memory if local storage is undefined).\n */\n private _storageGet(key: string): any {\n if (typeof window !== \"undefined\" && window?.localStorage) {\n const rawValue = window.localStorage.getItem(key) || \"\";\n try {\n return JSON.parse(rawValue);\n } catch (e) {\n // not a json\n return rawValue;\n }\n }\n\n // fallback\n return this.storageFallback[key];\n }\n\n /**\n * Stores a new data in the browser's local storage\n * (or runtime/memory if local storage is undefined).\n */\n private _storageSet(key: string, value: any) {\n if (typeof window !== \"undefined\" && window?.localStorage) {\n // store in local storage\n let normalizedVal = value;\n if (typeof value !== \"string\") {\n normalizedVal = JSON.stringify(value);\n }\n window.localStorage.setItem(key, normalizedVal);\n } else {\n // store in fallback\n this.storageFallback[key] = value;\n }\n }\n\n /**\n * Removes `key` from the browser's local storage and the runtime/memory.\n */\n private _storageRemove(key: string) {\n // delete from local storage\n if (typeof window !== \"undefined\" && window?.localStorage) {\n window.localStorage?.removeItem(key);\n }\n\n // delete from fallback\n delete this.storageFallback[key];\n }\n\n /**\n * Updates the current store state on localStorage change.\n */\n private _bindStorageEvent() {\n if (\n typeof window === \"undefined\" ||\n !window?.localStorage ||\n !window.addEventListener\n ) {\n return;\n }\n\n window.addEventListener(\"storage\", (e) => {\n if (e.key != this.storageKey) {\n return;\n }\n\n const data = this._storageGet(this.storageKey) || {};\n\n super.save(data.token || \"\", data.record || data.model || null);\n });\n }\n}\n","import Client from \"@/Client\";\n\n/**\n * BaseService class that should be inherited from all API services.\n */\nexport abstract class BaseService {\n readonly client: Client;\n\n constructor(client: Client) {\n this.client = client;\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\ninterface appleClientSecret {\n secret: string;\n}\n\nexport class SettingsService extends BaseService {\n /**\n * Fetch all available app settings.\n *\n * @throws {ClientResponseError}\n */\n async getAll(options?: CommonOptions): Promise<{ [key: string]: any }> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/settings\", options);\n }\n\n /**\n * Bulk updates app settings.\n *\n * @throws {ClientResponseError}\n */\n async update(\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise<{ [key: string]: any }> {\n options = Object.assign(\n {\n method: \"PATCH\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client.send(\"/api/settings\", options);\n }\n\n /**\n * Performs a S3 filesystem connection test.\n *\n * The currently supported `filesystem` are \"storage\" and \"backups\".\n *\n * @throws {ClientResponseError}\n */\n async testS3(\n filesystem: string = \"storage\",\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n filesystem: filesystem,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/test/s3\", options).then(() => true);\n }\n\n /**\n * Sends a test email.\n *\n * The possible `emailTemplate` values are:\n * - verification\n * - password-reset\n * - email-change\n *\n * @throws {ClientResponseError}\n */\n async testEmail(\n collectionIdOrName: string,\n toEmail: string,\n emailTemplate: string,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n email: toEmail,\n template: emailTemplate,\n collection: collectionIdOrName,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/test/email\", options).then(() => true);\n }\n\n /**\n * Generates a new Apple OAuth2 client secret.\n *\n * @throws {ClientResponseError}\n */\n async generateAppleClientSecret(\n clientId: string,\n teamId: string,\n keyId: string,\n privateKey: string,\n duration: number,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n clientId,\n teamId,\n keyId,\n privateKey,\n duration,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/apple/generate-client-secret\", options);\n }\n}\n","export interface SendOptions extends RequestInit {\n // for backward compatibility and to minimize the verbosity,\n // any top-level field that doesn't exist in RequestInit or the\n // fields below will be treated as query parameter.\n [key: string]: any;\n\n /**\n * Optional custom fetch function to use for sending the request.\n */\n fetch?: (url: RequestInfo | URL, config?: RequestInit) => Promise;\n\n /**\n * Custom headers to send with the requests.\n */\n headers?: { [key: string]: string };\n\n /**\n * The body of the request (serialized automatically for json requests).\n */\n body?: any;\n\n /**\n * Query parameters that will be appended to the request url.\n */\n query?: { [key: string]: any };\n\n /**\n * @deprecated use `query` instead\n *\n * for backward-compatibility `params` values are merged with `query`,\n * but this option may get removed in the final v1 release\n */\n params?: { [key: string]: any };\n\n /**\n * The request identifier that can be used to cancel pending requests.\n */\n requestKey?: string | null;\n\n /**\n * @deprecated use `requestKey:string` instead\n */\n $cancelKey?: string;\n\n /**\n * @deprecated use `requestKey:null` instead\n */\n $autoCancel?: boolean;\n}\n\nexport interface CommonOptions extends SendOptions {\n fields?: string;\n}\n\nexport interface ListOptions extends CommonOptions {\n page?: number;\n perPage?: number;\n sort?: string;\n filter?: string;\n skipTotal?: boolean;\n}\n\nexport interface FullListOptions extends ListOptions {\n batch?: number;\n}\n\nexport interface RecordOptions extends CommonOptions {\n expand?: string;\n}\n\nexport interface RecordListOptions extends ListOptions, RecordOptions {}\n\nexport interface RecordFullListOptions extends FullListOptions, RecordOptions {}\n\nexport interface RecordSubscribeOptions extends SendOptions {\n fields?: string;\n filter?: string;\n expand?: string;\n}\n\nexport interface LogStatsOptions extends CommonOptions {\n filter?: string;\n}\n\nexport interface FileOptions extends CommonOptions {\n thumb?: string;\n download?: boolean;\n}\n\nexport interface AuthOptions extends CommonOptions {\n /**\n * If autoRefreshThreshold is set it will take care to auto refresh\n * when necessary the auth data before each request to ensure that\n * the auth state is always valid.\n *\n * The value must be in seconds, aka. the amount of seconds\n * that will be subtracted from the current token `exp` claim in order\n * to determine whether it is going to expire within the specified time threshold.\n *\n * For example, if you want to auto refresh the token if it is\n * going to expire in the next 30mins (or already has expired),\n * it can be set to `1800`\n */\n autoRefreshThreshold?: number;\n}\n\n// -------------------------------------------------------------------\n\n// list of known SendOptions keys (everything else is treated as query param)\nconst knownSendOptionsKeys = [\n \"requestKey\",\n \"$cancelKey\",\n \"$autoCancel\",\n \"fetch\",\n \"headers\",\n \"body\",\n \"query\",\n \"params\",\n // ---,\n \"cache\",\n \"credentials\",\n \"headers\",\n \"integrity\",\n \"keepalive\",\n \"method\",\n \"mode\",\n \"redirect\",\n \"referrer\",\n \"referrerPolicy\",\n \"signal\",\n \"window\",\n];\n\n// modifies in place the provided options by moving unknown send options as query parameters.\nexport function normalizeUnknownQueryParams(options?: SendOptions): void {\n if (!options) {\n return;\n }\n\n options.query = options.query || {};\n for (let key in options) {\n if (knownSendOptionsKeys.includes(key)) {\n continue;\n }\n\n options.query[key] = options[key];\n delete options[key];\n }\n}\n\nexport function serializeQueryParams(params: { [key: string]: any }): string {\n const result: Array = [];\n\n for (const key in params) {\n const encodedKey = encodeURIComponent(key);\n const arrValue = Array.isArray(params[key]) ? params[key] : [params[key]];\n\n for (let v of arrValue) {\n v = prepareQueryParamValue(v);\n if (v === null) {\n continue;\n }\n result.push(encodedKey + \"=\" + v);\n }\n }\n\n return result.join(\"&\");\n}\n\n// encodes and normalizes the provided query param value.\nfunction prepareQueryParamValue(value: any): null | string {\n if (value === null || typeof value === \"undefined\") {\n return null;\n }\n\n if (value instanceof Date) {\n return encodeURIComponent(value.toISOString().replace(\"T\", \" \"));\n }\n\n if (typeof value === \"object\") {\n return encodeURIComponent(JSON.stringify(value));\n }\n\n return encodeURIComponent(value);\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseService } from \"@/services/BaseService\";\nimport { SendOptions, normalizeUnknownQueryParams } from \"@/tools/options\";\n\ninterface promiseCallbacks {\n resolve: Function;\n reject: Function;\n}\n\ntype Subscriptions = { [key: string]: Array };\n\nexport type UnsubscribeFunc = () => Promise;\n\nexport class RealtimeService extends BaseService {\n clientId: string = \"\";\n\n private eventSource: EventSource | null = null;\n private subscriptions: Subscriptions = {};\n private lastSentSubscriptions: Array = [];\n private connectTimeoutId: any;\n private maxConnectTimeout: number = 15000;\n private reconnectTimeoutId: any;\n private reconnectAttempts: number = 0;\n private maxReconnectAttempts: number = Infinity;\n private predefinedReconnectIntervals: Array = [\n 200, 300, 500, 1000, 1200, 1500, 2000,\n ];\n private pendingConnects: Array = [];\n\n /**\n * Returns whether the realtime connection has been established.\n */\n get isConnected(): boolean {\n return !!this.eventSource && !!this.clientId && !this.pendingConnects.length;\n }\n\n /**\n * An optional hook that is invoked when the realtime client disconnects\n * either when unsubscribing from all subscriptions or when the\n * connection was interrupted or closed by the server.\n *\n * The received argument could be used to determine whether the disconnect\n * is a result from unsubscribing (`activeSubscriptions.length == 0`)\n * or because of network/server error (`activeSubscriptions.length > 0`).\n *\n * If you want to listen for the opposite, aka. when the client connection is established,\n * subscribe to the `PB_CONNECT` event.\n */\n onDisconnect?: (activeSubscriptions: Array) => void;\n\n /**\n * Register the subscription listener.\n *\n * You can subscribe multiple times to the same topic.\n *\n * If the SSE connection is not started yet,\n * this method will also initialize it.\n */\n async subscribe(\n topic: string,\n callback: (data: any) => void,\n options?: SendOptions,\n ): Promise {\n if (!topic) {\n throw new Error(\"topic must be set.\");\n }\n\n let key = topic;\n\n // serialize and append the topic options (if any)\n if (options) {\n options = Object.assign({}, options); // shallow copy\n normalizeUnknownQueryParams(options);\n const serialized =\n \"options=\" +\n encodeURIComponent(\n JSON.stringify({ query: options.query, headers: options.headers }),\n );\n key += (key.includes(\"?\") ? \"&\" : \"?\") + serialized;\n }\n\n const listener = function (e: Event) {\n const msgEvent = e as MessageEvent;\n\n let data;\n try {\n data = JSON.parse(msgEvent?.data);\n } catch {}\n\n callback(data || {});\n };\n\n // store the listener\n if (!this.subscriptions[key]) {\n this.subscriptions[key] = [];\n }\n this.subscriptions[key].push(listener);\n\n if (!this.isConnected) {\n // initialize sse connection\n await this.connect();\n } else if (this.subscriptions[key].length === 1) {\n // send the updated subscriptions (if it is the first for the key)\n await this.submitSubscriptions();\n } else {\n // only register the listener\n this.eventSource?.addEventListener(key, listener);\n }\n\n return async (): Promise => {\n return this.unsubscribeByTopicAndListener(topic, listener);\n };\n }\n\n /**\n * Unsubscribe from all subscription listeners with the specified topic.\n *\n * If `topic` is not provided, then this method will unsubscribe\n * from all active subscriptions.\n *\n * This method is no-op if there are no active subscriptions.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribe(topic?: string): Promise {\n let needToSubmit = false;\n\n if (!topic) {\n // remove all subscriptions\n this.subscriptions = {};\n } else {\n // remove all listeners related to the topic\n const subs = this.getSubscriptionsByTopic(topic);\n for (let key in subs) {\n if (!this.hasSubscriptionListeners(key)) {\n continue; // already unsubscribed\n }\n\n for (let listener of this.subscriptions[key]) {\n this.eventSource?.removeEventListener(key, listener);\n }\n delete this.subscriptions[key];\n\n // mark for subscriptions change submit if there are no other listeners\n if (!needToSubmit) {\n needToSubmit = true;\n }\n }\n }\n\n if (!this.hasSubscriptionListeners()) {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n } else if (needToSubmit) {\n await this.submitSubscriptions();\n }\n }\n\n /**\n * Unsubscribe from all subscription listeners starting with the specified topic prefix.\n *\n * This method is no-op if there are no active subscriptions with the specified topic prefix.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribeByPrefix(keyPrefix: string): Promise {\n let hasAtleastOneTopic = false;\n for (let key in this.subscriptions) {\n // \"?\" so that it can be used as end delimiter for the prefix\n if (!(key + \"?\").startsWith(keyPrefix)) {\n continue;\n }\n\n hasAtleastOneTopic = true;\n for (let listener of this.subscriptions[key]) {\n this.eventSource?.removeEventListener(key, listener);\n }\n delete this.subscriptions[key];\n }\n\n if (!hasAtleastOneTopic) {\n return; // nothing to unsubscribe from\n }\n\n if (this.hasSubscriptionListeners()) {\n // submit the deleted subscriptions\n await this.submitSubscriptions();\n } else {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n }\n }\n\n /**\n * Unsubscribe from all subscriptions matching the specified topic and listener function.\n *\n * This method is no-op if there are no active subscription with\n * the specified topic and listener.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribeByTopicAndListener(\n topic: string,\n listener: EventListener,\n ): Promise {\n let needToSubmit = false;\n\n const subs = this.getSubscriptionsByTopic(topic);\n for (let key in subs) {\n if (\n !Array.isArray(this.subscriptions[key]) ||\n !this.subscriptions[key].length\n ) {\n continue; // already unsubscribed\n }\n\n let exist = false;\n for (let i = this.subscriptions[key].length - 1; i >= 0; i--) {\n if (this.subscriptions[key][i] !== listener) {\n continue;\n }\n\n exist = true; // has at least one matching listener\n delete this.subscriptions[key][i]; // removes the function reference\n this.subscriptions[key].splice(i, 1); // reindex the array\n this.eventSource?.removeEventListener(key, listener);\n }\n if (!exist) {\n continue;\n }\n\n // remove the key from the subscriptions list if there are no other listeners\n if (!this.subscriptions[key].length) {\n delete this.subscriptions[key];\n }\n\n // mark for subscriptions change submit if there are no other listeners\n if (!needToSubmit && !this.hasSubscriptionListeners(key)) {\n needToSubmit = true;\n }\n }\n\n if (!this.hasSubscriptionListeners()) {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n } else if (needToSubmit) {\n await this.submitSubscriptions();\n }\n }\n\n private hasSubscriptionListeners(keyToCheck?: string): boolean {\n this.subscriptions = this.subscriptions || {};\n\n // check the specified key\n if (keyToCheck) {\n return !!this.subscriptions[keyToCheck]?.length;\n }\n\n // check for at least one non-empty subscription\n for (let key in this.subscriptions) {\n if (!!this.subscriptions[key]?.length) {\n return true;\n }\n }\n\n return false;\n }\n\n private async submitSubscriptions(): Promise {\n if (!this.clientId) {\n return; // no client/subscriber\n }\n\n // optimistic update\n this.addAllSubscriptionListeners();\n\n this.lastSentSubscriptions = this.getNonEmptySubscriptionKeys();\n\n return this.client\n .send(\"/api/realtime\", {\n method: \"POST\",\n body: {\n clientId: this.clientId,\n subscriptions: this.lastSentSubscriptions,\n },\n requestKey: this.getSubscriptionsCancelKey(),\n })\n .catch((err) => {\n if (err?.isAbort) {\n return; // silently ignore aborted pending requests\n }\n throw err;\n });\n }\n\n private getSubscriptionsCancelKey(): string {\n return \"realtime_\" + this.clientId;\n }\n\n private getSubscriptionsByTopic(topic: string): Subscriptions {\n const result: Subscriptions = {};\n\n // \"?\" so that it can be used as end delimiter for the topic\n topic = topic.includes(\"?\") ? topic : topic + \"?\";\n\n for (let key in this.subscriptions) {\n if ((key + \"?\").startsWith(topic)) {\n result[key] = this.subscriptions[key];\n }\n }\n\n return result;\n }\n\n private getNonEmptySubscriptionKeys(): Array {\n const result: Array = [];\n\n for (let key in this.subscriptions) {\n if (this.subscriptions[key].length) {\n result.push(key);\n }\n }\n\n return result;\n }\n\n private addAllSubscriptionListeners(): void {\n if (!this.eventSource) {\n return;\n }\n\n this.removeAllSubscriptionListeners();\n\n for (let key in this.subscriptions) {\n for (let listener of this.subscriptions[key]) {\n this.eventSource.addEventListener(key, listener);\n }\n }\n }\n\n private removeAllSubscriptionListeners(): void {\n if (!this.eventSource) {\n return;\n }\n\n for (let key in this.subscriptions) {\n for (let listener of this.subscriptions[key]) {\n this.eventSource.removeEventListener(key, listener);\n }\n }\n }\n\n private async connect(): Promise {\n if (this.reconnectAttempts > 0) {\n // immediately resolve the promise to avoid indefinitely\n // blocking the client during reconnection\n return;\n }\n\n return new Promise((resolve, reject) => {\n this.pendingConnects.push({ resolve, reject });\n\n if (this.pendingConnects.length > 1) {\n // all promises will be resolved once the connection is established\n return;\n }\n\n this.initConnect();\n });\n }\n\n private initConnect() {\n this.disconnect(true);\n\n // wait up to 15s for connect\n clearTimeout(this.connectTimeoutId);\n this.connectTimeoutId = setTimeout(() => {\n this.connectErrorHandler(new Error(\"EventSource connect took too long.\"));\n }, this.maxConnectTimeout);\n\n this.eventSource = new EventSource(this.client.buildURL(\"/api/realtime\"));\n\n this.eventSource.onerror = (_) => {\n this.connectErrorHandler(\n new Error(\"Failed to establish realtime connection.\"),\n );\n };\n\n this.eventSource.addEventListener(\"PB_CONNECT\", (e) => {\n const msgEvent = e as MessageEvent;\n this.clientId = msgEvent?.lastEventId;\n\n this.submitSubscriptions()\n .then(async () => {\n let retries = 3;\n while (this.hasUnsentSubscriptions() && retries > 0) {\n retries--;\n // resubscribe to ensure that the latest topics are submitted\n //\n // This is needed because missed topics could happen on reconnect\n // if after the pending sent `submitSubscriptions()` call another `subscribe()`\n // was made before the submit was able to complete.\n await this.submitSubscriptions();\n }\n })\n .then(() => {\n for (let p of this.pendingConnects) {\n p.resolve();\n }\n\n // reset connect meta\n this.pendingConnects = [];\n this.reconnectAttempts = 0;\n clearTimeout(this.reconnectTimeoutId);\n clearTimeout(this.connectTimeoutId);\n\n // propagate the PB_CONNECT event\n const connectSubs = this.getSubscriptionsByTopic(\"PB_CONNECT\");\n for (let key in connectSubs) {\n for (let listener of connectSubs[key]) {\n listener(e);\n }\n }\n })\n .catch((err) => {\n this.clientId = \"\";\n this.connectErrorHandler(err);\n });\n });\n }\n\n private hasUnsentSubscriptions(): boolean {\n const latestTopics = this.getNonEmptySubscriptionKeys();\n if (latestTopics.length != this.lastSentSubscriptions.length) {\n return true;\n }\n\n for (const t of latestTopics) {\n if (!this.lastSentSubscriptions.includes(t)) {\n return true;\n }\n }\n\n return false;\n }\n\n private connectErrorHandler(err: any) {\n clearTimeout(this.connectTimeoutId);\n clearTimeout(this.reconnectTimeoutId);\n\n if (\n // wasn't previously connected -> direct reject\n (!this.clientId && !this.reconnectAttempts) ||\n // was previously connected but the max reconnection limit has been reached\n this.reconnectAttempts > this.maxReconnectAttempts\n ) {\n for (let p of this.pendingConnects) {\n p.reject(new ClientResponseError(err));\n }\n this.pendingConnects = [];\n this.disconnect();\n return;\n }\n\n // otherwise -> reconnect in the background\n this.disconnect(true);\n const timeout =\n this.predefinedReconnectIntervals[this.reconnectAttempts] ||\n this.predefinedReconnectIntervals[\n this.predefinedReconnectIntervals.length - 1\n ];\n this.reconnectAttempts++;\n this.reconnectTimeoutId = setTimeout(() => {\n this.initConnect();\n }, timeout);\n }\n\n private disconnect(fromReconnect = false): void {\n if (this.clientId && this.onDisconnect) {\n this.onDisconnect(Object.keys(this.subscriptions));\n }\n\n clearTimeout(this.connectTimeoutId);\n clearTimeout(this.reconnectTimeoutId);\n this.removeAllSubscriptionListeners();\n this.client.cancelRequest(this.getSubscriptionsCancelKey());\n this.eventSource?.close();\n this.eventSource = null;\n this.clientId = \"\";\n\n if (!fromReconnect) {\n this.reconnectAttempts = 0;\n\n // resolve any remaining connect promises\n //\n // this is done to avoid unnecessary throwing errors in case\n // unsubscribe is called before the pending connect promises complete\n // (see https://github.com/pocketbase/pocketbase/discussions/2897#discussioncomment-6423818)\n for (let p of this.pendingConnects) {\n p.resolve();\n }\n this.pendingConnects = [];\n }\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { ClientResponseError } from \"@/ClientResponseError\";\nimport { ListResult } from \"@/tools/dtos\";\nimport { CommonOptions, ListOptions, FullListOptions } from \"@/tools/options\";\n\nexport abstract class CrudService extends BaseService {\n /**\n * Base path for the crud actions (without trailing slash, eg. '/admins').\n */\n abstract get baseCrudPath(): string;\n\n /**\n * Response data decoder.\n */\n decode(data: { [key: string]: any }): T {\n return data as T;\n }\n\n /**\n * Returns a promise with all list items batch fetched at once\n * (by default 1000 items per request; to change it set the `batch` query param).\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: FullListOptions): Promise>;\n\n /**\n * Legacy version of getFullList with explicitly specified batch size.\n */\n async getFullList(batch?: number, options?: ListOptions): Promise>;\n\n async getFullList(\n batchOrqueryParams?: number | FullListOptions,\n options?: ListOptions,\n ): Promise> {\n if (typeof batchOrqueryParams == \"number\") {\n return this._getFullList(batchOrqueryParams, options);\n }\n\n options = Object.assign({}, batchOrqueryParams, options);\n\n let batch = 1000;\n if (options.batch) {\n batch = options.batch;\n delete options.batch;\n }\n\n return this._getFullList(batch, options);\n }\n\n /**\n * Returns paginated items list.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: ListOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n options.query = Object.assign(\n {\n page: page,\n perPage: perPage,\n },\n options.query,\n );\n\n return this.client.send(this.baseCrudPath, options).then((responseData: any) => {\n responseData.items =\n responseData.items?.map((item: any) => {\n return this.decode(item);\n }) || [];\n\n return responseData;\n });\n }\n\n /**\n * Returns the first found item by the specified filter.\n *\n * Internally it calls `getList(1, 1, { filter, skipTotal })` and\n * returns the first found item.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * For consistency with `getOne`, this method will throw a 404\n * ClientResponseError if no item was found.\n *\n * @throws {ClientResponseError}\n */\n async getFirstListItem(filter: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n requestKey: \"one_by_filter_\" + this.baseCrudPath + \"_\" + filter,\n },\n options,\n );\n\n options.query = Object.assign(\n {\n filter: filter,\n skipTotal: 1,\n },\n options.query,\n );\n\n return this.getList(1, 1, options).then((result) => {\n if (!result?.items?.length) {\n throw new ClientResponseError({\n status: 404,\n response: {\n code: 404,\n message: \"The requested resource wasn't found.\",\n data: {},\n },\n });\n }\n\n return result.items[0];\n });\n }\n\n /**\n * Returns single item by its id.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * If `id` is empty it will throw a 404 error.\n *\n * @throws {ClientResponseError}\n */\n async getOne(id: string, options?: CommonOptions): Promise {\n if (!id) {\n throw new ClientResponseError({\n url: this.client.buildURL(this.baseCrudPath + \"/\"),\n status: 404,\n response: {\n code: 404,\n message: \"Missing required record id.\",\n data: {},\n },\n });\n }\n\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Creates a new item.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath, options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Updates an existing item by its id.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"PATCH\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Deletes an existing item by its id.\n *\n * @throws {ClientResponseError}\n */\n async delete(id: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then(() => true);\n }\n\n /**\n * Returns a promise with all list items batch fetched at once.\n */\n protected _getFullList(\n batchSize = 1000,\n options?: ListOptions,\n ): Promise> {\n options = options || {};\n options.query = Object.assign(\n {\n skipTotal: 1,\n },\n options.query,\n );\n\n let result: Array = [];\n\n let request = async (page: number): Promise> => {\n return this.getList(page, batchSize || 1000, options).then((list) => {\n const castedList = list as any as ListResult;\n const items = castedList.items;\n\n result = result.concat(items);\n\n if (items.length == list.perPage) {\n return request(page + 1);\n }\n\n return result;\n });\n };\n\n return request(1);\n }\n}\n","import { SendOptions } from \"@/tools/options\";\n\nexport function normalizeLegacyOptionsArgs(\n legacyWarn: string,\n baseOptions: SendOptions,\n bodyOrOptions?: any,\n query?: any,\n): SendOptions {\n const hasBodyOrOptions = typeof bodyOrOptions !== \"undefined\";\n const hasQuery = typeof query !== \"undefined\";\n\n if (!hasQuery && !hasBodyOrOptions) {\n return baseOptions;\n }\n\n if (hasQuery) {\n console.warn(legacyWarn);\n baseOptions.body = Object.assign({}, baseOptions.body, bodyOrOptions);\n baseOptions.query = Object.assign({}, baseOptions.query, query);\n\n return baseOptions;\n }\n\n return Object.assign(baseOptions, bodyOrOptions);\n}\n","import Client from \"@/Client\";\nimport { isTokenExpired } from \"@/tools/jwt\";\n\n// reset previous auto refresh registrations\nexport function resetAutoRefresh(client: Client) {\n (client as any)._resetAutoRefresh?.();\n}\n\nexport function registerAutoRefresh(\n client: Client,\n threshold: number,\n refreshFunc: () => Promise,\n reauthenticateFunc: () => Promise,\n) {\n resetAutoRefresh(client);\n\n const oldBeforeSend = client.beforeSend;\n const oldModel = client.authStore.record;\n\n // unset the auto refresh in case the auth store was cleared\n // OR a new model was authenticated\n const unsubStoreChange = client.authStore.onChange((newToken, model) => {\n if (\n !newToken ||\n model?.id != oldModel?.id ||\n ((model?.collectionId || oldModel?.collectionId) &&\n model?.collectionId != oldModel?.collectionId)\n ) {\n resetAutoRefresh(client);\n }\n });\n\n // initialize a reset function and attach it dynamically to the client\n (client as any)._resetAutoRefresh = function () {\n unsubStoreChange();\n client.beforeSend = oldBeforeSend;\n delete (client as any)._resetAutoRefresh;\n };\n\n client.beforeSend = async (url, sendOptions) => {\n const oldToken = client.authStore.token;\n\n if (sendOptions.query?.autoRefresh) {\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n }\n\n let isValid = client.authStore.isValid;\n if (\n // is loosely valid\n isValid &&\n // but it is going to expire in the next \"threshold\" seconds\n isTokenExpired(client.authStore.token, threshold)\n ) {\n try {\n await refreshFunc();\n } catch (_) {\n isValid = false;\n }\n }\n\n // still invalid -> reauthenticate\n if (!isValid) {\n await reauthenticateFunc();\n }\n\n // the request wasn't sent with a custom token\n const headers = sendOptions.headers || {};\n for (let key in headers) {\n if (\n key.toLowerCase() == \"authorization\" &&\n // the request wasn't sent with a custom token\n oldToken == headers[key] &&\n client.authStore.token\n ) {\n // set the latest store token\n headers[key] = client.authStore.token;\n break;\n }\n }\n sendOptions.headers = headers;\n\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n };\n}\n","import Client from \"@/Client\";\nimport { ClientResponseError } from \"@/ClientResponseError\";\nimport { RealtimeService, UnsubscribeFunc } from \"@/services/RealtimeService\";\nimport { BaseAuthStore } from \"@/stores/BaseAuthStore\";\nimport { CrudService } from \"@/services/CrudService\";\nimport { ListResult, RecordModel } from \"@/tools/dtos\";\nimport { normalizeLegacyOptionsArgs } from \"@/tools/legacy\";\nimport {\n CommonOptions,\n RecordFullListOptions,\n RecordListOptions,\n RecordOptions,\n SendOptions,\n RecordSubscribeOptions,\n} from \"@/tools/options\";\nimport { getTokenPayload } from \"@/tools/jwt\";\nimport { registerAutoRefresh, resetAutoRefresh } from \"@/tools/refresh\";\n\nexport interface RecordAuthResponse {\n /**\n * The signed PocketBase auth record.\n */\n record: T;\n\n /**\n * The PocketBase record auth token.\n *\n * If you are looking for the OAuth2 access and refresh tokens\n * they are available under the `meta.accessToken` and `meta.refreshToken` props.\n */\n token: string;\n\n /**\n * Auth meta data usually filled when OAuth2 is used.\n */\n meta?: { [key: string]: any };\n}\n\nexport interface AuthProviderInfo {\n name: string;\n displayName: string;\n state: string;\n authURL: string;\n codeVerifier: string;\n codeChallenge: string;\n codeChallengeMethod: string;\n}\n\nexport interface AuthMethodsList {\n mfa: {\n enabled: boolean;\n duration: number;\n };\n otp: {\n enabled: boolean;\n duration: number;\n };\n password: {\n enabled: boolean;\n identityFields: Array;\n };\n oauth2: {\n enabled: boolean;\n providers: Array;\n };\n}\n\nexport interface RecordSubscription {\n action: string; // eg. create, update, delete\n record: T;\n}\n\nexport type OAuth2UrlCallback = (url: string) => void | Promise;\n\nexport interface OAuth2AuthConfig extends SendOptions {\n // the name of the OAuth2 provider (eg. \"google\")\n provider: string;\n\n // custom scopes to overwrite the default ones\n scopes?: Array;\n\n // optional record create data\n createData?: { [key: string]: any };\n\n // optional callback that is triggered after the OAuth2 sign-in/sign-up url generation\n urlCallback?: OAuth2UrlCallback;\n\n // optional query params to send with the PocketBase auth request (eg. fields, expand, etc.)\n query?: RecordOptions;\n}\n\nexport interface OTPResponse {\n otpId: string;\n}\n\nexport class RecordService extends CrudService {\n readonly collectionIdOrName: string;\n\n constructor(client: Client, collectionIdOrName: string) {\n super(client);\n\n this.collectionIdOrName = collectionIdOrName;\n }\n\n /**\n * @inheritdoc\n */\n get baseCrudPath(): string {\n return this.baseCollectionPath + \"/records\";\n }\n\n /**\n * Returns the current collection service base path.\n */\n get baseCollectionPath(): string {\n return \"/api/collections/\" + encodeURIComponent(this.collectionIdOrName);\n }\n\n /**\n * Returns whether the current service collection is superusers.\n */\n get isSuperusers(): boolean {\n return (\n this.collectionIdOrName == \"_superusers\" ||\n this.collectionIdOrName == \"_pbc_2773867675\"\n );\n }\n\n // ---------------------------------------------------------------\n // Realtime handlers\n // ---------------------------------------------------------------\n\n /**\n * Subscribe to realtime changes to the specified topic (\"*\" or record id).\n *\n * If `topic` is the wildcard \"*\", then this method will subscribe to\n * any record changes in the collection.\n *\n * If `topic` is a record id, then this method will subscribe only\n * to changes of the specified record id.\n *\n * It's OK to subscribe multiple times to the same topic.\n * You can use the returned `UnsubscribeFunc` to remove only a single subscription.\n * Or use `unsubscribe(topic)` if you want to remove all subscriptions attached to the topic.\n */\n async subscribe(\n topic: string,\n callback: (data: RecordSubscription) => void,\n options?: RecordSubscribeOptions,\n ): Promise {\n if (!topic) {\n throw new Error(\"Missing topic.\");\n }\n\n if (!callback) {\n throw new Error(\"Missing subscription callback.\");\n }\n\n return this.client.realtime.subscribe(\n this.collectionIdOrName + \"/\" + topic,\n callback,\n options,\n );\n }\n\n /**\n * Unsubscribe from all subscriptions of the specified topic\n * (\"*\" or record id).\n *\n * If `topic` is not set, then this method will unsubscribe from\n * all subscriptions associated to the current collection.\n */\n async unsubscribe(topic?: string): Promise {\n // unsubscribe from the specified topic\n if (topic) {\n return this.client.realtime.unsubscribe(\n this.collectionIdOrName + \"/\" + topic,\n );\n }\n\n // unsubscribe from everything related to the collection\n return this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName);\n }\n\n // ---------------------------------------------------------------\n // Crud handers\n // ---------------------------------------------------------------\n /**\n * @inheritdoc\n */\n async getFullList(options?: RecordFullListOptions): Promise>;\n\n /**\n * @inheritdoc\n */\n async getFullList(\n batch?: number,\n options?: RecordListOptions,\n ): Promise>;\n\n /**\n * @inheritdoc\n */\n async getFullList(\n batchOrOptions?: number | RecordFullListOptions,\n options?: RecordListOptions,\n ): Promise> {\n if (typeof batchOrOptions == \"number\") {\n return super.getFullList(batchOrOptions, options);\n }\n\n const params = Object.assign({}, batchOrOptions, options);\n\n return super.getFullList(params);\n }\n\n /**\n * @inheritdoc\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: RecordListOptions,\n ): Promise> {\n return super.getList(page, perPage, options);\n }\n\n /**\n * @inheritdoc\n */\n async getFirstListItem(\n filter: string,\n options?: RecordListOptions,\n ): Promise {\n return super.getFirstListItem(filter, options);\n }\n\n /**\n * @inheritdoc\n */\n async getOne(id: string, options?: RecordOptions): Promise {\n return super.getOne(id, options);\n }\n\n /**\n * @inheritdoc\n */\n async create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): Promise {\n return super.create(bodyParams, options);\n }\n\n /**\n * @inheritdoc\n *\n * If the current `client.authStore.record` matches with the updated id, then\n * on success the `client.authStore.record` will be updated with the new response record fields.\n */\n async update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): Promise {\n return super.update(id, bodyParams, options).then((item) => {\n if (\n // is record auth\n this.client.authStore.record?.id === item?.id &&\n (this.client.authStore.record?.collectionId === this.collectionIdOrName ||\n this.client.authStore.record?.collectionName ===\n this.collectionIdOrName)\n ) {\n let authExpand = Object.assign({}, this.client.authStore.record.expand);\n let authRecord = Object.assign({}, this.client.authStore.record, item);\n if (authExpand) {\n // for now \"merge\" only top-level expand\n authRecord.expand = Object.assign(authExpand, item.expand);\n }\n\n this.client.authStore.save(this.client.authStore.token, authRecord);\n }\n\n return item as any as T;\n });\n }\n\n /**\n * @inheritdoc\n *\n * If the current `client.authStore.record` matches with the deleted id,\n * then on success the `client.authStore` will be cleared.\n */\n async delete(id: string, options?: CommonOptions): Promise {\n return super.delete(id, options).then((success) => {\n if (\n success &&\n // is record auth\n this.client.authStore.record?.id === id &&\n (this.client.authStore.record?.collectionId === this.collectionIdOrName ||\n this.client.authStore.record?.collectionName ===\n this.collectionIdOrName)\n ) {\n this.client.authStore.clear();\n }\n\n return success;\n });\n }\n\n // ---------------------------------------------------------------\n // Auth handlers\n // ---------------------------------------------------------------\n\n /**\n * Prepare successful collection authorization response.\n */\n protected authResponse(responseData: any): RecordAuthResponse {\n const record = this.decode(responseData?.record || {});\n\n this.client.authStore.save(responseData?.token, record as any);\n\n return Object.assign({}, responseData, {\n // normalize common fields\n token: responseData?.token || \"\",\n record: record as any as T,\n });\n }\n\n /**\n * Returns all available collection auth methods.\n *\n * @throws {ClientResponseError}\n */\n async listAuthMethods(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"GET\",\n // @todo remove after deleting the pre v0.23 API response fields\n fields: \"mfa,otp,password,oauth2\",\n },\n options,\n );\n\n return this.client.send(this.baseCollectionPath + \"/auth-methods\", options);\n }\n\n /**\n * Authenticate a single auth collection record via its username/email and password.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n *\n * @throws {ClientResponseError}\n */\n async authWithPassword(\n usernameOrEmail: string,\n password: string,\n options?: RecordOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n identity: usernameOrEmail,\n password: password,\n },\n },\n options,\n );\n\n // note: consider to deprecate\n let autoRefreshThreshold;\n if (this.isSuperusers) {\n autoRefreshThreshold = options.autoRefreshThreshold;\n delete options.autoRefreshThreshold;\n if (!options.autoRefresh) {\n resetAutoRefresh(this.client);\n }\n }\n\n let authData = await this.client.send(\n this.baseCollectionPath + \"/auth-with-password\",\n options,\n );\n\n authData = this.authResponse(authData);\n\n if (autoRefreshThreshold && this.isSuperusers) {\n registerAutoRefresh(\n this.client,\n autoRefreshThreshold,\n () => this.authRefresh({ autoRefresh: true }),\n () =>\n this.authWithPassword(\n usernameOrEmail,\n password,\n Object.assign({ autoRefresh: true }, options),\n ),\n );\n }\n\n return authData;\n }\n\n /**\n * Authenticate a single auth collection record with OAuth2 code.\n *\n * If you don't have an OAuth2 code you may also want to check `authWithOAuth2` method.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n * - the OAuth2 account data (eg. name, email, avatar, etc.)\n *\n * @throws {ClientResponseError}\n */\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n options?: RecordOptions,\n ): Promise>;\n\n /**\n * @deprecated\n * Consider using authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createdData, options?).\n */\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n body?: any,\n query?: any,\n ): Promise>;\n\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n bodyOrOptions?: any,\n query?: any,\n ): Promise> {\n let options: any = {\n method: \"POST\",\n body: {\n provider: provider,\n code: code,\n codeVerifier: codeVerifier,\n redirectURL: redirectURL,\n createData: createData,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-with-oauth2\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * @deprecated This form of authWithOAuth2 is deprecated.\n *\n * Please use `authWithOAuth2Code()` OR its simplified realtime version\n * as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.\n */\n async authWithOAuth2(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n bodyParams?: { [key: string]: any },\n queryParams?: RecordOptions,\n ): Promise>;\n\n /**\n * Authenticate a single auth collection record with OAuth2\n * **without custom redirects, deeplinks or even page reload**.\n *\n * This method initializes a one-off realtime subscription and will\n * open a popup window with the OAuth2 vendor page to authenticate.\n * Once the external OAuth2 sign-in/sign-up flow is completed, the popup\n * window will be automatically closed and the OAuth2 data sent back\n * to the user through the previously established realtime connection.\n *\n * You can specify an optional `urlCallback` prop to customize\n * the default url `window.open` behavior.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n * - the OAuth2 account data (eg. name, email, avatar, etc.)\n *\n * Example:\n *\n * ```js\n * const authData = await pb.collection(\"users\").authWithOAuth2({\n * provider: \"google\",\n * })\n * ```\n *\n * Note1: When creating the OAuth2 app in the provider dashboard\n * you have to configure `https://yourdomain.com/api/oauth2-redirect`\n * as redirect URL.\n *\n * Note2: Safari may block the default `urlCallback` popup because\n * it doesn't allow `window.open` calls as part of an `async` click functions.\n * To workaround this you can either change your click handler to not be marked as `async`\n * OR manually call `window.open` before your `async` function and use the\n * window reference in your own custom `urlCallback` (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061).\n * For example:\n * ```js\n * \n * ...\n * document.getElementById(\"btn\").addEventListener(\"click\", () => {\n * pb.collection(\"users\").authWithOAuth2({\n * provider: \"gitlab\",\n * }).then((authData) => {\n * console.log(authData)\n * }).catch((err) => {\n * console.log(err, err.originalError);\n * });\n * })\n * ```\n *\n * @throws {ClientResponseError}\n */\n async authWithOAuth2(\n options: OAuth2AuthConfig,\n ): Promise>;\n\n authWithOAuth2(...args: any): Promise> {\n // fallback to legacy format\n if (args.length > 1 || typeof args?.[0] === \"string\") {\n console.warn(\n \"PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.\",\n );\n return this.authWithOAuth2Code(\n args?.[0] || \"\",\n args?.[1] || \"\",\n args?.[2] || \"\",\n args?.[3] || \"\",\n args?.[4] || {},\n args?.[5] || {},\n args?.[6] || {},\n );\n }\n\n const config = args?.[0] || {};\n\n // open a new popup window in case config.urlCallback is not set\n //\n // note: it is opened before any async calls due to Safari restrictions\n // (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061)\n let eagerDefaultPopup: Window | null = null;\n if (!config.urlCallback) {\n eagerDefaultPopup = openBrowserPopup(undefined);\n }\n\n // initialize a one-off realtime service\n const realtime = new RealtimeService(this.client);\n\n function cleanup() {\n eagerDefaultPopup?.close();\n realtime.unsubscribe();\n }\n\n const requestKeyOptions: SendOptions = {};\n const requestKey = config.requestKey;\n if (requestKey) {\n requestKeyOptions.requestKey = requestKey;\n }\n\n return this.listAuthMethods(requestKeyOptions)\n .then((authMethods) => {\n const provider = authMethods.oauth2.providers.find(\n (p) => p.name === config.provider,\n );\n if (!provider) {\n throw new ClientResponseError(\n new Error(`Missing or invalid provider \"${config.provider}\".`),\n );\n }\n\n const redirectURL = this.client.buildURL(\"/api/oauth2-redirect\");\n\n return new Promise(async (resolve, reject) => {\n // find the AbortController associated with the current request key (if any)\n const cancelController = requestKey\n ? this.client[\"cancelControllers\"]?.[requestKey]\n : undefined;\n if (cancelController) {\n cancelController.signal.onabort = () => {\n cleanup();\n reject(\n new ClientResponseError({\n isAbort: true,\n message: \"manually cancelled\",\n }),\n );\n };\n }\n\n // disconnected due to network/server error\n realtime.onDisconnect = (activeSubscriptions: Array) => {\n if (activeSubscriptions.length && reject) {\n cleanup();\n reject(\n new ClientResponseError(\n new Error(\"realtime connection interrupted\"),\n ),\n );\n }\n };\n\n try {\n await realtime.subscribe(\"@oauth2\", async (e) => {\n const oldState = realtime.clientId;\n\n try {\n if (!e.state || oldState !== e.state) {\n throw new Error(\"State parameters don't match.\");\n }\n\n if (e.error || !e.code) {\n throw new Error(\n \"OAuth2 redirect error or missing code: \" +\n e.error,\n );\n }\n\n // clear the non SendOptions props\n const options = Object.assign({}, config);\n delete options.provider;\n delete options.scopes;\n delete options.createData;\n delete options.urlCallback;\n\n // reset the cancelController listener as it will be triggered by the next api call\n if (cancelController?.signal?.onabort) {\n cancelController.signal.onabort = null;\n }\n\n const authData = await this.authWithOAuth2Code(\n provider.name,\n e.code,\n provider.codeVerifier,\n redirectURL,\n config.createData,\n options,\n );\n\n resolve(authData);\n } catch (err) {\n reject(new ClientResponseError(err));\n }\n\n cleanup();\n });\n\n const replacements: { [key: string]: any } = {\n state: realtime.clientId,\n };\n if (config.scopes?.length) {\n replacements[\"scope\"] = config.scopes.join(\" \");\n }\n\n const url = this._replaceQueryParams(\n provider.authURL + redirectURL,\n replacements,\n );\n\n let urlCallback =\n config.urlCallback ||\n function (url: string) {\n if (eagerDefaultPopup) {\n eagerDefaultPopup.location.href = url;\n } else {\n // it could have been blocked due to its empty initial url,\n // try again...\n eagerDefaultPopup = openBrowserPopup(url);\n }\n };\n\n await urlCallback(url);\n } catch (err) {\n // reset the cancelController listener in case the request key is reused\n if (cancelController?.signal?.onabort) {\n cancelController.signal.onabort = null;\n }\n\n cleanup();\n reject(new ClientResponseError(err));\n }\n });\n })\n .catch((err) => {\n cleanup();\n throw err; // rethrow\n }) as Promise>;\n }\n\n /**\n * Refreshes the current authenticated record instance and\n * returns a new token and record data.\n *\n * On success this method also automatically updates the client's AuthStore.\n *\n * @throws {ClientResponseError}\n */\n async authRefresh(options?: RecordOptions): Promise>;\n\n /**\n * @deprecated\n * Consider using authRefresh(options?).\n */\n async authRefresh(body?: any, query?: any): Promise>;\n\n async authRefresh(\n bodyOrOptions?: any,\n query?: any,\n ): Promise> {\n let options: any = {\n method: \"POST\",\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-refresh\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * Sends auth record password reset request.\n *\n * @throws {ClientResponseError}\n */\n async requestPasswordReset(email: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestPasswordReset(email, options?).\n */\n async requestPasswordReset(email: string, body?: any, query?: any): Promise;\n\n async requestPasswordReset(\n email: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n email: email,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-password-reset\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record password reset request.\n *\n * @throws {ClientResponseError}\n */\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmPasswordReset(passwordResetToken, password, passwordConfirm, options?).\n */\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: passwordResetToken,\n password: password,\n passwordConfirm: passwordConfirm,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-password-reset\", options)\n .then(() => true);\n }\n\n /**\n * Sends auth record verification email request.\n *\n * @throws {ClientResponseError}\n */\n async requestVerification(email: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestVerification(email, options?).\n */\n async requestVerification(email: string, body?: any, query?: any): Promise;\n\n async requestVerification(\n email: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n email: email,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-verification\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record email verification request.\n *\n * If the current `client.authStore.record` matches with the auth record from the token,\n * then on success the `client.authStore.record.verified` will be updated to `true`.\n *\n * @throws {ClientResponseError}\n */\n async confirmVerification(\n verificationToken: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmVerification(verificationToken, options?).\n */\n async confirmVerification(\n verificationToken: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmVerification(\n verificationToken: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: verificationToken,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-verification\", options)\n .then(() => {\n // on success manually update the current auth record verified state\n const payload = getTokenPayload(verificationToken);\n const model = this.client.authStore.record;\n if (\n model &&\n !model.verified &&\n model.id === payload.id &&\n model.collectionId === payload.collectionId\n ) {\n model.verified = true;\n this.client.authStore.save(this.client.authStore.token, model);\n }\n\n return true;\n });\n }\n\n /**\n * Sends an email change request to the authenticated record model.\n *\n * @throws {ClientResponseError}\n */\n async requestEmailChange(newEmail: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestEmailChange(newEmail, options?).\n */\n async requestEmailChange(newEmail: string, body?: any, query?: any): Promise;\n\n async requestEmailChange(\n newEmail: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n newEmail: newEmail,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-email-change\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record's new email address.\n *\n * If the current `client.authStore.record` matches with the auth record from the token,\n * then on success the `client.authStore` will be cleared.\n *\n * @throws {ClientResponseError}\n */\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmEmailChange(emailChangeToken, password, options?).\n */\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: emailChangeToken,\n password: password,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-email-change\", options)\n .then(() => {\n const payload = getTokenPayload(emailChangeToken);\n const model = this.client.authStore.record;\n if (\n model &&\n model.id === payload.id &&\n model.collectionId === payload.collectionId\n ) {\n this.client.authStore.clear();\n }\n\n return true;\n });\n }\n\n /**\n * @deprecated use collection(\"_externalAuths\").*\n *\n * Lists all linked external auth providers for the specified auth record.\n *\n * @throws {ClientResponseError}\n */\n async listExternalAuths(\n recordId: string,\n options?: CommonOptions,\n ): Promise> {\n return this.client.collection(\"_externalAuths\").getFullList(\n Object.assign({}, options, {\n filter: this.client.filter(\"recordRef = {:id}\", { id: recordId }),\n }),\n );\n }\n\n /**\n * @deprecated use collection(\"_externalAuths\").*\n *\n * Unlink a single external auth provider from the specified auth record.\n *\n * @throws {ClientResponseError}\n */\n async unlinkExternalAuth(\n recordId: string,\n provider: string,\n options?: CommonOptions,\n ): Promise {\n const ea = await this.client.collection(\"_externalAuths\").getFirstListItem(\n this.client.filter(\"recordRef = {:recordId} && provider = {:provider}\", {\n recordId,\n provider,\n }),\n );\n\n return this.client\n .collection(\"_externalAuths\")\n .delete(ea.id, options)\n .then(() => true);\n }\n\n /**\n * Sends auth record OTP to the provided email.\n *\n * @throws {ClientResponseError}\n */\n async requestOTP(email: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: { email: email },\n },\n options,\n );\n\n return this.client.send(this.baseCollectionPath + \"/request-otp\", options);\n }\n\n /**\n * Authenticate a single auth collection record via OTP.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n *\n * @throws {ClientResponseError}\n */\n async authWithOTP(\n otpId: string,\n password: string,\n options?: CommonOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"POST\",\n body: { otpId, password },\n },\n options,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-with-otp\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * Impersonate authenticates with the specified recordId and\n * returns a new client with the received auth token in a memory store.\n *\n * If `duration` is 0 the generated auth token will fallback\n * to the default collection auth token duration.\n *\n * This action currently requires superusers privileges.\n *\n * @throws {ClientResponseError}\n */\n async impersonate(\n recordId: string,\n duration: number,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: { duration: duration },\n },\n options,\n );\n options.headers = options.headers || {};\n if (!options.headers.Authorization) {\n options.headers.Authorization = this.client.authStore.token;\n }\n\n // create a new client loaded with the impersonated auth state\n // ---\n const client = new Client(\n this.client.baseURL,\n new BaseAuthStore(),\n this.client.lang,\n );\n\n const authData = await client.send(\n this.baseCollectionPath + \"/impersonate/\" + encodeURIComponent(recordId),\n options,\n );\n\n client.authStore.save(authData?.token, this.decode(authData?.record || {}));\n // ---\n\n return client;\n }\n\n // ---------------------------------------------------------------\n\n // very rudimentary url query params replacement because at the moment\n // URL (and URLSearchParams) doesn't seem to be fully supported in React Native\n //\n // note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html\n private _replaceQueryParams(\n url: string,\n replacements: { [key: string]: any } = {},\n ): string {\n let urlPath = url;\n let query = \"\";\n\n const queryIndex = url.indexOf(\"?\");\n if (queryIndex >= 0) {\n urlPath = url.substring(0, url.indexOf(\"?\"));\n query = url.substring(url.indexOf(\"?\") + 1);\n }\n\n const parsedParams: { [key: string]: string } = {};\n\n // parse the query parameters\n const rawParams = query.split(\"&\");\n for (const param of rawParams) {\n if (param == \"\") {\n continue;\n }\n\n const pair = param.split(\"=\");\n parsedParams[decodeURIComponent(pair[0].replace(/\\+/g, \" \"))] =\n decodeURIComponent((pair[1] || \"\").replace(/\\+/g, \" \"));\n }\n\n // apply the replacements\n for (let key in replacements) {\n if (!replacements.hasOwnProperty(key)) {\n continue;\n }\n\n if (replacements[key] == null) {\n delete parsedParams[key];\n } else {\n parsedParams[key] = replacements[key];\n }\n }\n\n // construct back the full query string\n query = \"\";\n for (let key in parsedParams) {\n if (!parsedParams.hasOwnProperty(key)) {\n continue;\n }\n\n if (query != \"\") {\n query += \"&\";\n }\n\n query +=\n encodeURIComponent(key.replace(/%20/g, \"+\")) +\n \"=\" +\n encodeURIComponent(parsedParams[key].replace(/%20/g, \"+\"));\n }\n\n return query != \"\" ? urlPath + \"?\" + query : urlPath;\n }\n}\n\nfunction openBrowserPopup(url?: string): Window | null {\n if (typeof window === \"undefined\" || !window?.open) {\n throw new ClientResponseError(\n new Error(\n `Not in a browser context - please pass a custom urlCallback function.`,\n ),\n );\n }\n\n let width = 1024;\n let height = 768;\n\n let windowWidth = window.innerWidth;\n let windowHeight = window.innerHeight;\n\n // normalize window size\n width = width > windowWidth ? windowWidth : width;\n height = height > windowHeight ? windowHeight : height;\n\n let left = windowWidth / 2 - width / 2;\n let top = windowHeight / 2 - height / 2;\n\n // note: we don't use the noopener and noreferrer attributes since\n // for some reason browser blocks such windows then url is undefined/blank\n return window.open(\n url,\n \"popup_window\",\n \"width=\" +\n width +\n \",height=\" +\n height +\n \",top=\" +\n top +\n \",left=\" +\n left +\n \",resizable,menubar=no\",\n );\n}\n","import { CrudService } from \"@/services/CrudService\";\nimport { CollectionModel } from \"@/tools/dtos\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport class CollectionService extends CrudService {\n /**\n * @inheritdoc\n */\n get baseCrudPath(): string {\n return \"/api/collections\";\n }\n\n /**\n * Imports the provided collections.\n *\n * If `deleteMissing` is `true`, all local collections and their fields,\n * that are not present in the imported configuration, WILL BE DELETED\n * (including their related records data)!\n *\n * @throws {ClientResponseError}\n */\n async import(\n collections: Array,\n deleteMissing: boolean = false,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"PUT\",\n body: {\n collections: collections,\n deleteMissing: deleteMissing,\n },\n },\n options,\n );\n\n return this.client.send(this.baseCrudPath + \"/import\", options).then(() => true);\n }\n\n /**\n * Returns type indexed map with scaffolded collection models\n * populated with their default field values.\n *\n * @throws {ClientResponseError}\n */\n async getScaffolds(\n options?: CommonOptions,\n ): Promise<{ [key: string]: CollectionModel }> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(this.baseCrudPath + \"/meta/scaffolds\", options);\n }\n\n /**\n * Deletes all records associated with the specified collection.\n *\n * @throws {ClientResponseError}\n */\n async truncate(collectionIdOrName: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(\n this.baseCrudPath +\n \"/\" +\n encodeURIComponent(collectionIdOrName) +\n \"/truncate\",\n options,\n )\n .then(() => true);\n }\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseService } from \"@/services/BaseService\";\nimport { ListResult, LogModel } from \"@/tools/dtos\";\nimport { CommonOptions, ListOptions, LogStatsOptions } from \"@/tools/options\";\n\nexport interface HourlyStats {\n total: number;\n date: string;\n}\n\nexport class LogService extends BaseService {\n /**\n * Returns paginated logs list.\n *\n * @throws {ClientResponseError}\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: ListOptions,\n ): Promise> {\n options = Object.assign({ method: \"GET\" }, options);\n\n options.query = Object.assign(\n {\n page: page,\n perPage: perPage,\n },\n options.query,\n );\n\n return this.client.send(\"/api/logs\", options);\n }\n\n /**\n * Returns a single log by its id.\n *\n * If `id` is empty it will throw a 404 error.\n *\n * @throws {ClientResponseError}\n */\n async getOne(id: string, options?: CommonOptions): Promise {\n if (!id) {\n throw new ClientResponseError({\n url: this.client.buildURL(\"/api/logs/\"),\n status: 404,\n response: {\n code: 404,\n message: \"Missing required log id.\",\n data: {},\n },\n });\n }\n\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/logs/\" + encodeURIComponent(id), options);\n }\n\n /**\n * Returns logs statistics.\n *\n * @throws {ClientResponseError}\n */\n async getStats(options?: LogStatsOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/logs/stats\", options);\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface HealthCheckResponse {\n code: number;\n message: string;\n data: { [key: string]: any };\n}\n\nexport class HealthService extends BaseService {\n /**\n * Checks the health status of the api.\n *\n * @throws {ClientResponseError}\n */\n async check(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/health\", options);\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions, FileOptions, serializeQueryParams } from \"@/tools/options\";\n\nexport class FileService extends BaseService {\n /**\n * @deprecated Please replace with `pb.files.getURL()`.\n */\n getUrl(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n console.warn(\"Please replace pb.files.getUrl() with pb.files.getURL()\");\n return this.getURL(record, filename, queryParams);\n }\n\n /**\n * Builds and returns an absolute record file url for the provided filename.\n */\n getURL(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n if (\n !filename ||\n !record?.id ||\n !(record?.collectionId || record?.collectionName)\n ) {\n return \"\";\n }\n\n const parts = [];\n parts.push(\"api\");\n parts.push(\"files\");\n parts.push(encodeURIComponent(record.collectionId || record.collectionName));\n parts.push(encodeURIComponent(record.id));\n parts.push(encodeURIComponent(filename));\n\n let result = this.client.buildURL(parts.join(\"/\"));\n\n // normalize the download query param for consistency with the Dart sdk\n if (queryParams.download === false) {\n delete queryParams.download;\n }\n\n const params = serializeQueryParams(queryParams);\n if (params) {\n result += (result.includes(\"?\") ? \"&\" : \"?\") + params;\n }\n\n return result;\n }\n\n /**\n * Requests a new private file access token for the current auth model.\n *\n * @throws {ClientResponseError}\n */\n async getToken(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(\"/api/files/token\", options)\n .then((data) => data?.token || \"\");\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface BackupFileInfo {\n key: string;\n size: number;\n modified: string;\n}\n\nexport class BackupService extends BaseService {\n /**\n * Returns list with all available backup files.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: CommonOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/backups\", options);\n }\n\n /**\n * Initializes a new backup.\n *\n * @throws {ClientResponseError}\n */\n async create(basename: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n name: basename,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/backups\", options).then(() => true);\n }\n\n /**\n * Uploads an existing backup file.\n *\n * Example:\n *\n * ```js\n * await pb.backups.upload({\n * file: new Blob([...]),\n * });\n * ```\n *\n * @throws {ClientResponseError}\n */\n async upload(\n bodyParams: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client.send(\"/api/backups/upload\", options).then(() => true);\n }\n\n /**\n * Deletes a single backup file.\n *\n * @throws {ClientResponseError}\n */\n async delete(key: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(`/api/backups/${encodeURIComponent(key)}`, options)\n .then(() => true);\n }\n\n /**\n * Initializes an app data restore from an existing backup.\n *\n * @throws {ClientResponseError}\n */\n async restore(key: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(`/api/backups/${encodeURIComponent(key)}/restore`, options)\n .then(() => true);\n }\n\n /**\n * @deprecated Please use `getDownloadURL()`.\n */\n getDownloadUrl(token: string, key: string): string {\n console.warn(\n \"Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()\",\n );\n return this.getDownloadURL(token, key);\n }\n\n /**\n * Builds a download url for a single existing backup using a\n * superuser file token and the backup file key.\n *\n * The file token can be generated via `pb.files.getToken()`.\n */\n getDownloadURL(token: string, key: string): string {\n return this.client.buildURL(\n `/api/backups/${encodeURIComponent(key)}?token=${encodeURIComponent(token)}`,\n );\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface CronJob {\n id: string;\n expression: string;\n}\n\nexport class CronService extends BaseService {\n /**\n * Returns list with all registered cron jobs.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: CommonOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/crons\", options);\n }\n\n /**\n * Runs the specified cron job.\n *\n * @throws {ClientResponseError}\n */\n async run(jobId: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(`/api/crons/${encodeURIComponent(jobId)}`, options)\n .then(() => true);\n }\n}\n","/**\n * Checks if the specified value is a file (aka. File, Blob, RN file object).\n */\nexport function isFile(val: any): boolean {\n return (\n (typeof Blob !== \"undefined\" && val instanceof Blob) ||\n (typeof File !== \"undefined\" && val instanceof File) ||\n // check for React Native file object format\n // (see https://github.com/pocketbase/pocketbase/discussions/2002#discussioncomment-5254168)\n (val !== null &&\n typeof val === \"object\" &&\n val.uri &&\n ((typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\") ||\n (typeof global !== \"undefined\" && (global as any).HermesInternal)))\n );\n}\n\n/**\n * Loosely checks if the specified body is a FormData instance.\n */\nexport function isFormData(body: any): boolean {\n return (\n body &&\n // we are checking the constructor name because FormData\n // is not available natively in some environments and the\n // polyfill(s) may not be globally accessible\n (body.constructor?.name === \"FormData\" ||\n // fallback to global FormData instance check\n // note: this is needed because the constructor.name could be different in case of\n // custom global FormData implementation, eg. React Native on Android/iOS\n (typeof FormData !== \"undefined\" && body instanceof FormData))\n );\n}\n\n/**\n * Checks if the submitted body object has at least one Blob/File field value.\n */\nexport function hasFileField(body: { [key: string]: any }): boolean {\n for (const key in body) {\n const values = Array.isArray(body[key]) ? body[key] : [body[key]];\n for (const v of values) {\n if (isFile(v)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Converts analyzes the provided body and converts it to FormData\n * in case a plain object with File/Blob values is used.\n */\nexport function convertToFormDataIfNeeded(body: any): any {\n if (\n typeof FormData === \"undefined\" ||\n typeof body === \"undefined\" ||\n typeof body !== \"object\" ||\n body === null ||\n isFormData(body) ||\n !hasFileField(body)\n ) {\n return body;\n }\n\n const form = new FormData();\n\n for (const key in body) {\n const val = body[key];\n\n // skip undefined values for consistency with JSON.stringify\n // (see https://github.com/pocketbase/pocketbase/issues/6731#issuecomment-2812382827)\n if (typeof val === \"undefined\") {\n continue;\n }\n\n if (typeof val === \"object\" && !hasFileField({ data: val })) {\n // send json-like values as jsonPayload to avoid the implicit string value normalization\n let payload: { [key: string]: any } = {};\n payload[key] = val;\n form.append(\"@jsonPayload\", JSON.stringify(payload));\n } else {\n // in case of mixed string and file/blob\n const normalizedVal = Array.isArray(val) ? val : [val];\n for (let v of normalizedVal) {\n form.append(key, v);\n }\n }\n }\n\n return form;\n}\n\n/**\n * Converts the provided FormData instance into a plain object.\n *\n * For consistency with the server multipart/form-data inferring,\n * the following normalization rules are applied for plain multipart string values:\n * - \"true\" is converted to the json \"true\"\n * - \"false\" is converted to the json \"false\"\n * - numeric strings are converted to json number ONLY if the resulted\n * minimal number string representation is the same as the provided raw string\n * (aka. scientific notations, \"Infinity\", \"0.0\", \"0001\", etc. are kept as string)\n * - any other string (empty string too) is left as it is\n */\nexport function convertFormDataToObject(formData: FormData): { [key: string]: any } {\n let result: { [key: string]: any } = {};\n\n formData.forEach((v, k) => {\n if (k === \"@jsonPayload\" && typeof v == \"string\") {\n try {\n let parsed = JSON.parse(v);\n Object.assign(result, parsed);\n } catch (err) {\n console.warn(\"@jsonPayload error:\", err);\n }\n } else {\n if (typeof result[k] !== \"undefined\") {\n if (!Array.isArray(result[k])) {\n result[k] = [result[k]];\n }\n result[k].push(inferFormDataValue(v));\n } else {\n result[k] = inferFormDataValue(v);\n }\n }\n });\n\n return result;\n}\n\nconst inferNumberCharsRegex = /^[\\-\\.\\d]+$/;\n\nfunction inferFormDataValue(value: any): any {\n if (typeof value != \"string\") {\n return value;\n }\n\n if (value == \"true\") {\n return true;\n }\n\n if (value == \"false\") {\n return false;\n }\n\n // note: expects the provided raw string to match exactly with the minimal string representation of the parsed number\n if (\n (value[0] === \"-\" || (value[0] >= \"0\" && value[0] <= \"9\")) &&\n inferNumberCharsRegex.test(value)\n ) {\n let num = +value;\n if (\"\" + num === value) {\n return num;\n }\n }\n\n return value;\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { isFile, isFormData, convertFormDataToObject } from \"@/tools/formdata\";\nimport {\n SendOptions,\n RecordOptions,\n normalizeUnknownQueryParams,\n serializeQueryParams,\n} from \"@/tools/options\";\n\nexport interface BatchRequest {\n method: string;\n url: string;\n json?: { [key: string]: any };\n files?: { [key: string]: Array };\n headers?: { [key: string]: string };\n}\n\nexport interface BatchRequestResult {\n status: number;\n body: any;\n}\n\nexport class BatchService extends BaseService {\n private requests: Array = [];\n private subs: { [key: string]: SubBatchService } = {};\n\n /**\n * Starts constructing a batch request entry for the specified collection.\n */\n collection(collectionIdOrName: string): SubBatchService {\n if (!this.subs[collectionIdOrName]) {\n this.subs[collectionIdOrName] = new SubBatchService(\n this.requests,\n collectionIdOrName,\n );\n }\n\n return this.subs[collectionIdOrName];\n }\n\n /**\n * Sends the batch requests.\n *\n * @throws {ClientResponseError}\n */\n async send(options?: SendOptions): Promise> {\n const formData = new FormData();\n\n const jsonData = [];\n\n for (let i = 0; i < this.requests.length; i++) {\n const req = this.requests[i];\n\n jsonData.push({\n method: req.method,\n url: req.url,\n headers: req.headers,\n body: req.json,\n });\n\n if (req.files) {\n for (let key in req.files) {\n const files = req.files[key] || [];\n for (let file of files) {\n formData.append(\"requests.\" + i + \".\" + key, file);\n }\n }\n }\n }\n\n formData.append(\"@jsonPayload\", JSON.stringify({ requests: jsonData }));\n\n options = Object.assign(\n {\n method: \"POST\",\n body: formData,\n },\n options,\n );\n\n return this.client.send(\"/api/batch\", options);\n }\n}\n\nexport class SubBatchService {\n private requests: Array = [];\n private readonly collectionIdOrName: string;\n\n constructor(requests: Array, collectionIdOrName: string) {\n this.requests = requests;\n this.collectionIdOrName = collectionIdOrName;\n }\n\n /**\n * Registers a record upsert request into the current batch queue.\n *\n * The request will be executed as update if `bodyParams` have a valid existing record `id` value, otherwise - create.\n */\n upsert(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"PUT\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records\",\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record create request into the current batch queue.\n */\n create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"POST\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records\",\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record update request into the current batch queue.\n */\n update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"PATCH\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records/\" +\n encodeURIComponent(id),\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record delete request into the current batch queue.\n */\n delete(id: string, options?: SendOptions): void {\n options = Object.assign({}, options);\n\n const request: BatchRequest = {\n method: \"DELETE\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records/\" +\n encodeURIComponent(id),\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n private prepareRequest(request: BatchRequest, options: SendOptions) {\n normalizeUnknownQueryParams(options);\n\n request.headers = options.headers;\n request.json = {};\n request.files = {};\n\n // serialize query parameters\n // -----------------------------------------------------------\n if (typeof options.query !== \"undefined\") {\n const query = serializeQueryParams(options.query);\n if (query) {\n request.url += (request.url.includes(\"?\") ? \"&\" : \"?\") + query;\n }\n }\n\n // extract json and files body data\n // -----------------------------------------------------------\n let body = options.body;\n if (isFormData(body)) {\n body = convertFormDataToObject(body);\n }\n\n for (const key in body) {\n const val = body[key];\n\n if (isFile(val)) {\n request.files[key] = request.files[key] || [];\n request.files[key].push(val);\n } else if (Array.isArray(val)) {\n const foundFiles = [];\n const foundRegular = [];\n for (const v of val) {\n if (isFile(v)) {\n foundFiles.push(v);\n } else {\n foundRegular.push(v);\n }\n }\n\n if (foundFiles.length > 0 && foundFiles.length == val.length) {\n // only files\n // ---\n request.files[key] = request.files[key] || [];\n for (let file of foundFiles) {\n request.files[key].push(file);\n }\n } else {\n // empty or mixed array (both regular and File/Blob values)\n // ---\n request.json[key] = foundRegular;\n\n if (foundFiles.length > 0) {\n // add \"+\" to append if not already since otherwise\n // the existing regular files will be deleted\n // (the mixed values order is preserved only within their corresponding groups)\n let fileKey = key;\n if (!key.startsWith(\"+\") && !key.endsWith(\"+\")) {\n fileKey += \"+\";\n }\n\n request.files[fileKey] = request.files[fileKey] || [];\n for (let file of foundFiles) {\n request.files[fileKey].push(file);\n }\n }\n }\n } else {\n request.json[key] = val;\n }\n }\n }\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseAuthStore } from \"@/stores/BaseAuthStore\";\nimport { LocalAuthStore } from \"@/stores/LocalAuthStore\";\nimport { SettingsService } from \"@/services/SettingsService\";\nimport { RecordService } from \"@/services/RecordService\";\nimport { CollectionService } from \"@/services/CollectionService\";\nimport { LogService } from \"@/services/LogService\";\nimport { RealtimeService } from \"@/services/RealtimeService\";\nimport { HealthService } from \"@/services/HealthService\";\nimport { FileService } from \"@/services/FileService\";\nimport { BackupService } from \"@/services/BackupService\";\nimport { CronService } from \"@/services/CronService\";\nimport { BatchService } from \"@/services/BatchService\";\nimport { RecordModel } from \"@/tools/dtos\";\nimport {\n SendOptions,\n FileOptions,\n normalizeUnknownQueryParams,\n serializeQueryParams,\n} from \"@/tools/options\";\nimport { isFormData, convertToFormDataIfNeeded } from \"@/tools/formdata\";\n\nexport interface BeforeSendResult {\n [key: string]: any; // for backward compatibility\n url?: string;\n options?: { [key: string]: any };\n}\n\n/**\n * PocketBase JS Client.\n */\nexport default class Client {\n /**\n * The base PocketBase backend url address (eg. 'http://127.0.0.1.8090').\n */\n baseURL: string;\n\n /**\n * Legacy getter alias for baseURL.\n * @deprecated Please replace with baseURL.\n */\n get baseUrl(): string {\n return this.baseURL;\n }\n\n /**\n * Legacy setter alias for baseURL.\n * @deprecated Please replace with baseURL.\n */\n set baseUrl(v: string) {\n this.baseURL = v;\n }\n\n /**\n * Hook that get triggered right before sending the fetch request,\n * allowing you to inspect and modify the url and request options.\n *\n * For list of the possible options check https://developer.mozilla.org/en-US/docs/Web/API/fetch#options\n *\n * You can return a non-empty result object `{ url, options }` to replace the url and request options entirely.\n *\n * Example:\n * ```js\n * const pb = new PocketBase(\"https://example.com\")\n *\n * pb.beforeSend = function (url, options) {\n * options.headers = Object.assign({}, options.headers, {\n * 'X-Custom-Header': 'example',\n * })\n *\n * return { url, options }\n * }\n *\n * // use the created client as usual...\n * ```\n */\n beforeSend?: (\n url: string,\n options: SendOptions,\n ) => BeforeSendResult | Promise;\n\n /**\n * Hook that get triggered after successfully sending the fetch request,\n * allowing you to inspect/modify the response object and its parsed data.\n *\n * Returns the new Promise resolved `data` that will be returned to the client.\n *\n * Example:\n * ```js\n * const pb = new PocketBase(\"https://example.com\")\n *\n * pb.afterSend = function (response, data, options) {\n * if (response.status != 200) {\n * throw new ClientResponseError({\n * url: response.url,\n * status: response.status,\n * response: { ... },\n * })\n * }\n *\n * return data;\n * }\n *\n * // use the created client as usual...\n * ```\n */\n afterSend?: ((response: Response, data: any) => any) &\n ((response: Response, data: any, options: SendOptions) => any);\n\n /**\n * Optional language code (default to `en-US`) that will be sent\n * with the requests to the server as `Accept-Language` header.\n */\n lang: string;\n\n /**\n * A replaceable instance of the local auth store service.\n */\n authStore: BaseAuthStore;\n\n /**\n * An instance of the service that handles the **Settings APIs**.\n */\n readonly settings: SettingsService;\n\n /**\n * An instance of the service that handles the **Collection APIs**.\n */\n readonly collections: CollectionService;\n\n /**\n * An instance of the service that handles the **File APIs**.\n */\n readonly files: FileService;\n\n /**\n * An instance of the service that handles the **Log APIs**.\n */\n readonly logs: LogService;\n\n /**\n * An instance of the service that handles the **Realtime APIs**.\n */\n readonly realtime: RealtimeService;\n\n /**\n * An instance of the service that handles the **Health APIs**.\n */\n readonly health: HealthService;\n\n /**\n * An instance of the service that handles the **Backup APIs**.\n */\n readonly backups: BackupService;\n\n /**\n * An instance of the service that handles the **Cron APIs**.\n */\n readonly crons: CronService;\n\n private cancelControllers: { [key: string]: AbortController } = {};\n private recordServices: { [key: string]: RecordService } = {};\n private enableAutoCancellation: boolean = true;\n\n constructor(baseURL = \"/\", authStore?: BaseAuthStore | null, lang = \"en-US\") {\n this.baseURL = baseURL;\n this.lang = lang;\n\n if (authStore) {\n this.authStore = authStore;\n } else if (typeof window != \"undefined\" && !!(window as any).Deno) {\n // note: to avoid common security issues we fallback to runtime/memory store in case the code is running in Deno env\n this.authStore = new BaseAuthStore();\n } else {\n this.authStore = new LocalAuthStore();\n }\n\n // common services\n this.collections = new CollectionService(this);\n this.files = new FileService(this);\n this.logs = new LogService(this);\n this.settings = new SettingsService(this);\n this.realtime = new RealtimeService(this);\n this.health = new HealthService(this);\n this.backups = new BackupService(this);\n this.crons = new CronService(this);\n }\n\n /**\n * @deprecated\n * With PocketBase v0.23.0 admins are converted to a regular auth\n * collection named \"_superusers\", aka. you can use directly collection(\"_superusers\").\n */\n get admins(): RecordService {\n return this.collection(\"_superusers\");\n }\n\n /**\n * Creates a new batch handler for sending multiple transactional\n * create/update/upsert/delete collection requests in one network call.\n *\n * Example:\n * ```js\n * const batch = pb.createBatch();\n *\n * batch.collection(\"example1\").create({ ... })\n * batch.collection(\"example2\").update(\"RECORD_ID\", { ... })\n * batch.collection(\"example3\").delete(\"RECORD_ID\")\n * batch.collection(\"example4\").upsert({ ... })\n *\n * await batch.send()\n * ```\n */\n createBatch(): BatchService {\n return new BatchService(this);\n }\n\n /**\n * Returns the RecordService associated to the specified collection.\n */\n collection(idOrName: string): RecordService {\n if (!this.recordServices[idOrName]) {\n this.recordServices[idOrName] = new RecordService(this, idOrName);\n }\n\n return this.recordServices[idOrName];\n }\n\n /**\n * Globally enable or disable auto cancellation for pending duplicated requests.\n */\n autoCancellation(enable: boolean): Client {\n this.enableAutoCancellation = !!enable;\n\n return this;\n }\n\n /**\n * Cancels single request by its cancellation key.\n */\n cancelRequest(requestKey: string): Client {\n if (this.cancelControllers[requestKey]) {\n this.cancelControllers[requestKey].abort();\n delete this.cancelControllers[requestKey];\n }\n\n return this;\n }\n\n /**\n * Cancels all pending requests.\n */\n cancelAllRequests(): Client {\n for (let k in this.cancelControllers) {\n this.cancelControllers[k].abort();\n }\n\n this.cancelControllers = {};\n\n return this;\n }\n\n /**\n * Constructs a filter expression with placeholders populated from a parameters object.\n *\n * Placeholder parameters are defined with the `{:paramName}` notation.\n *\n * The following parameter values are supported:\n *\n * - `string` (_single quotes are autoescaped_)\n * - `number`\n * - `boolean`\n * - `Date` object (_stringified into the PocketBase datetime format_)\n * - `null`\n * - everything else is converted to a string using `JSON.stringify()`\n *\n * Example:\n *\n * ```js\n * pb.collection(\"example\").getFirstListItem(pb.filter(\n * 'title ~ {:title} && created >= {:created}',\n * { title: \"example\", created: new Date()}\n * ))\n * ```\n */\n filter(raw: string, params?: { [key: string]: any }): string {\n if (!params) {\n return raw;\n }\n\n for (let key in params) {\n let val = params[key];\n switch (typeof val) {\n case \"boolean\":\n case \"number\":\n val = \"\" + val;\n break;\n case \"string\":\n val = \"'\" + val.replace(/'/g, \"\\\\'\") + \"'\";\n break;\n default:\n if (val === null) {\n val = \"null\";\n } else if (val instanceof Date) {\n val = \"'\" + val.toISOString().replace(\"T\", \" \") + \"'\";\n } else {\n val = \"'\" + JSON.stringify(val).replace(/'/g, \"\\\\'\") + \"'\";\n }\n }\n raw = raw.replaceAll(\"{:\" + key + \"}\", val);\n }\n\n return raw;\n }\n\n /**\n * @deprecated Please use `pb.files.getURL()`.\n */\n getFileUrl(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n console.warn(\"Please replace pb.getFileUrl() with pb.files.getURL()\");\n return this.files.getURL(record, filename, queryParams);\n }\n\n /**\n * @deprecated Please use `pb.buildURL()`.\n */\n buildUrl(path: string): string {\n console.warn(\"Please replace pb.buildUrl() with pb.buildURL()\");\n return this.buildURL(path);\n }\n\n /**\n * Builds a full client url by safely concatenating the provided path.\n */\n buildURL(path: string): string {\n let url = this.baseURL;\n\n // construct an absolute base url if in a browser environment\n if (\n typeof window !== \"undefined\" &&\n !!window.location &&\n !url.startsWith(\"https://\") &&\n !url.startsWith(\"http://\")\n ) {\n url = window.location.origin?.endsWith(\"/\")\n ? window.location.origin.substring(0, window.location.origin.length - 1)\n : window.location.origin || \"\";\n\n if (!this.baseURL.startsWith(\"/\")) {\n url += window.location.pathname || \"/\";\n url += url.endsWith(\"/\") ? \"\" : \"/\";\n }\n\n url += this.baseURL;\n }\n\n // concatenate the path\n if (path) {\n url += url.endsWith(\"/\") ? \"\" : \"/\"; // append trailing slash if missing\n url += path.startsWith(\"/\") ? path.substring(1) : path;\n }\n\n return url;\n }\n\n /**\n * Sends an api http request.\n *\n * @throws {ClientResponseError}\n */\n async send(path: string, options: SendOptions): Promise {\n options = this.initSendOptions(path, options);\n\n // build url + path\n let url = this.buildURL(path);\n\n if (this.beforeSend) {\n const result = Object.assign({}, await this.beforeSend(url, options));\n if (\n typeof result.url !== \"undefined\" ||\n typeof result.options !== \"undefined\"\n ) {\n url = result.url || url;\n options = result.options || options;\n } else if (Object.keys(result).length) {\n // legacy behavior\n options = result as SendOptions;\n console?.warn &&\n console.warn(\n \"Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`.\",\n );\n }\n }\n\n // serialize the query parameters\n if (typeof options.query !== \"undefined\") {\n const query = serializeQueryParams(options.query);\n if (query) {\n url += (url.includes(\"?\") ? \"&\" : \"?\") + query;\n }\n delete options.query;\n }\n\n // ensures that the json body is serialized\n if (\n this.getHeader(options.headers, \"Content-Type\") == \"application/json\" &&\n options.body &&\n typeof options.body !== \"string\"\n ) {\n options.body = JSON.stringify(options.body);\n }\n\n // early throw an abort error in case the request was already cancelled\n const fetchFunc = options.fetch || fetch;\n\n // send the request\n return fetchFunc(url, options)\n .then(async (response) => {\n let data: any = {};\n\n try {\n data = await response.json();\n } catch (err: any) {\n // @todo map against the response content type\n // all api responses are expected to return json\n // with exception of the realtime events and 204\n if (\n options.signal?.aborted ||\n err?.name == \"AbortError\" ||\n err?.message == \"Aborted\"\n ) {\n throw err;\n }\n }\n\n if (this.afterSend) {\n data = await this.afterSend(response, data, options);\n }\n\n if (response.status >= 400) {\n throw new ClientResponseError({\n url: response.url,\n status: response.status,\n data: data,\n });\n }\n\n return data as T;\n })\n .catch((err) => {\n // wrap to normalize all errors\n throw new ClientResponseError(err);\n });\n }\n\n /**\n * Shallow copy the provided object and takes care to initialize\n * any options required to preserve the backward compatability.\n *\n * @param {SendOptions} options\n * @return {SendOptions}\n */\n private initSendOptions(path: string, options: SendOptions): SendOptions {\n options = Object.assign({ method: \"GET\" } as SendOptions, options);\n\n // auto convert the body to FormData, if needed\n options.body = convertToFormDataIfNeeded(options.body);\n\n // move unknown send options as query parameters\n normalizeUnknownQueryParams(options);\n\n // requestKey normalizations for backward-compatibility\n // ---\n options.query = Object.assign({}, options.params, options.query);\n if (typeof options.requestKey === \"undefined\") {\n if (options.$autoCancel === false || options.query.$autoCancel === false) {\n options.requestKey = null;\n } else if (options.$cancelKey || options.query.$cancelKey) {\n options.requestKey = options.$cancelKey || options.query.$cancelKey;\n }\n }\n // remove the deprecated special cancellation params from the other query params\n delete options.$autoCancel;\n delete options.query.$autoCancel;\n delete options.$cancelKey;\n delete options.query.$cancelKey;\n // ---\n\n // add the json header, if not explicitly set\n // (for FormData body the Content-Type header should be skipped since the boundary is autogenerated)\n if (\n this.getHeader(options.headers, \"Content-Type\") === null &&\n !isFormData(options.body)\n ) {\n options.headers = Object.assign({}, options.headers, {\n \"Content-Type\": \"application/json\",\n });\n }\n\n // add Accept-Language header, if not explicitly set\n if (this.getHeader(options.headers, \"Accept-Language\") === null) {\n options.headers = Object.assign({}, options.headers, {\n \"Accept-Language\": this.lang,\n });\n }\n\n // check if Authorization header can be added\n if (\n // has valid token\n this.authStore.token &&\n // auth header is not explicitly set\n this.getHeader(options.headers, \"Authorization\") === null\n ) {\n options.headers = Object.assign({}, options.headers, {\n Authorization: this.authStore.token,\n });\n }\n\n // handle auto cancellation for duplicated pending request\n if (this.enableAutoCancellation && options.requestKey !== null) {\n const requestKey = options.requestKey || (options.method || \"GET\") + path;\n\n delete options.requestKey;\n\n // cancel previous pending requests\n this.cancelRequest(requestKey);\n\n // @todo evaluate if a cleanup after the request is necessary\n // (check also authWithOAuth2 as it currently relies on the controller)\n const controller = new AbortController();\n this.cancelControllers[requestKey] = controller;\n options.signal = controller.signal;\n }\n\n return options;\n }\n\n /**\n * Extracts the header with the provided name in case-insensitive manner.\n * Returns `null` if no header matching the name is found.\n */\n private getHeader(\n headers: { [key: string]: string } | undefined,\n name: string,\n ): string | null {\n headers = headers || {};\n name = name.toLowerCase();\n\n for (let key in headers) {\n if (key.toLowerCase() == name) {\n return headers[key];\n }\n }\n\n return null;\n }\n}\n","import { BaseAuthStore, AuthRecord } from \"@/stores/BaseAuthStore\";\n\nexport type AsyncSaveFunc = (serializedPayload: string) => Promise;\n\nexport type AsyncClearFunc = () => Promise;\n\ntype queueFunc = () => Promise;\n\n/**\n * AsyncAuthStore is a helper auth store implementation\n * that could be used with any external async persistent layer\n * (key-value db, local file, etc.).\n *\n * Here is an example with the React Native AsyncStorage package:\n *\n * ```\n * import AsyncStorage from \"@react-native-async-storage/async-storage\";\n * import PocketBase, { AsyncAuthStore } from \"pocketbase\";\n *\n * const store = new AsyncAuthStore({\n * save: async (serialized) => AsyncStorage.setItem(\"pb_auth\", serialized),\n * initial: AsyncStorage.getItem(\"pb_auth\"),\n * });\n *\n * const pb = new PocketBase(\"https://example.com\", store)\n * ```\n */\nexport class AsyncAuthStore extends BaseAuthStore {\n private saveFunc: AsyncSaveFunc;\n private clearFunc?: AsyncClearFunc;\n private queue: Array = [];\n\n constructor(config: {\n // The async function that is called every time\n // when the auth store state needs to be persisted.\n save: AsyncSaveFunc;\n\n /// An *optional* async function that is called every time\n /// when the auth store needs to be cleared.\n ///\n /// If not explicitly set, `saveFunc` with empty data will be used.\n clear?: AsyncClearFunc;\n\n // An *optional* initial data to load into the store.\n initial?: string | Promise;\n }) {\n super();\n\n this.saveFunc = config.save;\n this.clearFunc = config.clear;\n\n this._enqueue(() => this._loadInitial(config.initial));\n }\n\n /**\n * @inheritdoc\n */\n save(token: string, record?: AuthRecord): void {\n super.save(token, record);\n\n let value = \"\";\n try {\n value = JSON.stringify({ token, record });\n } catch (err) {\n console.warn(\"AsyncAuthStore: failed to stringify the new state\");\n }\n\n this._enqueue(() => this.saveFunc(value));\n }\n\n /**\n * @inheritdoc\n */\n clear(): void {\n super.clear();\n\n if (this.clearFunc) {\n this._enqueue(() => this.clearFunc!());\n } else {\n this._enqueue(() => this.saveFunc(\"\"));\n }\n }\n\n /**\n * Initializes the auth store state.\n */\n private async _loadInitial(payload?: string | Promise) {\n try {\n payload = await payload;\n\n if (payload) {\n let parsed;\n if (typeof payload === \"string\") {\n parsed = JSON.parse(payload) || {};\n } else if (typeof payload === \"object\") {\n parsed = payload;\n }\n\n this.save(parsed.token || \"\", parsed.record || parsed.model || null);\n }\n } catch (_) {}\n }\n\n /**\n * Appends an async function to the queue.\n */\n private _enqueue(asyncCallback: () => Promise) {\n this.queue.push(asyncCallback);\n\n if (this.queue.length == 1) {\n this._dequeue();\n }\n }\n\n /**\n * Starts the queue processing.\n */\n private _dequeue() {\n if (!this.queue.length) {\n return;\n }\n\n this.queue[0]().finally(() => {\n this.queue.shift();\n\n if (!this.queue.length) {\n return;\n }\n\n this._dequeue();\n });\n }\n}\n"],"names":["ClientResponseError","Error","constructor","errData","super","this","url","status","response","isAbort","originalError","Object","setPrototypeOf","prototype","name","message","data","cause","includes","toJSON","fieldContentRegExp","cookieParse","str","options","result","decode","assign","defaultDecode","index","length","eqIdx","indexOf","endIdx","lastIndexOf","key","slice","trim","undefined","val","charCodeAt","_","cookieSerialize","opt","encode","defaultEncode","test","TypeError","value","maxAge","isNaN","isFinite","Math","floor","domain","path","expires","isDate","toString","call","Date","valueOf","toUTCString","httpOnly","secure","priority","toLowerCase","sameSite","decodeURIComponent","encodeURIComponent","isReactNative","navigator","product","global","HermesInternal","atobPolyfill","getTokenPayload","token","encodedPayload","split","map","c","join","JSON","parse","e","isTokenExpired","expirationThreshold","payload","keys","exp","now","atob","input","String","replace","bs","buffer","bc","idx","output","charAt","fromCharCode","defaultCookieKey","BaseAuthStore","baseToken","baseModel","_onChangeCallbacks","record","model","isValid","isSuperuser","type","collectionName","collectionId","isAdmin","console","warn","isAuthRecord","save","triggerChange","clear","loadFromCookie","cookie","rawData","Array","isArray","exportToCookie","defaultOptions","stringify","resultLength","Blob","size","id","email","extraProps","prop","onChange","callback","fireImmediately","push","i","splice","LocalAuthStore","storageKey","storageFallback","_bindStorageEvent","_storageGet","_storageSet","_storageRemove","window","localStorage","rawValue","getItem","normalizedVal","setItem","removeItem","addEventListener","BaseService","client","SettingsService","getAll","method","send","update","bodyParams","body","testS3","filesystem","then","testEmail","collectionIdOrName","toEmail","emailTemplate","template","collection","generateAppleClientSecret","clientId","teamId","keyId","privateKey","duration","knownSendOptionsKeys","normalizeUnknownQueryParams","query","serializeQueryParams","params","encodedKey","arrValue","v","prepareQueryParamValue","toISOString","RealtimeService","eventSource","subscriptions","lastSentSubscriptions","maxConnectTimeout","reconnectAttempts","maxReconnectAttempts","Infinity","predefinedReconnectIntervals","pendingConnects","isConnected","subscribe","topic","serialized","headers","listener","msgEvent","submitSubscriptions","connect","async","unsubscribeByTopicAndListener","unsubscribe","needToSubmit","subs","getSubscriptionsByTopic","hasSubscriptionListeners","removeEventListener","disconnect","unsubscribeByPrefix","keyPrefix","hasAtleastOneTopic","startsWith","exist","keyToCheck","addAllSubscriptionListeners","getNonEmptySubscriptionKeys","requestKey","getSubscriptionsCancelKey","catch","err","removeAllSubscriptionListeners","Promise","resolve","reject","initConnect","clearTimeout","connectTimeoutId","setTimeout","connectErrorHandler","EventSource","buildURL","onerror","lastEventId","retries","hasUnsentSubscriptions","p","reconnectTimeoutId","connectSubs","latestTopics","t","timeout","fromReconnect","onDisconnect","cancelRequest","close","CrudService","getFullList","batchOrqueryParams","_getFullList","batch","getList","page","perPage","baseCrudPath","responseData","items","item","getFirstListItem","filter","skipTotal","code","getOne","create","batchSize","request","list","concat","normalizeLegacyOptionsArgs","legacyWarn","baseOptions","bodyOrOptions","hasQuery","resetAutoRefresh","_resetAutoRefresh","RecordService","baseCollectionPath","isSuperusers","realtime","batchOrOptions","authStore","authExpand","expand","authRecord","delete","success","authResponse","listAuthMethods","fields","authWithPassword","usernameOrEmail","password","autoRefreshThreshold","identity","autoRefresh","authData","registerAutoRefresh","threshold","refreshFunc","reauthenticateFunc","oldBeforeSend","beforeSend","oldModel","unsubStoreChange","newToken","sendOptions","oldToken","authRefresh","authWithOAuth2Code","provider","codeVerifier","redirectURL","createData","authWithOAuth2","args","config","eagerDefaultPopup","urlCallback","openBrowserPopup","cleanup","requestKeyOptions","authMethods","oauth2","providers","find","cancelController","signal","onabort","activeSubscriptions","oldState","state","error","scopes","replacements","_replaceQueryParams","authURL","location","href","requestPasswordReset","confirmPasswordReset","passwordResetToken","passwordConfirm","requestVerification","confirmVerification","verificationToken","verified","requestEmailChange","newEmail","confirmEmailChange","emailChangeToken","listExternalAuths","recordId","unlinkExternalAuth","ea","requestOTP","authWithOTP","otpId","impersonate","Authorization","Client","baseURL","lang","urlPath","substring","parsedParams","rawParams","param","pair","hasOwnProperty","open","width","height","windowWidth","innerWidth","windowHeight","innerHeight","left","top","CollectionService","import","collections","deleteMissing","getScaffolds","truncate","LogService","getStats","HealthService","check","FileService","getUrl","filename","queryParams","getURL","parts","download","getToken","BackupService","basename","upload","restore","getDownloadUrl","getDownloadURL","CronService","run","jobId","isFile","File","uri","isFormData","FormData","hasFileField","values","inferNumberCharsRegex","inferFormDataValue","num","BatchService","requests","SubBatchService","formData","jsonData","req","json","files","file","append","upsert","prepareRequest","convertFormDataToObject","forEach","k","parsed","foundFiles","foundRegular","fileKey","endsWith","baseUrl","cancelControllers","recordServices","enableAutoCancellation","Deno","logs","settings","health","backups","crons","admins","createBatch","idOrName","autoCancellation","enable","abort","cancelAllRequests","raw","replaceAll","getFileUrl","buildUrl","origin","pathname","initSendOptions","getHeader","fetch","aborted","afterSend","convertToFormDataIfNeeded","form","$autoCancel","$cancelKey","controller","AbortController","AsyncAuthStore","queue","saveFunc","clearFunc","_enqueue","_loadInitial","initial","asyncCallback","_dequeue","finally","shift"],"mappings":"AAIM,MAAOA,4BAA4BC,MAOrC,WAAAC,CAAYC,GACRC,MAAM,uBAPVC,KAAGC,IAAW,GACdD,KAAME,OAAW,EACjBF,KAAQG,SAA2B,GACnCH,KAAOI,SAAY,EACnBJ,KAAaK,cAAQ,KAOjBC,OAAOC,eAAeP,KAAML,oBAAoBa,WAEhC,OAAZV,GAAuC,iBAAZA,IAC3BE,KAAKK,cAAgBP,EAAQO,cAC7BL,KAAKC,IAA6B,iBAAhBH,EAAQG,IAAmBH,EAAQG,IAAM,GAC3DD,KAAKE,OAAmC,iBAAnBJ,EAAQI,OAAsBJ,EAAQI,OAAS,EAIpEF,KAAKI,UACCN,EAAQM,SACO,eAAjBN,EAAQW,MACY,YAApBX,EAAQY,QAEa,OAArBZ,EAAQK,UAAiD,iBAArBL,EAAQK,SAC5CH,KAAKG,SAAWL,EAAQK,SACA,OAAjBL,EAAQa,MAAyC,iBAAjBb,EAAQa,KAC/CX,KAAKG,SAAWL,EAAQa,KAExBX,KAAKG,SAAW,IAInBH,KAAKK,eAAmBP,aAAmBH,sBAC5CK,KAAKK,cAAgBP,GAGzBE,KAAKS,KAAO,uBAAyBT,KAAKE,OAC1CF,KAAKU,QAAUV,KAAKG,UAAUO,QACzBV,KAAKU,UACFV,KAAKI,QACLJ,KAAKU,QACD,yIACGV,KAAKK,eAAeO,OAAOF,SAASG,SAAS,oBACpDb,KAAKU,QACD,qJAEJV,KAAKU,QAAU,yBAMvBV,KAAKY,MAAQZ,KAAKK,aACrB,CAKD,QAAIM,GACA,OAAOX,KAAKG,QACf,CAMD,MAAAW,GACI,MAAO,IAAKd,KACf,EC7DL,MAAMe,EAAqB,wCAUX,SAAAC,YAAYC,EAAaC,GACrC,MAAMC,EAAiC,CAAA,EAEvC,GAAmB,iBAARF,EACP,OAAOE,EAGX,MACMC,EADMd,OAAOe,OAAO,CAAA,EAAIH,GAAW,CAAA,GACtBE,QAAUE,cAE7B,IAAIC,EAAQ,EACZ,KAAOA,EAAQN,EAAIO,QAAQ,CACvB,MAAMC,EAAQR,EAAIS,QAAQ,IAAKH,GAG/B,IAAe,IAAXE,EACA,MAGJ,IAAIE,EAASV,EAAIS,QAAQ,IAAKH,GAE9B,IAAgB,IAAZI,EACAA,EAASV,EAAIO,YACV,GAAIG,EAASF,EAAO,CAEvBF,EAAQN,EAAIW,YAAY,IAAKH,EAAQ,GAAK,EAC1C,QACH,CAED,MAAMI,EAAMZ,EAAIa,MAAMP,EAAOE,GAAOM,OAGpC,QAAIC,IAAcb,EAAOU,GAAM,CAC3B,IAAII,EAAMhB,EAAIa,MAAML,EAAQ,EAAGE,GAAQI,OAGb,KAAtBE,EAAIC,WAAW,KACfD,EAAMA,EAAIH,MAAM,GAAI,IAGxB,IACIX,EAAOU,GAAOT,EAAOa,EACxB,CAAC,MAAOE,GACLhB,EAAOU,GAAOI,CACjB,CACJ,CAEDV,EAAQI,EAAS,CACpB,CAED,OAAOR,CACX,UAwBgBiB,gBACZ3B,EACAwB,EACAf,GAEA,MAAMmB,EAAM/B,OAAOe,OAAO,CAAA,EAAIH,GAAW,CAAA,GACnCoB,EAASD,EAAIC,QAAUC,cAE7B,IAAKxB,EAAmByB,KAAK/B,GACzB,MAAM,IAAIgC,UAAU,4BAGxB,MAAMC,EAAQJ,EAAOL,GAErB,GAAIS,IAAU3B,EAAmByB,KAAKE,GAClC,MAAM,IAAID,UAAU,2BAGxB,IAAItB,EAASV,EAAO,IAAMiC,EAE1B,GAAkB,MAAdL,EAAIM,OAAgB,CACpB,MAAMA,EAASN,EAAIM,OAAS,EAE5B,GAAIC,MAAMD,KAAYE,SAASF,GAC3B,MAAM,IAAIF,UAAU,4BAGxBtB,GAAU,aAAe2B,KAAKC,MAAMJ,EACvC,CAED,GAAIN,EAAIW,OAAQ,CACZ,IAAKjC,EAAmByB,KAAKH,EAAIW,QAC7B,MAAM,IAAIP,UAAU,4BAGxBtB,GAAU,YAAckB,EAAIW,MAC/B,CAED,GAAIX,EAAIY,KAAM,CACV,IAAKlC,EAAmByB,KAAKH,EAAIY,MAC7B,MAAM,IAAIR,UAAU,0BAGxBtB,GAAU,UAAYkB,EAAIY,IAC7B,CAED,GAAIZ,EAAIa,QAAS,CACb,IA6ER,SAASC,OAAOlB,GACZ,MAA+C,kBAAxC3B,OAAOE,UAAU4C,SAASC,KAAKpB,IAA4BA,aAAeqB,IACrF,CA/EaH,CAAOd,EAAIa,UAAYN,MAAMP,EAAIa,QAAQK,WAC1C,MAAM,IAAId,UAAU,6BAGxBtB,GAAU,aAAekB,EAAIa,QAAQM,aACxC,CAUD,GARInB,EAAIoB,WACJtC,GAAU,cAGVkB,EAAIqB,SACJvC,GAAU,YAGVkB,EAAIsB,SAAU,CAId,OAF4B,iBAAjBtB,EAAIsB,SAAwBtB,EAAIsB,SAASC,cAAgBvB,EAAIsB,UAGpE,IAAK,MACDxC,GAAU,iBACV,MACJ,IAAK,SACDA,GAAU,oBACV,MACJ,IAAK,OACDA,GAAU,kBACV,MACJ,QACI,MAAM,IAAIsB,UAAU,8BAE/B,CAED,GAAIJ,EAAIwB,SAAU,CAId,OAF4B,iBAAjBxB,EAAIwB,SAAwBxB,EAAIwB,SAASD,cAAgBvB,EAAIwB,UAGpE,KAAK,EACD1C,GAAU,oBACV,MACJ,IAAK,MACDA,GAAU,iBACV,MACJ,IAAK,SACDA,GAAU,oBACV,MACJ,IAAK,OACDA,GAAU,kBACV,MACJ,QACI,MAAM,IAAIsB,UAAU,8BAE/B,CAED,OAAOtB,CACX,CAMA,SAASG,cAAcW,GACnB,OAA6B,IAAtBA,EAAIP,QAAQ,KAAcoC,mBAAmB7B,GAAOA,CAC/D,CAKA,SAASM,cAAcN,GACnB,OAAO8B,mBAAmB9B,EAC9B,CCzNA,MAAM+B,EACoB,oBAAdC,WAAmD,gBAAtBA,UAAUC,SAC5B,oBAAXC,QAA2BA,OAAeC,eAEtD,IAAIC,EA2CE,SAAUC,gBAAgBC,GAC5B,GAAIA,EACA,IACI,MAAMC,EAAiBV,mBACnBO,EAAaE,EAAME,MAAM,KAAK,IACzBA,MAAM,IACNC,KAAI,SAAUC,GACX,MAAO,KAAO,KAAOA,EAAEzC,WAAW,GAAGkB,SAAS,KAAKtB,OAAO,EAC9D,IACC8C,KAAK,KAGd,OAAOC,KAAKC,MAAMN,IAAmB,CAAA,CACxC,CAAC,MAAOO,GAAK,CAGlB,MAAO,EACX,UAUgBC,eAAeT,EAAeU,EAAsB,GAChE,IAAIC,EAAUZ,gBAAgBC,GAE9B,QACIjE,OAAO6E,KAAKD,GAAS1D,OAAS,KAC5B0D,EAAQE,KAAOF,EAAQE,IAAMH,EAAsB3B,KAAK+B,MAAQ,KAM1E,CAzEIhB,EAPgB,mBAATiB,MAAwBtB,EAOfuB,IAGZ,IAAItE,EAAMuE,OAAOD,GAAOE,QAAQ,MAAO,IACvC,GAAIxE,EAAIO,OAAS,GAAK,EAClB,MAAM,IAAI5B,MACN,qEAIR,IAEI,IAAY8F,EAAIC,EAAZC,EAAK,EAAeC,EAAM,EAAGC,EAAS,GAEzCH,EAAS1E,EAAI8E,OAAOF,MAEpBF,IACCD,EAAKE,EAAK,EAAkB,GAAbF,EAAkBC,EAASA,EAG5CC,IAAO,GACAE,GAAUN,OAAOQ,aAAa,IAAON,KAAS,EAAIE,EAAM,IACzD,EAGND,EAxBU,oEAwBKjE,QAAQiE,GAG3B,OAAOG,CAAM,EAlCFR,KCGnB,MAAMW,EAAmB,gBAQZC,cAAb,WAAArG,GACcG,KAASmG,UAAW,GACpBnG,KAASoG,UAAe,KAE1BpG,KAAkBqG,mBAA6B,EAuN1D,CAlNG,SAAI9B,GACA,OAAOvE,KAAKmG,SACf,CAKD,UAAIG,GACA,OAAOtG,KAAKoG,SACf,CAKD,SAAIG,GACA,OAAOvG,KAAKoG,SACf,CAKD,WAAII,GACA,OAAQxB,eAAehF,KAAKuE,MAC/B,CAOD,eAAIkC,GACA,IAAIvB,EAAUZ,gBAAgBtE,KAAKuE,OAEnC,MACoB,QAAhBW,EAAQwB,OACwB,eAA/B1G,KAAKsG,QAAQK,iBAGR3G,KAAKsG,QAAQK,gBACa,kBAAxBzB,EAAQ0B,aAEvB,CAKD,WAAIC,GAIA,OAHAC,QAAQC,KACJ,sIAEG/G,KAAKyG,WACf,CAKD,gBAAIO,GAIA,OAHAF,QAAQC,KACJ,4IAEuC,QAApCzC,gBAAgBtE,KAAKuE,OAAOmC,OAAmB1G,KAAKyG,WAC9D,CAKD,IAAAQ,CAAK1C,EAAe+B,GAChBtG,KAAKmG,UAAY5B,GAAS,GAC1BvE,KAAKoG,UAAYE,GAAU,KAE3BtG,KAAKkH,eACR,CAKD,KAAAC,GACInH,KAAKmG,UAAY,GACjBnG,KAAKoG,UAAY,KACjBpG,KAAKkH,eACR,CA0BD,cAAAE,CAAeC,EAAgBxF,EAAMoE,GACjC,MAAMqB,EAAUtG,YAAYqG,GAAU,IAAIxF,IAAQ,GAElD,IAAIlB,EAA+B,CAAA,EACnC,IACIA,EAAOkE,KAAKC,MAAMwC,IAEE,cAAT3G,GAAiC,iBAATA,GAAqB4G,MAAMC,QAAQ7G,MAClEA,EAAO,CAAA,EAEd,CAAC,MAAOwB,GAAK,CAEdnC,KAAKiH,KAAKtG,EAAK4D,OAAS,GAAI5D,EAAK2F,QAAU3F,EAAK4F,OAAS,KAC5D,CAgBD,cAAAkB,CAAevG,EAA4BW,EAAMoE,GAC7C,MAAMyB,EAAmC,CACrChE,QAAQ,EACRG,UAAU,EACVJ,UAAU,EACVR,KAAM,KAIJiC,EAAUZ,gBAAgBtE,KAAKuE,OAEjCmD,EAAexE,QADfgC,GAASE,IACgB,IAAI9B,KAAmB,IAAd4B,EAAQE,KAEjB,IAAI9B,KAAK,cAItCpC,EAAUZ,OAAOe,OAAO,CAAE,EAAEqG,EAAgBxG,GAE5C,MAAMoG,EAAU,CACZ/C,MAAOvE,KAAKuE,MACZ+B,OAAQtG,KAAKsG,OAASzB,KAAKC,MAAMD,KAAK8C,UAAU3H,KAAKsG,SAAW,MAGpE,IAAInF,EAASiB,gBAAgBP,EAAKgD,KAAK8C,UAAUL,GAAUpG,GAE3D,MAAM0G,EACc,oBAATC,KAAuB,IAAIA,KAAK,CAAC1G,IAAS2G,KAAO3G,EAAOK,OAGnE,GAAI8F,EAAQhB,QAAUsB,EAAe,KAAM,CACvCN,EAAQhB,OAAS,CAAEyB,GAAIT,EAAQhB,QAAQyB,GAAIC,MAAOV,EAAQhB,QAAQ0B,OAClE,MAAMC,EAAa,CAAC,eAAgB,iBAAkB,YACtD,IAAK,MAAMC,KAAQlI,KAAKsG,OAChB2B,EAAWpH,SAASqH,KACpBZ,EAAQhB,OAAO4B,GAAQlI,KAAKsG,OAAO4B,IAG3C/G,EAASiB,gBAAgBP,EAAKgD,KAAK8C,UAAUL,GAAUpG,EAC1D,CAED,OAAOC,CACV,CAUD,QAAAgH,CAASC,EAA6BC,GAAkB,GAOpD,OANArI,KAAKqG,mBAAmBiC,KAAKF,GAEzBC,GACAD,EAASpI,KAAKuE,MAAOvE,KAAKsG,QAGvB,KACH,IAAK,IAAIiC,EAAIvI,KAAKqG,mBAAmB7E,OAAS,EAAG+G,GAAK,EAAGA,IACrD,GAAIvI,KAAKqG,mBAAmBkC,IAAMH,EAG9B,cAFOpI,KAAKqG,mBAAmBkC,QAC/BvI,KAAKqG,mBAAmBmC,OAAOD,EAAG,EAGzC,CAER,CAES,aAAArB,GACN,IAAK,MAAMkB,KAAYpI,KAAKqG,mBACxB+B,GAAYA,EAASpI,KAAKuE,MAAOvE,KAAKsG,OAE7C,ECtOC,MAAOmC,uBAAuBvC,cAIhC,WAAArG,CAAY6I,EAAa,mBACrB3I,QAJIC,KAAe2I,gBAA2B,GAM9C3I,KAAK0I,WAAaA,EAElB1I,KAAK4I,mBACR,CAKD,SAAIrE,GAGA,OAFavE,KAAK6I,YAAY7I,KAAK0I,aAAe,IAEtCnE,OAAS,EACxB,CAKD,UAAI+B,GACA,MAAM3F,EAAOX,KAAK6I,YAAY7I,KAAK0I,aAAe,GAElD,OAAO/H,EAAK2F,QAAU3F,EAAK4F,OAAS,IACvC,CAKD,SAAIA,GACA,OAAOvG,KAAKsG,MACf,CAKD,IAAAW,CAAK1C,EAAe+B,GAChBtG,KAAK8I,YAAY9I,KAAK0I,WAAY,CAC9BnE,MAAOA,EACP+B,OAAQA,IAGZvG,MAAMkH,KAAK1C,EAAO+B,EACrB,CAKD,KAAAa,GACInH,KAAK+I,eAAe/I,KAAK0I,YAEzB3I,MAAMoH,OACT,CAUO,WAAA0B,CAAYhH,GAChB,GAAsB,oBAAXmH,QAA0BA,QAAQC,aAAc,CACvD,MAAMC,EAAWF,OAAOC,aAAaE,QAAQtH,IAAQ,GACrD,IACI,OAAOgD,KAAKC,MAAMoE,EACrB,CAAC,MAAOnE,GAEL,OAAOmE,CACV,CACJ,CAGD,OAAOlJ,KAAK2I,gBAAgB9G,EAC/B,CAMO,WAAAiH,CAAYjH,EAAaa,GAC7B,GAAsB,oBAAXsG,QAA0BA,QAAQC,aAAc,CAEvD,IAAIG,EAAgB1G,EACC,iBAAVA,IACP0G,EAAgBvE,KAAK8C,UAAUjF,IAEnCsG,OAAOC,aAAaI,QAAQxH,EAAKuH,EACpC,MAEGpJ,KAAK2I,gBAAgB9G,GAAOa,CAEnC,CAKO,cAAAqG,CAAelH,GAEG,oBAAXmH,QAA0BA,QAAQC,cACzCD,OAAOC,cAAcK,WAAWzH,UAI7B7B,KAAK2I,gBAAgB9G,EAC/B,CAKO,iBAAA+G,GAEkB,oBAAXI,QACNA,QAAQC,cACRD,OAAOO,kBAKZP,OAAOO,iBAAiB,WAAYxE,IAChC,GAAIA,EAAElD,KAAO7B,KAAK0I,WACd,OAGJ,MAAM/H,EAAOX,KAAK6I,YAAY7I,KAAK0I,aAAe,GAElD3I,MAAMkH,KAAKtG,EAAK4D,OAAS,GAAI5D,EAAK2F,QAAU3F,EAAK4F,OAAS,KAAK,GAEtE,QCtIiBiD,YAGlB,WAAA3J,CAAY4J,GACRzJ,KAAKyJ,OAASA,CACjB,ECHC,MAAOC,wBAAwBF,YAMjC,YAAMG,CAAOzI,GAQT,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,gBAAiB3I,EAC5C,CAOD,YAAM4I,CACFC,EACA7I,GAUA,OARAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,QACRI,KAAMD,GAEV7I,GAGGlB,KAAKyJ,OAAOI,KAAK,gBAAiB3I,EAC5C,CASD,YAAM+I,CACFC,EAAqB,UACrBhJ,GAYA,OAVAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OACRI,KAAM,CACFE,WAAYA,IAGpBhJ,GAGGlB,KAAKyJ,OAAOI,KAAK,wBAAyB3I,GAASiJ,MAAK,KAAM,GACxE,CAYD,eAAMC,CACFC,EACAC,EACAC,EACArJ,GAcA,OAZAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OACRI,KAAM,CACFhC,MAAOsC,EACPE,SAAUD,EACVE,WAAYJ,IAGpBnJ,GAGGlB,KAAKyJ,OAAOI,KAAK,2BAA4B3I,GAASiJ,MAAK,KAAM,GAC3E,CAOD,+BAAMO,CACFC,EACAC,EACAC,EACAC,EACAC,EACA7J,GAgBA,OAdAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OACRI,KAAM,CACFW,WACAC,SACAC,QACAC,aACAC,aAGR7J,GAGGlB,KAAKyJ,OAAOI,KAAK,6CAA8C3I,EACzE,EClBL,MAAM8J,EAAuB,CACzB,aACA,aACA,cACA,QACA,UACA,OACA,QACA,SAEA,QACA,cACA,UACA,YACA,YACA,SACA,OACA,WACA,WACA,iBACA,SACA,UAIE,SAAUC,4BAA4B/J,GACxC,GAAKA,EAAL,CAIAA,EAAQgK,MAAQhK,EAAQgK,OAAS,CAAA,EACjC,IAAK,IAAIrJ,KAAOX,EACR8J,EAAqBnK,SAASgB,KAIlCX,EAAQgK,MAAMrJ,GAAOX,EAAQW,UACtBX,EAAQW,GATlB,CAWL,CAEM,SAAUsJ,qBAAqBC,GACjC,MAAMjK,EAAwB,GAE9B,IAAK,MAAMU,KAAOuJ,EAAQ,CACtB,MAAMC,EAAatH,mBAAmBlC,GAChCyJ,EAAW/D,MAAMC,QAAQ4D,EAAOvJ,IAAQuJ,EAAOvJ,GAAO,CAACuJ,EAAOvJ,IAEpE,IAAK,IAAI0J,KAAKD,EACVC,EAAIC,uBAAuBD,GACjB,OAANA,GAGJpK,EAAOmH,KAAK+C,EAAa,IAAME,EAEtC,CAED,OAAOpK,EAAOyD,KAAK,IACvB,CAGA,SAAS4G,uBAAuB9I,GAC5B,OAAIA,QACO,KAGPA,aAAiBY,KACVS,mBAAmBrB,EAAM+I,cAAchG,QAAQ,IAAK,MAG1C,iBAAV/C,EACAqB,mBAAmBc,KAAK8C,UAAUjF,IAGtCqB,mBAAmBrB,EAC9B,CC3KM,MAAOgJ,wBAAwBlC,YAArC,WAAA3J,uBACIG,KAAQ2K,SAAW,GAEX3K,KAAW2L,YAAuB,KAClC3L,KAAa4L,cAAkB,GAC/B5L,KAAqB6L,sBAAkB,GAEvC7L,KAAiB8L,kBAAW,KAE5B9L,KAAiB+L,kBAAW,EAC5B/L,KAAoBgM,qBAAWC,IAC/BjM,KAAAkM,6BAA8C,CAClD,IAAK,IAAK,IAAK,IAAM,KAAM,KAAM,KAE7BlM,KAAemM,gBAA4B,EAgetD,CA3dG,eAAIC,GACA,QAASpM,KAAK2L,eAAiB3L,KAAK2K,WAAa3K,KAAKmM,gBAAgB3K,MACzE,CAwBD,eAAM6K,CACFC,EACAlE,EACAlH,GAEA,IAAKoL,EACD,MAAM,IAAI1M,MAAM,sBAGpB,IAAIiC,EAAMyK,EAGV,GAAIpL,EAAS,CAET+J,4BADA/J,EAAUZ,OAAOe,OAAO,CAAE,EAAEH,IAE5B,MAAMqL,EACF,WACAxI,mBACIc,KAAK8C,UAAU,CAAEuD,MAAOhK,EAAQgK,MAAOsB,QAAStL,EAAQsL,WAEhE3K,IAAQA,EAAIhB,SAAS,KAAO,IAAM,KAAO0L,CAC5C,CAED,MAAME,SAAW,SAAU1H,GACvB,MAAM2H,EAAW3H,EAEjB,IAAIpE,EACJ,IACIA,EAAOkE,KAAKC,MAAM4H,GAAU/L,KAC/B,CAAC,MAAQ,CAEVyH,EAASzH,GAAQ,CAAA,EACrB,EAmBA,OAhBKX,KAAK4L,cAAc/J,KACpB7B,KAAK4L,cAAc/J,GAAO,IAE9B7B,KAAK4L,cAAc/J,GAAKyG,KAAKmE,UAExBzM,KAAKoM,YAGoC,IAAnCpM,KAAK4L,cAAc/J,GAAKL,aAEzBxB,KAAK2M,sBAGX3M,KAAK2L,aAAapC,iBAAiB1H,EAAK4K,gBANlCzM,KAAK4M,UASRC,SACI7M,KAAK8M,8BAA8BR,EAAOG,SAExD,CAaD,iBAAMM,CAAYT,GACd,IAAIU,GAAe,EAEnB,GAAKV,EAGE,CAEH,MAAMW,EAAOjN,KAAKkN,wBAAwBZ,GAC1C,IAAK,IAAIzK,KAAOoL,EACZ,GAAKjN,KAAKmN,yBAAyBtL,GAAnC,CAIA,IAAK,IAAI4K,KAAYzM,KAAK4L,cAAc/J,GACpC7B,KAAK2L,aAAayB,oBAAoBvL,EAAK4K,UAExCzM,KAAK4L,cAAc/J,GAGrBmL,IACDA,GAAe,EATlB,CAYR,MAnBGhN,KAAK4L,cAAgB,GAqBpB5L,KAAKmN,2BAGCH,SACDhN,KAAK2M,sBAFX3M,KAAKqN,YAIZ,CAUD,yBAAMC,CAAoBC,GACtB,IAAIC,GAAqB,EACzB,IAAK,IAAI3L,KAAO7B,KAAK4L,cAEjB,IAAM/J,EAAM,KAAK4L,WAAWF,GAA5B,CAIAC,GAAqB,EACrB,IAAK,IAAIf,KAAYzM,KAAK4L,cAAc/J,GACpC7B,KAAK2L,aAAayB,oBAAoBvL,EAAK4K,UAExCzM,KAAK4L,cAAc/J,EANzB,CASA2L,IAIDxN,KAAKmN,iCAECnN,KAAK2M,sBAGX3M,KAAKqN,aAEZ,CAWD,mCAAMP,CACFR,EACAG,GAEA,IAAIO,GAAe,EAEnB,MAAMC,EAAOjN,KAAKkN,wBAAwBZ,GAC1C,IAAK,IAAIzK,KAAOoL,EAAM,CAClB,IACK1F,MAAMC,QAAQxH,KAAK4L,cAAc/J,MACjC7B,KAAK4L,cAAc/J,GAAKL,OAEzB,SAGJ,IAAIkM,GAAQ,EACZ,IAAK,IAAInF,EAAIvI,KAAK4L,cAAc/J,GAAKL,OAAS,EAAG+G,GAAK,EAAGA,IACjDvI,KAAK4L,cAAc/J,GAAK0G,KAAOkE,IAInCiB,GAAQ,SACD1N,KAAK4L,cAAc/J,GAAK0G,GAC/BvI,KAAK4L,cAAc/J,GAAK2G,OAAOD,EAAG,GAClCvI,KAAK2L,aAAayB,oBAAoBvL,EAAK4K,IAE1CiB,IAKA1N,KAAK4L,cAAc/J,GAAKL,eAClBxB,KAAK4L,cAAc/J,GAIzBmL,GAAiBhN,KAAKmN,yBAAyBtL,KAChDmL,GAAe,GAEtB,CAEIhN,KAAKmN,2BAGCH,SACDhN,KAAK2M,sBAFX3M,KAAKqN,YAIZ,CAEO,wBAAAF,CAAyBQ,GAI7B,GAHA3N,KAAK4L,cAAgB5L,KAAK4L,eAAiB,CAAA,EAGvC+B,EACA,QAAS3N,KAAK4L,cAAc+B,IAAanM,OAI7C,IAAK,IAAIK,KAAO7B,KAAK4L,cACjB,GAAM5L,KAAK4L,cAAc/J,IAAML,OAC3B,OAAO,EAIf,OAAO,CACV,CAEO,yBAAMmL,GACV,GAAK3M,KAAK2K,SASV,OAJA3K,KAAK4N,8BAEL5N,KAAK6L,sBAAwB7L,KAAK6N,8BAE3B7N,KAAKyJ,OACPI,KAAK,gBAAiB,CACnBD,OAAQ,OACRI,KAAM,CACFW,SAAU3K,KAAK2K,SACfiB,cAAe5L,KAAK6L,uBAExBiC,WAAY9N,KAAK+N,8BAEpBC,OAAOC,IACJ,IAAIA,GAAK7N,QAGT,MAAM6N,CAAG,GAEpB,CAEO,yBAAAF,GACJ,MAAO,YAAc/N,KAAK2K,QAC7B,CAEO,uBAAAuC,CAAwBZ,GAC5B,MAAMnL,EAAwB,CAAA,EAG9BmL,EAAQA,EAAMzL,SAAS,KAAOyL,EAAQA,EAAQ,IAE9C,IAAK,IAAIzK,KAAO7B,KAAK4L,eACZ/J,EAAM,KAAK4L,WAAWnB,KACvBnL,EAAOU,GAAO7B,KAAK4L,cAAc/J,IAIzC,OAAOV,CACV,CAEO,2BAAA0M,GACJ,MAAM1M,EAAwB,GAE9B,IAAK,IAAIU,KAAO7B,KAAK4L,cACb5L,KAAK4L,cAAc/J,GAAKL,QACxBL,EAAOmH,KAAKzG,GAIpB,OAAOV,CACV,CAEO,2BAAAyM,GACJ,GAAK5N,KAAK2L,YAAV,CAIA3L,KAAKkO,iCAEL,IAAK,IAAIrM,KAAO7B,KAAK4L,cACjB,IAAK,IAAIa,KAAYzM,KAAK4L,cAAc/J,GACpC7B,KAAK2L,YAAYpC,iBAAiB1H,EAAK4K,EAN9C,CASJ,CAEO,8BAAAyB,GACJ,GAAKlO,KAAK2L,YAIV,IAAK,IAAI9J,KAAO7B,KAAK4L,cACjB,IAAK,IAAIa,KAAYzM,KAAK4L,cAAc/J,GACpC7B,KAAK2L,YAAYyB,oBAAoBvL,EAAK4K,EAGrD,CAEO,aAAMG,GACV,KAAI5M,KAAK+L,kBAAoB,GAM7B,OAAO,IAAIoC,SAAQ,CAACC,EAASC,KACzBrO,KAAKmM,gBAAgB7D,KAAK,CAAE8F,UAASC,WAEjCrO,KAAKmM,gBAAgB3K,OAAS,GAKlCxB,KAAKsO,aAAa,GAEzB,CAEO,WAAAA,GACJtO,KAAKqN,YAAW,GAGhBkB,aAAavO,KAAKwO,kBAClBxO,KAAKwO,iBAAmBC,YAAW,KAC/BzO,KAAK0O,oBAAoB,IAAI9O,MAAM,sCAAsC,GAC1EI,KAAK8L,mBAER9L,KAAK2L,YAAc,IAAIgD,YAAY3O,KAAKyJ,OAAOmF,SAAS,kBAExD5O,KAAK2L,YAAYkD,QAAW1M,IACxBnC,KAAK0O,oBACD,IAAI9O,MAAM,4CACb,EAGLI,KAAK2L,YAAYpC,iBAAiB,cAAexE,IAC7C,MAAM2H,EAAW3H,EACjB/E,KAAK2K,SAAW+B,GAAUoC,YAE1B9O,KAAK2M,sBACAxC,MAAK0C,UACF,IAAIkC,EAAU,EACd,KAAO/O,KAAKgP,0BAA4BD,EAAU,GAC9CA,UAMM/O,KAAK2M,qBACd,IAEJxC,MAAK,KACF,IAAK,IAAI8E,KAAKjP,KAAKmM,gBACf8C,EAAEb,UAINpO,KAAKmM,gBAAkB,GACvBnM,KAAK+L,kBAAoB,EACzBwC,aAAavO,KAAKkP,oBAClBX,aAAavO,KAAKwO,kBAGlB,MAAMW,EAAcnP,KAAKkN,wBAAwB,cACjD,IAAK,IAAIrL,KAAOsN,EACZ,IAAK,IAAI1C,KAAY0C,EAAYtN,GAC7B4K,EAAS1H,EAEhB,IAEJiJ,OAAOC,IACJjO,KAAK2K,SAAW,GAChB3K,KAAK0O,oBAAoBT,EAAI,GAC/B,GAEb,CAEO,sBAAAe,GACJ,MAAMI,EAAepP,KAAK6N,8BAC1B,GAAIuB,EAAa5N,QAAUxB,KAAK6L,sBAAsBrK,OAClD,OAAO,EAGX,IAAK,MAAM6N,KAAKD,EACZ,IAAKpP,KAAK6L,sBAAsBhL,SAASwO,GACrC,OAAO,EAIf,OAAO,CACV,CAEO,mBAAAX,CAAoBT,GAIxB,GAHAM,aAAavO,KAAKwO,kBAClBD,aAAavO,KAAKkP,qBAIZlP,KAAK2K,WAAa3K,KAAK+L,mBAEzB/L,KAAK+L,kBAAoB/L,KAAKgM,qBAChC,CACE,IAAK,IAAIiD,KAAKjP,KAAKmM,gBACf8C,EAAEZ,OAAO,IAAI1O,oBAAoBsO,IAIrC,OAFAjO,KAAKmM,gBAAkB,QACvBnM,KAAKqN,YAER,CAGDrN,KAAKqN,YAAW,GAChB,MAAMiC,EACFtP,KAAKkM,6BAA6BlM,KAAK+L,oBACvC/L,KAAKkM,6BACDlM,KAAKkM,6BAA6B1K,OAAS,GAEnDxB,KAAK+L,oBACL/L,KAAKkP,mBAAqBT,YAAW,KACjCzO,KAAKsO,aAAa,GACnBgB,EACN,CAEO,UAAAjC,CAAWkC,GAAgB,GAa/B,GAZIvP,KAAK2K,UAAY3K,KAAKwP,cACtBxP,KAAKwP,aAAalP,OAAO6E,KAAKnF,KAAK4L,gBAGvC2C,aAAavO,KAAKwO,kBAClBD,aAAavO,KAAKkP,oBAClBlP,KAAKkO,iCACLlO,KAAKyJ,OAAOgG,cAAczP,KAAK+N,6BAC/B/N,KAAK2L,aAAa+D,QAClB1P,KAAK2L,YAAc,KACnB3L,KAAK2K,SAAW,IAEX4E,EAAe,CAChBvP,KAAK+L,kBAAoB,EAOzB,IAAK,IAAIkD,KAAKjP,KAAKmM,gBACf8C,EAAEb,UAENpO,KAAKmM,gBAAkB,EAC1B,CACJ,ECrfC,MAAgBwD,oBAAuBnG,YASzC,MAAApI,CAAcT,GACV,OAAOA,CACV,CAiBD,iBAAMiP,CACFC,EACA3O,GAEA,GAAiC,iBAAtB2O,EACP,OAAO7P,KAAK8P,aAAgBD,EAAoB3O,GAKpD,IAAI6O,EAAQ,IAMZ,OARA7O,EAAUZ,OAAOe,OAAO,CAAE,EAAEwO,EAAoB3O,IAGpC6O,QACRA,EAAQ7O,EAAQ6O,aACT7O,EAAQ6O,OAGZ/P,KAAK8P,aAAgBC,EAAO7O,EACtC,CASD,aAAM8O,CACFC,EAAO,EACPC,EAAU,GACVhP,GAiBA,OAfAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OAEZ1I,IAGIgK,MAAQ5K,OAAOe,OACnB,CACI4O,KAAMA,EACNC,QAASA,GAEbhP,EAAQgK,OAGLlL,KAAKyJ,OAAOI,KAAK7J,KAAKmQ,aAAcjP,GAASiJ,MAAMiG,IACtDA,EAAaC,MACTD,EAAaC,OAAO3L,KAAK4L,GACdtQ,KAAKoB,OAAUkP,MACpB,GAEHF,IAEd,CAeD,sBAAMG,CAAwBC,EAAgBtP,GAgB1C,OAfAA,EAAUZ,OAAOe,OACb,CACIyM,WAAY,iBAAmB9N,KAAKmQ,aAAe,IAAMK,GAE7DtP,IAGIgK,MAAQ5K,OAAOe,OACnB,CACImP,OAAQA,EACRC,UAAW,GAEfvP,EAAQgK,OAGLlL,KAAKgQ,QAAW,EAAG,EAAG9O,GAASiJ,MAAMhJ,IACxC,IAAKA,GAAQkP,OAAO7O,OAChB,MAAM,IAAI7B,oBAAoB,CAC1BO,OAAQ,IACRC,SAAU,CACNuQ,KAAM,IACNhQ,QAAS,uCACTC,KAAM,CAAE,KAKpB,OAAOQ,EAAOkP,MAAM,EAAE,GAE7B,CAWD,YAAMM,CAAc5I,EAAY7G,GAC5B,IAAK6G,EACD,MAAM,IAAIpI,oBAAoB,CAC1BM,IAAKD,KAAKyJ,OAAOmF,SAAS5O,KAAKmQ,aAAe,KAC9CjQ,OAAQ,IACRC,SAAU,CACNuQ,KAAM,IACNhQ,QAAS,8BACTC,KAAM,CAAE,KAYpB,OAPAO,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK7J,KAAKmQ,aAAe,IAAMpM,mBAAmBgE,GAAK7G,GACvDiJ,MAAMiG,GAAsBpQ,KAAKoB,OAAUgP,IACnD,CASD,YAAMQ,CACF7G,EACA7I,GAUA,OARAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OACRI,KAAMD,GAEV7I,GAGGlB,KAAKyJ,OACPI,KAAK7J,KAAKmQ,aAAcjP,GACxBiJ,MAAMiG,GAAsBpQ,KAAKoB,OAAUgP,IACnD,CASD,YAAMtG,CACF/B,EACAgC,EACA7I,GAUA,OARAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,QACRI,KAAMD,GAEV7I,GAGGlB,KAAKyJ,OACPI,KAAK7J,KAAKmQ,aAAe,IAAMpM,mBAAmBgE,GAAK7G,GACvDiJ,MAAMiG,GAAsBpQ,KAAKoB,OAAUgP,IACnD,CAOD,YAAM,CAAOrI,EAAY7G,GAQrB,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,UAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK7J,KAAKmQ,aAAe,IAAMpM,mBAAmBgE,GAAK7G,GACvDiJ,MAAK,KAAM,GACnB,CAKS,YAAA2F,CACNe,EAAY,IACZ3P,IAEAA,EAAUA,GAAW,IACbgK,MAAQ5K,OAAOe,OACnB,CACIoP,UAAW,GAEfvP,EAAQgK,OAGZ,IAAI/J,EAAmB,GAEnB2P,QAAUjE,MAAOoD,GACVjQ,KAAKgQ,QAAQC,EAAMY,GAAa,IAAM3P,GAASiJ,MAAM4G,IACxD,MACMV,EADaU,EACMV,MAIzB,OAFAlP,EAASA,EAAO6P,OAAOX,GAEnBA,EAAM7O,QAAUuP,EAAKb,QACdY,QAAQb,EAAO,GAGnB9O,CAAM,IAIrB,OAAO2P,QAAQ,EAClB,EC1QC,SAAUG,2BACZC,EACAC,EACAC,EACAlG,GAEA,MACMmG,OAA4B,IAAVnG,EAExB,OAAKmG,QAH6C,IAAlBD,EAO5BC,GACAvK,QAAQC,KAAKmK,GACbC,EAAYnH,KAAO1J,OAAOe,OAAO,CAAE,EAAE8P,EAAYnH,KAAMoH,GACvDD,EAAYjG,MAAQ5K,OAAOe,OAAO,CAAE,EAAE8P,EAAYjG,MAAOA,GAElDiG,GAGJ7Q,OAAOe,OAAO8P,EAAaC,GAXvBD,CAYf,CCpBM,SAAUG,iBAAiB7H,GAC5BA,EAAe8H,qBACpB,CCyFM,MAAOC,sBAAuC7B,YAGhD,WAAA9P,CAAY4J,EAAgBY,GACxBtK,MAAM0J,GAENzJ,KAAKqK,mBAAqBA,CAC7B,CAKD,gBAAI8F,GACA,OAAOnQ,KAAKyR,mBAAqB,UACpC,CAKD,sBAAIA,GACA,MAAO,oBAAsB1N,mBAAmB/D,KAAKqK,mBACxD,CAKD,gBAAIqH,GACA,MAC+B,eAA3B1R,KAAKqK,oBACsB,mBAA3BrK,KAAKqK,kBAEZ,CAmBD,eAAMgC,CACFC,EACAlE,EACAlH,GAEA,IAAKoL,EACD,MAAM,IAAI1M,MAAM,kBAGpB,IAAKwI,EACD,MAAM,IAAIxI,MAAM,kCAGpB,OAAOI,KAAKyJ,OAAOkI,SAAStF,UACxBrM,KAAKqK,mBAAqB,IAAMiC,EAChClE,EACAlH,EAEP,CASD,iBAAM6L,CAAYT,GAEd,OAAIA,EACOtM,KAAKyJ,OAAOkI,SAAS5E,YACxB/M,KAAKqK,mBAAqB,IAAMiC,GAKjCtM,KAAKyJ,OAAOkI,SAASrE,oBAAoBtN,KAAKqK,mBACxD,CAqBD,iBAAMuF,CACFgC,EACA1Q,GAEA,GAA6B,iBAAlB0Q,EACP,OAAO7R,MAAM6P,YAAegC,EAAgB1Q,GAGhD,MAAMkK,EAAS9K,OAAOe,OAAO,CAAA,EAAIuQ,EAAgB1Q,GAEjD,OAAOnB,MAAM6P,YAAexE,EAC/B,CAKD,aAAM4E,CACFC,EAAO,EACPC,EAAU,GACVhP,GAEA,OAAOnB,MAAMiQ,QAAWC,EAAMC,EAAShP,EAC1C,CAKD,sBAAMqP,CACFC,EACAtP,GAEA,OAAOnB,MAAMwQ,iBAAoBC,EAAQtP,EAC5C,CAKD,YAAMyP,CAAc5I,EAAY7G,GAC5B,OAAOnB,MAAM4Q,OAAU5I,EAAI7G,EAC9B,CAKD,YAAM0P,CACF7G,EACA7I,GAEA,OAAOnB,MAAM6Q,OAAU7G,EAAY7I,EACtC,CAQD,YAAM4I,CACF/B,EACAgC,EACA7I,GAEA,OAAOnB,MAAM+J,OAAoB/B,EAAIgC,EAAY7I,GAASiJ,MAAMmG,IAC5D,GAEItQ,KAAKyJ,OAAOoI,UAAUvL,QAAQyB,KAAOuI,GAAMvI,KAC1C/H,KAAKyJ,OAAOoI,UAAUvL,QAAQM,eAAiB5G,KAAKqK,oBACjDrK,KAAKyJ,OAAOoI,UAAUvL,QAAQK,iBAC1B3G,KAAKqK,oBACf,CACE,IAAIyH,EAAaxR,OAAOe,OAAO,CAAE,EAAErB,KAAKyJ,OAAOoI,UAAUvL,OAAOyL,QAC5DC,EAAa1R,OAAOe,OAAO,CAAE,EAAErB,KAAKyJ,OAAOoI,UAAUvL,OAAQgK,GAC7DwB,IAEAE,EAAWD,OAASzR,OAAOe,OAAOyQ,EAAYxB,EAAKyB,SAGvD/R,KAAKyJ,OAAOoI,UAAU5K,KAAKjH,KAAKyJ,OAAOoI,UAAUtN,MAAOyN,EAC3D,CAED,OAAO1B,CAAgB,GAE9B,CAQD,YAAM,CAAOvI,EAAY7G,GACrB,OAAOnB,MAAMkS,OAAOlK,EAAI7G,GAASiJ,MAAM+H,KAE/BA,GAEAlS,KAAKyJ,OAAOoI,UAAUvL,QAAQyB,KAAOA,GACpC/H,KAAKyJ,OAAOoI,UAAUvL,QAAQM,eAAiB5G,KAAKqK,oBACjDrK,KAAKyJ,OAAOoI,UAAUvL,QAAQK,iBAC1B3G,KAAKqK,oBAEbrK,KAAKyJ,OAAOoI,UAAU1K,QAGnB+K,IAEd,CASS,YAAAC,CAAoB/B,GAC1B,MAAM9J,EAAStG,KAAKoB,OAAOgP,GAAc9J,QAAU,CAAA,GAInD,OAFAtG,KAAKyJ,OAAOoI,UAAU5K,KAAKmJ,GAAc7L,MAAO+B,GAEzChG,OAAOe,OAAO,CAAE,EAAE+O,EAAc,CAEnC7L,MAAO6L,GAAc7L,OAAS,GAC9B+B,OAAQA,GAEf,CAOD,qBAAM8L,CAAgBlR,GAUlB,OATAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,MAERyI,OAAQ,2BAEZnR,GAGGlB,KAAKyJ,OAAOI,KAAK7J,KAAKyR,mBAAqB,gBAAiBvQ,EACtE,CAYD,sBAAMoR,CACFC,EACAC,EACAtR,GAcA,IAAIuR,EAZJvR,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OACRI,KAAM,CACF0I,SAAUH,EACVC,SAAUA,IAGlBtR,GAKAlB,KAAK0R,eACLe,EAAuBvR,EAAQuR,4BACxBvR,EAAQuR,qBACVvR,EAAQyR,aACTrB,iBAAiBtR,KAAKyJ,SAI9B,IAAImJ,QAAiB5S,KAAKyJ,OAAOI,KAC7B7J,KAAKyR,mBAAqB,sBAC1BvQ,GAmBJ,OAhBA0R,EAAW5S,KAAKmS,aAAgBS,GAE5BH,GAAwBzS,KAAK0R,cD9XnC,SAAUmB,oBACZpJ,EACAqJ,EACAC,EACAC,GAEA1B,iBAAiB7H,GAEjB,MAAMwJ,EAAgBxJ,EAAOyJ,WACvBC,EAAW1J,EAAOoI,UAAUvL,OAI5B8M,EAAmB3J,EAAOoI,UAAU1J,UAAS,CAACkL,EAAU9M,OAErD8M,GACD9M,GAAOwB,IAAMoL,GAAUpL,KACrBxB,GAAOK,cAAgBuM,GAAUvM,eAC/BL,GAAOK,cAAgBuM,GAAUvM,eAErC0K,iBAAiB7H,EACpB,IAIJA,EAAe8H,kBAAoB,WAChC6B,IACA3J,EAAOyJ,WAAaD,SACZxJ,EAAe8H,iBAC3B,EAEA9H,EAAOyJ,WAAarG,MAAO5M,EAAKqT,KAC5B,MAAMC,EAAW9J,EAAOoI,UAAUtN,MAElC,GAAI+O,EAAYpI,OAAOyH,YACnB,OAAOM,EAAgBA,EAAchT,EAAKqT,GAAe,CAAErT,MAAKqT,eAGpE,IAAI9M,EAAUiD,EAAOoI,UAAUrL,QAC/B,GAEIA,GAEAxB,eAAeyE,EAAOoI,UAAUtN,MAAOuO,GAEvC,UACUC,GACT,CAAC,MAAO5Q,GACLqE,GAAU,CACb,CAIAA,SACKwM,IAIV,MAAMxG,EAAU8G,EAAY9G,SAAW,GACvC,IAAK,IAAI3K,KAAO2K,EACZ,GACyB,iBAArB3K,EAAI+B,eAEJ2P,GAAY/G,EAAQ3K,IACpB4H,EAAOoI,UAAUtN,MACnB,CAEEiI,EAAQ3K,GAAO4H,EAAOoI,UAAUtN,MAChC,KACH,CAIL,OAFA+O,EAAY9G,QAAUA,EAEfyG,EAAgBA,EAAchT,EAAKqT,GAAe,CAAErT,MAAKqT,cAAa,CAErF,CCoTYT,CACI7S,KAAKyJ,OACLgJ,GACA,IAAMzS,KAAKwT,YAAY,CAAEb,aAAa,MACtC,IACI3S,KAAKsS,iBACDC,EACAC,EACAlS,OAAOe,OAAO,CAAEsR,aAAa,GAAQzR,MAK9C0R,CACV,CAsCD,wBAAMa,CACFC,EACAhD,EACAiD,EACAC,EACAC,EACAzC,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACF0J,SAAUA,EACVhD,KAAMA,EACNiD,aAAcA,EACdC,YAAaA,EACbC,WAAYA,IAWpB,OAPA3S,EAAU+P,2BACN,yOACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,oBAAqBvQ,GACpDiJ,MAAMxJ,GAASX,KAAKmS,aAAgBxR,IAC5C,CA2ED,cAAAmT,IAAyBC,GAErB,GAAIA,EAAKvS,OAAS,GAA0B,iBAAduS,IAAO,GAIjC,OAHAjN,QAAQC,KACJ,4PAEG/G,KAAKyT,mBACRM,IAAO,IAAM,GACbA,IAAO,IAAM,GACbA,IAAO,IAAM,GACbA,IAAO,IAAM,GACbA,IAAO,IAAM,CAAA,EACbA,IAAO,IAAM,CAAA,EACbA,IAAO,IAAM,CAAE,GAIvB,MAAMC,EAASD,IAAO,IAAM,CAAA,EAM5B,IAAIE,EAAmC,KAClCD,EAAOE,cACRD,EAAoBE,sBAAiBnS,IAIzC,MAAM2P,EAAW,IAAIjG,gBAAgB1L,KAAKyJ,QAE1C,SAAS2K,UACLH,GAAmBvE,QACnBiC,EAAS5E,aACZ,CAED,MAAMsH,EAAiC,CAAA,EACjCvG,EAAakG,EAAOlG,WAK1B,OAJIA,IACAuG,EAAkBvG,WAAaA,GAG5B9N,KAAKoS,gBAAgBiC,GACvBlK,MAAMmK,IACH,MAAMZ,EAAWY,EAAYC,OAAOC,UAAUC,MACzCxF,GAAMA,EAAExO,OAASuT,EAAON,WAE7B,IAAKA,EACD,MAAM,IAAI/T,oBACN,IAAIC,MAAM,gCAAgCoU,EAAON,eAIzD,MAAME,EAAc5T,KAAKyJ,OAAOmF,SAAS,wBAEzC,OAAO,IAAIT,SAAQtB,MAAOuB,EAASC,KAE/B,MAAMqG,EAAmB5G,EACnB9N,KAAKyJ,OAA0B,oBAAIqE,QACnC9L,EACF0S,IACAA,EAAiBC,OAAOC,QAAU,KAC9BR,UACA/F,EACI,IAAI1O,oBAAoB,CACpBS,SAAS,EACTM,QAAS,uBAEhB,GAKTiR,EAASnC,aAAgBqF,IACjBA,EAAoBrT,QAAU6M,IAC9B+F,UACA/F,EACI,IAAI1O,oBACA,IAAIC,MAAM,qCAGrB,EAGL,UACU+R,EAAStF,UAAU,WAAWQ,MAAO9H,IACvC,MAAM+P,EAAWnD,EAAShH,SAE1B,IACI,IAAK5F,EAAEgQ,OAASD,IAAa/P,EAAEgQ,MAC3B,MAAM,IAAInV,MAAM,iCAGpB,GAAImF,EAAEiQ,QAAUjQ,EAAE2L,KACd,MAAM,IAAI9Q,MACN,0CACImF,EAAEiQ,OAKd,MAAM9T,EAAUZ,OAAOe,OAAO,CAAE,EAAE2S,UAC3B9S,EAAQwS,gBACRxS,EAAQ+T,cACR/T,EAAQ2S,kBACR3S,EAAQgT,YAGXQ,GAAkBC,QAAQC,UAC1BF,EAAiBC,OAAOC,QAAU,MAGtC,MAAMhC,QAAiB5S,KAAKyT,mBACxBC,EAASjT,KACTsE,EAAE2L,KACFgD,EAASC,aACTC,EACAI,EAAOH,WACP3S,GAGJkN,EAAQwE,EACX,CAAC,MAAO3E,GACLI,EAAO,IAAI1O,oBAAoBsO,GAClC,CAEDmG,SAAS,IAGb,MAAMc,EAAuC,CACzCH,MAAOpD,EAAShH,UAEhBqJ,EAAOiB,QAAQzT,SACf0T,EAAoB,MAAIlB,EAAOiB,OAAOrQ,KAAK,MAG/C,MAAM3E,EAAMD,KAAKmV,oBACbzB,EAAS0B,QAAUxB,EACnBsB,GAGJ,IAAIhB,EACAF,EAAOE,aACP,SAAUjU,GACFgU,EACAA,EAAkBoB,SAASC,KAAOrV,EAIlCgU,EAAoBE,iBAAiBlU,EAE7C,QAEEiU,EAAYjU,EACrB,CAAC,MAAOgO,GAEDyG,GAAkBC,QAAQC,UAC1BF,EAAiBC,OAAOC,QAAU,MAGtCR,UACA/F,EAAO,IAAI1O,oBAAoBsO,GAClC,IACH,IAELD,OAAOC,IAEJ,MADAmG,UACMnG,CAAG,GAEpB,CAkBD,iBAAMuF,CACFpC,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,QAUZ,OAPA1I,EAAU+P,2BACN,2GACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,gBAAiBvQ,GAChDiJ,MAAMxJ,GAASX,KAAKmS,aAAgBxR,IAC5C,CAeD,0BAAM4U,CACFvN,EACAoJ,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFhC,MAAOA,IAWf,OAPA9G,EAAU+P,2BACN,2IACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,0BAA2BvQ,GAC1DiJ,MAAK,KAAM,GACnB,CA0BD,0BAAMqL,CACFC,EACAjD,EACAkD,EACAtE,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFzF,MAAOkR,EACPjD,SAAUA,EACVkD,gBAAiBA,IAWzB,OAPAxU,EAAU+P,2BACN,iMACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,0BAA2BvQ,GAC1DiJ,MAAK,KAAM,GACnB,CAeD,yBAAMwL,CACF3N,EACAoJ,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFhC,MAAOA,IAWf,OAPA9G,EAAU+P,2BACN,yIACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,wBAAyBvQ,GACxDiJ,MAAK,KAAM,GACnB,CAyBD,yBAAMyL,CACFC,EACAzE,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFzF,MAAOsR,IAWf,OAPA3U,EAAU+P,2BACN,yIACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,wBAAyBvQ,GACxDiJ,MAAK,KAEF,MAAMjF,EAAUZ,gBAAgBuR,GAC1BtP,EAAQvG,KAAKyJ,OAAOoI,UAAUvL,OAWpC,OATIC,IACCA,EAAMuP,UACPvP,EAAMwB,KAAO7C,EAAQ6C,IACrBxB,EAAMK,eAAiB1B,EAAQ0B,eAE/BL,EAAMuP,UAAW,EACjB9V,KAAKyJ,OAAOoI,UAAU5K,KAAKjH,KAAKyJ,OAAOoI,UAAUtN,MAAOgC,KAGrD,CAAI,GAEtB,CAeD,wBAAMwP,CACFC,EACA5E,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFgM,SAAUA,IAWlB,OAPA9U,EAAU+P,2BACN,6IACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,wBAAyBvQ,GACxDiJ,MAAK,KAAM,GACnB,CA2BD,wBAAM8L,CACFC,EACA1D,EACApB,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFzF,MAAO2R,EACP1D,SAAUA,IAWlB,OAPAtR,EAAU+P,2BACN,2JACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,wBAAyBvQ,GACxDiJ,MAAK,KACF,MAAMjF,EAAUZ,gBAAgB4R,GAC1B3P,EAAQvG,KAAKyJ,OAAOoI,UAAUvL,OASpC,OAPIC,GACAA,EAAMwB,KAAO7C,EAAQ6C,IACrBxB,EAAMK,eAAiB1B,EAAQ0B,cAE/B5G,KAAKyJ,OAAOoI,UAAU1K,SAGnB,CAAI,GAEtB,CASD,uBAAMgP,CACFC,EACAlV,GAEA,OAAOlB,KAAKyJ,OAAOgB,WAAW,kBAAkBmF,YAC5CtP,OAAOe,OAAO,CAAE,EAAEH,EAAS,CACvBsP,OAAQxQ,KAAKyJ,OAAO+G,OAAO,oBAAqB,CAAEzI,GAAIqO,MAGjE,CASD,wBAAMC,CACFD,EACA1C,EACAxS,GAEA,MAAMoV,QAAWtW,KAAKyJ,OAAOgB,WAAW,kBAAkB8F,iBACtDvQ,KAAKyJ,OAAO+G,OAAO,oDAAqD,CACpE4F,WACA1C,cAIR,OAAO1T,KAAKyJ,OACPgB,WAAW,kBACXwH,OAAOqE,EAAGvO,GAAI7G,GACdiJ,MAAK,KAAM,GACnB,CAOD,gBAAMoM,CAAWvO,EAAe9G,GAS5B,OARAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OACRI,KAAM,CAAEhC,MAAOA,IAEnB9G,GAGGlB,KAAKyJ,OAAOI,KAAK7J,KAAKyR,mBAAqB,eAAgBvQ,EACrE,CAYD,iBAAMsV,CACFC,EACAjE,EACAtR,GAUA,OARAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OACRI,KAAM,CAAEyM,QAAOjE,aAEnBtR,GAGGlB,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,iBAAkBvQ,GACjDiJ,MAAMxJ,GAASX,KAAKmS,aAAgBxR,IAC5C,CAaD,iBAAM+V,CACFN,EACArL,EACA7J,IAEAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OACRI,KAAM,CAAEe,SAAUA,IAEtB7J,IAEIsL,QAAUtL,EAAQsL,SAAW,CAAA,EAChCtL,EAAQsL,QAAQmK,gBACjBzV,EAAQsL,QAAQmK,cAAgB3W,KAAKyJ,OAAOoI,UAAUtN,OAK1D,MAAMkF,EAAS,IAAImN,OACf5W,KAAKyJ,OAAOoN,QACZ,IAAI3Q,cACJlG,KAAKyJ,OAAOqN,MAGVlE,QAAiBnJ,EAAOI,KAC1B7J,KAAKyR,mBAAqB,gBAAkB1N,mBAAmBqS,GAC/DlV,GAMJ,OAHAuI,EAAOoI,UAAU5K,KAAK2L,GAAUrO,MAAOvE,KAAKoB,OAAOwR,GAAUtM,QAAU,CAAA,IAGhEmD,CACV,CAQO,mBAAA0L,CACJlV,EACAiV,EAAuC,IAEvC,IAAI6B,EAAU9W,EACViL,EAAQ,GAEOjL,EAAIyB,QAAQ,MACb,IACdqV,EAAU9W,EAAI+W,UAAU,EAAG/W,EAAIyB,QAAQ,MACvCwJ,EAAQjL,EAAI+W,UAAU/W,EAAIyB,QAAQ,KAAO,IAG7C,MAAMuV,EAA0C,CAAA,EAG1CC,EAAYhM,EAAMzG,MAAM,KAC9B,IAAK,MAAM0S,KAASD,EAAW,CAC3B,GAAa,IAATC,EACA,SAGJ,MAAMC,EAAOD,EAAM1S,MAAM,KACzBwS,EAAanT,mBAAmBsT,EAAK,GAAG3R,QAAQ,MAAO,OACnD3B,oBAAoBsT,EAAK,IAAM,IAAI3R,QAAQ,MAAO,KACzD,CAGD,IAAK,IAAI5D,KAAOqT,EACPA,EAAamC,eAAexV,KAIR,MAArBqT,EAAarT,UACNoV,EAAapV,GAEpBoV,EAAapV,GAAOqT,EAAarT,IAKzCqJ,EAAQ,GACR,IAAK,IAAIrJ,KAAOoV,EACPA,EAAaI,eAAexV,KAIpB,IAATqJ,IACAA,GAAS,KAGbA,GACInH,mBAAmBlC,EAAI4D,QAAQ,OAAQ,MACvC,IACA1B,mBAAmBkT,EAAapV,GAAK4D,QAAQ,OAAQ,OAG7D,MAAgB,IAATyF,EAAc6L,EAAU,IAAM7L,EAAQ6L,CAChD,EAGL,SAAS5C,iBAAiBlU,GACtB,GAAsB,oBAAX+I,SAA2BA,QAAQsO,KAC1C,MAAM,IAAI3X,oBACN,IAAIC,MACA,0EAKZ,IAAI2X,EAAQ,KACRC,EAAS,IAETC,EAAczO,OAAO0O,WACrBC,EAAe3O,OAAO4O,YAG1BL,EAAQA,EAAQE,EAAcA,EAAcF,EAC5CC,EAASA,EAASG,EAAeA,EAAeH,EAEhD,IAAIK,EAAOJ,EAAc,EAAIF,EAAQ,EACjCO,EAAMH,EAAe,EAAIH,EAAS,EAItC,OAAOxO,OAAOsO,KACVrX,EACA,eACA,SACIsX,EACA,WACAC,EACA,QACAM,EACA,SACAD,EACA,wBAEZ,CC9vCM,MAAOE,0BAA0BpI,YAInC,gBAAIQ,GACA,MAAO,kBACV,CAWD,YAAM6H,CACFC,EACAC,GAAyB,EACzBhX,GAaA,OAXAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,MACRI,KAAM,CACFiO,YAAaA,EACbC,cAAeA,IAGvBhX,GAGGlB,KAAKyJ,OAAOI,KAAK7J,KAAKmQ,aAAe,UAAWjP,GAASiJ,MAAK,KAAM,GAC9E,CAQD,kBAAMgO,CACFjX,GASA,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK7J,KAAKmQ,aAAe,kBAAmBjP,EAClE,CAOD,cAAMkX,CAAS/N,EAA4BnJ,GAQvC,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,UAEZ1I,GAGGlB,KAAKyJ,OACPI,KACG7J,KAAKmQ,aACD,IACApM,mBAAmBsG,GACnB,YACJnJ,GAEHiJ,MAAK,KAAM,GACnB,ECvEC,MAAOkO,mBAAmB7O,YAM5B,aAAMwG,CACFC,EAAO,EACPC,EAAU,GACVhP,GAYA,OAVAA,EAAUZ,OAAOe,OAAO,CAAEuI,OAAQ,OAAS1I,IAEnCgK,MAAQ5K,OAAOe,OACnB,CACI4O,KAAMA,EACNC,QAASA,GAEbhP,EAAQgK,OAGLlL,KAAKyJ,OAAOI,KAAK,YAAa3I,EACxC,CASD,YAAMyP,CAAO5I,EAAY7G,GACrB,IAAK6G,EACD,MAAM,IAAIpI,oBAAoB,CAC1BM,IAAKD,KAAKyJ,OAAOmF,SAAS,cAC1B1O,OAAQ,IACRC,SAAU,CACNuQ,KAAM,IACNhQ,QAAS,2BACTC,KAAM,CAAE,KAYpB,OAPAO,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,aAAe9F,mBAAmBgE,GAAK7G,EAClE,CAOD,cAAMoX,CAASpX,GAQX,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,kBAAmB3I,EAC9C,ECrEC,MAAOqX,sBAAsB/O,YAM/B,WAAMgP,CAAMtX,GAQR,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,cAAe3I,EAC1C,ECrBC,MAAOuX,oBAAoBjP,YAI7B,MAAAkP,CACIpS,EACAqS,EACAC,EAA2B,CAAA,GAG3B,OADA9R,QAAQC,KAAK,2DACN/G,KAAK6Y,OAAOvS,EAAQqS,EAAUC,EACxC,CAKD,MAAAC,CACIvS,EACAqS,EACAC,EAA2B,CAAA,GAE3B,IACKD,IACArS,GAAQyB,KACPzB,GAAQM,eAAgBN,GAAQK,eAElC,MAAO,GAGX,MAAMmS,EAAQ,GACdA,EAAMxQ,KAAK,OACXwQ,EAAMxQ,KAAK,SACXwQ,EAAMxQ,KAAKvE,mBAAmBuC,EAAOM,cAAgBN,EAAOK,iBAC5DmS,EAAMxQ,KAAKvE,mBAAmBuC,EAAOyB,KACrC+Q,EAAMxQ,KAAKvE,mBAAmB4U,IAE9B,IAAIxX,EAASnB,KAAKyJ,OAAOmF,SAASkK,EAAMlU,KAAK,OAGhB,IAAzBgU,EAAYG,iBACLH,EAAYG,SAGvB,MAAM3N,EAASD,qBAAqByN,GAKpC,OAJIxN,IACAjK,IAAWA,EAAON,SAAS,KAAO,IAAM,KAAOuK,GAG5CjK,CACV,CAOD,cAAM6X,CAAS9X,GAQX,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,QAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK,mBAAoB3I,GACzBiJ,MAAMxJ,GAASA,GAAM4D,OAAS,IACtC,EC7DC,MAAO0U,sBAAsBzP,YAM/B,iBAAMoG,CAAY1O,GAQd,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,eAAgB3I,EAC3C,CAOD,YAAM0P,CAAOsI,EAAkBhY,GAW3B,OAVAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OACRI,KAAM,CACFvJ,KAAMyY,IAGdhY,GAGGlB,KAAKyJ,OAAOI,KAAK,eAAgB3I,GAASiJ,MAAK,KAAM,GAC/D,CAeD,YAAMgP,CACFpP,EACA7I,GAUA,OARAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OACRI,KAAMD,GAEV7I,GAGGlB,KAAKyJ,OAAOI,KAAK,sBAAuB3I,GAASiJ,MAAK,KAAM,GACtE,CAOD,YAAM,CAAOtI,EAAaX,GAQtB,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,UAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK,gBAAgB9F,mBAAmBlC,KAAQX,GAChDiJ,MAAK,KAAM,GACnB,CAOD,aAAMiP,CAAQvX,EAAaX,GAQvB,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,QAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK,gBAAgB9F,mBAAmBlC,aAAgBX,GACxDiJ,MAAK,KAAM,GACnB,CAKD,cAAAkP,CAAe9U,EAAe1C,GAI1B,OAHAiF,QAAQC,KACJ,+EAEG/G,KAAKsZ,eAAe/U,EAAO1C,EACrC,CAQD,cAAAyX,CAAe/U,EAAe1C,GAC1B,OAAO7B,KAAKyJ,OAAOmF,SACf,gBAAgB7K,mBAAmBlC,YAAckC,mBAAmBQ,KAE3E,ECzHC,MAAOgV,oBAAoB/P,YAM7B,iBAAMoG,CAAY1O,GAQd,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,aAAc3I,EACzC,CAOD,SAAMsY,CAAIC,EAAevY,GAQrB,OAPAA,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,QAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK,cAAc9F,mBAAmB0V,KAAUvY,GAChDiJ,MAAK,KAAM,GACnB,ECtCC,SAAUuP,OAAOzX,GACnB,MACqB,oBAAT4F,MAAwB5F,aAAe4F,MAC9B,oBAAT8R,MAAwB1X,aAAe0X,MAGtC,OAAR1X,GACkB,iBAARA,GACPA,EAAI2X,MACmB,oBAAd3V,WAAmD,gBAAtBA,UAAUC,SACzB,oBAAXC,QAA2BA,OAAeC,eAElE,CAKM,SAAUyV,WAAW7P,GACvB,OACIA,IAI4B,aAA3BA,EAAKnK,aAAaY,MAIM,oBAAbqZ,UAA4B9P,aAAgB8P,SAEhE,CAKM,SAAUC,aAAa/P,GACzB,IAAK,MAAMnI,KAAOmI,EAAM,CACpB,MAAMgQ,EAASzS,MAAMC,QAAQwC,EAAKnI,IAAQmI,EAAKnI,GAAO,CAACmI,EAAKnI,IAC5D,IAAK,MAAM0J,KAAKyO,EACZ,GAAIN,OAAOnO,GACP,OAAO,CAGlB,CAED,OAAO,CACX,CAoFA,MAAM0O,EAAwB,cAE9B,SAASC,mBAAmBxX,GACxB,GAAoB,iBAATA,EACP,OAAOA,EAGX,GAAa,QAATA,EACA,OAAO,EAGX,GAAa,SAATA,EACA,OAAO,EAIX,IACkB,MAAbA,EAAM,IAAeA,EAAM,IAAM,KAAOA,EAAM,IAAM,MACrDuX,EAAsBzX,KAAKE,GAC7B,CACE,IAAIyX,GAAOzX,EACX,GAAI,GAAKyX,IAAQzX,EACb,OAAOyX,CAEd,CAED,OAAOzX,CACX,CCzIM,MAAO0X,qBAAqB5Q,YAAlC,WAAA3J,uBACYG,KAAQqa,SAAwB,GAChCra,KAAIiN,KAAuC,EA0DtD,CArDG,UAAAxC,CAAWJ,GAQP,OAPKrK,KAAKiN,KAAK5C,KACXrK,KAAKiN,KAAK5C,GAAsB,IAAIiQ,gBAChCta,KAAKqa,SACLhQ,IAIDrK,KAAKiN,KAAK5C,EACpB,CAOD,UAAMR,CAAK3I,GACP,MAAMqZ,EAAW,IAAIT,SAEfU,EAAW,GAEjB,IAAK,IAAIjS,EAAI,EAAGA,EAAIvI,KAAKqa,SAAS7Y,OAAQ+G,IAAK,CAC3C,MAAMkS,EAAMza,KAAKqa,SAAS9R,GAS1B,GAPAiS,EAASlS,KAAK,CACVsB,OAAQ6Q,EAAI7Q,OACZ3J,IAAKwa,EAAIxa,IACTuM,QAASiO,EAAIjO,QACbxC,KAAMyQ,EAAIC,OAGVD,EAAIE,MACJ,IAAK,IAAI9Y,KAAO4Y,EAAIE,MAAO,CACvB,MAAMA,EAAQF,EAAIE,MAAM9Y,IAAQ,GAChC,IAAK,IAAI+Y,KAAQD,EACbJ,EAASM,OAAO,YAActS,EAAI,IAAM1G,EAAK+Y,EAEpD,CAER,CAYD,OAVAL,EAASM,OAAO,eAAgBhW,KAAK8C,UAAU,CAAE0S,SAAUG,KAE3DtZ,EAAUZ,OAAOe,OACb,CACIuI,OAAQ,OACRI,KAAMuQ,GAEVrZ,GAGGlB,KAAKyJ,OAAOI,KAAK,aAAc3I,EACzC,QAGQoZ,gBAIT,WAAAza,CAAYwa,EAA+BhQ,GAHnCrK,KAAQqa,SAAwB,GAIpCra,KAAKqa,SAAWA,EAChBra,KAAKqK,mBAAqBA,CAC7B,CAOD,MAAAyQ,CACI/Q,EACA7I,GAEAA,EAAUZ,OAAOe,OACb,CACI2I,KAAMD,GAAc,CAAE,GAE1B7I,GAGJ,MAAM4P,EAAwB,CAC1BlH,OAAQ,MACR3J,IACI,oBACA8D,mBAAmB/D,KAAKqK,oBACxB,YAGRrK,KAAK+a,eAAejK,EAAS5P,GAE7BlB,KAAKqa,SAAS/R,KAAKwI,EACtB,CAKD,MAAAF,CACI7G,EACA7I,GAEAA,EAAUZ,OAAOe,OACb,CACI2I,KAAMD,GAAc,CAAE,GAE1B7I,GAGJ,MAAM4P,EAAwB,CAC1BlH,OAAQ,OACR3J,IACI,oBACA8D,mBAAmB/D,KAAKqK,oBACxB,YAGRrK,KAAK+a,eAAejK,EAAS5P,GAE7BlB,KAAKqa,SAAS/R,KAAKwI,EACtB,CAKD,MAAAhH,CACI/B,EACAgC,EACA7I,GAEAA,EAAUZ,OAAOe,OACb,CACI2I,KAAMD,GAAc,CAAE,GAE1B7I,GAGJ,MAAM4P,EAAwB,CAC1BlH,OAAQ,QACR3J,IACI,oBACA8D,mBAAmB/D,KAAKqK,oBACxB,YACAtG,mBAAmBgE,IAG3B/H,KAAK+a,eAAejK,EAAS5P,GAE7BlB,KAAKqa,SAAS/R,KAAKwI,EACtB,CAKD,OAAO/I,EAAY7G,GACfA,EAAUZ,OAAOe,OAAO,CAAE,EAAEH,GAE5B,MAAM4P,EAAwB,CAC1BlH,OAAQ,SACR3J,IACI,oBACA8D,mBAAmB/D,KAAKqK,oBACxB,YACAtG,mBAAmBgE,IAG3B/H,KAAK+a,eAAejK,EAAS5P,GAE7BlB,KAAKqa,SAAS/R,KAAKwI,EACtB,CAEO,cAAAiK,CAAejK,EAAuB5P,GAS1C,GARA+J,4BAA4B/J,GAE5B4P,EAAQtE,QAAUtL,EAAQsL,QAC1BsE,EAAQ4J,KAAO,GACf5J,EAAQ6J,MAAQ,QAIa,IAAlBzZ,EAAQgK,MAAuB,CACtC,MAAMA,EAAQC,qBAAqBjK,EAAQgK,OACvCA,IACA4F,EAAQ7Q,MAAQ6Q,EAAQ7Q,IAAIY,SAAS,KAAO,IAAM,KAAOqK,EAEhE,CAID,IAAIlB,EAAO9I,EAAQ8I,KACf6P,WAAW7P,KACXA,EDhHN,SAAUgR,wBAAwBT,GACpC,IAAIpZ,EAAiC,CAAA,EAsBrC,OApBAoZ,EAASU,SAAQ,CAAC1P,EAAG2P,KACjB,GAAU,iBAANA,GAAoC,iBAAL3P,EAC/B,IACI,IAAI4P,EAAStW,KAAKC,MAAMyG,GACxBjL,OAAOe,OAAOF,EAAQga,EACzB,CAAC,MAAOlN,GACLnH,QAAQC,KAAK,sBAAuBkH,EACvC,WAEwB,IAAd9M,EAAO+Z,IACT3T,MAAMC,QAAQrG,EAAO+Z,MACtB/Z,EAAO+Z,GAAK,CAAC/Z,EAAO+Z,KAExB/Z,EAAO+Z,GAAG5S,KAAK4R,mBAAmB3O,KAElCpK,EAAO+Z,GAAKhB,mBAAmB3O,EAEtC,IAGEpK,CACX,CCwFmB6Z,CAAwBhR,IAGnC,IAAK,MAAMnI,KAAOmI,EAAM,CACpB,MAAM/H,EAAM+H,EAAKnI,GAEjB,GAAI6X,OAAOzX,GACP6O,EAAQ6J,MAAM9Y,GAAOiP,EAAQ6J,MAAM9Y,IAAQ,GAC3CiP,EAAQ6J,MAAM9Y,GAAKyG,KAAKrG,QACrB,GAAIsF,MAAMC,QAAQvF,GAAM,CAC3B,MAAMmZ,EAAa,GACbC,EAAe,GACrB,IAAK,MAAM9P,KAAKtJ,EACRyX,OAAOnO,GACP6P,EAAW9S,KAAKiD,GAEhB8P,EAAa/S,KAAKiD,GAI1B,GAAI6P,EAAW5Z,OAAS,GAAK4Z,EAAW5Z,QAAUS,EAAIT,OAAQ,CAG1DsP,EAAQ6J,MAAM9Y,GAAOiP,EAAQ6J,MAAM9Y,IAAQ,GAC3C,IAAK,IAAI+Y,KAAQQ,EACbtK,EAAQ6J,MAAM9Y,GAAKyG,KAAKsS,EAE/B,MAKG,GAFA9J,EAAQ4J,KAAK7Y,GAAOwZ,EAEhBD,EAAW5Z,OAAS,EAAG,CAIvB,IAAI8Z,EAAUzZ,EACTA,EAAI4L,WAAW,MAAS5L,EAAI0Z,SAAS,OACtCD,GAAW,KAGfxK,EAAQ6J,MAAMW,GAAWxK,EAAQ6J,MAAMW,IAAY,GACnD,IAAK,IAAIV,KAAQQ,EACbtK,EAAQ6J,MAAMW,GAAShT,KAAKsS,EAEnC,CAER,MACG9J,EAAQ4J,KAAK7Y,GAAOI,CAE3B,CACJ,EC9OS,MAAO2U,OAUjB,WAAI4E,GACA,OAAOxb,KAAK6W,OACf,CAMD,WAAI2E,CAAQjQ,GACRvL,KAAK6W,QAAUtL,CAClB,CAiHD,WAAA1L,CAAYgX,EAAU,IAAKhF,EAAkCiF,EAAO,SAJ5D9W,KAAiByb,kBAAuC,GACxDzb,KAAc0b,eAAqC,GACnD1b,KAAsB2b,wBAAY,EAGtC3b,KAAK6W,QAAUA,EACf7W,KAAK8W,KAAOA,EAERjF,EACA7R,KAAK6R,UAAYA,EACO,oBAAV7I,QAA4BA,OAAe4S,KAEzD5b,KAAK6R,UAAY,IAAI3L,cAErBlG,KAAK6R,UAAY,IAAIpJ,eAIzBzI,KAAKiY,YAAc,IAAIF,kBAAkB/X,MACzCA,KAAK2a,MAAQ,IAAIlC,YAAYzY,MAC7BA,KAAK6b,KAAO,IAAIxD,WAAWrY,MAC3BA,KAAK8b,SAAW,IAAIpS,gBAAgB1J,MACpCA,KAAK2R,SAAW,IAAIjG,gBAAgB1L,MACpCA,KAAK+b,OAAS,IAAIxD,cAAcvY,MAChCA,KAAKgc,QAAU,IAAI/C,cAAcjZ,MACjCA,KAAKic,MAAQ,IAAI1C,YAAYvZ,KAChC,CAOD,UAAIkc,GACA,OAAOlc,KAAKyK,WAAW,cAC1B,CAkBD,WAAA0R,GACI,OAAO,IAAI/B,aAAapa,KAC3B,CAKD,UAAAyK,CAA4B2R,GAKxB,OAJKpc,KAAK0b,eAAeU,KACrBpc,KAAK0b,eAAeU,GAAY,IAAI5K,cAAcxR,KAAMoc,IAGrDpc,KAAK0b,eAAeU,EAC9B,CAKD,gBAAAC,CAAiBC,GAGb,OAFAtc,KAAK2b,yBAA2BW,EAEzBtc,IACV,CAKD,aAAAyP,CAAc3B,GAMV,OALI9N,KAAKyb,kBAAkB3N,KACvB9N,KAAKyb,kBAAkB3N,GAAYyO,eAC5Bvc,KAAKyb,kBAAkB3N,IAG3B9N,IACV,CAKD,iBAAAwc,GACI,IAAK,IAAItB,KAAKlb,KAAKyb,kBACfzb,KAAKyb,kBAAkBP,GAAGqB,QAK9B,OAFAvc,KAAKyb,kBAAoB,GAElBzb,IACV,CAyBD,MAAAwQ,CAAOiM,EAAarR,GAChB,IAAKA,EACD,OAAOqR,EAGX,IAAK,IAAI5a,KAAOuJ,EAAQ,CACpB,IAAInJ,EAAMmJ,EAAOvJ,GACjB,cAAeI,GACX,IAAK,UACL,IAAK,SACDA,EAAM,GAAKA,EACX,MACJ,IAAK,SACDA,EAAM,IAAMA,EAAIwD,QAAQ,KAAM,OAAS,IACvC,MACJ,QAEQxD,EADQ,OAARA,EACM,OACCA,aAAeqB,KAChB,IAAMrB,EAAIwJ,cAAchG,QAAQ,IAAK,KAAO,IAE5C,IAAMZ,KAAK8C,UAAU1F,GAAKwD,QAAQ,KAAM,OAAS,IAGnEgX,EAAMA,EAAIC,WAAW,KAAO7a,EAAM,IAAKI,EAC1C,CAED,OAAOwa,CACV,CAKD,UAAAE,CACIrW,EACAqS,EACAC,EAA2B,CAAA,GAG3B,OADA9R,QAAQC,KAAK,yDACN/G,KAAK2a,MAAM9B,OAAOvS,EAAQqS,EAAUC,EAC9C,CAKD,QAAAgE,CAAS3Z,GAEL,OADA6D,QAAQC,KAAK,mDACN/G,KAAK4O,SAAS3L,EACxB,CAKD,QAAA2L,CAAS3L,GACL,IAAIhD,EAAMD,KAAK6W,QA2Bf,MAvBsB,oBAAX7N,SACLA,OAAOqM,UACRpV,EAAIwN,WAAW,aACfxN,EAAIwN,WAAW,aAEhBxN,EAAM+I,OAAOqM,SAASwH,QAAQtB,SAAS,KACjCvS,OAAOqM,SAASwH,OAAO7F,UAAU,EAAGhO,OAAOqM,SAASwH,OAAOrb,OAAS,GACpEwH,OAAOqM,SAASwH,QAAU,GAE3B7c,KAAK6W,QAAQpJ,WAAW,OACzBxN,GAAO+I,OAAOqM,SAASyH,UAAY,IACnC7c,GAAOA,EAAIsb,SAAS,KAAO,GAAK,KAGpCtb,GAAOD,KAAK6W,SAIZ5T,IACAhD,GAAOA,EAAIsb,SAAS,KAAO,GAAK,IAChCtb,GAAOgD,EAAKwK,WAAW,KAAOxK,EAAK+T,UAAU,GAAK/T,GAG/ChD,CACV,CAOD,UAAM4J,CAAc5G,EAAc/B,GAC9BA,EAAUlB,KAAK+c,gBAAgB9Z,EAAM/B,GAGrC,IAAIjB,EAAMD,KAAK4O,SAAS3L,GAExB,GAAIjD,KAAKkT,WAAY,CACjB,MAAM/R,EAASb,OAAOe,OAAO,CAAE,QAAQrB,KAAKkT,WAAWjT,EAAKiB,SAElC,IAAfC,EAAOlB,UACY,IAAnBkB,EAAOD,SAEdjB,EAAMkB,EAAOlB,KAAOA,EACpBiB,EAAUC,EAAOD,SAAWA,GACrBZ,OAAO6E,KAAKhE,GAAQK,SAE3BN,EAAUC,EACV2F,SAASC,MACLD,QAAQC,KACJ,8GAGf,CAGD,QAA6B,IAAlB7F,EAAQgK,MAAuB,CACtC,MAAMA,EAAQC,qBAAqBjK,EAAQgK,OACvCA,IACAjL,IAAQA,EAAIY,SAAS,KAAO,IAAM,KAAOqK,UAEtChK,EAAQgK,KAClB,CAIsD,oBAAnDlL,KAAKgd,UAAU9b,EAAQsL,QAAS,iBAChCtL,EAAQ8I,MACgB,iBAAjB9I,EAAQ8I,OAEf9I,EAAQ8I,KAAOnF,KAAK8C,UAAUzG,EAAQ8I,OAO1C,OAHkB9I,EAAQ+b,OAASA,OAGlBhd,EAAKiB,GACjBiJ,MAAK0C,MAAO1M,IACT,IAAIQ,EAAY,CAAA,EAEhB,IACIA,QAAaR,EAASua,MACzB,CAAC,MAAOzM,GAIL,GACI/M,EAAQyT,QAAQuI,SACH,cAAbjP,GAAKxN,MACW,WAAhBwN,GAAKvN,QAEL,MAAMuN,CAEb,CAMD,GAJIjO,KAAKmd,YACLxc,QAAaX,KAAKmd,UAAUhd,EAAUQ,EAAMO,IAG5Cf,EAASD,QAAU,IACnB,MAAM,IAAIP,oBAAoB,CAC1BM,IAAKE,EAASF,IACdC,OAAQC,EAASD,OACjBS,KAAMA,IAId,OAAOA,CAAS,IAEnBqN,OAAOC,IAEJ,MAAM,IAAItO,oBAAoBsO,EAAI,GAE7C,CASO,eAAA8O,CAAgB9Z,EAAc/B,GAyDlC,IAxDAA,EAAUZ,OAAOe,OAAO,CAAEuI,OAAQ,OAAwB1I,IAGlD8I,KFhaV,SAAUoT,0BAA0BpT,GACtC,GACwB,oBAAb8P,eACS,IAAT9P,GACS,iBAATA,GACE,OAATA,GACA6P,WAAW7P,KACV+P,aAAa/P,GAEd,OAAOA,EAGX,MAAMqT,EAAO,IAAIvD,SAEjB,IAAK,MAAMjY,KAAOmI,EAAM,CACpB,MAAM/H,EAAM+H,EAAKnI,GAIjB,QAAmB,IAARI,EAIX,GAAmB,iBAARA,GAAqB8X,aAAa,CAAEpZ,KAAMsB,IAK9C,CAEH,MAAMmH,EAAgB7B,MAAMC,QAAQvF,GAAOA,EAAM,CAACA,GAClD,IAAK,IAAIsJ,KAAKnC,EACViU,EAAKxC,OAAOhZ,EAAK0J,EAExB,KAX4D,CAEzD,IAAIrG,EAAkC,CAAA,EACtCA,EAAQrD,GAAOI,EACfob,EAAKxC,OAAO,eAAgBhW,KAAK8C,UAAUzC,GAC9C,CAOJ,CAED,OAAOmY,CACX,CE0XuBD,CAA0Blc,EAAQ8I,MAGjDiB,4BAA4B/J,GAI5BA,EAAQgK,MAAQ5K,OAAOe,OAAO,CAAA,EAAIH,EAAQkK,OAAQlK,EAAQgK,YACxB,IAAvBhK,EAAQ4M,cACa,IAAxB5M,EAAQoc,cAAuD,IAA9Bpc,EAAQgK,MAAMoS,YAC/Cpc,EAAQ4M,WAAa,MACd5M,EAAQqc,YAAcrc,EAAQgK,MAAMqS,cAC3Crc,EAAQ4M,WAAa5M,EAAQqc,YAAcrc,EAAQgK,MAAMqS,oBAI1Drc,EAAQoc,mBACRpc,EAAQgK,MAAMoS,mBACdpc,EAAQqc,kBACRrc,EAAQgK,MAAMqS,WAMmC,OAApDvd,KAAKgd,UAAU9b,EAAQsL,QAAS,iBAC/BqN,WAAW3Y,EAAQ8I,QAEpB9I,EAAQsL,QAAUlM,OAAOe,OAAO,CAAE,EAAEH,EAAQsL,QAAS,CACjD,eAAgB,sBAKmC,OAAvDxM,KAAKgd,UAAU9b,EAAQsL,QAAS,qBAChCtL,EAAQsL,QAAUlM,OAAOe,OAAO,CAAE,EAAEH,EAAQsL,QAAS,CACjD,kBAAmBxM,KAAK8W,QAO5B9W,KAAK6R,UAAUtN,OAEsC,OAArDvE,KAAKgd,UAAU9b,EAAQsL,QAAS,mBAEhCtL,EAAQsL,QAAUlM,OAAOe,OAAO,CAAE,EAAEH,EAAQsL,QAAS,CACjDmK,cAAe3W,KAAK6R,UAAUtN,SAKlCvE,KAAK2b,wBAAiD,OAAvBza,EAAQ4M,WAAqB,CAC5D,MAAMA,EAAa5M,EAAQ4M,aAAe5M,EAAQ0I,QAAU,OAAS3G,SAE9D/B,EAAQ4M,WAGf9N,KAAKyP,cAAc3B,GAInB,MAAM0P,EAAa,IAAIC,gBACvBzd,KAAKyb,kBAAkB3N,GAAc0P,EACrCtc,EAAQyT,OAAS6I,EAAW7I,MAC/B,CAED,OAAOzT,CACV,CAMO,SAAA8b,CACJxQ,EACA/L,GAEA+L,EAAUA,GAAW,GACrB/L,EAAOA,EAAKmD,cAEZ,IAAK,IAAI/B,KAAO2K,EACZ,GAAI3K,EAAI+B,eAAiBnD,EACrB,OAAO+L,EAAQ3K,GAIvB,OAAO,IACV,ECphBC,MAAO6b,uBAAuBxX,cAKhC,WAAArG,CAAYmU,GAcRjU,QAhBIC,KAAK2d,MAAqB,GAkB9B3d,KAAK4d,SAAW5J,EAAO/M,KACvBjH,KAAK6d,UAAY7J,EAAO7M,MAExBnH,KAAK8d,UAAS,IAAM9d,KAAK+d,aAAa/J,EAAOgK,UAChD,CAKD,IAAA/W,CAAK1C,EAAe+B,GAChBvG,MAAMkH,KAAK1C,EAAO+B,GAElB,IAAI5D,EAAQ,GACZ,IACIA,EAAQmC,KAAK8C,UAAU,CAAEpD,QAAO+B,UACnC,CAAC,MAAO2H,GACLnH,QAAQC,KAAK,oDAChB,CAED/G,KAAK8d,UAAS,IAAM9d,KAAK4d,SAASlb,IACrC,CAKD,KAAAyE,GACIpH,MAAMoH,QAEFnH,KAAK6d,UACL7d,KAAK8d,UAAS,IAAM9d,KAAK6d,cAEzB7d,KAAK8d,UAAS,IAAM9d,KAAK4d,SAAS,KAEzC,CAKO,kBAAMG,CAAa7Y,GACvB,IAGI,GAFAA,QAAgBA,EAEH,CACT,IAAIiW,EACmB,iBAAZjW,EACPiW,EAAStW,KAAKC,MAAMI,IAAY,CAAA,EACN,iBAAZA,IACdiW,EAASjW,GAGblF,KAAKiH,KAAKkU,EAAO5W,OAAS,GAAI4W,EAAO7U,QAAU6U,EAAO5U,OAAS,KAClE,CACJ,CAAC,MAAOpE,GAAK,CACjB,CAKO,QAAA2b,CAASG,GACbje,KAAK2d,MAAMrV,KAAK2V,GAES,GAArBje,KAAK2d,MAAMnc,QACXxB,KAAKke,UAEZ,CAKO,QAAAA,GACCle,KAAK2d,MAAMnc,QAIhBxB,KAAK2d,MAAM,KAAKQ,SAAQ,KACpBne,KAAK2d,MAAMS,QAENpe,KAAK2d,MAAMnc,QAIhBxB,KAAKke,UAAU,GAEtB"} \ No newline at end of file diff --git a/script/node_modules/pocketbase/dist/pocketbase.iife.d.ts b/script/node_modules/pocketbase/dist/pocketbase.iife.d.ts new file mode 100644 index 0000000..beeecc0 --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.iife.d.ts @@ -0,0 +1,1468 @@ +interface SerializeOptions { + encode?: (val: string | number | boolean) => string; + maxAge?: number; + domain?: string; + path?: string; + expires?: Date; + httpOnly?: boolean; + secure?: boolean; + priority?: string; + sameSite?: boolean | string; +} +interface ListResult { + page: number; + perPage: number; + totalItems: number; + totalPages: number; + items: Array; +} +interface BaseModel { + [key: string]: any; + id: string; +} +interface LogModel extends BaseModel { + level: string; + message: string; + created: string; + updated: string; + data: { + [key: string]: any; + }; +} +interface RecordModel extends BaseModel { + collectionId: string; + collectionName: string; + expand?: { + [key: string]: any; + }; +} +// ------------------------------------------------------------------- +// Collection types +// ------------------------------------------------------------------- +interface CollectionField { + [key: string]: any; + id: string; + name: string; + type: string; + system: boolean; + hidden: boolean; + presentable: boolean; +} +interface TokenConfig { + duration: number; + secret?: string; +} +interface AuthAlertConfig { + enabled: boolean; + emailTemplate: EmailTemplate; +} +interface OTPConfig { + enabled: boolean; + duration: number; + length: number; + emailTemplate: EmailTemplate; +} +interface MFAConfig { + enabled: boolean; + duration: number; + rule: string; +} +interface PasswordAuthConfig { + enabled: boolean; + identityFields: Array; +} +interface OAuth2Provider { + pkce?: boolean; + clientId: string; + name: string; + clientSecret: string; + authURL: string; + tokenURL: string; + userInfoURL: string; + displayName: string; + extra?: { + [key: string]: any; + }; +} +interface OAuth2Config { + enabled: boolean; + mappedFields: { + [key: string]: string; + }; + providers: Array; +} +interface EmailTemplate { + subject: string; + body: string; +} +interface collection extends BaseModel { + name: string; + fields: Array; + indexes: Array; + system: boolean; + listRule?: string; + viewRule?: string; + createRule?: string; + updateRule?: string; + deleteRule?: string; +} +interface BaseCollectionModel extends collection { + type: "base"; +} +interface ViewCollectionModel extends collection { + type: "view"; + viewQuery: string; +} +interface AuthCollectionModel extends collection { + type: "auth"; + authRule?: string; + manageRule?: string; + authAlert: AuthAlertConfig; + oauth2: OAuth2Config; + passwordAuth: PasswordAuthConfig; + mfa: MFAConfig; + otp: OTPConfig; + authToken: TokenConfig; + passwordResetToken: TokenConfig; + emailChangeToken: TokenConfig; + verificationToken: TokenConfig; + fileToken: TokenConfig; + verificationTemplate: EmailTemplate; + resetPasswordTemplate: EmailTemplate; + confirmEmailChangeTemplate: EmailTemplate; +} +type CollectionModel = BaseCollectionModel | ViewCollectionModel | AuthCollectionModel; +type AuthRecord = RecordModel | null; +// for backward compatibility +type OnStoreChangeFunc = (token: string, record: AuthRecord) => void; +/** + * Base AuthStore class that stores the auth state in runtime memory (aka. only for the duration of the store instane). + * + * Usually you wouldn't use it directly and instead use the builtin LocalAuthStore, AsyncAuthStore + * or extend it with your own custom implementation. + */ +declare class BaseAuthStore { + protected baseToken: string; + protected baseModel: AuthRecord; + private _onChangeCallbacks; + /** + * Retrieves the stored token (if any). + */ + get token(): string; + /** + * Retrieves the stored model data (if any). + */ + get record(): AuthRecord; + /** + * @deprecated use `record` instead. + */ + get model(): AuthRecord; + /** + * Loosely checks if the store has valid token (aka. existing and unexpired exp claim). + */ + get isValid(): boolean; + /** + * Loosely checks whether the currently loaded store state is for superuser. + * + * Alternatively you can also compare directly `pb.authStore.record?.collectionName`. + */ + get isSuperuser(): boolean; + /** + * @deprecated use `isSuperuser` instead or simply check the record.collectionName property. + */ + get isAdmin(): boolean; + /** + * @deprecated use `!isSuperuser` instead or simply check the record.collectionName property. + */ + get isAuthRecord(): boolean; + /** + * Saves the provided new token and model data in the auth store. + */ + save(token: string, record?: AuthRecord): void; + /** + * Removes the stored token and model data form the auth store. + */ + clear(): void; + /** + * Parses the provided cookie string and updates the store state + * with the cookie's token and model data. + * + * NB! This function doesn't validate the token or its data. + * Usually this isn't a concern if you are interacting only with the + * PocketBase API because it has the proper server-side security checks in place, + * but if you are using the store `isValid` state for permission controls + * in a node server (eg. SSR), then it is recommended to call `authRefresh()` + * after loading the cookie to ensure an up-to-date token and model state. + * For example: + * + * ```js + * pb.authStore.loadFromCookie("cookie string..."); + * + * try { + * // get an up-to-date auth store state by veryfing and refreshing the loaded auth model (if any) + * pb.authStore.isValid && await pb.collection('users').authRefresh(); + * } catch (_) { + * // clear the auth store on failed refresh + * pb.authStore.clear(); + * } + * ``` + */ + loadFromCookie(cookie: string, key?: string): void; + /** + * Exports the current store state as cookie string. + * + * By default the following optional attributes are added: + * - Secure + * - HttpOnly + * - SameSite=Strict + * - Path=/ + * - Expires={the token expiration date} + * + * NB! If the generated cookie exceeds 4096 bytes, this method will + * strip the model data to the bare minimum to try to fit within the + * recommended size in https://www.rfc-editor.org/rfc/rfc6265#section-6.1. + */ + exportToCookie(options?: SerializeOptions, key?: string): string; + /** + * Register a callback function that will be called on store change. + * + * You can set the `fireImmediately` argument to true in order to invoke + * the provided callback right after registration. + * + * Returns a removal function that you could call to "unsubscribe" from the changes. + */ + onChange(callback: OnStoreChangeFunc, fireImmediately?: boolean): () => void; + protected triggerChange(): void; +} +/** + * BaseService class that should be inherited from all API services. + */ +declare abstract class BaseService { + readonly client: Client; + constructor(client: Client); +} +interface SendOptions extends RequestInit { + // for backward compatibility and to minimize the verbosity, + // any top-level field that doesn't exist in RequestInit or the + // fields below will be treated as query parameter. + [key: string]: any; + /** + * Optional custom fetch function to use for sending the request. + */ + fetch?: (url: RequestInfo | URL, config?: RequestInit) => Promise; + /** + * Custom headers to send with the requests. + */ + headers?: { + [key: string]: string; + }; + /** + * The body of the request (serialized automatically for json requests). + */ + body?: any; + /** + * Query parameters that will be appended to the request url. + */ + query?: { + [key: string]: any; + }; + /** + * @deprecated use `query` instead + * + * for backward-compatibility `params` values are merged with `query`, + * but this option may get removed in the final v1 release + */ + params?: { + [key: string]: any; + }; + /** + * The request identifier that can be used to cancel pending requests. + */ + requestKey?: string | null; + /** + * @deprecated use `requestKey:string` instead + */ + $cancelKey?: string; + /** + * @deprecated use `requestKey:null` instead + */ + $autoCancel?: boolean; +} +interface CommonOptions extends SendOptions { + fields?: string; +} +interface ListOptions extends CommonOptions { + page?: number; + perPage?: number; + sort?: string; + filter?: string; + skipTotal?: boolean; +} +interface FullListOptions extends ListOptions { + batch?: number; +} +interface RecordOptions extends CommonOptions { + expand?: string; +} +interface RecordListOptions extends ListOptions, RecordOptions { +} +interface RecordFullListOptions extends FullListOptions, RecordOptions { +} +interface RecordSubscribeOptions extends SendOptions { + fields?: string; + filter?: string; + expand?: string; +} +interface LogStatsOptions extends CommonOptions { + filter?: string; +} +interface FileOptions extends CommonOptions { + thumb?: string; + download?: boolean; +} +interface appleClientSecret { + secret: string; +} +declare class SettingsService extends BaseService { + /** + * Fetch all available app settings. + * + * @throws {ClientResponseError} + */ + getAll(options?: CommonOptions): Promise<{ + [key: string]: any; + }>; + /** + * Bulk updates app settings. + * + * @throws {ClientResponseError} + */ + update(bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise<{ + [key: string]: any; + }>; + /** + * Performs a S3 filesystem connection test. + * + * The currently supported `filesystem` are "storage" and "backups". + * + * @throws {ClientResponseError} + */ + testS3(filesystem?: string, options?: CommonOptions): Promise; + /** + * Sends a test email. + * + * The possible `emailTemplate` values are: + * - verification + * - password-reset + * - email-change + * + * @throws {ClientResponseError} + */ + testEmail(collectionIdOrName: string, toEmail: string, emailTemplate: string, options?: CommonOptions): Promise; + /** + * Generates a new Apple OAuth2 client secret. + * + * @throws {ClientResponseError} + */ + generateAppleClientSecret(clientId: string, teamId: string, keyId: string, privateKey: string, duration: number, options?: CommonOptions): Promise; +} +type UnsubscribeFunc = () => Promise; +declare class RealtimeService extends BaseService { + clientId: string; + private eventSource; + private subscriptions; + private lastSentSubscriptions; + private connectTimeoutId; + private maxConnectTimeout; + private reconnectTimeoutId; + private reconnectAttempts; + private maxReconnectAttempts; + private predefinedReconnectIntervals; + private pendingConnects; + /** + * Returns whether the realtime connection has been established. + */ + get isConnected(): boolean; + /** + * An optional hook that is invoked when the realtime client disconnects + * either when unsubscribing from all subscriptions or when the + * connection was interrupted or closed by the server. + * + * The received argument could be used to determine whether the disconnect + * is a result from unsubscribing (`activeSubscriptions.length == 0`) + * or because of network/server error (`activeSubscriptions.length > 0`). + * + * If you want to listen for the opposite, aka. when the client connection is established, + * subscribe to the `PB_CONNECT` event. + */ + onDisconnect?: (activeSubscriptions: Array) => void; + /** + * Register the subscription listener. + * + * You can subscribe multiple times to the same topic. + * + * If the SSE connection is not started yet, + * this method will also initialize it. + */ + subscribe(topic: string, callback: (data: any) => void, options?: SendOptions): Promise; + /** + * Unsubscribe from all subscription listeners with the specified topic. + * + * If `topic` is not provided, then this method will unsubscribe + * from all active subscriptions. + * + * This method is no-op if there are no active subscriptions. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribe(topic?: string): Promise; + /** + * Unsubscribe from all subscription listeners starting with the specified topic prefix. + * + * This method is no-op if there are no active subscriptions with the specified topic prefix. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribeByPrefix(keyPrefix: string): Promise; + /** + * Unsubscribe from all subscriptions matching the specified topic and listener function. + * + * This method is no-op if there are no active subscription with + * the specified topic and listener. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribeByTopicAndListener(topic: string, listener: EventListener): Promise; + private hasSubscriptionListeners; + private submitSubscriptions; + private getSubscriptionsCancelKey; + private getSubscriptionsByTopic; + private getNonEmptySubscriptionKeys; + private addAllSubscriptionListeners; + private removeAllSubscriptionListeners; + private connect; + private initConnect; + private hasUnsentSubscriptions; + private connectErrorHandler; + private disconnect; +} +declare abstract class CrudService extends BaseService { + /** + * Base path for the crud actions (without trailing slash, eg. '/admins'). + */ + abstract get baseCrudPath(): string; + /** + * Response data decoder. + */ + decode(data: { + [key: string]: any; + }): T; + /** + * Returns a promise with all list items batch fetched at once + * (by default 1000 items per request; to change it set the `batch` query param). + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + getFullList(options?: FullListOptions): Promise>; + /** + * Legacy version of getFullList with explicitly specified batch size. + */ + getFullList(batch?: number, options?: ListOptions): Promise>; + /** + * Returns paginated items list. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + getList(page?: number, perPage?: number, options?: ListOptions): Promise>; + /** + * Returns the first found item by the specified filter. + * + * Internally it calls `getList(1, 1, { filter, skipTotal })` and + * returns the first found item. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * For consistency with `getOne`, this method will throw a 404 + * ClientResponseError if no item was found. + * + * @throws {ClientResponseError} + */ + getFirstListItem(filter: string, options?: CommonOptions): Promise; + /** + * Returns single item by its id. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * If `id` is empty it will throw a 404 error. + * + * @throws {ClientResponseError} + */ + getOne(id: string, options?: CommonOptions): Promise; + /** + * Creates a new item. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Updates an existing item by its id. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Deletes an existing item by its id. + * + * @throws {ClientResponseError} + */ + delete(id: string, options?: CommonOptions): Promise; + /** + * Returns a promise with all list items batch fetched at once. + */ + protected _getFullList(batchSize?: number, options?: ListOptions): Promise>; +} +interface RecordAuthResponse { + /** + * The signed PocketBase auth record. + */ + record: T; + /** + * The PocketBase record auth token. + * + * If you are looking for the OAuth2 access and refresh tokens + * they are available under the `meta.accessToken` and `meta.refreshToken` props. + */ + token: string; + /** + * Auth meta data usually filled when OAuth2 is used. + */ + meta?: { + [key: string]: any; + }; +} +interface AuthProviderInfo { + name: string; + displayName: string; + state: string; + authURL: string; + codeVerifier: string; + codeChallenge: string; + codeChallengeMethod: string; +} +interface AuthMethodsList { + mfa: { + enabled: boolean; + duration: number; + }; + otp: { + enabled: boolean; + duration: number; + }; + password: { + enabled: boolean; + identityFields: Array; + }; + oauth2: { + enabled: boolean; + providers: Array; + }; +} +interface RecordSubscription { + action: string; // eg. create, update, delete + record: T; +} +type OAuth2UrlCallback = (url: string) => void | Promise; +interface OAuth2AuthConfig extends SendOptions { + // the name of the OAuth2 provider (eg. "google") + provider: string; + // custom scopes to overwrite the default ones + scopes?: Array; + // optional record create data + createData?: { + [key: string]: any; + }; + // optional callback that is triggered after the OAuth2 sign-in/sign-up url generation + urlCallback?: OAuth2UrlCallback; + // optional query params to send with the PocketBase auth request (eg. fields, expand, etc.) + query?: RecordOptions; +} +interface OTPResponse { + otpId: string; +} +declare class RecordService extends CrudService { + readonly collectionIdOrName: string; + constructor(client: Client, collectionIdOrName: string); + /** + * @inheritdoc + */ + get baseCrudPath(): string; + /** + * Returns the current collection service base path. + */ + get baseCollectionPath(): string; + /** + * Returns whether the current service collection is superusers. + */ + get isSuperusers(): boolean; + // --------------------------------------------------------------- + // Realtime handlers + // --------------------------------------------------------------- + /** + * Subscribe to realtime changes to the specified topic ("*" or record id). + * + * If `topic` is the wildcard "*", then this method will subscribe to + * any record changes in the collection. + * + * If `topic` is a record id, then this method will subscribe only + * to changes of the specified record id. + * + * It's OK to subscribe multiple times to the same topic. + * You can use the returned `UnsubscribeFunc` to remove only a single subscription. + * Or use `unsubscribe(topic)` if you want to remove all subscriptions attached to the topic. + */ + subscribe(topic: string, callback: (data: RecordSubscription) => void, options?: RecordSubscribeOptions): Promise; + /** + * Unsubscribe from all subscriptions of the specified topic + * ("*" or record id). + * + * If `topic` is not set, then this method will unsubscribe from + * all subscriptions associated to the current collection. + */ + unsubscribe(topic?: string): Promise; + // --------------------------------------------------------------- + // Crud handers + // --------------------------------------------------------------- + /** + * @inheritdoc + */ + getFullList(options?: RecordFullListOptions): Promise>; + /** + * @inheritdoc + */ + getFullList(batch?: number, options?: RecordListOptions): Promise>; + /** + * @inheritdoc + */ + getList(page?: number, perPage?: number, options?: RecordListOptions): Promise>; + /** + * @inheritdoc + */ + getFirstListItem(filter: string, options?: RecordListOptions): Promise; + /** + * @inheritdoc + */ + getOne(id: string, options?: RecordOptions): Promise; + /** + * @inheritdoc + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): Promise; + /** + * @inheritdoc + * + * If the current `client.authStore.record` matches with the updated id, then + * on success the `client.authStore.record` will be updated with the new response record fields. + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): Promise; + /** + * @inheritdoc + * + * If the current `client.authStore.record` matches with the deleted id, + * then on success the `client.authStore` will be cleared. + */ + delete(id: string, options?: CommonOptions): Promise; + // --------------------------------------------------------------- + // Auth handlers + // --------------------------------------------------------------- + /** + * Prepare successful collection authorization response. + */ + protected authResponse(responseData: any): RecordAuthResponse; + /** + * Returns all available collection auth methods. + * + * @throws {ClientResponseError} + */ + listAuthMethods(options?: CommonOptions): Promise; + /** + * Authenticate a single auth collection record via its username/email and password. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * + * @throws {ClientResponseError} + */ + authWithPassword(usernameOrEmail: string, password: string, options?: RecordOptions): Promise>; + /** + * Authenticate a single auth collection record with OAuth2 code. + * + * If you don't have an OAuth2 code you may also want to check `authWithOAuth2` method. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * - the OAuth2 account data (eg. name, email, avatar, etc.) + * + * @throws {ClientResponseError} + */ + authWithOAuth2Code(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, options?: RecordOptions): Promise>; + /** + * @deprecated + * Consider using authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createdData, options?). + */ + authWithOAuth2Code(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, body?: any, query?: any): Promise>; + /** + * @deprecated This form of authWithOAuth2 is deprecated. + * + * Please use `authWithOAuth2Code()` OR its simplified realtime version + * as shown in https://pocketbase.io/docs/authentication/#oauth2-integration. + */ + authWithOAuth2(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, bodyParams?: { + [key: string]: any; + }, queryParams?: RecordOptions): Promise>; + /** + * Authenticate a single auth collection record with OAuth2 + * **without custom redirects, deeplinks or even page reload**. + * + * This method initializes a one-off realtime subscription and will + * open a popup window with the OAuth2 vendor page to authenticate. + * Once the external OAuth2 sign-in/sign-up flow is completed, the popup + * window will be automatically closed and the OAuth2 data sent back + * to the user through the previously established realtime connection. + * + * You can specify an optional `urlCallback` prop to customize + * the default url `window.open` behavior. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * - the OAuth2 account data (eg. name, email, avatar, etc.) + * + * Example: + * + * ```js + * const authData = await pb.collection("users").authWithOAuth2({ + * provider: "google", + * }) + * ``` + * + * Note1: When creating the OAuth2 app in the provider dashboard + * you have to configure `https://yourdomain.com/api/oauth2-redirect` + * as redirect URL. + * + * Note2: Safari may block the default `urlCallback` popup because + * it doesn't allow `window.open` calls as part of an `async` click functions. + * To workaround this you can either change your click handler to not be marked as `async` + * OR manually call `window.open` before your `async` function and use the + * window reference in your own custom `urlCallback` (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061). + * For example: + * ```js + * + * ... + * document.getElementById("btn").addEventListener("click", () => { + * pb.collection("users").authWithOAuth2({ + * provider: "gitlab", + * }).then((authData) => { + * console.log(authData) + * }).catch((err) => { + * console.log(err, err.originalError); + * }); + * }) + * ``` + * + * @throws {ClientResponseError} + */ + authWithOAuth2(options: OAuth2AuthConfig): Promise>; + /** + * Refreshes the current authenticated record instance and + * returns a new token and record data. + * + * On success this method also automatically updates the client's AuthStore. + * + * @throws {ClientResponseError} + */ + authRefresh(options?: RecordOptions): Promise>; + /** + * @deprecated + * Consider using authRefresh(options?). + */ + authRefresh(body?: any, query?: any): Promise>; + /** + * Sends auth record password reset request. + * + * @throws {ClientResponseError} + */ + requestPasswordReset(email: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestPasswordReset(email, options?). + */ + requestPasswordReset(email: string, body?: any, query?: any): Promise; + /** + * Confirms auth record password reset request. + * + * @throws {ClientResponseError} + */ + confirmPasswordReset(passwordResetToken: string, password: string, passwordConfirm: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmPasswordReset(passwordResetToken, password, passwordConfirm, options?). + */ + confirmPasswordReset(passwordResetToken: string, password: string, passwordConfirm: string, body?: any, query?: any): Promise; + /** + * Sends auth record verification email request. + * + * @throws {ClientResponseError} + */ + requestVerification(email: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestVerification(email, options?). + */ + requestVerification(email: string, body?: any, query?: any): Promise; + /** + * Confirms auth record email verification request. + * + * If the current `client.authStore.record` matches with the auth record from the token, + * then on success the `client.authStore.record.verified` will be updated to `true`. + * + * @throws {ClientResponseError} + */ + confirmVerification(verificationToken: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmVerification(verificationToken, options?). + */ + confirmVerification(verificationToken: string, body?: any, query?: any): Promise; + /** + * Sends an email change request to the authenticated record model. + * + * @throws {ClientResponseError} + */ + requestEmailChange(newEmail: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestEmailChange(newEmail, options?). + */ + requestEmailChange(newEmail: string, body?: any, query?: any): Promise; + /** + * Confirms auth record's new email address. + * + * If the current `client.authStore.record` matches with the auth record from the token, + * then on success the `client.authStore` will be cleared. + * + * @throws {ClientResponseError} + */ + confirmEmailChange(emailChangeToken: string, password: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmEmailChange(emailChangeToken, password, options?). + */ + confirmEmailChange(emailChangeToken: string, password: string, body?: any, query?: any): Promise; + /** + * @deprecated use collection("_externalAuths").* + * + * Lists all linked external auth providers for the specified auth record. + * + * @throws {ClientResponseError} + */ + listExternalAuths(recordId: string, options?: CommonOptions): Promise>; + /** + * @deprecated use collection("_externalAuths").* + * + * Unlink a single external auth provider from the specified auth record. + * + * @throws {ClientResponseError} + */ + unlinkExternalAuth(recordId: string, provider: string, options?: CommonOptions): Promise; + /** + * Sends auth record OTP to the provided email. + * + * @throws {ClientResponseError} + */ + requestOTP(email: string, options?: CommonOptions): Promise; + /** + * Authenticate a single auth collection record via OTP. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * + * @throws {ClientResponseError} + */ + authWithOTP(otpId: string, password: string, options?: CommonOptions): Promise>; + /** + * Impersonate authenticates with the specified recordId and + * returns a new client with the received auth token in a memory store. + * + * If `duration` is 0 the generated auth token will fallback + * to the default collection auth token duration. + * + * This action currently requires superusers privileges. + * + * @throws {ClientResponseError} + */ + impersonate(recordId: string, duration: number, options?: CommonOptions): Promise; + // --------------------------------------------------------------- + // very rudimentary url query params replacement because at the moment + // URL (and URLSearchParams) doesn't seem to be fully supported in React Native + // + // note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html + private _replaceQueryParams; +} +declare class CollectionService extends CrudService { + /** + * @inheritdoc + */ + get baseCrudPath(): string; + /** + * Imports the provided collections. + * + * If `deleteMissing` is `true`, all local collections and their fields, + * that are not present in the imported configuration, WILL BE DELETED + * (including their related records data)! + * + * @throws {ClientResponseError} + */ + import(collections: Array, deleteMissing?: boolean, options?: CommonOptions): Promise; + /** + * Returns type indexed map with scaffolded collection models + * populated with their default field values. + * + * @throws {ClientResponseError} + */ + getScaffolds(options?: CommonOptions): Promise<{ + [key: string]: CollectionModel; + }>; + /** + * Deletes all records associated with the specified collection. + * + * @throws {ClientResponseError} + */ + truncate(collectionIdOrName: string, options?: CommonOptions): Promise; +} +interface HourlyStats { + total: number; + date: string; +} +declare class LogService extends BaseService { + /** + * Returns paginated logs list. + * + * @throws {ClientResponseError} + */ + getList(page?: number, perPage?: number, options?: ListOptions): Promise>; + /** + * Returns a single log by its id. + * + * If `id` is empty it will throw a 404 error. + * + * @throws {ClientResponseError} + */ + getOne(id: string, options?: CommonOptions): Promise; + /** + * Returns logs statistics. + * + * @throws {ClientResponseError} + */ + getStats(options?: LogStatsOptions): Promise>; +} +interface HealthCheckResponse { + code: number; + message: string; + data: { + [key: string]: any; + }; +} +declare class HealthService extends BaseService { + /** + * Checks the health status of the api. + * + * @throws {ClientResponseError} + */ + check(options?: CommonOptions): Promise; +} +declare class FileService extends BaseService { + /** + * @deprecated Please replace with `pb.files.getURL()`. + */ + getUrl(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * Builds and returns an absolute record file url for the provided filename. + */ + getURL(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * Requests a new private file access token for the current auth model. + * + * @throws {ClientResponseError} + */ + getToken(options?: CommonOptions): Promise; +} +interface BackupFileInfo { + key: string; + size: number; + modified: string; +} +declare class BackupService extends BaseService { + /** + * Returns list with all available backup files. + * + * @throws {ClientResponseError} + */ + getFullList(options?: CommonOptions): Promise>; + /** + * Initializes a new backup. + * + * @throws {ClientResponseError} + */ + create(basename: string, options?: CommonOptions): Promise; + /** + * Uploads an existing backup file. + * + * Example: + * + * ```js + * await pb.backups.upload({ + * file: new Blob([...]), + * }); + * ``` + * + * @throws {ClientResponseError} + */ + upload(bodyParams: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Deletes a single backup file. + * + * @throws {ClientResponseError} + */ + delete(key: string, options?: CommonOptions): Promise; + /** + * Initializes an app data restore from an existing backup. + * + * @throws {ClientResponseError} + */ + restore(key: string, options?: CommonOptions): Promise; + /** + * @deprecated Please use `getDownloadURL()`. + */ + getDownloadUrl(token: string, key: string): string; + /** + * Builds a download url for a single existing backup using a + * superuser file token and the backup file key. + * + * The file token can be generated via `pb.files.getToken()`. + */ + getDownloadURL(token: string, key: string): string; +} +interface CronJob { + id: string; + expression: string; +} +declare class CronService extends BaseService { + /** + * Returns list with all registered cron jobs. + * + * @throws {ClientResponseError} + */ + getFullList(options?: CommonOptions): Promise>; + /** + * Runs the specified cron job. + * + * @throws {ClientResponseError} + */ + run(jobId: string, options?: CommonOptions): Promise; +} +interface BatchRequest { + method: string; + url: string; + json?: { + [key: string]: any; + }; + files?: { + [key: string]: Array; + }; + headers?: { + [key: string]: string; + }; +} +interface BatchRequestResult { + status: number; + body: any; +} +declare class BatchService extends BaseService { + private requests; + private subs; + /** + * Starts constructing a batch request entry for the specified collection. + */ + collection(collectionIdOrName: string): SubBatchService; + /** + * Sends the batch requests. + * + * @throws {ClientResponseError} + */ + send(options?: SendOptions): Promise>; +} +declare class SubBatchService { + private requests; + private readonly collectionIdOrName; + constructor(requests: Array, collectionIdOrName: string); + /** + * Registers a record upsert request into the current batch queue. + * + * The request will be executed as update if `bodyParams` have a valid existing record `id` value, otherwise - create. + */ + upsert(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record create request into the current batch queue. + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record update request into the current batch queue. + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record delete request into the current batch queue. + */ + delete(id: string, options?: SendOptions): void; + private prepareRequest; +} +interface BeforeSendResult { + [key: string]: any; + url?: string; + options?: { + [key: string]: any; + }; +} +/** + * PocketBase JS Client. + */ +declare class Client { + /** + * The base PocketBase backend url address (eg. 'http://127.0.0.1.8090'). + */ + baseURL: string; + /** + * Legacy getter alias for baseURL. + * @deprecated Please replace with baseURL. + */ + get baseUrl(): string; + /** + * Legacy setter alias for baseURL. + * @deprecated Please replace with baseURL. + */ + set baseUrl(v: string); + /** + * Hook that get triggered right before sending the fetch request, + * allowing you to inspect and modify the url and request options. + * + * For list of the possible options check https://developer.mozilla.org/en-US/docs/Web/API/fetch#options + * + * You can return a non-empty result object `{ url, options }` to replace the url and request options entirely. + * + * Example: + * ```js + * const pb = new PocketBase("https://example.com") + * + * pb.beforeSend = function (url, options) { + * options.headers = Object.assign({}, options.headers, { + * 'X-Custom-Header': 'example', + * }) + * + * return { url, options } + * } + * + * // use the created client as usual... + * ``` + */ + beforeSend?: (url: string, options: SendOptions) => BeforeSendResult | Promise; + /** + * Hook that get triggered after successfully sending the fetch request, + * allowing you to inspect/modify the response object and its parsed data. + * + * Returns the new Promise resolved `data` that will be returned to the client. + * + * Example: + * ```js + * const pb = new PocketBase("https://example.com") + * + * pb.afterSend = function (response, data, options) { + * if (response.status != 200) { + * throw new ClientResponseError({ + * url: response.url, + * status: response.status, + * response: { ... }, + * }) + * } + * + * return data; + * } + * + * // use the created client as usual... + * ``` + */ + afterSend?: ((response: Response, data: any) => any) & ((response: Response, data: any, options: SendOptions) => any); + /** + * Optional language code (default to `en-US`) that will be sent + * with the requests to the server as `Accept-Language` header. + */ + lang: string; + /** + * A replaceable instance of the local auth store service. + */ + authStore: BaseAuthStore; + /** + * An instance of the service that handles the **Settings APIs**. + */ + readonly settings: SettingsService; + /** + * An instance of the service that handles the **Collection APIs**. + */ + readonly collections: CollectionService; + /** + * An instance of the service that handles the **File APIs**. + */ + readonly files: FileService; + /** + * An instance of the service that handles the **Log APIs**. + */ + readonly logs: LogService; + /** + * An instance of the service that handles the **Realtime APIs**. + */ + readonly realtime: RealtimeService; + /** + * An instance of the service that handles the **Health APIs**. + */ + readonly health: HealthService; + /** + * An instance of the service that handles the **Backup APIs**. + */ + readonly backups: BackupService; + /** + * An instance of the service that handles the **Cron APIs**. + */ + readonly crons: CronService; + private cancelControllers; + private recordServices; + private enableAutoCancellation; + constructor(baseURL?: string, authStore?: BaseAuthStore | null, lang?: string); + /** + * @deprecated + * With PocketBase v0.23.0 admins are converted to a regular auth + * collection named "_superusers", aka. you can use directly collection("_superusers"). + */ + get admins(): RecordService; + /** + * Creates a new batch handler for sending multiple transactional + * create/update/upsert/delete collection requests in one network call. + * + * Example: + * ```js + * const batch = pb.createBatch(); + * + * batch.collection("example1").create({ ... }) + * batch.collection("example2").update("RECORD_ID", { ... }) + * batch.collection("example3").delete("RECORD_ID") + * batch.collection("example4").upsert({ ... }) + * + * await batch.send() + * ``` + */ + /** + * Creates a new batch handler for sending multiple transactional + * create/update/upsert/delete collection requests in one network call. + * + * Example: + * ```js + * const batch = pb.createBatch(); + * + * batch.collection("example1").create({ ... }) + * batch.collection("example2").update("RECORD_ID", { ... }) + * batch.collection("example3").delete("RECORD_ID") + * batch.collection("example4").upsert({ ... }) + * + * await batch.send() + * ``` + */ + createBatch(): BatchService; + /** + * Returns the RecordService associated to the specified collection. + */ + /** + * Returns the RecordService associated to the specified collection. + */ + collection(idOrName: string): RecordService; + /** + * Globally enable or disable auto cancellation for pending duplicated requests. + */ + /** + * Globally enable or disable auto cancellation for pending duplicated requests. + */ + autoCancellation(enable: boolean): Client; + /** + * Cancels single request by its cancellation key. + */ + /** + * Cancels single request by its cancellation key. + */ + cancelRequest(requestKey: string): Client; + /** + * Cancels all pending requests. + */ + /** + * Cancels all pending requests. + */ + cancelAllRequests(): Client; + /** + * Constructs a filter expression with placeholders populated from a parameters object. + * + * Placeholder parameters are defined with the `{:paramName}` notation. + * + * The following parameter values are supported: + * + * - `string` (_single quotes are autoescaped_) + * - `number` + * - `boolean` + * - `Date` object (_stringified into the PocketBase datetime format_) + * - `null` + * - everything else is converted to a string using `JSON.stringify()` + * + * Example: + * + * ```js + * pb.collection("example").getFirstListItem(pb.filter( + * 'title ~ {:title} && created >= {:created}', + * { title: "example", created: new Date()} + * )) + * ``` + */ + /** + * Constructs a filter expression with placeholders populated from a parameters object. + * + * Placeholder parameters are defined with the `{:paramName}` notation. + * + * The following parameter values are supported: + * + * - `string` (_single quotes are autoescaped_) + * - `number` + * - `boolean` + * - `Date` object (_stringified into the PocketBase datetime format_) + * - `null` + * - everything else is converted to a string using `JSON.stringify()` + * + * Example: + * + * ```js + * pb.collection("example").getFirstListItem(pb.filter( + * 'title ~ {:title} && created >= {:created}', + * { title: "example", created: new Date()} + * )) + * ``` + */ + filter(raw: string, params?: { + [key: string]: any; + }): string; + /** + * @deprecated Please use `pb.files.getURL()`. + */ + /** + * @deprecated Please use `pb.files.getURL()`. + */ + getFileUrl(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * @deprecated Please use `pb.buildURL()`. + */ + /** + * @deprecated Please use `pb.buildURL()`. + */ + buildUrl(path: string): string; + /** + * Builds a full client url by safely concatenating the provided path. + */ + /** + * Builds a full client url by safely concatenating the provided path. + */ + buildURL(path: string): string; + /** + * Sends an api http request. + * + * @throws {ClientResponseError} + */ + /** + * Sends an api http request. + * + * @throws {ClientResponseError} + */ + send(path: string, options: SendOptions): Promise; + /** + * Shallow copy the provided object and takes care to initialize + * any options required to preserve the backward compatability. + * + * @param {SendOptions} options + * @return {SendOptions} + */ + /** + * Shallow copy the provided object and takes care to initialize + * any options required to preserve the backward compatability. + * + * @param {SendOptions} options + * @return {SendOptions} + */ + private initSendOptions; + /** + * Extracts the header with the provided name in case-insensitive manner. + * Returns `null` if no header matching the name is found. + */ + /** + * Extracts the header with the provided name in case-insensitive manner. + * Returns `null` if no header matching the name is found. + */ + private getHeader; +} +export { BeforeSendResult, Client as default }; diff --git a/script/node_modules/pocketbase/dist/pocketbase.iife.js b/script/node_modules/pocketbase/dist/pocketbase.iife.js new file mode 100644 index 0000000..6269cc9 --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.iife.js @@ -0,0 +1,2 @@ +var PocketBase=function(){"use strict";class ClientResponseError extends Error{constructor(e){super("ClientResponseError"),this.url="",this.status=0,this.response={},this.isAbort=!1,this.originalError=null,Object.setPrototypeOf(this,ClientResponseError.prototype),null!==e&&"object"==typeof e&&(this.originalError=e.originalError,this.url="string"==typeof e.url?e.url:"",this.status="number"==typeof e.status?e.status:0,this.isAbort=!!e.isAbort||"AbortError"===e.name||"Aborted"===e.message,null!==e.response&&"object"==typeof e.response?this.response=e.response:null!==e.data&&"object"==typeof e.data?this.response=e.data:this.response={}),this.originalError||e instanceof ClientResponseError||(this.originalError=e),this.name="ClientResponseError "+this.status,this.message=this.response?.message,this.message||(this.isAbort?this.message="The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).":this.originalError?.cause?.message?.includes("ECONNREFUSED ::1")?this.message="Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).":this.message="Something went wrong."),this.cause=this.originalError}get data(){return this.response}toJSON(){return{...this}}}const e=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function cookieSerialize(t,s,i){const n=Object.assign({},i||{}),r=n.encode||defaultEncode;if(!e.test(t))throw new TypeError("argument name is invalid");const o=r(s);if(o&&!e.test(o))throw new TypeError("argument val is invalid");let a=t+"="+o;if(null!=n.maxAge){const e=n.maxAge-0;if(isNaN(e)||!isFinite(e))throw new TypeError("option maxAge is invalid");a+="; Max-Age="+Math.floor(e)}if(n.domain){if(!e.test(n.domain))throw new TypeError("option domain is invalid");a+="; Domain="+n.domain}if(n.path){if(!e.test(n.path))throw new TypeError("option path is invalid");a+="; Path="+n.path}if(n.expires){if(!function isDate(e){return"[object Date]"===Object.prototype.toString.call(e)||e instanceof Date}(n.expires)||isNaN(n.expires.valueOf()))throw new TypeError("option expires is invalid");a+="; Expires="+n.expires.toUTCString()}if(n.httpOnly&&(a+="; HttpOnly"),n.secure&&(a+="; Secure"),n.priority){switch("string"==typeof n.priority?n.priority.toLowerCase():n.priority){case"low":a+="; Priority=Low";break;case"medium":a+="; Priority=Medium";break;case"high":a+="; Priority=High";break;default:throw new TypeError("option priority is invalid")}}if(n.sameSite){switch("string"==typeof n.sameSite?n.sameSite.toLowerCase():n.sameSite){case!0:a+="; SameSite=Strict";break;case"lax":a+="; SameSite=Lax";break;case"strict":a+="; SameSite=Strict";break;case"none":a+="; SameSite=None";break;default:throw new TypeError("option sameSite is invalid")}}return a}function defaultDecode(e){return-1!==e.indexOf("%")?decodeURIComponent(e):e}function defaultEncode(e){return encodeURIComponent(e)}const t="undefined"!=typeof navigator&&"ReactNative"===navigator.product||"undefined"!=typeof global&&global.HermesInternal;let s;function getTokenPayload(e){if(e)try{const t=decodeURIComponent(s(e.split(".")[1]).split("").map((function(e){return"%"+("00"+e.charCodeAt(0).toString(16)).slice(-2)})).join(""));return JSON.parse(t)||{}}catch(e){}return{}}function isTokenExpired(e,t=0){let s=getTokenPayload(e);return!(Object.keys(s).length>0&&(!s.exp||s.exp-t>Date.now()/1e3))}s="function"!=typeof atob||t?e=>{let t=String(e).replace(/=+$/,"");if(t.length%4==1)throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");for(var s,i,n=0,r=0,o="";i=t.charAt(r++);~i&&(s=n%4?64*s+i:i,n++%4)?o+=String.fromCharCode(255&s>>(-2*n&6)):0)i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(i);return o}:atob;const i="pb_auth";class BaseAuthStore{constructor(){this.baseToken="",this.baseModel=null,this._onChangeCallbacks=[]}get token(){return this.baseToken}get record(){return this.baseModel}get model(){return this.baseModel}get isValid(){return!isTokenExpired(this.token)}get isSuperuser(){let e=getTokenPayload(this.token);return"auth"==e.type&&("_superusers"==this.record?.collectionName||!this.record?.collectionName&&"pbc_3142635823"==e.collectionId)}get isAdmin(){return console.warn("Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName"),this.isSuperuser}get isAuthRecord(){return console.warn("Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName"),"auth"==getTokenPayload(this.token).type&&!this.isSuperuser}save(e,t){this.baseToken=e||"",this.baseModel=t||null,this.triggerChange()}clear(){this.baseToken="",this.baseModel=null,this.triggerChange()}loadFromCookie(e,t=i){const s=function cookieParse(e,t){const s={};if("string"!=typeof e)return s;const i=Object.assign({},{}).decode||defaultDecode;let n=0;for(;n4096){r.record={id:r.record?.id,email:r.record?.email};const s=["collectionId","collectionName","verified"];for(const e in this.record)s.includes(e)&&(r.record[e]=this.record[e]);o=cookieSerialize(t,JSON.stringify(r),e)}return o}onChange(e,t=!1){return this._onChangeCallbacks.push(e),t&&e(this.token,this.record),()=>{for(let t=this._onChangeCallbacks.length-1;t>=0;t--)if(this._onChangeCallbacks[t]==e)return delete this._onChangeCallbacks[t],void this._onChangeCallbacks.splice(t,1)}}triggerChange(){for(const e of this._onChangeCallbacks)e&&e(this.token,this.record)}}class LocalAuthStore extends BaseAuthStore{constructor(e="pocketbase_auth"){super(),this.storageFallback={},this.storageKey=e,this._bindStorageEvent()}get token(){return(this._storageGet(this.storageKey)||{}).token||""}get record(){const e=this._storageGet(this.storageKey)||{};return e.record||e.model||null}get model(){return this.record}save(e,t){this._storageSet(this.storageKey,{token:e,record:t}),super.save(e,t)}clear(){this._storageRemove(this.storageKey),super.clear()}_storageGet(e){if("undefined"!=typeof window&&window?.localStorage){const t=window.localStorage.getItem(e)||"";try{return JSON.parse(t)}catch(e){return t}}return this.storageFallback[e]}_storageSet(e,t){if("undefined"!=typeof window&&window?.localStorage){let s=t;"string"!=typeof t&&(s=JSON.stringify(t)),window.localStorage.setItem(e,s)}else this.storageFallback[e]=t}_storageRemove(e){"undefined"!=typeof window&&window?.localStorage&&window.localStorage?.removeItem(e),delete this.storageFallback[e]}_bindStorageEvent(){"undefined"!=typeof window&&window?.localStorage&&window.addEventListener&&window.addEventListener("storage",(e=>{if(e.key!=this.storageKey)return;const t=this._storageGet(this.storageKey)||{};super.save(t.token||"",t.record||t.model||null)}))}}class BaseService{constructor(e){this.client=e}}class SettingsService extends BaseService{async getAll(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/settings",e)}async update(e,t){return t=Object.assign({method:"PATCH",body:e},t),this.client.send("/api/settings",t)}async testS3(e="storage",t){return t=Object.assign({method:"POST",body:{filesystem:e}},t),this.client.send("/api/settings/test/s3",t).then((()=>!0))}async testEmail(e,t,s,i){return i=Object.assign({method:"POST",body:{email:t,template:s,collection:e}},i),this.client.send("/api/settings/test/email",i).then((()=>!0))}async generateAppleClientSecret(e,t,s,i,n,r){return r=Object.assign({method:"POST",body:{clientId:e,teamId:t,keyId:s,privateKey:i,duration:n}},r),this.client.send("/api/settings/apple/generate-client-secret",r)}}const n=["requestKey","$cancelKey","$autoCancel","fetch","headers","body","query","params","cache","credentials","headers","integrity","keepalive","method","mode","redirect","referrer","referrerPolicy","signal","window"];function normalizeUnknownQueryParams(e){if(e){e.query=e.query||{};for(let t in e)n.includes(t)||(e.query[t]=e[t],delete e[t])}}function serializeQueryParams(e){const t=[];for(const s in e){const i=encodeURIComponent(s),n=Array.isArray(e[s])?e[s]:[e[s]];for(let e of n)e=prepareQueryParamValue(e),null!==e&&t.push(i+"="+e)}return t.join("&")}function prepareQueryParamValue(e){return null==e?null:e instanceof Date?encodeURIComponent(e.toISOString().replace("T"," ")):"object"==typeof e?encodeURIComponent(JSON.stringify(e)):encodeURIComponent(e)}class RealtimeService extends BaseService{constructor(){super(...arguments),this.clientId="",this.eventSource=null,this.subscriptions={},this.lastSentSubscriptions=[],this.maxConnectTimeout=15e3,this.reconnectAttempts=0,this.maxReconnectAttempts=1/0,this.predefinedReconnectIntervals=[200,300,500,1e3,1200,1500,2e3],this.pendingConnects=[]}get isConnected(){return!!this.eventSource&&!!this.clientId&&!this.pendingConnects.length}async subscribe(e,t,s){if(!e)throw new Error("topic must be set.");let i=e;if(s){normalizeUnknownQueryParams(s=Object.assign({},s));const e="options="+encodeURIComponent(JSON.stringify({query:s.query,headers:s.headers}));i+=(i.includes("?")?"&":"?")+e}const listener=function(e){const s=e;let i;try{i=JSON.parse(s?.data)}catch{}t(i||{})};return this.subscriptions[i]||(this.subscriptions[i]=[]),this.subscriptions[i].push(listener),this.isConnected?1===this.subscriptions[i].length?await this.submitSubscriptions():this.eventSource?.addEventListener(i,listener):await this.connect(),async()=>this.unsubscribeByTopicAndListener(e,listener)}async unsubscribe(e){let t=!1;if(e){const s=this.getSubscriptionsByTopic(e);for(let e in s)if(this.hasSubscriptionListeners(e)){for(let t of this.subscriptions[e])this.eventSource?.removeEventListener(e,t);delete this.subscriptions[e],t||(t=!0)}}else this.subscriptions={};this.hasSubscriptionListeners()?t&&await this.submitSubscriptions():this.disconnect()}async unsubscribeByPrefix(e){let t=!1;for(let s in this.subscriptions)if((s+"?").startsWith(e)){t=!0;for(let e of this.subscriptions[s])this.eventSource?.removeEventListener(s,e);delete this.subscriptions[s]}t&&(this.hasSubscriptionListeners()?await this.submitSubscriptions():this.disconnect())}async unsubscribeByTopicAndListener(e,t){let s=!1;const i=this.getSubscriptionsByTopic(e);for(let e in i){if(!Array.isArray(this.subscriptions[e])||!this.subscriptions[e].length)continue;let i=!1;for(let s=this.subscriptions[e].length-1;s>=0;s--)this.subscriptions[e][s]===t&&(i=!0,delete this.subscriptions[e][s],this.subscriptions[e].splice(s,1),this.eventSource?.removeEventListener(e,t));i&&(this.subscriptions[e].length||delete this.subscriptions[e],s||this.hasSubscriptionListeners(e)||(s=!0))}this.hasSubscriptionListeners()?s&&await this.submitSubscriptions():this.disconnect()}hasSubscriptionListeners(e){if(this.subscriptions=this.subscriptions||{},e)return!!this.subscriptions[e]?.length;for(let e in this.subscriptions)if(this.subscriptions[e]?.length)return!0;return!1}async submitSubscriptions(){if(this.clientId)return this.addAllSubscriptionListeners(),this.lastSentSubscriptions=this.getNonEmptySubscriptionKeys(),this.client.send("/api/realtime",{method:"POST",body:{clientId:this.clientId,subscriptions:this.lastSentSubscriptions},requestKey:this.getSubscriptionsCancelKey()}).catch((e=>{if(!e?.isAbort)throw e}))}getSubscriptionsCancelKey(){return"realtime_"+this.clientId}getSubscriptionsByTopic(e){const t={};e=e.includes("?")?e:e+"?";for(let s in this.subscriptions)(s+"?").startsWith(e)&&(t[s]=this.subscriptions[s]);return t}getNonEmptySubscriptionKeys(){const e=[];for(let t in this.subscriptions)this.subscriptions[t].length&&e.push(t);return e}addAllSubscriptionListeners(){if(this.eventSource){this.removeAllSubscriptionListeners();for(let e in this.subscriptions)for(let t of this.subscriptions[e])this.eventSource.addEventListener(e,t)}}removeAllSubscriptionListeners(){if(this.eventSource)for(let e in this.subscriptions)for(let t of this.subscriptions[e])this.eventSource.removeEventListener(e,t)}async connect(){if(!(this.reconnectAttempts>0))return new Promise(((e,t)=>{this.pendingConnects.push({resolve:e,reject:t}),this.pendingConnects.length>1||this.initConnect()}))}initConnect(){this.disconnect(!0),clearTimeout(this.connectTimeoutId),this.connectTimeoutId=setTimeout((()=>{this.connectErrorHandler(new Error("EventSource connect took too long."))}),this.maxConnectTimeout),this.eventSource=new EventSource(this.client.buildURL("/api/realtime")),this.eventSource.onerror=e=>{this.connectErrorHandler(new Error("Failed to establish realtime connection."))},this.eventSource.addEventListener("PB_CONNECT",(e=>{const t=e;this.clientId=t?.lastEventId,this.submitSubscriptions().then((async()=>{let e=3;for(;this.hasUnsentSubscriptions()&&e>0;)e--,await this.submitSubscriptions()})).then((()=>{for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[],this.reconnectAttempts=0,clearTimeout(this.reconnectTimeoutId),clearTimeout(this.connectTimeoutId);const t=this.getSubscriptionsByTopic("PB_CONNECT");for(let s in t)for(let i of t[s])i(e)})).catch((e=>{this.clientId="",this.connectErrorHandler(e)}))}))}hasUnsentSubscriptions(){const e=this.getNonEmptySubscriptionKeys();if(e.length!=this.lastSentSubscriptions.length)return!0;for(const t of e)if(!this.lastSentSubscriptions.includes(t))return!0;return!1}connectErrorHandler(e){if(clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),!this.clientId&&!this.reconnectAttempts||this.reconnectAttempts>this.maxReconnectAttempts){for(let t of this.pendingConnects)t.reject(new ClientResponseError(e));return this.pendingConnects=[],void this.disconnect()}this.disconnect(!0);const t=this.predefinedReconnectIntervals[this.reconnectAttempts]||this.predefinedReconnectIntervals[this.predefinedReconnectIntervals.length-1];this.reconnectAttempts++,this.reconnectTimeoutId=setTimeout((()=>{this.initConnect()}),t)}disconnect(e=!1){if(this.clientId&&this.onDisconnect&&this.onDisconnect(Object.keys(this.subscriptions)),clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),this.removeAllSubscriptionListeners(),this.client.cancelRequest(this.getSubscriptionsCancelKey()),this.eventSource?.close(),this.eventSource=null,this.clientId="",!e){this.reconnectAttempts=0;for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[]}}}class CrudService extends BaseService{decode(e){return e}async getFullList(e,t){if("number"==typeof e)return this._getFullList(e,t);let s=1e3;return(t=Object.assign({},e,t)).batch&&(s=t.batch,delete t.batch),this._getFullList(s,t)}async getList(e=1,t=30,s){return(s=Object.assign({method:"GET"},s)).query=Object.assign({page:e,perPage:t},s.query),this.client.send(this.baseCrudPath,s).then((e=>(e.items=e.items?.map((e=>this.decode(e)))||[],e)))}async getFirstListItem(e,t){return(t=Object.assign({requestKey:"one_by_filter_"+this.baseCrudPath+"_"+e},t)).query=Object.assign({filter:e,skipTotal:1},t.query),this.getList(1,1,t).then((e=>{if(!e?.items?.length)throw new ClientResponseError({status:404,response:{code:404,message:"The requested resource wasn't found.",data:{}}});return e.items[0]}))}async getOne(e,t){if(!e)throw new ClientResponseError({url:this.client.buildURL(this.baseCrudPath+"/"),status:404,response:{code:404,message:"Missing required record id.",data:{}}});return t=Object.assign({method:"GET"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),t).then((e=>this.decode(e)))}async create(e,t){return t=Object.assign({method:"POST",body:e},t),this.client.send(this.baseCrudPath,t).then((e=>this.decode(e)))}async update(e,t,s){return s=Object.assign({method:"PATCH",body:t},s),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),s).then((e=>this.decode(e)))}async delete(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),t).then((()=>!0))}_getFullList(e=1e3,t){(t=t||{}).query=Object.assign({skipTotal:1},t.query);let s=[],request=async i=>this.getList(i,e||1e3,t).then((e=>{const t=e.items;return s=s.concat(t),t.length==e.perPage?request(i+1):s}));return request(1)}}function normalizeLegacyOptionsArgs(e,t,s,i){const n=void 0!==i;return n||void 0!==s?n?(console.warn(e),t.body=Object.assign({},t.body,s),t.query=Object.assign({},t.query,i),t):Object.assign(t,s):t}function resetAutoRefresh(e){e._resetAutoRefresh?.()}class RecordService extends CrudService{constructor(e,t){super(e),this.collectionIdOrName=t}get baseCrudPath(){return this.baseCollectionPath+"/records"}get baseCollectionPath(){return"/api/collections/"+encodeURIComponent(this.collectionIdOrName)}get isSuperusers(){return"_superusers"==this.collectionIdOrName||"_pbc_2773867675"==this.collectionIdOrName}async subscribe(e,t,s){if(!e)throw new Error("Missing topic.");if(!t)throw new Error("Missing subscription callback.");return this.client.realtime.subscribe(this.collectionIdOrName+"/"+e,t,s)}async unsubscribe(e){return e?this.client.realtime.unsubscribe(this.collectionIdOrName+"/"+e):this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName)}async getFullList(e,t){if("number"==typeof e)return super.getFullList(e,t);const s=Object.assign({},e,t);return super.getFullList(s)}async getList(e=1,t=30,s){return super.getList(e,t,s)}async getFirstListItem(e,t){return super.getFirstListItem(e,t)}async getOne(e,t){return super.getOne(e,t)}async create(e,t){return super.create(e,t)}async update(e,t,s){return super.update(e,t,s).then((e=>{if(this.client.authStore.record?.id===e?.id&&(this.client.authStore.record?.collectionId===this.collectionIdOrName||this.client.authStore.record?.collectionName===this.collectionIdOrName)){let t=Object.assign({},this.client.authStore.record.expand),s=Object.assign({},this.client.authStore.record,e);t&&(s.expand=Object.assign(t,e.expand)),this.client.authStore.save(this.client.authStore.token,s)}return e}))}async delete(e,t){return super.delete(e,t).then((t=>(!t||this.client.authStore.record?.id!==e||this.client.authStore.record?.collectionId!==this.collectionIdOrName&&this.client.authStore.record?.collectionName!==this.collectionIdOrName||this.client.authStore.clear(),t)))}authResponse(e){const t=this.decode(e?.record||{});return this.client.authStore.save(e?.token,t),Object.assign({},e,{token:e?.token||"",record:t})}async listAuthMethods(e){return e=Object.assign({method:"GET",fields:"mfa,otp,password,oauth2"},e),this.client.send(this.baseCollectionPath+"/auth-methods",e)}async authWithPassword(e,t,s){let i;s=Object.assign({method:"POST",body:{identity:e,password:t}},s),this.isSuperusers&&(i=s.autoRefreshThreshold,delete s.autoRefreshThreshold,s.autoRefresh||resetAutoRefresh(this.client));let n=await this.client.send(this.baseCollectionPath+"/auth-with-password",s);return n=this.authResponse(n),i&&this.isSuperusers&&function registerAutoRefresh(e,t,s,i){resetAutoRefresh(e);const n=e.beforeSend,r=e.authStore.record,o=e.authStore.onChange(((t,s)=>{(!t||s?.id!=r?.id||(s?.collectionId||r?.collectionId)&&s?.collectionId!=r?.collectionId)&&resetAutoRefresh(e)}));e._resetAutoRefresh=function(){o(),e.beforeSend=n,delete e._resetAutoRefresh},e.beforeSend=async(r,o)=>{const a=e.authStore.token;if(o.query?.autoRefresh)return n?n(r,o):{url:r,sendOptions:o};let c=e.authStore.isValid;if(c&&isTokenExpired(e.authStore.token,t))try{await s()}catch(e){c=!1}c||await i();const l=o.headers||{};for(let t in l)if("authorization"==t.toLowerCase()&&a==l[t]&&e.authStore.token){l[t]=e.authStore.token;break}return o.headers=l,n?n(r,o):{url:r,sendOptions:o}}}(this.client,i,(()=>this.authRefresh({autoRefresh:!0})),(()=>this.authWithPassword(e,t,Object.assign({autoRefresh:!0},s)))),n}async authWithOAuth2Code(e,t,s,i,n,r,o){let a={method:"POST",body:{provider:e,code:t,codeVerifier:s,redirectURL:i,createData:n}};return a=normalizeLegacyOptionsArgs("This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).",a,r,o),this.client.send(this.baseCollectionPath+"/auth-with-oauth2",a).then((e=>this.authResponse(e)))}authWithOAuth2(...e){if(e.length>1||"string"==typeof e?.[0])return console.warn("PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration."),this.authWithOAuth2Code(e?.[0]||"",e?.[1]||"",e?.[2]||"",e?.[3]||"",e?.[4]||{},e?.[5]||{},e?.[6]||{});const t=e?.[0]||{};let s=null;t.urlCallback||(s=openBrowserPopup(void 0));const i=new RealtimeService(this.client);function cleanup(){s?.close(),i.unsubscribe()}const n={},r=t.requestKey;return r&&(n.requestKey=r),this.listAuthMethods(n).then((e=>{const n=e.oauth2.providers.find((e=>e.name===t.provider));if(!n)throw new ClientResponseError(new Error(`Missing or invalid provider "${t.provider}".`));const o=this.client.buildURL("/api/oauth2-redirect");return new Promise((async(e,a)=>{const c=r?this.client.cancelControllers?.[r]:void 0;c&&(c.signal.onabort=()=>{cleanup(),a(new ClientResponseError({isAbort:!0,message:"manually cancelled"}))}),i.onDisconnect=e=>{e.length&&a&&(cleanup(),a(new ClientResponseError(new Error("realtime connection interrupted"))))};try{await i.subscribe("@oauth2",(async s=>{const r=i.clientId;try{if(!s.state||r!==s.state)throw new Error("State parameters don't match.");if(s.error||!s.code)throw new Error("OAuth2 redirect error or missing code: "+s.error);const i=Object.assign({},t);delete i.provider,delete i.scopes,delete i.createData,delete i.urlCallback,c?.signal?.onabort&&(c.signal.onabort=null);const a=await this.authWithOAuth2Code(n.name,s.code,n.codeVerifier,o,t.createData,i);e(a)}catch(e){a(new ClientResponseError(e))}cleanup()}));const r={state:i.clientId};t.scopes?.length&&(r.scope=t.scopes.join(" "));const l=this._replaceQueryParams(n.authURL+o,r);let h=t.urlCallback||function(e){s?s.location.href=e:s=openBrowserPopup(e)};await h(l)}catch(e){c?.signal?.onabort&&(c.signal.onabort=null),cleanup(),a(new ClientResponseError(e))}}))})).catch((e=>{throw cleanup(),e}))}async authRefresh(e,t){let s={method:"POST"};return s=normalizeLegacyOptionsArgs("This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).",s,e,t),this.client.send(this.baseCollectionPath+"/auth-refresh",s).then((e=>this.authResponse(e)))}async requestPasswordReset(e,t,s){let i={method:"POST",body:{email:e}};return i=normalizeLegacyOptionsArgs("This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-password-reset",i).then((()=>!0))}async confirmPasswordReset(e,t,s,i,n){let r={method:"POST",body:{token:e,password:t,passwordConfirm:s}};return r=normalizeLegacyOptionsArgs("This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).",r,i,n),this.client.send(this.baseCollectionPath+"/confirm-password-reset",r).then((()=>!0))}async requestVerification(e,t,s){let i={method:"POST",body:{email:e}};return i=normalizeLegacyOptionsArgs("This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-verification",i).then((()=>!0))}async confirmVerification(e,t,s){let i={method:"POST",body:{token:e}};return i=normalizeLegacyOptionsArgs("This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/confirm-verification",i).then((()=>{const t=getTokenPayload(e),s=this.client.authStore.record;return s&&!s.verified&&s.id===t.id&&s.collectionId===t.collectionId&&(s.verified=!0,this.client.authStore.save(this.client.authStore.token,s)),!0}))}async requestEmailChange(e,t,s){let i={method:"POST",body:{newEmail:e}};return i=normalizeLegacyOptionsArgs("This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-email-change",i).then((()=>!0))}async confirmEmailChange(e,t,s,i){let n={method:"POST",body:{token:e,password:t}};return n=normalizeLegacyOptionsArgs("This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).",n,s,i),this.client.send(this.baseCollectionPath+"/confirm-email-change",n).then((()=>{const t=getTokenPayload(e),s=this.client.authStore.record;return s&&s.id===t.id&&s.collectionId===t.collectionId&&this.client.authStore.clear(),!0}))}async listExternalAuths(e,t){return this.client.collection("_externalAuths").getFullList(Object.assign({},t,{filter:this.client.filter("recordRef = {:id}",{id:e})}))}async unlinkExternalAuth(e,t,s){const i=await this.client.collection("_externalAuths").getFirstListItem(this.client.filter("recordRef = {:recordId} && provider = {:provider}",{recordId:e,provider:t}));return this.client.collection("_externalAuths").delete(i.id,s).then((()=>!0))}async requestOTP(e,t){return t=Object.assign({method:"POST",body:{email:e}},t),this.client.send(this.baseCollectionPath+"/request-otp",t)}async authWithOTP(e,t,s){return s=Object.assign({method:"POST",body:{otpId:e,password:t}},s),this.client.send(this.baseCollectionPath+"/auth-with-otp",s).then((e=>this.authResponse(e)))}async impersonate(e,t,s){(s=Object.assign({method:"POST",body:{duration:t}},s)).headers=s.headers||{},s.headers.Authorization||(s.headers.Authorization=this.client.authStore.token);const i=new Client(this.client.baseURL,new BaseAuthStore,this.client.lang),n=await i.send(this.baseCollectionPath+"/impersonate/"+encodeURIComponent(e),s);return i.authStore.save(n?.token,this.decode(n?.record||{})),i}_replaceQueryParams(e,t={}){let s=e,i="";e.indexOf("?")>=0&&(s=e.substring(0,e.indexOf("?")),i=e.substring(e.indexOf("?")+1));const n={},r=i.split("&");for(const e of r){if(""==e)continue;const t=e.split("=");n[decodeURIComponent(t[0].replace(/\+/g," "))]=decodeURIComponent((t[1]||"").replace(/\+/g," "))}for(let e in t)t.hasOwnProperty(e)&&(null==t[e]?delete n[e]:n[e]=t[e]);i="";for(let e in n)n.hasOwnProperty(e)&&(""!=i&&(i+="&"),i+=encodeURIComponent(e.replace(/%20/g,"+"))+"="+encodeURIComponent(n[e].replace(/%20/g,"+")));return""!=i?s+"?"+i:s}}function openBrowserPopup(e){if("undefined"==typeof window||!window?.open)throw new ClientResponseError(new Error("Not in a browser context - please pass a custom urlCallback function."));let t=1024,s=768,i=window.innerWidth,n=window.innerHeight;t=t>i?i:t,s=s>n?n:s;let r=i/2-t/2,o=n/2-s/2;return window.open(e,"popup_window","width="+t+",height="+s+",top="+o+",left="+r+",resizable,menubar=no")}class CollectionService extends CrudService{get baseCrudPath(){return"/api/collections"}async import(e,t=!1,s){return s=Object.assign({method:"PUT",body:{collections:e,deleteMissing:t}},s),this.client.send(this.baseCrudPath+"/import",s).then((()=>!0))}async getScaffolds(e){return e=Object.assign({method:"GET"},e),this.client.send(this.baseCrudPath+"/meta/scaffolds",e)}async truncate(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e)+"/truncate",t).then((()=>!0))}}class LogService extends BaseService{async getList(e=1,t=30,s){return(s=Object.assign({method:"GET"},s)).query=Object.assign({page:e,perPage:t},s.query),this.client.send("/api/logs",s)}async getOne(e,t){if(!e)throw new ClientResponseError({url:this.client.buildURL("/api/logs/"),status:404,response:{code:404,message:"Missing required log id.",data:{}}});return t=Object.assign({method:"GET"},t),this.client.send("/api/logs/"+encodeURIComponent(e),t)}async getStats(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/logs/stats",e)}}class HealthService extends BaseService{async check(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/health",e)}}class FileService extends BaseService{getUrl(e,t,s={}){return console.warn("Please replace pb.files.getUrl() with pb.files.getURL()"),this.getURL(e,t,s)}getURL(e,t,s={}){if(!t||!e?.id||!e?.collectionId&&!e?.collectionName)return"";const i=[];i.push("api"),i.push("files"),i.push(encodeURIComponent(e.collectionId||e.collectionName)),i.push(encodeURIComponent(e.id)),i.push(encodeURIComponent(t));let n=this.client.buildURL(i.join("/"));!1===s.download&&delete s.download;const r=serializeQueryParams(s);return r&&(n+=(n.includes("?")?"&":"?")+r),n}async getToken(e){return e=Object.assign({method:"POST"},e),this.client.send("/api/files/token",e).then((e=>e?.token||""))}}class BackupService extends BaseService{async getFullList(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/backups",e)}async create(e,t){return t=Object.assign({method:"POST",body:{name:e}},t),this.client.send("/api/backups",t).then((()=>!0))}async upload(e,t){return t=Object.assign({method:"POST",body:e},t),this.client.send("/api/backups/upload",t).then((()=>!0))}async delete(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(`/api/backups/${encodeURIComponent(e)}`,t).then((()=>!0))}async restore(e,t){return t=Object.assign({method:"POST"},t),this.client.send(`/api/backups/${encodeURIComponent(e)}/restore`,t).then((()=>!0))}getDownloadUrl(e,t){return console.warn("Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()"),this.getDownloadURL(e,t)}getDownloadURL(e,t){return this.client.buildURL(`/api/backups/${encodeURIComponent(t)}?token=${encodeURIComponent(e)}`)}}class CronService extends BaseService{async getFullList(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/crons",e)}async run(e,t){return t=Object.assign({method:"POST"},t),this.client.send(`/api/crons/${encodeURIComponent(e)}`,t).then((()=>!0))}}function isFile(e){return"undefined"!=typeof Blob&&e instanceof Blob||"undefined"!=typeof File&&e instanceof File||null!==e&&"object"==typeof e&&e.uri&&("undefined"!=typeof navigator&&"ReactNative"===navigator.product||"undefined"!=typeof global&&global.HermesInternal)}function isFormData(e){return e&&("FormData"===e.constructor?.name||"undefined"!=typeof FormData&&e instanceof FormData)}function hasFileField(e){for(const t in e){const s=Array.isArray(e[t])?e[t]:[e[t]];for(const e of s)if(isFile(e))return!0}return!1}const r=/^[\-\.\d]+$/;function inferFormDataValue(e){if("string"!=typeof e)return e;if("true"==e)return!0;if("false"==e)return!1;if(("-"===e[0]||e[0]>="0"&&e[0]<="9")&&r.test(e)){let t=+e;if(""+t===e)return t}return e}class BatchService extends BaseService{constructor(){super(...arguments),this.requests=[],this.subs={}}collection(e){return this.subs[e]||(this.subs[e]=new SubBatchService(this.requests,e)),this.subs[e]}async send(e){const t=new FormData,s=[];for(let e=0;e{if("@jsonPayload"===s&&"string"==typeof e)try{let s=JSON.parse(e);Object.assign(t,s)}catch(e){console.warn("@jsonPayload error:",e)}else void 0!==t[s]?(Array.isArray(t[s])||(t[s]=[t[s]]),t[s].push(inferFormDataValue(e))):t[s]=inferFormDataValue(e)})),t}(s));for(const t in s){const i=s[t];if(isFile(i))e.files[t]=e.files[t]||[],e.files[t].push(i);else if(Array.isArray(i)){const s=[],n=[];for(const e of i)isFile(e)?s.push(e):n.push(e);if(s.length>0&&s.length==i.length){e.files[t]=e.files[t]||[];for(let i of s)e.files[t].push(i)}else if(e.json[t]=n,s.length>0){let i=t;t.startsWith("+")||t.endsWith("+")||(i+="+"),e.files[i]=e.files[i]||[];for(let t of s)e.files[i].push(t)}}else e.json[t]=i}}}class Client{get baseUrl(){return this.baseURL}set baseUrl(e){this.baseURL=e}constructor(e="/",t,s="en-US"){this.cancelControllers={},this.recordServices={},this.enableAutoCancellation=!0,this.baseURL=e,this.lang=s,t?this.authStore=t:"undefined"!=typeof window&&window.Deno?this.authStore=new BaseAuthStore:this.authStore=new LocalAuthStore,this.collections=new CollectionService(this),this.files=new FileService(this),this.logs=new LogService(this),this.settings=new SettingsService(this),this.realtime=new RealtimeService(this),this.health=new HealthService(this),this.backups=new BackupService(this),this.crons=new CronService(this)}get admins(){return this.collection("_superusers")}createBatch(){return new BatchService(this)}collection(e){return this.recordServices[e]||(this.recordServices[e]=new RecordService(this,e)),this.recordServices[e]}autoCancellation(e){return this.enableAutoCancellation=!!e,this}cancelRequest(e){return this.cancelControllers[e]&&(this.cancelControllers[e].abort(),delete this.cancelControllers[e]),this}cancelAllRequests(){for(let e in this.cancelControllers)this.cancelControllers[e].abort();return this.cancelControllers={},this}filter(e,t){if(!t)return e;for(let s in t){let i=t[s];switch(typeof i){case"boolean":case"number":i=""+i;break;case"string":i="'"+i.replace(/'/g,"\\'")+"'";break;default:i=null===i?"null":i instanceof Date?"'"+i.toISOString().replace("T"," ")+"'":"'"+JSON.stringify(i).replace(/'/g,"\\'")+"'"}e=e.replaceAll("{:"+s+"}",i)}return e}getFileUrl(e,t,s={}){return console.warn("Please replace pb.getFileUrl() with pb.files.getURL()"),this.files.getURL(e,t,s)}buildUrl(e){return console.warn("Please replace pb.buildUrl() with pb.buildURL()"),this.buildURL(e)}buildURL(e){let t=this.baseURL;return"undefined"==typeof window||!window.location||t.startsWith("https://")||t.startsWith("http://")||(t=window.location.origin?.endsWith("/")?window.location.origin.substring(0,window.location.origin.length-1):window.location.origin||"",this.baseURL.startsWith("/")||(t+=window.location.pathname||"/",t+=t.endsWith("/")?"":"/"),t+=this.baseURL),e&&(t+=t.endsWith("/")?"":"/",t+=e.startsWith("/")?e.substring(1):e),t}async send(e,t){t=this.initSendOptions(e,t);let s=this.buildURL(e);if(this.beforeSend){const e=Object.assign({},await this.beforeSend(s,t));void 0!==e.url||void 0!==e.options?(s=e.url||s,t=e.options||t):Object.keys(e).length&&(t=e,console?.warn&&console.warn("Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`."))}if(void 0!==t.query){const e=serializeQueryParams(t.query);e&&(s+=(s.includes("?")?"&":"?")+e),delete t.query}"application/json"==this.getHeader(t.headers,"Content-Type")&&t.body&&"string"!=typeof t.body&&(t.body=JSON.stringify(t.body));return(t.fetch||fetch)(s,t).then((async e=>{let s={};try{s=await e.json()}catch(e){if(t.signal?.aborted||"AbortError"==e?.name||"Aborted"==e?.message)throw e}if(this.afterSend&&(s=await this.afterSend(e,s,t)),e.status>=400)throw new ClientResponseError({url:e.url,status:e.status,data:s});return s})).catch((e=>{throw new ClientResponseError(e)}))}initSendOptions(e,t){if((t=Object.assign({method:"GET"},t)).body=function convertToFormDataIfNeeded(e){if("undefined"==typeof FormData||void 0===e||"object"!=typeof e||null===e||isFormData(e)||!hasFileField(e))return e;const t=new FormData;for(const s in e){const i=e[s];if(void 0!==i)if("object"!=typeof i||hasFileField({data:i})){const e=Array.isArray(i)?i:[i];for(let i of e)t.append(s,i)}else{let e={};e[s]=i,t.append("@jsonPayload",JSON.stringify(e))}}return t}(t.body),normalizeUnknownQueryParams(t),t.query=Object.assign({},t.params,t.query),void 0===t.requestKey&&(!1===t.$autoCancel||!1===t.query.$autoCancel?t.requestKey=null:(t.$cancelKey||t.query.$cancelKey)&&(t.requestKey=t.$cancelKey||t.query.$cancelKey)),delete t.$autoCancel,delete t.query.$autoCancel,delete t.$cancelKey,delete t.query.$cancelKey,null!==this.getHeader(t.headers,"Content-Type")||isFormData(t.body)||(t.headers=Object.assign({},t.headers,{"Content-Type":"application/json"})),null===this.getHeader(t.headers,"Accept-Language")&&(t.headers=Object.assign({},t.headers,{"Accept-Language":this.lang})),this.authStore.token&&null===this.getHeader(t.headers,"Authorization")&&(t.headers=Object.assign({},t.headers,{Authorization:this.authStore.token})),this.enableAutoCancellation&&null!==t.requestKey){const s=t.requestKey||(t.method||"GET")+e;delete t.requestKey,this.cancelRequest(s);const i=new AbortController;this.cancelControllers[s]=i,t.signal=i.signal}return t}getHeader(e,t){e=e||{},t=t.toLowerCase();for(let s in e)if(s.toLowerCase()==t)return e[s];return null}}return Client}(); +//# sourceMappingURL=pocketbase.iife.js.map diff --git a/script/node_modules/pocketbase/dist/pocketbase.iife.js.map b/script/node_modules/pocketbase/dist/pocketbase.iife.js.map new file mode 100644 index 0000000..cd8ffef --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.iife.js.map @@ -0,0 +1 @@ +{"version":3,"file":"pocketbase.iife.js","sources":["../src/ClientResponseError.ts","../src/tools/cookie.ts","../src/tools/jwt.ts","../src/stores/BaseAuthStore.ts","../src/stores/LocalAuthStore.ts","../src/services/BaseService.ts","../src/services/SettingsService.ts","../src/tools/options.ts","../src/services/RealtimeService.ts","../src/services/CrudService.ts","../src/tools/legacy.ts","../src/tools/refresh.ts","../src/services/RecordService.ts","../src/services/CollectionService.ts","../src/services/LogService.ts","../src/services/HealthService.ts","../src/services/FileService.ts","../src/services/BackupService.ts","../src/services/CronService.ts","../src/tools/formdata.ts","../src/services/BatchService.ts","../src/Client.ts"],"sourcesContent":["/**\n * ClientResponseError is a custom Error class that is intended to wrap\n * and normalize any error thrown by `Client.send()`.\n */\nexport class ClientResponseError extends Error {\n url: string = \"\";\n status: number = 0;\n response: { [key: string]: any } = {};\n isAbort: boolean = false;\n originalError: any = null;\n\n constructor(errData?: any) {\n super(\"ClientResponseError\");\n\n // Set the prototype explicitly.\n // https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, ClientResponseError.prototype);\n\n if (errData !== null && typeof errData === \"object\") {\n this.originalError = errData.originalError;\n this.url = typeof errData.url === \"string\" ? errData.url : \"\";\n this.status = typeof errData.status === \"number\" ? errData.status : 0;\n\n // note: DOMException is not implemented yet in React Native\n // and aborting a fetch throws a plain Error with message \"Aborted\".\n this.isAbort =\n !!errData.isAbort ||\n errData.name === \"AbortError\" ||\n errData.message === \"Aborted\";\n\n if (errData.response !== null && typeof errData.response === \"object\") {\n this.response = errData.response;\n } else if (errData.data !== null && typeof errData.data === \"object\") {\n this.response = errData.data;\n } else {\n this.response = {};\n }\n }\n\n if (!this.originalError && !(errData instanceof ClientResponseError)) {\n this.originalError = errData;\n }\n\n this.name = \"ClientResponseError \" + this.status;\n this.message = this.response?.message;\n if (!this.message) {\n if (this.isAbort) {\n this.message =\n \"The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).\";\n } else if (this.originalError?.cause?.message?.includes(\"ECONNREFUSED ::1\")) {\n this.message =\n \"Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).\";\n } else {\n this.message = \"Something went wrong.\";\n }\n }\n\n // set this.cause so that JS debugging tools can automatically connect\n // the dots between the original error and the wrapped one\n this.cause = this.originalError;\n }\n\n /**\n * Alias for `this.response` for backward compatibility.\n */\n get data() {\n return this.response;\n }\n\n /**\n * Make a POJO's copy of the current error class instance.\n * @see https://github.com/vuex-orm/vuex-orm/issues/255\n */\n toJSON() {\n return { ...this };\n }\n}\n","/**\n * -------------------------------------------------------------------\n * Simple cookie parse and serialize utilities mostly based on the\n * node module https://github.com/jshttp/cookie.\n * -------------------------------------------------------------------\n */\n\n/**\n * RegExp to match field-content in RFC 7230 sec 3.2\n *\n * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]\n * field-vchar = VCHAR / obs-text\n * obs-text = %x80-FF\n */\nconst fieldContentRegExp = /^[\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+$/;\n\nexport interface ParseOptions {\n decode?: (val: string) => string;\n}\n\n/**\n * Parses the given cookie header string into an object\n * The object has the various cookies as keys(names) => values\n */\nexport function cookieParse(str: string, options?: ParseOptions): { [key: string]: any } {\n const result: { [key: string]: any } = {};\n\n if (typeof str !== \"string\") {\n return result;\n }\n\n const opt = Object.assign({}, options || {});\n const decode = opt.decode || defaultDecode;\n\n let index = 0;\n while (index < str.length) {\n const eqIdx = str.indexOf(\"=\", index);\n\n // no more cookie pairs\n if (eqIdx === -1) {\n break;\n }\n\n let endIdx = str.indexOf(\";\", index);\n\n if (endIdx === -1) {\n endIdx = str.length;\n } else if (endIdx < eqIdx) {\n // backtrack on prior semicolon\n index = str.lastIndexOf(\";\", eqIdx - 1) + 1;\n continue;\n }\n\n const key = str.slice(index, eqIdx).trim();\n\n // only assign once\n if (undefined === result[key]) {\n let val = str.slice(eqIdx + 1, endIdx).trim();\n\n // quoted values\n if (val.charCodeAt(0) === 0x22) {\n val = val.slice(1, -1);\n }\n\n try {\n result[key] = decode(val);\n } catch (_) {\n result[key] = val; // no decoding\n }\n }\n\n index = endIdx + 1;\n }\n\n return result;\n}\n\nexport interface SerializeOptions {\n encode?: (val: string | number | boolean) => string;\n maxAge?: number;\n domain?: string;\n path?: string;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n priority?: string;\n sameSite?: boolean | string;\n}\n\n/**\n * Serialize data into a cookie header.\n *\n * Serialize the a name value pair into a cookie string suitable for\n * http headers. An optional options object specified cookie parameters.\n *\n * ```js\n * cookieSerialize('foo', 'bar', { httpOnly: true }) // \"foo=bar; httpOnly\"\n * ```\n */\nexport function cookieSerialize(\n name: string,\n val: string,\n options?: SerializeOptions,\n): string {\n const opt = Object.assign({}, options || {});\n const encode = opt.encode || defaultEncode;\n\n if (!fieldContentRegExp.test(name)) {\n throw new TypeError(\"argument name is invalid\");\n }\n\n const value = encode(val);\n\n if (value && !fieldContentRegExp.test(value)) {\n throw new TypeError(\"argument val is invalid\");\n }\n\n let result = name + \"=\" + value;\n\n if (opt.maxAge != null) {\n const maxAge = opt.maxAge - 0;\n\n if (isNaN(maxAge) || !isFinite(maxAge)) {\n throw new TypeError(\"option maxAge is invalid\");\n }\n\n result += \"; Max-Age=\" + Math.floor(maxAge);\n }\n\n if (opt.domain) {\n if (!fieldContentRegExp.test(opt.domain)) {\n throw new TypeError(\"option domain is invalid\");\n }\n\n result += \"; Domain=\" + opt.domain;\n }\n\n if (opt.path) {\n if (!fieldContentRegExp.test(opt.path)) {\n throw new TypeError(\"option path is invalid\");\n }\n\n result += \"; Path=\" + opt.path;\n }\n\n if (opt.expires) {\n if (!isDate(opt.expires) || isNaN(opt.expires.valueOf())) {\n throw new TypeError(\"option expires is invalid\");\n }\n\n result += \"; Expires=\" + opt.expires.toUTCString();\n }\n\n if (opt.httpOnly) {\n result += \"; HttpOnly\";\n }\n\n if (opt.secure) {\n result += \"; Secure\";\n }\n\n if (opt.priority) {\n const priority =\n typeof opt.priority === \"string\" ? opt.priority.toLowerCase() : opt.priority;\n\n switch (priority) {\n case \"low\":\n result += \"; Priority=Low\";\n break;\n case \"medium\":\n result += \"; Priority=Medium\";\n break;\n case \"high\":\n result += \"; Priority=High\";\n break;\n default:\n throw new TypeError(\"option priority is invalid\");\n }\n }\n\n if (opt.sameSite) {\n const sameSite =\n typeof opt.sameSite === \"string\" ? opt.sameSite.toLowerCase() : opt.sameSite;\n\n switch (sameSite) {\n case true:\n result += \"; SameSite=Strict\";\n break;\n case \"lax\":\n result += \"; SameSite=Lax\";\n break;\n case \"strict\":\n result += \"; SameSite=Strict\";\n break;\n case \"none\":\n result += \"; SameSite=None\";\n break;\n default:\n throw new TypeError(\"option sameSite is invalid\");\n }\n }\n\n return result;\n}\n\n/**\n * Default URL-decode string value function.\n * Optimized to skip native call when no `%`.\n */\nfunction defaultDecode(val: string): string {\n return val.indexOf(\"%\") !== -1 ? decodeURIComponent(val) : val;\n}\n\n/**\n * Default URL-encode value function.\n */\nfunction defaultEncode(val: string | number | boolean): string {\n return encodeURIComponent(val);\n}\n\n/**\n * Determines if value is a Date.\n */\nfunction isDate(val: any): boolean {\n return Object.prototype.toString.call(val) === \"[object Date]\" || val instanceof Date;\n}\n","// @todo remove after https://github.com/reactwg/react-native-releases/issues/287\nconst isReactNative =\n (typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\") ||\n (typeof global !== \"undefined\" && (global as any).HermesInternal);\n\nlet atobPolyfill: Function;\nif (typeof atob === \"function\" && !isReactNative) {\n atobPolyfill = atob;\n} else {\n /**\n * The code was extracted from:\n * https://github.com/davidchambers/Base64.js\n */\n atobPolyfill = (input: any) => {\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\";\n\n let str = String(input).replace(/=+$/, \"\");\n if (str.length % 4 == 1) {\n throw new Error(\n \"'atob' failed: The string to be decoded is not correctly encoded.\",\n );\n }\n\n for (\n // initialize result and counters\n var bc = 0, bs, buffer, idx = 0, output = \"\";\n // get next character\n (buffer = str.charAt(idx++));\n // character found in table? initialize bit storage and add its ascii value;\n ~buffer &&\n ((bs = bc % 4 ? (bs as any) * 64 + buffer : buffer),\n // and if not first of each 4 characters,\n // convert the first 8 bits to one ascii character\n bc++ % 4)\n ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))\n : 0\n ) {\n // try to find character in table (0-63, not found => -1)\n buffer = chars.indexOf(buffer);\n }\n\n return output;\n };\n}\n\n/**\n * Returns JWT token's payload data.\n */\nexport function getTokenPayload(token: string): { [key: string]: any } {\n if (token) {\n try {\n const encodedPayload = decodeURIComponent(\n atobPolyfill(token.split(\".\")[1])\n .split(\"\")\n .map(function (c: string) {\n return \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2);\n })\n .join(\"\"),\n );\n\n return JSON.parse(encodedPayload) || {};\n } catch (e) {}\n }\n\n return {};\n}\n\n/**\n * Checks whether a JWT token is expired or not.\n * Tokens without `exp` payload key are considered valid.\n * Tokens with empty payload (eg. invalid token strings) are considered expired.\n *\n * @param token The token to check.\n * @param [expirationThreshold] Time in seconds that will be subtracted from the token `exp` property.\n */\nexport function isTokenExpired(token: string, expirationThreshold = 0): boolean {\n let payload = getTokenPayload(token);\n\n if (\n Object.keys(payload).length > 0 &&\n (!payload.exp || payload.exp - expirationThreshold > Date.now() / 1000)\n ) {\n return false;\n }\n\n return true;\n}\n","import { cookieParse, cookieSerialize, SerializeOptions } from \"@/tools/cookie\";\nimport { isTokenExpired, getTokenPayload } from \"@/tools/jwt\";\nimport { RecordModel } from \"@/tools/dtos\";\n\nexport type AuthRecord = RecordModel | null;\n\nexport type AuthModel = AuthRecord; // for backward compatibility\n\nexport type OnStoreChangeFunc = (token: string, record: AuthRecord) => void;\n\nconst defaultCookieKey = \"pb_auth\";\n\n/**\n * Base AuthStore class that stores the auth state in runtime memory (aka. only for the duration of the store instane).\n *\n * Usually you wouldn't use it directly and instead use the builtin LocalAuthStore, AsyncAuthStore\n * or extend it with your own custom implementation.\n */\nexport class BaseAuthStore {\n protected baseToken: string = \"\";\n protected baseModel: AuthRecord = null;\n\n private _onChangeCallbacks: Array = [];\n\n /**\n * Retrieves the stored token (if any).\n */\n get token(): string {\n return this.baseToken;\n }\n\n /**\n * Retrieves the stored model data (if any).\n */\n get record(): AuthRecord {\n return this.baseModel;\n }\n\n /**\n * @deprecated use `record` instead.\n */\n get model(): AuthRecord {\n return this.baseModel;\n }\n\n /**\n * Loosely checks if the store has valid token (aka. existing and unexpired exp claim).\n */\n get isValid(): boolean {\n return !isTokenExpired(this.token);\n }\n\n /**\n * Loosely checks whether the currently loaded store state is for superuser.\n *\n * Alternatively you can also compare directly `pb.authStore.record?.collectionName`.\n */\n get isSuperuser(): boolean {\n let payload = getTokenPayload(this.token);\n\n return (\n payload.type == \"auth\" &&\n (this.record?.collectionName == \"_superusers\" ||\n // fallback in case the record field is not populated and assuming\n // that the collection crc32 checksum id wasn't manually changed\n (!this.record?.collectionName &&\n payload.collectionId == \"pbc_3142635823\"))\n );\n }\n\n /**\n * @deprecated use `isSuperuser` instead or simply check the record.collectionName property.\n */\n get isAdmin(): boolean {\n console.warn(\n \"Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName\",\n );\n return this.isSuperuser;\n }\n\n /**\n * @deprecated use `!isSuperuser` instead or simply check the record.collectionName property.\n */\n get isAuthRecord(): boolean {\n console.warn(\n \"Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName\",\n );\n return getTokenPayload(this.token).type == \"auth\" && !this.isSuperuser;\n }\n\n /**\n * Saves the provided new token and model data in the auth store.\n */\n save(token: string, record?: AuthRecord): void {\n this.baseToken = token || \"\";\n this.baseModel = record || null;\n\n this.triggerChange();\n }\n\n /**\n * Removes the stored token and model data form the auth store.\n */\n clear(): void {\n this.baseToken = \"\";\n this.baseModel = null;\n this.triggerChange();\n }\n\n /**\n * Parses the provided cookie string and updates the store state\n * with the cookie's token and model data.\n *\n * NB! This function doesn't validate the token or its data.\n * Usually this isn't a concern if you are interacting only with the\n * PocketBase API because it has the proper server-side security checks in place,\n * but if you are using the store `isValid` state for permission controls\n * in a node server (eg. SSR), then it is recommended to call `authRefresh()`\n * after loading the cookie to ensure an up-to-date token and model state.\n * For example:\n *\n * ```js\n * pb.authStore.loadFromCookie(\"cookie string...\");\n *\n * try {\n * // get an up-to-date auth store state by veryfing and refreshing the loaded auth model (if any)\n * pb.authStore.isValid && await pb.collection('users').authRefresh();\n * } catch (_) {\n * // clear the auth store on failed refresh\n * pb.authStore.clear();\n * }\n * ```\n */\n loadFromCookie(cookie: string, key = defaultCookieKey): void {\n const rawData = cookieParse(cookie || \"\")[key] || \"\";\n\n let data: { [key: string]: any } = {};\n try {\n data = JSON.parse(rawData);\n // normalize\n if (typeof data === null || typeof data !== \"object\" || Array.isArray(data)) {\n data = {};\n }\n } catch (_) {}\n\n this.save(data.token || \"\", data.record || data.model || null);\n }\n\n /**\n * Exports the current store state as cookie string.\n *\n * By default the following optional attributes are added:\n * - Secure\n * - HttpOnly\n * - SameSite=Strict\n * - Path=/\n * - Expires={the token expiration date}\n *\n * NB! If the generated cookie exceeds 4096 bytes, this method will\n * strip the model data to the bare minimum to try to fit within the\n * recommended size in https://www.rfc-editor.org/rfc/rfc6265#section-6.1.\n */\n exportToCookie(options?: SerializeOptions, key = defaultCookieKey): string {\n const defaultOptions: SerializeOptions = {\n secure: true,\n sameSite: true,\n httpOnly: true,\n path: \"/\",\n };\n\n // extract the token expiration date\n const payload = getTokenPayload(this.token);\n if (payload?.exp) {\n defaultOptions.expires = new Date(payload.exp * 1000);\n } else {\n defaultOptions.expires = new Date(\"1970-01-01\");\n }\n\n // merge with the user defined options\n options = Object.assign({}, defaultOptions, options);\n\n const rawData = {\n token: this.token,\n record: this.record ? JSON.parse(JSON.stringify(this.record)) : null,\n };\n\n let result = cookieSerialize(key, JSON.stringify(rawData), options);\n\n const resultLength =\n typeof Blob !== \"undefined\" ? new Blob([result]).size : result.length;\n\n // strip down the model data to the bare minimum\n if (rawData.record && resultLength > 4096) {\n rawData.record = { id: rawData.record?.id, email: rawData.record?.email };\n const extraProps = [\"collectionId\", \"collectionName\", \"verified\"];\n for (const prop in this.record) {\n if (extraProps.includes(prop)) {\n rawData.record[prop] = this.record[prop];\n }\n }\n result = cookieSerialize(key, JSON.stringify(rawData), options);\n }\n\n return result;\n }\n\n /**\n * Register a callback function that will be called on store change.\n *\n * You can set the `fireImmediately` argument to true in order to invoke\n * the provided callback right after registration.\n *\n * Returns a removal function that you could call to \"unsubscribe\" from the changes.\n */\n onChange(callback: OnStoreChangeFunc, fireImmediately = false): () => void {\n this._onChangeCallbacks.push(callback);\n\n if (fireImmediately) {\n callback(this.token, this.record);\n }\n\n return () => {\n for (let i = this._onChangeCallbacks.length - 1; i >= 0; i--) {\n if (this._onChangeCallbacks[i] == callback) {\n delete this._onChangeCallbacks[i]; // removes the function reference\n this._onChangeCallbacks.splice(i, 1); // reindex the array\n return;\n }\n }\n };\n }\n\n protected triggerChange(): void {\n for (const callback of this._onChangeCallbacks) {\n callback && callback(this.token, this.record);\n }\n }\n}\n","import { BaseAuthStore, AuthRecord } from \"@/stores/BaseAuthStore\";\n\n/**\n * The default token store for browsers with auto fallback\n * to runtime/memory if local storage is undefined (e.g. in node env).\n */\nexport class LocalAuthStore extends BaseAuthStore {\n private storageFallback: { [key: string]: any } = {};\n private storageKey: string;\n\n constructor(storageKey = \"pocketbase_auth\") {\n super();\n\n this.storageKey = storageKey;\n\n this._bindStorageEvent();\n }\n\n /**\n * @inheritdoc\n */\n get token(): string {\n const data = this._storageGet(this.storageKey) || {};\n\n return data.token || \"\";\n }\n\n /**\n * @inheritdoc\n */\n get record(): AuthRecord {\n const data = this._storageGet(this.storageKey) || {};\n\n return data.record || data.model || null;\n }\n\n /**\n * @deprecated use `record` instead.\n */\n get model(): AuthRecord {\n return this.record;\n }\n\n /**\n * @inheritdoc\n */\n save(token: string, record?: AuthRecord) {\n this._storageSet(this.storageKey, {\n token: token,\n record: record,\n });\n\n super.save(token, record);\n }\n\n /**\n * @inheritdoc\n */\n clear() {\n this._storageRemove(this.storageKey);\n\n super.clear();\n }\n\n // ---------------------------------------------------------------\n // Internal helpers:\n // ---------------------------------------------------------------\n\n /**\n * Retrieves `key` from the browser's local storage\n * (or runtime/memory if local storage is undefined).\n */\n private _storageGet(key: string): any {\n if (typeof window !== \"undefined\" && window?.localStorage) {\n const rawValue = window.localStorage.getItem(key) || \"\";\n try {\n return JSON.parse(rawValue);\n } catch (e) {\n // not a json\n return rawValue;\n }\n }\n\n // fallback\n return this.storageFallback[key];\n }\n\n /**\n * Stores a new data in the browser's local storage\n * (or runtime/memory if local storage is undefined).\n */\n private _storageSet(key: string, value: any) {\n if (typeof window !== \"undefined\" && window?.localStorage) {\n // store in local storage\n let normalizedVal = value;\n if (typeof value !== \"string\") {\n normalizedVal = JSON.stringify(value);\n }\n window.localStorage.setItem(key, normalizedVal);\n } else {\n // store in fallback\n this.storageFallback[key] = value;\n }\n }\n\n /**\n * Removes `key` from the browser's local storage and the runtime/memory.\n */\n private _storageRemove(key: string) {\n // delete from local storage\n if (typeof window !== \"undefined\" && window?.localStorage) {\n window.localStorage?.removeItem(key);\n }\n\n // delete from fallback\n delete this.storageFallback[key];\n }\n\n /**\n * Updates the current store state on localStorage change.\n */\n private _bindStorageEvent() {\n if (\n typeof window === \"undefined\" ||\n !window?.localStorage ||\n !window.addEventListener\n ) {\n return;\n }\n\n window.addEventListener(\"storage\", (e) => {\n if (e.key != this.storageKey) {\n return;\n }\n\n const data = this._storageGet(this.storageKey) || {};\n\n super.save(data.token || \"\", data.record || data.model || null);\n });\n }\n}\n","import Client from \"@/Client\";\n\n/**\n * BaseService class that should be inherited from all API services.\n */\nexport abstract class BaseService {\n readonly client: Client;\n\n constructor(client: Client) {\n this.client = client;\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\ninterface appleClientSecret {\n secret: string;\n}\n\nexport class SettingsService extends BaseService {\n /**\n * Fetch all available app settings.\n *\n * @throws {ClientResponseError}\n */\n async getAll(options?: CommonOptions): Promise<{ [key: string]: any }> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/settings\", options);\n }\n\n /**\n * Bulk updates app settings.\n *\n * @throws {ClientResponseError}\n */\n async update(\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise<{ [key: string]: any }> {\n options = Object.assign(\n {\n method: \"PATCH\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client.send(\"/api/settings\", options);\n }\n\n /**\n * Performs a S3 filesystem connection test.\n *\n * The currently supported `filesystem` are \"storage\" and \"backups\".\n *\n * @throws {ClientResponseError}\n */\n async testS3(\n filesystem: string = \"storage\",\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n filesystem: filesystem,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/test/s3\", options).then(() => true);\n }\n\n /**\n * Sends a test email.\n *\n * The possible `emailTemplate` values are:\n * - verification\n * - password-reset\n * - email-change\n *\n * @throws {ClientResponseError}\n */\n async testEmail(\n collectionIdOrName: string,\n toEmail: string,\n emailTemplate: string,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n email: toEmail,\n template: emailTemplate,\n collection: collectionIdOrName,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/test/email\", options).then(() => true);\n }\n\n /**\n * Generates a new Apple OAuth2 client secret.\n *\n * @throws {ClientResponseError}\n */\n async generateAppleClientSecret(\n clientId: string,\n teamId: string,\n keyId: string,\n privateKey: string,\n duration: number,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n clientId,\n teamId,\n keyId,\n privateKey,\n duration,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/apple/generate-client-secret\", options);\n }\n}\n","export interface SendOptions extends RequestInit {\n // for backward compatibility and to minimize the verbosity,\n // any top-level field that doesn't exist in RequestInit or the\n // fields below will be treated as query parameter.\n [key: string]: any;\n\n /**\n * Optional custom fetch function to use for sending the request.\n */\n fetch?: (url: RequestInfo | URL, config?: RequestInit) => Promise;\n\n /**\n * Custom headers to send with the requests.\n */\n headers?: { [key: string]: string };\n\n /**\n * The body of the request (serialized automatically for json requests).\n */\n body?: any;\n\n /**\n * Query parameters that will be appended to the request url.\n */\n query?: { [key: string]: any };\n\n /**\n * @deprecated use `query` instead\n *\n * for backward-compatibility `params` values are merged with `query`,\n * but this option may get removed in the final v1 release\n */\n params?: { [key: string]: any };\n\n /**\n * The request identifier that can be used to cancel pending requests.\n */\n requestKey?: string | null;\n\n /**\n * @deprecated use `requestKey:string` instead\n */\n $cancelKey?: string;\n\n /**\n * @deprecated use `requestKey:null` instead\n */\n $autoCancel?: boolean;\n}\n\nexport interface CommonOptions extends SendOptions {\n fields?: string;\n}\n\nexport interface ListOptions extends CommonOptions {\n page?: number;\n perPage?: number;\n sort?: string;\n filter?: string;\n skipTotal?: boolean;\n}\n\nexport interface FullListOptions extends ListOptions {\n batch?: number;\n}\n\nexport interface RecordOptions extends CommonOptions {\n expand?: string;\n}\n\nexport interface RecordListOptions extends ListOptions, RecordOptions {}\n\nexport interface RecordFullListOptions extends FullListOptions, RecordOptions {}\n\nexport interface RecordSubscribeOptions extends SendOptions {\n fields?: string;\n filter?: string;\n expand?: string;\n}\n\nexport interface LogStatsOptions extends CommonOptions {\n filter?: string;\n}\n\nexport interface FileOptions extends CommonOptions {\n thumb?: string;\n download?: boolean;\n}\n\nexport interface AuthOptions extends CommonOptions {\n /**\n * If autoRefreshThreshold is set it will take care to auto refresh\n * when necessary the auth data before each request to ensure that\n * the auth state is always valid.\n *\n * The value must be in seconds, aka. the amount of seconds\n * that will be subtracted from the current token `exp` claim in order\n * to determine whether it is going to expire within the specified time threshold.\n *\n * For example, if you want to auto refresh the token if it is\n * going to expire in the next 30mins (or already has expired),\n * it can be set to `1800`\n */\n autoRefreshThreshold?: number;\n}\n\n// -------------------------------------------------------------------\n\n// list of known SendOptions keys (everything else is treated as query param)\nconst knownSendOptionsKeys = [\n \"requestKey\",\n \"$cancelKey\",\n \"$autoCancel\",\n \"fetch\",\n \"headers\",\n \"body\",\n \"query\",\n \"params\",\n // ---,\n \"cache\",\n \"credentials\",\n \"headers\",\n \"integrity\",\n \"keepalive\",\n \"method\",\n \"mode\",\n \"redirect\",\n \"referrer\",\n \"referrerPolicy\",\n \"signal\",\n \"window\",\n];\n\n// modifies in place the provided options by moving unknown send options as query parameters.\nexport function normalizeUnknownQueryParams(options?: SendOptions): void {\n if (!options) {\n return;\n }\n\n options.query = options.query || {};\n for (let key in options) {\n if (knownSendOptionsKeys.includes(key)) {\n continue;\n }\n\n options.query[key] = options[key];\n delete options[key];\n }\n}\n\nexport function serializeQueryParams(params: { [key: string]: any }): string {\n const result: Array = [];\n\n for (const key in params) {\n const encodedKey = encodeURIComponent(key);\n const arrValue = Array.isArray(params[key]) ? params[key] : [params[key]];\n\n for (let v of arrValue) {\n v = prepareQueryParamValue(v);\n if (v === null) {\n continue;\n }\n result.push(encodedKey + \"=\" + v);\n }\n }\n\n return result.join(\"&\");\n}\n\n// encodes and normalizes the provided query param value.\nfunction prepareQueryParamValue(value: any): null | string {\n if (value === null || typeof value === \"undefined\") {\n return null;\n }\n\n if (value instanceof Date) {\n return encodeURIComponent(value.toISOString().replace(\"T\", \" \"));\n }\n\n if (typeof value === \"object\") {\n return encodeURIComponent(JSON.stringify(value));\n }\n\n return encodeURIComponent(value);\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseService } from \"@/services/BaseService\";\nimport { SendOptions, normalizeUnknownQueryParams } from \"@/tools/options\";\n\ninterface promiseCallbacks {\n resolve: Function;\n reject: Function;\n}\n\ntype Subscriptions = { [key: string]: Array };\n\nexport type UnsubscribeFunc = () => Promise;\n\nexport class RealtimeService extends BaseService {\n clientId: string = \"\";\n\n private eventSource: EventSource | null = null;\n private subscriptions: Subscriptions = {};\n private lastSentSubscriptions: Array = [];\n private connectTimeoutId: any;\n private maxConnectTimeout: number = 15000;\n private reconnectTimeoutId: any;\n private reconnectAttempts: number = 0;\n private maxReconnectAttempts: number = Infinity;\n private predefinedReconnectIntervals: Array = [\n 200, 300, 500, 1000, 1200, 1500, 2000,\n ];\n private pendingConnects: Array = [];\n\n /**\n * Returns whether the realtime connection has been established.\n */\n get isConnected(): boolean {\n return !!this.eventSource && !!this.clientId && !this.pendingConnects.length;\n }\n\n /**\n * An optional hook that is invoked when the realtime client disconnects\n * either when unsubscribing from all subscriptions or when the\n * connection was interrupted or closed by the server.\n *\n * The received argument could be used to determine whether the disconnect\n * is a result from unsubscribing (`activeSubscriptions.length == 0`)\n * or because of network/server error (`activeSubscriptions.length > 0`).\n *\n * If you want to listen for the opposite, aka. when the client connection is established,\n * subscribe to the `PB_CONNECT` event.\n */\n onDisconnect?: (activeSubscriptions: Array) => void;\n\n /**\n * Register the subscription listener.\n *\n * You can subscribe multiple times to the same topic.\n *\n * If the SSE connection is not started yet,\n * this method will also initialize it.\n */\n async subscribe(\n topic: string,\n callback: (data: any) => void,\n options?: SendOptions,\n ): Promise {\n if (!topic) {\n throw new Error(\"topic must be set.\");\n }\n\n let key = topic;\n\n // serialize and append the topic options (if any)\n if (options) {\n options = Object.assign({}, options); // shallow copy\n normalizeUnknownQueryParams(options);\n const serialized =\n \"options=\" +\n encodeURIComponent(\n JSON.stringify({ query: options.query, headers: options.headers }),\n );\n key += (key.includes(\"?\") ? \"&\" : \"?\") + serialized;\n }\n\n const listener = function (e: Event) {\n const msgEvent = e as MessageEvent;\n\n let data;\n try {\n data = JSON.parse(msgEvent?.data);\n } catch {}\n\n callback(data || {});\n };\n\n // store the listener\n if (!this.subscriptions[key]) {\n this.subscriptions[key] = [];\n }\n this.subscriptions[key].push(listener);\n\n if (!this.isConnected) {\n // initialize sse connection\n await this.connect();\n } else if (this.subscriptions[key].length === 1) {\n // send the updated subscriptions (if it is the first for the key)\n await this.submitSubscriptions();\n } else {\n // only register the listener\n this.eventSource?.addEventListener(key, listener);\n }\n\n return async (): Promise => {\n return this.unsubscribeByTopicAndListener(topic, listener);\n };\n }\n\n /**\n * Unsubscribe from all subscription listeners with the specified topic.\n *\n * If `topic` is not provided, then this method will unsubscribe\n * from all active subscriptions.\n *\n * This method is no-op if there are no active subscriptions.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribe(topic?: string): Promise {\n let needToSubmit = false;\n\n if (!topic) {\n // remove all subscriptions\n this.subscriptions = {};\n } else {\n // remove all listeners related to the topic\n const subs = this.getSubscriptionsByTopic(topic);\n for (let key in subs) {\n if (!this.hasSubscriptionListeners(key)) {\n continue; // already unsubscribed\n }\n\n for (let listener of this.subscriptions[key]) {\n this.eventSource?.removeEventListener(key, listener);\n }\n delete this.subscriptions[key];\n\n // mark for subscriptions change submit if there are no other listeners\n if (!needToSubmit) {\n needToSubmit = true;\n }\n }\n }\n\n if (!this.hasSubscriptionListeners()) {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n } else if (needToSubmit) {\n await this.submitSubscriptions();\n }\n }\n\n /**\n * Unsubscribe from all subscription listeners starting with the specified topic prefix.\n *\n * This method is no-op if there are no active subscriptions with the specified topic prefix.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribeByPrefix(keyPrefix: string): Promise {\n let hasAtleastOneTopic = false;\n for (let key in this.subscriptions) {\n // \"?\" so that it can be used as end delimiter for the prefix\n if (!(key + \"?\").startsWith(keyPrefix)) {\n continue;\n }\n\n hasAtleastOneTopic = true;\n for (let listener of this.subscriptions[key]) {\n this.eventSource?.removeEventListener(key, listener);\n }\n delete this.subscriptions[key];\n }\n\n if (!hasAtleastOneTopic) {\n return; // nothing to unsubscribe from\n }\n\n if (this.hasSubscriptionListeners()) {\n // submit the deleted subscriptions\n await this.submitSubscriptions();\n } else {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n }\n }\n\n /**\n * Unsubscribe from all subscriptions matching the specified topic and listener function.\n *\n * This method is no-op if there are no active subscription with\n * the specified topic and listener.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribeByTopicAndListener(\n topic: string,\n listener: EventListener,\n ): Promise {\n let needToSubmit = false;\n\n const subs = this.getSubscriptionsByTopic(topic);\n for (let key in subs) {\n if (\n !Array.isArray(this.subscriptions[key]) ||\n !this.subscriptions[key].length\n ) {\n continue; // already unsubscribed\n }\n\n let exist = false;\n for (let i = this.subscriptions[key].length - 1; i >= 0; i--) {\n if (this.subscriptions[key][i] !== listener) {\n continue;\n }\n\n exist = true; // has at least one matching listener\n delete this.subscriptions[key][i]; // removes the function reference\n this.subscriptions[key].splice(i, 1); // reindex the array\n this.eventSource?.removeEventListener(key, listener);\n }\n if (!exist) {\n continue;\n }\n\n // remove the key from the subscriptions list if there are no other listeners\n if (!this.subscriptions[key].length) {\n delete this.subscriptions[key];\n }\n\n // mark for subscriptions change submit if there are no other listeners\n if (!needToSubmit && !this.hasSubscriptionListeners(key)) {\n needToSubmit = true;\n }\n }\n\n if (!this.hasSubscriptionListeners()) {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n } else if (needToSubmit) {\n await this.submitSubscriptions();\n }\n }\n\n private hasSubscriptionListeners(keyToCheck?: string): boolean {\n this.subscriptions = this.subscriptions || {};\n\n // check the specified key\n if (keyToCheck) {\n return !!this.subscriptions[keyToCheck]?.length;\n }\n\n // check for at least one non-empty subscription\n for (let key in this.subscriptions) {\n if (!!this.subscriptions[key]?.length) {\n return true;\n }\n }\n\n return false;\n }\n\n private async submitSubscriptions(): Promise {\n if (!this.clientId) {\n return; // no client/subscriber\n }\n\n // optimistic update\n this.addAllSubscriptionListeners();\n\n this.lastSentSubscriptions = this.getNonEmptySubscriptionKeys();\n\n return this.client\n .send(\"/api/realtime\", {\n method: \"POST\",\n body: {\n clientId: this.clientId,\n subscriptions: this.lastSentSubscriptions,\n },\n requestKey: this.getSubscriptionsCancelKey(),\n })\n .catch((err) => {\n if (err?.isAbort) {\n return; // silently ignore aborted pending requests\n }\n throw err;\n });\n }\n\n private getSubscriptionsCancelKey(): string {\n return \"realtime_\" + this.clientId;\n }\n\n private getSubscriptionsByTopic(topic: string): Subscriptions {\n const result: Subscriptions = {};\n\n // \"?\" so that it can be used as end delimiter for the topic\n topic = topic.includes(\"?\") ? topic : topic + \"?\";\n\n for (let key in this.subscriptions) {\n if ((key + \"?\").startsWith(topic)) {\n result[key] = this.subscriptions[key];\n }\n }\n\n return result;\n }\n\n private getNonEmptySubscriptionKeys(): Array {\n const result: Array = [];\n\n for (let key in this.subscriptions) {\n if (this.subscriptions[key].length) {\n result.push(key);\n }\n }\n\n return result;\n }\n\n private addAllSubscriptionListeners(): void {\n if (!this.eventSource) {\n return;\n }\n\n this.removeAllSubscriptionListeners();\n\n for (let key in this.subscriptions) {\n for (let listener of this.subscriptions[key]) {\n this.eventSource.addEventListener(key, listener);\n }\n }\n }\n\n private removeAllSubscriptionListeners(): void {\n if (!this.eventSource) {\n return;\n }\n\n for (let key in this.subscriptions) {\n for (let listener of this.subscriptions[key]) {\n this.eventSource.removeEventListener(key, listener);\n }\n }\n }\n\n private async connect(): Promise {\n if (this.reconnectAttempts > 0) {\n // immediately resolve the promise to avoid indefinitely\n // blocking the client during reconnection\n return;\n }\n\n return new Promise((resolve, reject) => {\n this.pendingConnects.push({ resolve, reject });\n\n if (this.pendingConnects.length > 1) {\n // all promises will be resolved once the connection is established\n return;\n }\n\n this.initConnect();\n });\n }\n\n private initConnect() {\n this.disconnect(true);\n\n // wait up to 15s for connect\n clearTimeout(this.connectTimeoutId);\n this.connectTimeoutId = setTimeout(() => {\n this.connectErrorHandler(new Error(\"EventSource connect took too long.\"));\n }, this.maxConnectTimeout);\n\n this.eventSource = new EventSource(this.client.buildURL(\"/api/realtime\"));\n\n this.eventSource.onerror = (_) => {\n this.connectErrorHandler(\n new Error(\"Failed to establish realtime connection.\"),\n );\n };\n\n this.eventSource.addEventListener(\"PB_CONNECT\", (e) => {\n const msgEvent = e as MessageEvent;\n this.clientId = msgEvent?.lastEventId;\n\n this.submitSubscriptions()\n .then(async () => {\n let retries = 3;\n while (this.hasUnsentSubscriptions() && retries > 0) {\n retries--;\n // resubscribe to ensure that the latest topics are submitted\n //\n // This is needed because missed topics could happen on reconnect\n // if after the pending sent `submitSubscriptions()` call another `subscribe()`\n // was made before the submit was able to complete.\n await this.submitSubscriptions();\n }\n })\n .then(() => {\n for (let p of this.pendingConnects) {\n p.resolve();\n }\n\n // reset connect meta\n this.pendingConnects = [];\n this.reconnectAttempts = 0;\n clearTimeout(this.reconnectTimeoutId);\n clearTimeout(this.connectTimeoutId);\n\n // propagate the PB_CONNECT event\n const connectSubs = this.getSubscriptionsByTopic(\"PB_CONNECT\");\n for (let key in connectSubs) {\n for (let listener of connectSubs[key]) {\n listener(e);\n }\n }\n })\n .catch((err) => {\n this.clientId = \"\";\n this.connectErrorHandler(err);\n });\n });\n }\n\n private hasUnsentSubscriptions(): boolean {\n const latestTopics = this.getNonEmptySubscriptionKeys();\n if (latestTopics.length != this.lastSentSubscriptions.length) {\n return true;\n }\n\n for (const t of latestTopics) {\n if (!this.lastSentSubscriptions.includes(t)) {\n return true;\n }\n }\n\n return false;\n }\n\n private connectErrorHandler(err: any) {\n clearTimeout(this.connectTimeoutId);\n clearTimeout(this.reconnectTimeoutId);\n\n if (\n // wasn't previously connected -> direct reject\n (!this.clientId && !this.reconnectAttempts) ||\n // was previously connected but the max reconnection limit has been reached\n this.reconnectAttempts > this.maxReconnectAttempts\n ) {\n for (let p of this.pendingConnects) {\n p.reject(new ClientResponseError(err));\n }\n this.pendingConnects = [];\n this.disconnect();\n return;\n }\n\n // otherwise -> reconnect in the background\n this.disconnect(true);\n const timeout =\n this.predefinedReconnectIntervals[this.reconnectAttempts] ||\n this.predefinedReconnectIntervals[\n this.predefinedReconnectIntervals.length - 1\n ];\n this.reconnectAttempts++;\n this.reconnectTimeoutId = setTimeout(() => {\n this.initConnect();\n }, timeout);\n }\n\n private disconnect(fromReconnect = false): void {\n if (this.clientId && this.onDisconnect) {\n this.onDisconnect(Object.keys(this.subscriptions));\n }\n\n clearTimeout(this.connectTimeoutId);\n clearTimeout(this.reconnectTimeoutId);\n this.removeAllSubscriptionListeners();\n this.client.cancelRequest(this.getSubscriptionsCancelKey());\n this.eventSource?.close();\n this.eventSource = null;\n this.clientId = \"\";\n\n if (!fromReconnect) {\n this.reconnectAttempts = 0;\n\n // resolve any remaining connect promises\n //\n // this is done to avoid unnecessary throwing errors in case\n // unsubscribe is called before the pending connect promises complete\n // (see https://github.com/pocketbase/pocketbase/discussions/2897#discussioncomment-6423818)\n for (let p of this.pendingConnects) {\n p.resolve();\n }\n this.pendingConnects = [];\n }\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { ClientResponseError } from \"@/ClientResponseError\";\nimport { ListResult } from \"@/tools/dtos\";\nimport { CommonOptions, ListOptions, FullListOptions } from \"@/tools/options\";\n\nexport abstract class CrudService extends BaseService {\n /**\n * Base path for the crud actions (without trailing slash, eg. '/admins').\n */\n abstract get baseCrudPath(): string;\n\n /**\n * Response data decoder.\n */\n decode(data: { [key: string]: any }): T {\n return data as T;\n }\n\n /**\n * Returns a promise with all list items batch fetched at once\n * (by default 1000 items per request; to change it set the `batch` query param).\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: FullListOptions): Promise>;\n\n /**\n * Legacy version of getFullList with explicitly specified batch size.\n */\n async getFullList(batch?: number, options?: ListOptions): Promise>;\n\n async getFullList(\n batchOrqueryParams?: number | FullListOptions,\n options?: ListOptions,\n ): Promise> {\n if (typeof batchOrqueryParams == \"number\") {\n return this._getFullList(batchOrqueryParams, options);\n }\n\n options = Object.assign({}, batchOrqueryParams, options);\n\n let batch = 1000;\n if (options.batch) {\n batch = options.batch;\n delete options.batch;\n }\n\n return this._getFullList(batch, options);\n }\n\n /**\n * Returns paginated items list.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: ListOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n options.query = Object.assign(\n {\n page: page,\n perPage: perPage,\n },\n options.query,\n );\n\n return this.client.send(this.baseCrudPath, options).then((responseData: any) => {\n responseData.items =\n responseData.items?.map((item: any) => {\n return this.decode(item);\n }) || [];\n\n return responseData;\n });\n }\n\n /**\n * Returns the first found item by the specified filter.\n *\n * Internally it calls `getList(1, 1, { filter, skipTotal })` and\n * returns the first found item.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * For consistency with `getOne`, this method will throw a 404\n * ClientResponseError if no item was found.\n *\n * @throws {ClientResponseError}\n */\n async getFirstListItem(filter: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n requestKey: \"one_by_filter_\" + this.baseCrudPath + \"_\" + filter,\n },\n options,\n );\n\n options.query = Object.assign(\n {\n filter: filter,\n skipTotal: 1,\n },\n options.query,\n );\n\n return this.getList(1, 1, options).then((result) => {\n if (!result?.items?.length) {\n throw new ClientResponseError({\n status: 404,\n response: {\n code: 404,\n message: \"The requested resource wasn't found.\",\n data: {},\n },\n });\n }\n\n return result.items[0];\n });\n }\n\n /**\n * Returns single item by its id.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * If `id` is empty it will throw a 404 error.\n *\n * @throws {ClientResponseError}\n */\n async getOne(id: string, options?: CommonOptions): Promise {\n if (!id) {\n throw new ClientResponseError({\n url: this.client.buildURL(this.baseCrudPath + \"/\"),\n status: 404,\n response: {\n code: 404,\n message: \"Missing required record id.\",\n data: {},\n },\n });\n }\n\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Creates a new item.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath, options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Updates an existing item by its id.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"PATCH\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Deletes an existing item by its id.\n *\n * @throws {ClientResponseError}\n */\n async delete(id: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then(() => true);\n }\n\n /**\n * Returns a promise with all list items batch fetched at once.\n */\n protected _getFullList(\n batchSize = 1000,\n options?: ListOptions,\n ): Promise> {\n options = options || {};\n options.query = Object.assign(\n {\n skipTotal: 1,\n },\n options.query,\n );\n\n let result: Array = [];\n\n let request = async (page: number): Promise> => {\n return this.getList(page, batchSize || 1000, options).then((list) => {\n const castedList = list as any as ListResult;\n const items = castedList.items;\n\n result = result.concat(items);\n\n if (items.length == list.perPage) {\n return request(page + 1);\n }\n\n return result;\n });\n };\n\n return request(1);\n }\n}\n","import { SendOptions } from \"@/tools/options\";\n\nexport function normalizeLegacyOptionsArgs(\n legacyWarn: string,\n baseOptions: SendOptions,\n bodyOrOptions?: any,\n query?: any,\n): SendOptions {\n const hasBodyOrOptions = typeof bodyOrOptions !== \"undefined\";\n const hasQuery = typeof query !== \"undefined\";\n\n if (!hasQuery && !hasBodyOrOptions) {\n return baseOptions;\n }\n\n if (hasQuery) {\n console.warn(legacyWarn);\n baseOptions.body = Object.assign({}, baseOptions.body, bodyOrOptions);\n baseOptions.query = Object.assign({}, baseOptions.query, query);\n\n return baseOptions;\n }\n\n return Object.assign(baseOptions, bodyOrOptions);\n}\n","import Client from \"@/Client\";\nimport { isTokenExpired } from \"@/tools/jwt\";\n\n// reset previous auto refresh registrations\nexport function resetAutoRefresh(client: Client) {\n (client as any)._resetAutoRefresh?.();\n}\n\nexport function registerAutoRefresh(\n client: Client,\n threshold: number,\n refreshFunc: () => Promise,\n reauthenticateFunc: () => Promise,\n) {\n resetAutoRefresh(client);\n\n const oldBeforeSend = client.beforeSend;\n const oldModel = client.authStore.record;\n\n // unset the auto refresh in case the auth store was cleared\n // OR a new model was authenticated\n const unsubStoreChange = client.authStore.onChange((newToken, model) => {\n if (\n !newToken ||\n model?.id != oldModel?.id ||\n ((model?.collectionId || oldModel?.collectionId) &&\n model?.collectionId != oldModel?.collectionId)\n ) {\n resetAutoRefresh(client);\n }\n });\n\n // initialize a reset function and attach it dynamically to the client\n (client as any)._resetAutoRefresh = function () {\n unsubStoreChange();\n client.beforeSend = oldBeforeSend;\n delete (client as any)._resetAutoRefresh;\n };\n\n client.beforeSend = async (url, sendOptions) => {\n const oldToken = client.authStore.token;\n\n if (sendOptions.query?.autoRefresh) {\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n }\n\n let isValid = client.authStore.isValid;\n if (\n // is loosely valid\n isValid &&\n // but it is going to expire in the next \"threshold\" seconds\n isTokenExpired(client.authStore.token, threshold)\n ) {\n try {\n await refreshFunc();\n } catch (_) {\n isValid = false;\n }\n }\n\n // still invalid -> reauthenticate\n if (!isValid) {\n await reauthenticateFunc();\n }\n\n // the request wasn't sent with a custom token\n const headers = sendOptions.headers || {};\n for (let key in headers) {\n if (\n key.toLowerCase() == \"authorization\" &&\n // the request wasn't sent with a custom token\n oldToken == headers[key] &&\n client.authStore.token\n ) {\n // set the latest store token\n headers[key] = client.authStore.token;\n break;\n }\n }\n sendOptions.headers = headers;\n\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n };\n}\n","import Client from \"@/Client\";\nimport { ClientResponseError } from \"@/ClientResponseError\";\nimport { RealtimeService, UnsubscribeFunc } from \"@/services/RealtimeService\";\nimport { BaseAuthStore } from \"@/stores/BaseAuthStore\";\nimport { CrudService } from \"@/services/CrudService\";\nimport { ListResult, RecordModel } from \"@/tools/dtos\";\nimport { normalizeLegacyOptionsArgs } from \"@/tools/legacy\";\nimport {\n CommonOptions,\n RecordFullListOptions,\n RecordListOptions,\n RecordOptions,\n SendOptions,\n RecordSubscribeOptions,\n} from \"@/tools/options\";\nimport { getTokenPayload } from \"@/tools/jwt\";\nimport { registerAutoRefresh, resetAutoRefresh } from \"@/tools/refresh\";\n\nexport interface RecordAuthResponse {\n /**\n * The signed PocketBase auth record.\n */\n record: T;\n\n /**\n * The PocketBase record auth token.\n *\n * If you are looking for the OAuth2 access and refresh tokens\n * they are available under the `meta.accessToken` and `meta.refreshToken` props.\n */\n token: string;\n\n /**\n * Auth meta data usually filled when OAuth2 is used.\n */\n meta?: { [key: string]: any };\n}\n\nexport interface AuthProviderInfo {\n name: string;\n displayName: string;\n state: string;\n authURL: string;\n codeVerifier: string;\n codeChallenge: string;\n codeChallengeMethod: string;\n}\n\nexport interface AuthMethodsList {\n mfa: {\n enabled: boolean;\n duration: number;\n };\n otp: {\n enabled: boolean;\n duration: number;\n };\n password: {\n enabled: boolean;\n identityFields: Array;\n };\n oauth2: {\n enabled: boolean;\n providers: Array;\n };\n}\n\nexport interface RecordSubscription {\n action: string; // eg. create, update, delete\n record: T;\n}\n\nexport type OAuth2UrlCallback = (url: string) => void | Promise;\n\nexport interface OAuth2AuthConfig extends SendOptions {\n // the name of the OAuth2 provider (eg. \"google\")\n provider: string;\n\n // custom scopes to overwrite the default ones\n scopes?: Array;\n\n // optional record create data\n createData?: { [key: string]: any };\n\n // optional callback that is triggered after the OAuth2 sign-in/sign-up url generation\n urlCallback?: OAuth2UrlCallback;\n\n // optional query params to send with the PocketBase auth request (eg. fields, expand, etc.)\n query?: RecordOptions;\n}\n\nexport interface OTPResponse {\n otpId: string;\n}\n\nexport class RecordService extends CrudService {\n readonly collectionIdOrName: string;\n\n constructor(client: Client, collectionIdOrName: string) {\n super(client);\n\n this.collectionIdOrName = collectionIdOrName;\n }\n\n /**\n * @inheritdoc\n */\n get baseCrudPath(): string {\n return this.baseCollectionPath + \"/records\";\n }\n\n /**\n * Returns the current collection service base path.\n */\n get baseCollectionPath(): string {\n return \"/api/collections/\" + encodeURIComponent(this.collectionIdOrName);\n }\n\n /**\n * Returns whether the current service collection is superusers.\n */\n get isSuperusers(): boolean {\n return (\n this.collectionIdOrName == \"_superusers\" ||\n this.collectionIdOrName == \"_pbc_2773867675\"\n );\n }\n\n // ---------------------------------------------------------------\n // Realtime handlers\n // ---------------------------------------------------------------\n\n /**\n * Subscribe to realtime changes to the specified topic (\"*\" or record id).\n *\n * If `topic` is the wildcard \"*\", then this method will subscribe to\n * any record changes in the collection.\n *\n * If `topic` is a record id, then this method will subscribe only\n * to changes of the specified record id.\n *\n * It's OK to subscribe multiple times to the same topic.\n * You can use the returned `UnsubscribeFunc` to remove only a single subscription.\n * Or use `unsubscribe(topic)` if you want to remove all subscriptions attached to the topic.\n */\n async subscribe(\n topic: string,\n callback: (data: RecordSubscription) => void,\n options?: RecordSubscribeOptions,\n ): Promise {\n if (!topic) {\n throw new Error(\"Missing topic.\");\n }\n\n if (!callback) {\n throw new Error(\"Missing subscription callback.\");\n }\n\n return this.client.realtime.subscribe(\n this.collectionIdOrName + \"/\" + topic,\n callback,\n options,\n );\n }\n\n /**\n * Unsubscribe from all subscriptions of the specified topic\n * (\"*\" or record id).\n *\n * If `topic` is not set, then this method will unsubscribe from\n * all subscriptions associated to the current collection.\n */\n async unsubscribe(topic?: string): Promise {\n // unsubscribe from the specified topic\n if (topic) {\n return this.client.realtime.unsubscribe(\n this.collectionIdOrName + \"/\" + topic,\n );\n }\n\n // unsubscribe from everything related to the collection\n return this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName);\n }\n\n // ---------------------------------------------------------------\n // Crud handers\n // ---------------------------------------------------------------\n /**\n * @inheritdoc\n */\n async getFullList(options?: RecordFullListOptions): Promise>;\n\n /**\n * @inheritdoc\n */\n async getFullList(\n batch?: number,\n options?: RecordListOptions,\n ): Promise>;\n\n /**\n * @inheritdoc\n */\n async getFullList(\n batchOrOptions?: number | RecordFullListOptions,\n options?: RecordListOptions,\n ): Promise> {\n if (typeof batchOrOptions == \"number\") {\n return super.getFullList(batchOrOptions, options);\n }\n\n const params = Object.assign({}, batchOrOptions, options);\n\n return super.getFullList(params);\n }\n\n /**\n * @inheritdoc\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: RecordListOptions,\n ): Promise> {\n return super.getList(page, perPage, options);\n }\n\n /**\n * @inheritdoc\n */\n async getFirstListItem(\n filter: string,\n options?: RecordListOptions,\n ): Promise {\n return super.getFirstListItem(filter, options);\n }\n\n /**\n * @inheritdoc\n */\n async getOne(id: string, options?: RecordOptions): Promise {\n return super.getOne(id, options);\n }\n\n /**\n * @inheritdoc\n */\n async create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): Promise {\n return super.create(bodyParams, options);\n }\n\n /**\n * @inheritdoc\n *\n * If the current `client.authStore.record` matches with the updated id, then\n * on success the `client.authStore.record` will be updated with the new response record fields.\n */\n async update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): Promise {\n return super.update(id, bodyParams, options).then((item) => {\n if (\n // is record auth\n this.client.authStore.record?.id === item?.id &&\n (this.client.authStore.record?.collectionId === this.collectionIdOrName ||\n this.client.authStore.record?.collectionName ===\n this.collectionIdOrName)\n ) {\n let authExpand = Object.assign({}, this.client.authStore.record.expand);\n let authRecord = Object.assign({}, this.client.authStore.record, item);\n if (authExpand) {\n // for now \"merge\" only top-level expand\n authRecord.expand = Object.assign(authExpand, item.expand);\n }\n\n this.client.authStore.save(this.client.authStore.token, authRecord);\n }\n\n return item as any as T;\n });\n }\n\n /**\n * @inheritdoc\n *\n * If the current `client.authStore.record` matches with the deleted id,\n * then on success the `client.authStore` will be cleared.\n */\n async delete(id: string, options?: CommonOptions): Promise {\n return super.delete(id, options).then((success) => {\n if (\n success &&\n // is record auth\n this.client.authStore.record?.id === id &&\n (this.client.authStore.record?.collectionId === this.collectionIdOrName ||\n this.client.authStore.record?.collectionName ===\n this.collectionIdOrName)\n ) {\n this.client.authStore.clear();\n }\n\n return success;\n });\n }\n\n // ---------------------------------------------------------------\n // Auth handlers\n // ---------------------------------------------------------------\n\n /**\n * Prepare successful collection authorization response.\n */\n protected authResponse(responseData: any): RecordAuthResponse {\n const record = this.decode(responseData?.record || {});\n\n this.client.authStore.save(responseData?.token, record as any);\n\n return Object.assign({}, responseData, {\n // normalize common fields\n token: responseData?.token || \"\",\n record: record as any as T,\n });\n }\n\n /**\n * Returns all available collection auth methods.\n *\n * @throws {ClientResponseError}\n */\n async listAuthMethods(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"GET\",\n // @todo remove after deleting the pre v0.23 API response fields\n fields: \"mfa,otp,password,oauth2\",\n },\n options,\n );\n\n return this.client.send(this.baseCollectionPath + \"/auth-methods\", options);\n }\n\n /**\n * Authenticate a single auth collection record via its username/email and password.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n *\n * @throws {ClientResponseError}\n */\n async authWithPassword(\n usernameOrEmail: string,\n password: string,\n options?: RecordOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n identity: usernameOrEmail,\n password: password,\n },\n },\n options,\n );\n\n // note: consider to deprecate\n let autoRefreshThreshold;\n if (this.isSuperusers) {\n autoRefreshThreshold = options.autoRefreshThreshold;\n delete options.autoRefreshThreshold;\n if (!options.autoRefresh) {\n resetAutoRefresh(this.client);\n }\n }\n\n let authData = await this.client.send(\n this.baseCollectionPath + \"/auth-with-password\",\n options,\n );\n\n authData = this.authResponse(authData);\n\n if (autoRefreshThreshold && this.isSuperusers) {\n registerAutoRefresh(\n this.client,\n autoRefreshThreshold,\n () => this.authRefresh({ autoRefresh: true }),\n () =>\n this.authWithPassword(\n usernameOrEmail,\n password,\n Object.assign({ autoRefresh: true }, options),\n ),\n );\n }\n\n return authData;\n }\n\n /**\n * Authenticate a single auth collection record with OAuth2 code.\n *\n * If you don't have an OAuth2 code you may also want to check `authWithOAuth2` method.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n * - the OAuth2 account data (eg. name, email, avatar, etc.)\n *\n * @throws {ClientResponseError}\n */\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n options?: RecordOptions,\n ): Promise>;\n\n /**\n * @deprecated\n * Consider using authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createdData, options?).\n */\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n body?: any,\n query?: any,\n ): Promise>;\n\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n bodyOrOptions?: any,\n query?: any,\n ): Promise> {\n let options: any = {\n method: \"POST\",\n body: {\n provider: provider,\n code: code,\n codeVerifier: codeVerifier,\n redirectURL: redirectURL,\n createData: createData,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-with-oauth2\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * @deprecated This form of authWithOAuth2 is deprecated.\n *\n * Please use `authWithOAuth2Code()` OR its simplified realtime version\n * as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.\n */\n async authWithOAuth2(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n bodyParams?: { [key: string]: any },\n queryParams?: RecordOptions,\n ): Promise>;\n\n /**\n * Authenticate a single auth collection record with OAuth2\n * **without custom redirects, deeplinks or even page reload**.\n *\n * This method initializes a one-off realtime subscription and will\n * open a popup window with the OAuth2 vendor page to authenticate.\n * Once the external OAuth2 sign-in/sign-up flow is completed, the popup\n * window will be automatically closed and the OAuth2 data sent back\n * to the user through the previously established realtime connection.\n *\n * You can specify an optional `urlCallback` prop to customize\n * the default url `window.open` behavior.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n * - the OAuth2 account data (eg. name, email, avatar, etc.)\n *\n * Example:\n *\n * ```js\n * const authData = await pb.collection(\"users\").authWithOAuth2({\n * provider: \"google\",\n * })\n * ```\n *\n * Note1: When creating the OAuth2 app in the provider dashboard\n * you have to configure `https://yourdomain.com/api/oauth2-redirect`\n * as redirect URL.\n *\n * Note2: Safari may block the default `urlCallback` popup because\n * it doesn't allow `window.open` calls as part of an `async` click functions.\n * To workaround this you can either change your click handler to not be marked as `async`\n * OR manually call `window.open` before your `async` function and use the\n * window reference in your own custom `urlCallback` (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061).\n * For example:\n * ```js\n * \n * ...\n * document.getElementById(\"btn\").addEventListener(\"click\", () => {\n * pb.collection(\"users\").authWithOAuth2({\n * provider: \"gitlab\",\n * }).then((authData) => {\n * console.log(authData)\n * }).catch((err) => {\n * console.log(err, err.originalError);\n * });\n * })\n * ```\n *\n * @throws {ClientResponseError}\n */\n async authWithOAuth2(\n options: OAuth2AuthConfig,\n ): Promise>;\n\n authWithOAuth2(...args: any): Promise> {\n // fallback to legacy format\n if (args.length > 1 || typeof args?.[0] === \"string\") {\n console.warn(\n \"PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.\",\n );\n return this.authWithOAuth2Code(\n args?.[0] || \"\",\n args?.[1] || \"\",\n args?.[2] || \"\",\n args?.[3] || \"\",\n args?.[4] || {},\n args?.[5] || {},\n args?.[6] || {},\n );\n }\n\n const config = args?.[0] || {};\n\n // open a new popup window in case config.urlCallback is not set\n //\n // note: it is opened before any async calls due to Safari restrictions\n // (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061)\n let eagerDefaultPopup: Window | null = null;\n if (!config.urlCallback) {\n eagerDefaultPopup = openBrowserPopup(undefined);\n }\n\n // initialize a one-off realtime service\n const realtime = new RealtimeService(this.client);\n\n function cleanup() {\n eagerDefaultPopup?.close();\n realtime.unsubscribe();\n }\n\n const requestKeyOptions: SendOptions = {};\n const requestKey = config.requestKey;\n if (requestKey) {\n requestKeyOptions.requestKey = requestKey;\n }\n\n return this.listAuthMethods(requestKeyOptions)\n .then((authMethods) => {\n const provider = authMethods.oauth2.providers.find(\n (p) => p.name === config.provider,\n );\n if (!provider) {\n throw new ClientResponseError(\n new Error(`Missing or invalid provider \"${config.provider}\".`),\n );\n }\n\n const redirectURL = this.client.buildURL(\"/api/oauth2-redirect\");\n\n return new Promise(async (resolve, reject) => {\n // find the AbortController associated with the current request key (if any)\n const cancelController = requestKey\n ? this.client[\"cancelControllers\"]?.[requestKey]\n : undefined;\n if (cancelController) {\n cancelController.signal.onabort = () => {\n cleanup();\n reject(\n new ClientResponseError({\n isAbort: true,\n message: \"manually cancelled\",\n }),\n );\n };\n }\n\n // disconnected due to network/server error\n realtime.onDisconnect = (activeSubscriptions: Array) => {\n if (activeSubscriptions.length && reject) {\n cleanup();\n reject(\n new ClientResponseError(\n new Error(\"realtime connection interrupted\"),\n ),\n );\n }\n };\n\n try {\n await realtime.subscribe(\"@oauth2\", async (e) => {\n const oldState = realtime.clientId;\n\n try {\n if (!e.state || oldState !== e.state) {\n throw new Error(\"State parameters don't match.\");\n }\n\n if (e.error || !e.code) {\n throw new Error(\n \"OAuth2 redirect error or missing code: \" +\n e.error,\n );\n }\n\n // clear the non SendOptions props\n const options = Object.assign({}, config);\n delete options.provider;\n delete options.scopes;\n delete options.createData;\n delete options.urlCallback;\n\n // reset the cancelController listener as it will be triggered by the next api call\n if (cancelController?.signal?.onabort) {\n cancelController.signal.onabort = null;\n }\n\n const authData = await this.authWithOAuth2Code(\n provider.name,\n e.code,\n provider.codeVerifier,\n redirectURL,\n config.createData,\n options,\n );\n\n resolve(authData);\n } catch (err) {\n reject(new ClientResponseError(err));\n }\n\n cleanup();\n });\n\n const replacements: { [key: string]: any } = {\n state: realtime.clientId,\n };\n if (config.scopes?.length) {\n replacements[\"scope\"] = config.scopes.join(\" \");\n }\n\n const url = this._replaceQueryParams(\n provider.authURL + redirectURL,\n replacements,\n );\n\n let urlCallback =\n config.urlCallback ||\n function (url: string) {\n if (eagerDefaultPopup) {\n eagerDefaultPopup.location.href = url;\n } else {\n // it could have been blocked due to its empty initial url,\n // try again...\n eagerDefaultPopup = openBrowserPopup(url);\n }\n };\n\n await urlCallback(url);\n } catch (err) {\n // reset the cancelController listener in case the request key is reused\n if (cancelController?.signal?.onabort) {\n cancelController.signal.onabort = null;\n }\n\n cleanup();\n reject(new ClientResponseError(err));\n }\n });\n })\n .catch((err) => {\n cleanup();\n throw err; // rethrow\n }) as Promise>;\n }\n\n /**\n * Refreshes the current authenticated record instance and\n * returns a new token and record data.\n *\n * On success this method also automatically updates the client's AuthStore.\n *\n * @throws {ClientResponseError}\n */\n async authRefresh(options?: RecordOptions): Promise>;\n\n /**\n * @deprecated\n * Consider using authRefresh(options?).\n */\n async authRefresh(body?: any, query?: any): Promise>;\n\n async authRefresh(\n bodyOrOptions?: any,\n query?: any,\n ): Promise> {\n let options: any = {\n method: \"POST\",\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-refresh\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * Sends auth record password reset request.\n *\n * @throws {ClientResponseError}\n */\n async requestPasswordReset(email: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestPasswordReset(email, options?).\n */\n async requestPasswordReset(email: string, body?: any, query?: any): Promise;\n\n async requestPasswordReset(\n email: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n email: email,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-password-reset\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record password reset request.\n *\n * @throws {ClientResponseError}\n */\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmPasswordReset(passwordResetToken, password, passwordConfirm, options?).\n */\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: passwordResetToken,\n password: password,\n passwordConfirm: passwordConfirm,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-password-reset\", options)\n .then(() => true);\n }\n\n /**\n * Sends auth record verification email request.\n *\n * @throws {ClientResponseError}\n */\n async requestVerification(email: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestVerification(email, options?).\n */\n async requestVerification(email: string, body?: any, query?: any): Promise;\n\n async requestVerification(\n email: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n email: email,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-verification\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record email verification request.\n *\n * If the current `client.authStore.record` matches with the auth record from the token,\n * then on success the `client.authStore.record.verified` will be updated to `true`.\n *\n * @throws {ClientResponseError}\n */\n async confirmVerification(\n verificationToken: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmVerification(verificationToken, options?).\n */\n async confirmVerification(\n verificationToken: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmVerification(\n verificationToken: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: verificationToken,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-verification\", options)\n .then(() => {\n // on success manually update the current auth record verified state\n const payload = getTokenPayload(verificationToken);\n const model = this.client.authStore.record;\n if (\n model &&\n !model.verified &&\n model.id === payload.id &&\n model.collectionId === payload.collectionId\n ) {\n model.verified = true;\n this.client.authStore.save(this.client.authStore.token, model);\n }\n\n return true;\n });\n }\n\n /**\n * Sends an email change request to the authenticated record model.\n *\n * @throws {ClientResponseError}\n */\n async requestEmailChange(newEmail: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestEmailChange(newEmail, options?).\n */\n async requestEmailChange(newEmail: string, body?: any, query?: any): Promise;\n\n async requestEmailChange(\n newEmail: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n newEmail: newEmail,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-email-change\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record's new email address.\n *\n * If the current `client.authStore.record` matches with the auth record from the token,\n * then on success the `client.authStore` will be cleared.\n *\n * @throws {ClientResponseError}\n */\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmEmailChange(emailChangeToken, password, options?).\n */\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: emailChangeToken,\n password: password,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-email-change\", options)\n .then(() => {\n const payload = getTokenPayload(emailChangeToken);\n const model = this.client.authStore.record;\n if (\n model &&\n model.id === payload.id &&\n model.collectionId === payload.collectionId\n ) {\n this.client.authStore.clear();\n }\n\n return true;\n });\n }\n\n /**\n * @deprecated use collection(\"_externalAuths\").*\n *\n * Lists all linked external auth providers for the specified auth record.\n *\n * @throws {ClientResponseError}\n */\n async listExternalAuths(\n recordId: string,\n options?: CommonOptions,\n ): Promise> {\n return this.client.collection(\"_externalAuths\").getFullList(\n Object.assign({}, options, {\n filter: this.client.filter(\"recordRef = {:id}\", { id: recordId }),\n }),\n );\n }\n\n /**\n * @deprecated use collection(\"_externalAuths\").*\n *\n * Unlink a single external auth provider from the specified auth record.\n *\n * @throws {ClientResponseError}\n */\n async unlinkExternalAuth(\n recordId: string,\n provider: string,\n options?: CommonOptions,\n ): Promise {\n const ea = await this.client.collection(\"_externalAuths\").getFirstListItem(\n this.client.filter(\"recordRef = {:recordId} && provider = {:provider}\", {\n recordId,\n provider,\n }),\n );\n\n return this.client\n .collection(\"_externalAuths\")\n .delete(ea.id, options)\n .then(() => true);\n }\n\n /**\n * Sends auth record OTP to the provided email.\n *\n * @throws {ClientResponseError}\n */\n async requestOTP(email: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: { email: email },\n },\n options,\n );\n\n return this.client.send(this.baseCollectionPath + \"/request-otp\", options);\n }\n\n /**\n * Authenticate a single auth collection record via OTP.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n *\n * @throws {ClientResponseError}\n */\n async authWithOTP(\n otpId: string,\n password: string,\n options?: CommonOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"POST\",\n body: { otpId, password },\n },\n options,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-with-otp\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * Impersonate authenticates with the specified recordId and\n * returns a new client with the received auth token in a memory store.\n *\n * If `duration` is 0 the generated auth token will fallback\n * to the default collection auth token duration.\n *\n * This action currently requires superusers privileges.\n *\n * @throws {ClientResponseError}\n */\n async impersonate(\n recordId: string,\n duration: number,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: { duration: duration },\n },\n options,\n );\n options.headers = options.headers || {};\n if (!options.headers.Authorization) {\n options.headers.Authorization = this.client.authStore.token;\n }\n\n // create a new client loaded with the impersonated auth state\n // ---\n const client = new Client(\n this.client.baseURL,\n new BaseAuthStore(),\n this.client.lang,\n );\n\n const authData = await client.send(\n this.baseCollectionPath + \"/impersonate/\" + encodeURIComponent(recordId),\n options,\n );\n\n client.authStore.save(authData?.token, this.decode(authData?.record || {}));\n // ---\n\n return client;\n }\n\n // ---------------------------------------------------------------\n\n // very rudimentary url query params replacement because at the moment\n // URL (and URLSearchParams) doesn't seem to be fully supported in React Native\n //\n // note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html\n private _replaceQueryParams(\n url: string,\n replacements: { [key: string]: any } = {},\n ): string {\n let urlPath = url;\n let query = \"\";\n\n const queryIndex = url.indexOf(\"?\");\n if (queryIndex >= 0) {\n urlPath = url.substring(0, url.indexOf(\"?\"));\n query = url.substring(url.indexOf(\"?\") + 1);\n }\n\n const parsedParams: { [key: string]: string } = {};\n\n // parse the query parameters\n const rawParams = query.split(\"&\");\n for (const param of rawParams) {\n if (param == \"\") {\n continue;\n }\n\n const pair = param.split(\"=\");\n parsedParams[decodeURIComponent(pair[0].replace(/\\+/g, \" \"))] =\n decodeURIComponent((pair[1] || \"\").replace(/\\+/g, \" \"));\n }\n\n // apply the replacements\n for (let key in replacements) {\n if (!replacements.hasOwnProperty(key)) {\n continue;\n }\n\n if (replacements[key] == null) {\n delete parsedParams[key];\n } else {\n parsedParams[key] = replacements[key];\n }\n }\n\n // construct back the full query string\n query = \"\";\n for (let key in parsedParams) {\n if (!parsedParams.hasOwnProperty(key)) {\n continue;\n }\n\n if (query != \"\") {\n query += \"&\";\n }\n\n query +=\n encodeURIComponent(key.replace(/%20/g, \"+\")) +\n \"=\" +\n encodeURIComponent(parsedParams[key].replace(/%20/g, \"+\"));\n }\n\n return query != \"\" ? urlPath + \"?\" + query : urlPath;\n }\n}\n\nfunction openBrowserPopup(url?: string): Window | null {\n if (typeof window === \"undefined\" || !window?.open) {\n throw new ClientResponseError(\n new Error(\n `Not in a browser context - please pass a custom urlCallback function.`,\n ),\n );\n }\n\n let width = 1024;\n let height = 768;\n\n let windowWidth = window.innerWidth;\n let windowHeight = window.innerHeight;\n\n // normalize window size\n width = width > windowWidth ? windowWidth : width;\n height = height > windowHeight ? windowHeight : height;\n\n let left = windowWidth / 2 - width / 2;\n let top = windowHeight / 2 - height / 2;\n\n // note: we don't use the noopener and noreferrer attributes since\n // for some reason browser blocks such windows then url is undefined/blank\n return window.open(\n url,\n \"popup_window\",\n \"width=\" +\n width +\n \",height=\" +\n height +\n \",top=\" +\n top +\n \",left=\" +\n left +\n \",resizable,menubar=no\",\n );\n}\n","import { CrudService } from \"@/services/CrudService\";\nimport { CollectionModel } from \"@/tools/dtos\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport class CollectionService extends CrudService {\n /**\n * @inheritdoc\n */\n get baseCrudPath(): string {\n return \"/api/collections\";\n }\n\n /**\n * Imports the provided collections.\n *\n * If `deleteMissing` is `true`, all local collections and their fields,\n * that are not present in the imported configuration, WILL BE DELETED\n * (including their related records data)!\n *\n * @throws {ClientResponseError}\n */\n async import(\n collections: Array,\n deleteMissing: boolean = false,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"PUT\",\n body: {\n collections: collections,\n deleteMissing: deleteMissing,\n },\n },\n options,\n );\n\n return this.client.send(this.baseCrudPath + \"/import\", options).then(() => true);\n }\n\n /**\n * Returns type indexed map with scaffolded collection models\n * populated with their default field values.\n *\n * @throws {ClientResponseError}\n */\n async getScaffolds(\n options?: CommonOptions,\n ): Promise<{ [key: string]: CollectionModel }> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(this.baseCrudPath + \"/meta/scaffolds\", options);\n }\n\n /**\n * Deletes all records associated with the specified collection.\n *\n * @throws {ClientResponseError}\n */\n async truncate(collectionIdOrName: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(\n this.baseCrudPath +\n \"/\" +\n encodeURIComponent(collectionIdOrName) +\n \"/truncate\",\n options,\n )\n .then(() => true);\n }\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseService } from \"@/services/BaseService\";\nimport { ListResult, LogModel } from \"@/tools/dtos\";\nimport { CommonOptions, ListOptions, LogStatsOptions } from \"@/tools/options\";\n\nexport interface HourlyStats {\n total: number;\n date: string;\n}\n\nexport class LogService extends BaseService {\n /**\n * Returns paginated logs list.\n *\n * @throws {ClientResponseError}\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: ListOptions,\n ): Promise> {\n options = Object.assign({ method: \"GET\" }, options);\n\n options.query = Object.assign(\n {\n page: page,\n perPage: perPage,\n },\n options.query,\n );\n\n return this.client.send(\"/api/logs\", options);\n }\n\n /**\n * Returns a single log by its id.\n *\n * If `id` is empty it will throw a 404 error.\n *\n * @throws {ClientResponseError}\n */\n async getOne(id: string, options?: CommonOptions): Promise {\n if (!id) {\n throw new ClientResponseError({\n url: this.client.buildURL(\"/api/logs/\"),\n status: 404,\n response: {\n code: 404,\n message: \"Missing required log id.\",\n data: {},\n },\n });\n }\n\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/logs/\" + encodeURIComponent(id), options);\n }\n\n /**\n * Returns logs statistics.\n *\n * @throws {ClientResponseError}\n */\n async getStats(options?: LogStatsOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/logs/stats\", options);\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface HealthCheckResponse {\n code: number;\n message: string;\n data: { [key: string]: any };\n}\n\nexport class HealthService extends BaseService {\n /**\n * Checks the health status of the api.\n *\n * @throws {ClientResponseError}\n */\n async check(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/health\", options);\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions, FileOptions, serializeQueryParams } from \"@/tools/options\";\n\nexport class FileService extends BaseService {\n /**\n * @deprecated Please replace with `pb.files.getURL()`.\n */\n getUrl(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n console.warn(\"Please replace pb.files.getUrl() with pb.files.getURL()\");\n return this.getURL(record, filename, queryParams);\n }\n\n /**\n * Builds and returns an absolute record file url for the provided filename.\n */\n getURL(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n if (\n !filename ||\n !record?.id ||\n !(record?.collectionId || record?.collectionName)\n ) {\n return \"\";\n }\n\n const parts = [];\n parts.push(\"api\");\n parts.push(\"files\");\n parts.push(encodeURIComponent(record.collectionId || record.collectionName));\n parts.push(encodeURIComponent(record.id));\n parts.push(encodeURIComponent(filename));\n\n let result = this.client.buildURL(parts.join(\"/\"));\n\n // normalize the download query param for consistency with the Dart sdk\n if (queryParams.download === false) {\n delete queryParams.download;\n }\n\n const params = serializeQueryParams(queryParams);\n if (params) {\n result += (result.includes(\"?\") ? \"&\" : \"?\") + params;\n }\n\n return result;\n }\n\n /**\n * Requests a new private file access token for the current auth model.\n *\n * @throws {ClientResponseError}\n */\n async getToken(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(\"/api/files/token\", options)\n .then((data) => data?.token || \"\");\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface BackupFileInfo {\n key: string;\n size: number;\n modified: string;\n}\n\nexport class BackupService extends BaseService {\n /**\n * Returns list with all available backup files.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: CommonOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/backups\", options);\n }\n\n /**\n * Initializes a new backup.\n *\n * @throws {ClientResponseError}\n */\n async create(basename: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n name: basename,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/backups\", options).then(() => true);\n }\n\n /**\n * Uploads an existing backup file.\n *\n * Example:\n *\n * ```js\n * await pb.backups.upload({\n * file: new Blob([...]),\n * });\n * ```\n *\n * @throws {ClientResponseError}\n */\n async upload(\n bodyParams: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client.send(\"/api/backups/upload\", options).then(() => true);\n }\n\n /**\n * Deletes a single backup file.\n *\n * @throws {ClientResponseError}\n */\n async delete(key: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(`/api/backups/${encodeURIComponent(key)}`, options)\n .then(() => true);\n }\n\n /**\n * Initializes an app data restore from an existing backup.\n *\n * @throws {ClientResponseError}\n */\n async restore(key: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(`/api/backups/${encodeURIComponent(key)}/restore`, options)\n .then(() => true);\n }\n\n /**\n * @deprecated Please use `getDownloadURL()`.\n */\n getDownloadUrl(token: string, key: string): string {\n console.warn(\n \"Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()\",\n );\n return this.getDownloadURL(token, key);\n }\n\n /**\n * Builds a download url for a single existing backup using a\n * superuser file token and the backup file key.\n *\n * The file token can be generated via `pb.files.getToken()`.\n */\n getDownloadURL(token: string, key: string): string {\n return this.client.buildURL(\n `/api/backups/${encodeURIComponent(key)}?token=${encodeURIComponent(token)}`,\n );\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface CronJob {\n id: string;\n expression: string;\n}\n\nexport class CronService extends BaseService {\n /**\n * Returns list with all registered cron jobs.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: CommonOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/crons\", options);\n }\n\n /**\n * Runs the specified cron job.\n *\n * @throws {ClientResponseError}\n */\n async run(jobId: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(`/api/crons/${encodeURIComponent(jobId)}`, options)\n .then(() => true);\n }\n}\n","/**\n * Checks if the specified value is a file (aka. File, Blob, RN file object).\n */\nexport function isFile(val: any): boolean {\n return (\n (typeof Blob !== \"undefined\" && val instanceof Blob) ||\n (typeof File !== \"undefined\" && val instanceof File) ||\n // check for React Native file object format\n // (see https://github.com/pocketbase/pocketbase/discussions/2002#discussioncomment-5254168)\n (val !== null &&\n typeof val === \"object\" &&\n val.uri &&\n ((typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\") ||\n (typeof global !== \"undefined\" && (global as any).HermesInternal)))\n );\n}\n\n/**\n * Loosely checks if the specified body is a FormData instance.\n */\nexport function isFormData(body: any): boolean {\n return (\n body &&\n // we are checking the constructor name because FormData\n // is not available natively in some environments and the\n // polyfill(s) may not be globally accessible\n (body.constructor?.name === \"FormData\" ||\n // fallback to global FormData instance check\n // note: this is needed because the constructor.name could be different in case of\n // custom global FormData implementation, eg. React Native on Android/iOS\n (typeof FormData !== \"undefined\" && body instanceof FormData))\n );\n}\n\n/**\n * Checks if the submitted body object has at least one Blob/File field value.\n */\nexport function hasFileField(body: { [key: string]: any }): boolean {\n for (const key in body) {\n const values = Array.isArray(body[key]) ? body[key] : [body[key]];\n for (const v of values) {\n if (isFile(v)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Converts analyzes the provided body and converts it to FormData\n * in case a plain object with File/Blob values is used.\n */\nexport function convertToFormDataIfNeeded(body: any): any {\n if (\n typeof FormData === \"undefined\" ||\n typeof body === \"undefined\" ||\n typeof body !== \"object\" ||\n body === null ||\n isFormData(body) ||\n !hasFileField(body)\n ) {\n return body;\n }\n\n const form = new FormData();\n\n for (const key in body) {\n const val = body[key];\n\n // skip undefined values for consistency with JSON.stringify\n // (see https://github.com/pocketbase/pocketbase/issues/6731#issuecomment-2812382827)\n if (typeof val === \"undefined\") {\n continue;\n }\n\n if (typeof val === \"object\" && !hasFileField({ data: val })) {\n // send json-like values as jsonPayload to avoid the implicit string value normalization\n let payload: { [key: string]: any } = {};\n payload[key] = val;\n form.append(\"@jsonPayload\", JSON.stringify(payload));\n } else {\n // in case of mixed string and file/blob\n const normalizedVal = Array.isArray(val) ? val : [val];\n for (let v of normalizedVal) {\n form.append(key, v);\n }\n }\n }\n\n return form;\n}\n\n/**\n * Converts the provided FormData instance into a plain object.\n *\n * For consistency with the server multipart/form-data inferring,\n * the following normalization rules are applied for plain multipart string values:\n * - \"true\" is converted to the json \"true\"\n * - \"false\" is converted to the json \"false\"\n * - numeric strings are converted to json number ONLY if the resulted\n * minimal number string representation is the same as the provided raw string\n * (aka. scientific notations, \"Infinity\", \"0.0\", \"0001\", etc. are kept as string)\n * - any other string (empty string too) is left as it is\n */\nexport function convertFormDataToObject(formData: FormData): { [key: string]: any } {\n let result: { [key: string]: any } = {};\n\n formData.forEach((v, k) => {\n if (k === \"@jsonPayload\" && typeof v == \"string\") {\n try {\n let parsed = JSON.parse(v);\n Object.assign(result, parsed);\n } catch (err) {\n console.warn(\"@jsonPayload error:\", err);\n }\n } else {\n if (typeof result[k] !== \"undefined\") {\n if (!Array.isArray(result[k])) {\n result[k] = [result[k]];\n }\n result[k].push(inferFormDataValue(v));\n } else {\n result[k] = inferFormDataValue(v);\n }\n }\n });\n\n return result;\n}\n\nconst inferNumberCharsRegex = /^[\\-\\.\\d]+$/;\n\nfunction inferFormDataValue(value: any): any {\n if (typeof value != \"string\") {\n return value;\n }\n\n if (value == \"true\") {\n return true;\n }\n\n if (value == \"false\") {\n return false;\n }\n\n // note: expects the provided raw string to match exactly with the minimal string representation of the parsed number\n if (\n (value[0] === \"-\" || (value[0] >= \"0\" && value[0] <= \"9\")) &&\n inferNumberCharsRegex.test(value)\n ) {\n let num = +value;\n if (\"\" + num === value) {\n return num;\n }\n }\n\n return value;\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { isFile, isFormData, convertFormDataToObject } from \"@/tools/formdata\";\nimport {\n SendOptions,\n RecordOptions,\n normalizeUnknownQueryParams,\n serializeQueryParams,\n} from \"@/tools/options\";\n\nexport interface BatchRequest {\n method: string;\n url: string;\n json?: { [key: string]: any };\n files?: { [key: string]: Array };\n headers?: { [key: string]: string };\n}\n\nexport interface BatchRequestResult {\n status: number;\n body: any;\n}\n\nexport class BatchService extends BaseService {\n private requests: Array = [];\n private subs: { [key: string]: SubBatchService } = {};\n\n /**\n * Starts constructing a batch request entry for the specified collection.\n */\n collection(collectionIdOrName: string): SubBatchService {\n if (!this.subs[collectionIdOrName]) {\n this.subs[collectionIdOrName] = new SubBatchService(\n this.requests,\n collectionIdOrName,\n );\n }\n\n return this.subs[collectionIdOrName];\n }\n\n /**\n * Sends the batch requests.\n *\n * @throws {ClientResponseError}\n */\n async send(options?: SendOptions): Promise> {\n const formData = new FormData();\n\n const jsonData = [];\n\n for (let i = 0; i < this.requests.length; i++) {\n const req = this.requests[i];\n\n jsonData.push({\n method: req.method,\n url: req.url,\n headers: req.headers,\n body: req.json,\n });\n\n if (req.files) {\n for (let key in req.files) {\n const files = req.files[key] || [];\n for (let file of files) {\n formData.append(\"requests.\" + i + \".\" + key, file);\n }\n }\n }\n }\n\n formData.append(\"@jsonPayload\", JSON.stringify({ requests: jsonData }));\n\n options = Object.assign(\n {\n method: \"POST\",\n body: formData,\n },\n options,\n );\n\n return this.client.send(\"/api/batch\", options);\n }\n}\n\nexport class SubBatchService {\n private requests: Array = [];\n private readonly collectionIdOrName: string;\n\n constructor(requests: Array, collectionIdOrName: string) {\n this.requests = requests;\n this.collectionIdOrName = collectionIdOrName;\n }\n\n /**\n * Registers a record upsert request into the current batch queue.\n *\n * The request will be executed as update if `bodyParams` have a valid existing record `id` value, otherwise - create.\n */\n upsert(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"PUT\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records\",\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record create request into the current batch queue.\n */\n create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"POST\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records\",\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record update request into the current batch queue.\n */\n update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"PATCH\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records/\" +\n encodeURIComponent(id),\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record delete request into the current batch queue.\n */\n delete(id: string, options?: SendOptions): void {\n options = Object.assign({}, options);\n\n const request: BatchRequest = {\n method: \"DELETE\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records/\" +\n encodeURIComponent(id),\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n private prepareRequest(request: BatchRequest, options: SendOptions) {\n normalizeUnknownQueryParams(options);\n\n request.headers = options.headers;\n request.json = {};\n request.files = {};\n\n // serialize query parameters\n // -----------------------------------------------------------\n if (typeof options.query !== \"undefined\") {\n const query = serializeQueryParams(options.query);\n if (query) {\n request.url += (request.url.includes(\"?\") ? \"&\" : \"?\") + query;\n }\n }\n\n // extract json and files body data\n // -----------------------------------------------------------\n let body = options.body;\n if (isFormData(body)) {\n body = convertFormDataToObject(body);\n }\n\n for (const key in body) {\n const val = body[key];\n\n if (isFile(val)) {\n request.files[key] = request.files[key] || [];\n request.files[key].push(val);\n } else if (Array.isArray(val)) {\n const foundFiles = [];\n const foundRegular = [];\n for (const v of val) {\n if (isFile(v)) {\n foundFiles.push(v);\n } else {\n foundRegular.push(v);\n }\n }\n\n if (foundFiles.length > 0 && foundFiles.length == val.length) {\n // only files\n // ---\n request.files[key] = request.files[key] || [];\n for (let file of foundFiles) {\n request.files[key].push(file);\n }\n } else {\n // empty or mixed array (both regular and File/Blob values)\n // ---\n request.json[key] = foundRegular;\n\n if (foundFiles.length > 0) {\n // add \"+\" to append if not already since otherwise\n // the existing regular files will be deleted\n // (the mixed values order is preserved only within their corresponding groups)\n let fileKey = key;\n if (!key.startsWith(\"+\") && !key.endsWith(\"+\")) {\n fileKey += \"+\";\n }\n\n request.files[fileKey] = request.files[fileKey] || [];\n for (let file of foundFiles) {\n request.files[fileKey].push(file);\n }\n }\n }\n } else {\n request.json[key] = val;\n }\n }\n }\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseAuthStore } from \"@/stores/BaseAuthStore\";\nimport { LocalAuthStore } from \"@/stores/LocalAuthStore\";\nimport { SettingsService } from \"@/services/SettingsService\";\nimport { RecordService } from \"@/services/RecordService\";\nimport { CollectionService } from \"@/services/CollectionService\";\nimport { LogService } from \"@/services/LogService\";\nimport { RealtimeService } from \"@/services/RealtimeService\";\nimport { HealthService } from \"@/services/HealthService\";\nimport { FileService } from \"@/services/FileService\";\nimport { BackupService } from \"@/services/BackupService\";\nimport { CronService } from \"@/services/CronService\";\nimport { BatchService } from \"@/services/BatchService\";\nimport { RecordModel } from \"@/tools/dtos\";\nimport {\n SendOptions,\n FileOptions,\n normalizeUnknownQueryParams,\n serializeQueryParams,\n} from \"@/tools/options\";\nimport { isFormData, convertToFormDataIfNeeded } from \"@/tools/formdata\";\n\nexport interface BeforeSendResult {\n [key: string]: any; // for backward compatibility\n url?: string;\n options?: { [key: string]: any };\n}\n\n/**\n * PocketBase JS Client.\n */\nexport default class Client {\n /**\n * The base PocketBase backend url address (eg. 'http://127.0.0.1.8090').\n */\n baseURL: string;\n\n /**\n * Legacy getter alias for baseURL.\n * @deprecated Please replace with baseURL.\n */\n get baseUrl(): string {\n return this.baseURL;\n }\n\n /**\n * Legacy setter alias for baseURL.\n * @deprecated Please replace with baseURL.\n */\n set baseUrl(v: string) {\n this.baseURL = v;\n }\n\n /**\n * Hook that get triggered right before sending the fetch request,\n * allowing you to inspect and modify the url and request options.\n *\n * For list of the possible options check https://developer.mozilla.org/en-US/docs/Web/API/fetch#options\n *\n * You can return a non-empty result object `{ url, options }` to replace the url and request options entirely.\n *\n * Example:\n * ```js\n * const pb = new PocketBase(\"https://example.com\")\n *\n * pb.beforeSend = function (url, options) {\n * options.headers = Object.assign({}, options.headers, {\n * 'X-Custom-Header': 'example',\n * })\n *\n * return { url, options }\n * }\n *\n * // use the created client as usual...\n * ```\n */\n beforeSend?: (\n url: string,\n options: SendOptions,\n ) => BeforeSendResult | Promise;\n\n /**\n * Hook that get triggered after successfully sending the fetch request,\n * allowing you to inspect/modify the response object and its parsed data.\n *\n * Returns the new Promise resolved `data` that will be returned to the client.\n *\n * Example:\n * ```js\n * const pb = new PocketBase(\"https://example.com\")\n *\n * pb.afterSend = function (response, data, options) {\n * if (response.status != 200) {\n * throw new ClientResponseError({\n * url: response.url,\n * status: response.status,\n * response: { ... },\n * })\n * }\n *\n * return data;\n * }\n *\n * // use the created client as usual...\n * ```\n */\n afterSend?: ((response: Response, data: any) => any) &\n ((response: Response, data: any, options: SendOptions) => any);\n\n /**\n * Optional language code (default to `en-US`) that will be sent\n * with the requests to the server as `Accept-Language` header.\n */\n lang: string;\n\n /**\n * A replaceable instance of the local auth store service.\n */\n authStore: BaseAuthStore;\n\n /**\n * An instance of the service that handles the **Settings APIs**.\n */\n readonly settings: SettingsService;\n\n /**\n * An instance of the service that handles the **Collection APIs**.\n */\n readonly collections: CollectionService;\n\n /**\n * An instance of the service that handles the **File APIs**.\n */\n readonly files: FileService;\n\n /**\n * An instance of the service that handles the **Log APIs**.\n */\n readonly logs: LogService;\n\n /**\n * An instance of the service that handles the **Realtime APIs**.\n */\n readonly realtime: RealtimeService;\n\n /**\n * An instance of the service that handles the **Health APIs**.\n */\n readonly health: HealthService;\n\n /**\n * An instance of the service that handles the **Backup APIs**.\n */\n readonly backups: BackupService;\n\n /**\n * An instance of the service that handles the **Cron APIs**.\n */\n readonly crons: CronService;\n\n private cancelControllers: { [key: string]: AbortController } = {};\n private recordServices: { [key: string]: RecordService } = {};\n private enableAutoCancellation: boolean = true;\n\n constructor(baseURL = \"/\", authStore?: BaseAuthStore | null, lang = \"en-US\") {\n this.baseURL = baseURL;\n this.lang = lang;\n\n if (authStore) {\n this.authStore = authStore;\n } else if (typeof window != \"undefined\" && !!(window as any).Deno) {\n // note: to avoid common security issues we fallback to runtime/memory store in case the code is running in Deno env\n this.authStore = new BaseAuthStore();\n } else {\n this.authStore = new LocalAuthStore();\n }\n\n // common services\n this.collections = new CollectionService(this);\n this.files = new FileService(this);\n this.logs = new LogService(this);\n this.settings = new SettingsService(this);\n this.realtime = new RealtimeService(this);\n this.health = new HealthService(this);\n this.backups = new BackupService(this);\n this.crons = new CronService(this);\n }\n\n /**\n * @deprecated\n * With PocketBase v0.23.0 admins are converted to a regular auth\n * collection named \"_superusers\", aka. you can use directly collection(\"_superusers\").\n */\n get admins(): RecordService {\n return this.collection(\"_superusers\");\n }\n\n /**\n * Creates a new batch handler for sending multiple transactional\n * create/update/upsert/delete collection requests in one network call.\n *\n * Example:\n * ```js\n * const batch = pb.createBatch();\n *\n * batch.collection(\"example1\").create({ ... })\n * batch.collection(\"example2\").update(\"RECORD_ID\", { ... })\n * batch.collection(\"example3\").delete(\"RECORD_ID\")\n * batch.collection(\"example4\").upsert({ ... })\n *\n * await batch.send()\n * ```\n */\n createBatch(): BatchService {\n return new BatchService(this);\n }\n\n /**\n * Returns the RecordService associated to the specified collection.\n */\n collection(idOrName: string): RecordService {\n if (!this.recordServices[idOrName]) {\n this.recordServices[idOrName] = new RecordService(this, idOrName);\n }\n\n return this.recordServices[idOrName];\n }\n\n /**\n * Globally enable or disable auto cancellation for pending duplicated requests.\n */\n autoCancellation(enable: boolean): Client {\n this.enableAutoCancellation = !!enable;\n\n return this;\n }\n\n /**\n * Cancels single request by its cancellation key.\n */\n cancelRequest(requestKey: string): Client {\n if (this.cancelControllers[requestKey]) {\n this.cancelControllers[requestKey].abort();\n delete this.cancelControllers[requestKey];\n }\n\n return this;\n }\n\n /**\n * Cancels all pending requests.\n */\n cancelAllRequests(): Client {\n for (let k in this.cancelControllers) {\n this.cancelControllers[k].abort();\n }\n\n this.cancelControllers = {};\n\n return this;\n }\n\n /**\n * Constructs a filter expression with placeholders populated from a parameters object.\n *\n * Placeholder parameters are defined with the `{:paramName}` notation.\n *\n * The following parameter values are supported:\n *\n * - `string` (_single quotes are autoescaped_)\n * - `number`\n * - `boolean`\n * - `Date` object (_stringified into the PocketBase datetime format_)\n * - `null`\n * - everything else is converted to a string using `JSON.stringify()`\n *\n * Example:\n *\n * ```js\n * pb.collection(\"example\").getFirstListItem(pb.filter(\n * 'title ~ {:title} && created >= {:created}',\n * { title: \"example\", created: new Date()}\n * ))\n * ```\n */\n filter(raw: string, params?: { [key: string]: any }): string {\n if (!params) {\n return raw;\n }\n\n for (let key in params) {\n let val = params[key];\n switch (typeof val) {\n case \"boolean\":\n case \"number\":\n val = \"\" + val;\n break;\n case \"string\":\n val = \"'\" + val.replace(/'/g, \"\\\\'\") + \"'\";\n break;\n default:\n if (val === null) {\n val = \"null\";\n } else if (val instanceof Date) {\n val = \"'\" + val.toISOString().replace(\"T\", \" \") + \"'\";\n } else {\n val = \"'\" + JSON.stringify(val).replace(/'/g, \"\\\\'\") + \"'\";\n }\n }\n raw = raw.replaceAll(\"{:\" + key + \"}\", val);\n }\n\n return raw;\n }\n\n /**\n * @deprecated Please use `pb.files.getURL()`.\n */\n getFileUrl(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n console.warn(\"Please replace pb.getFileUrl() with pb.files.getURL()\");\n return this.files.getURL(record, filename, queryParams);\n }\n\n /**\n * @deprecated Please use `pb.buildURL()`.\n */\n buildUrl(path: string): string {\n console.warn(\"Please replace pb.buildUrl() with pb.buildURL()\");\n return this.buildURL(path);\n }\n\n /**\n * Builds a full client url by safely concatenating the provided path.\n */\n buildURL(path: string): string {\n let url = this.baseURL;\n\n // construct an absolute base url if in a browser environment\n if (\n typeof window !== \"undefined\" &&\n !!window.location &&\n !url.startsWith(\"https://\") &&\n !url.startsWith(\"http://\")\n ) {\n url = window.location.origin?.endsWith(\"/\")\n ? window.location.origin.substring(0, window.location.origin.length - 1)\n : window.location.origin || \"\";\n\n if (!this.baseURL.startsWith(\"/\")) {\n url += window.location.pathname || \"/\";\n url += url.endsWith(\"/\") ? \"\" : \"/\";\n }\n\n url += this.baseURL;\n }\n\n // concatenate the path\n if (path) {\n url += url.endsWith(\"/\") ? \"\" : \"/\"; // append trailing slash if missing\n url += path.startsWith(\"/\") ? path.substring(1) : path;\n }\n\n return url;\n }\n\n /**\n * Sends an api http request.\n *\n * @throws {ClientResponseError}\n */\n async send(path: string, options: SendOptions): Promise {\n options = this.initSendOptions(path, options);\n\n // build url + path\n let url = this.buildURL(path);\n\n if (this.beforeSend) {\n const result = Object.assign({}, await this.beforeSend(url, options));\n if (\n typeof result.url !== \"undefined\" ||\n typeof result.options !== \"undefined\"\n ) {\n url = result.url || url;\n options = result.options || options;\n } else if (Object.keys(result).length) {\n // legacy behavior\n options = result as SendOptions;\n console?.warn &&\n console.warn(\n \"Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`.\",\n );\n }\n }\n\n // serialize the query parameters\n if (typeof options.query !== \"undefined\") {\n const query = serializeQueryParams(options.query);\n if (query) {\n url += (url.includes(\"?\") ? \"&\" : \"?\") + query;\n }\n delete options.query;\n }\n\n // ensures that the json body is serialized\n if (\n this.getHeader(options.headers, \"Content-Type\") == \"application/json\" &&\n options.body &&\n typeof options.body !== \"string\"\n ) {\n options.body = JSON.stringify(options.body);\n }\n\n // early throw an abort error in case the request was already cancelled\n const fetchFunc = options.fetch || fetch;\n\n // send the request\n return fetchFunc(url, options)\n .then(async (response) => {\n let data: any = {};\n\n try {\n data = await response.json();\n } catch (err: any) {\n // @todo map against the response content type\n // all api responses are expected to return json\n // with exception of the realtime events and 204\n if (\n options.signal?.aborted ||\n err?.name == \"AbortError\" ||\n err?.message == \"Aborted\"\n ) {\n throw err;\n }\n }\n\n if (this.afterSend) {\n data = await this.afterSend(response, data, options);\n }\n\n if (response.status >= 400) {\n throw new ClientResponseError({\n url: response.url,\n status: response.status,\n data: data,\n });\n }\n\n return data as T;\n })\n .catch((err) => {\n // wrap to normalize all errors\n throw new ClientResponseError(err);\n });\n }\n\n /**\n * Shallow copy the provided object and takes care to initialize\n * any options required to preserve the backward compatability.\n *\n * @param {SendOptions} options\n * @return {SendOptions}\n */\n private initSendOptions(path: string, options: SendOptions): SendOptions {\n options = Object.assign({ method: \"GET\" } as SendOptions, options);\n\n // auto convert the body to FormData, if needed\n options.body = convertToFormDataIfNeeded(options.body);\n\n // move unknown send options as query parameters\n normalizeUnknownQueryParams(options);\n\n // requestKey normalizations for backward-compatibility\n // ---\n options.query = Object.assign({}, options.params, options.query);\n if (typeof options.requestKey === \"undefined\") {\n if (options.$autoCancel === false || options.query.$autoCancel === false) {\n options.requestKey = null;\n } else if (options.$cancelKey || options.query.$cancelKey) {\n options.requestKey = options.$cancelKey || options.query.$cancelKey;\n }\n }\n // remove the deprecated special cancellation params from the other query params\n delete options.$autoCancel;\n delete options.query.$autoCancel;\n delete options.$cancelKey;\n delete options.query.$cancelKey;\n // ---\n\n // add the json header, if not explicitly set\n // (for FormData body the Content-Type header should be skipped since the boundary is autogenerated)\n if (\n this.getHeader(options.headers, \"Content-Type\") === null &&\n !isFormData(options.body)\n ) {\n options.headers = Object.assign({}, options.headers, {\n \"Content-Type\": \"application/json\",\n });\n }\n\n // add Accept-Language header, if not explicitly set\n if (this.getHeader(options.headers, \"Accept-Language\") === null) {\n options.headers = Object.assign({}, options.headers, {\n \"Accept-Language\": this.lang,\n });\n }\n\n // check if Authorization header can be added\n if (\n // has valid token\n this.authStore.token &&\n // auth header is not explicitly set\n this.getHeader(options.headers, \"Authorization\") === null\n ) {\n options.headers = Object.assign({}, options.headers, {\n Authorization: this.authStore.token,\n });\n }\n\n // handle auto cancellation for duplicated pending request\n if (this.enableAutoCancellation && options.requestKey !== null) {\n const requestKey = options.requestKey || (options.method || \"GET\") + path;\n\n delete options.requestKey;\n\n // cancel previous pending requests\n this.cancelRequest(requestKey);\n\n // @todo evaluate if a cleanup after the request is necessary\n // (check also authWithOAuth2 as it currently relies on the controller)\n const controller = new AbortController();\n this.cancelControllers[requestKey] = controller;\n options.signal = controller.signal;\n }\n\n return options;\n }\n\n /**\n * Extracts the header with the provided name in case-insensitive manner.\n * Returns `null` if no header matching the name is found.\n */\n private getHeader(\n headers: { [key: string]: string } | undefined,\n name: string,\n ): string | null {\n headers = headers || {};\n name = name.toLowerCase();\n\n for (let key in headers) {\n if (key.toLowerCase() == name) {\n return headers[key];\n }\n }\n\n return null;\n }\n}\n"],"names":["ClientResponseError","Error","constructor","errData","super","this","url","status","response","isAbort","originalError","Object","setPrototypeOf","prototype","name","message","data","cause","includes","toJSON","fieldContentRegExp","cookieSerialize","val","options","opt","assign","encode","defaultEncode","test","TypeError","value","result","maxAge","isNaN","isFinite","Math","floor","domain","path","expires","isDate","toString","call","Date","valueOf","toUTCString","httpOnly","secure","priority","toLowerCase","sameSite","defaultDecode","indexOf","decodeURIComponent","encodeURIComponent","isReactNative","navigator","product","global","HermesInternal","atobPolyfill","getTokenPayload","token","encodedPayload","split","map","c","charCodeAt","slice","join","JSON","parse","e","isTokenExpired","expirationThreshold","payload","keys","length","exp","now","atob","input","str","String","replace","bs","buffer","bc","idx","output","charAt","fromCharCode","defaultCookieKey","BaseAuthStore","baseToken","baseModel","_onChangeCallbacks","record","model","isValid","isSuperuser","type","collectionName","collectionId","isAdmin","console","warn","isAuthRecord","save","triggerChange","clear","loadFromCookie","cookie","key","rawData","cookieParse","decode","index","eqIdx","endIdx","lastIndexOf","trim","undefined","_","Array","isArray","exportToCookie","defaultOptions","stringify","resultLength","Blob","size","id","email","extraProps","prop","onChange","callback","fireImmediately","push","i","splice","LocalAuthStore","storageKey","storageFallback","_bindStorageEvent","_storageGet","_storageSet","_storageRemove","window","localStorage","rawValue","getItem","normalizedVal","setItem","removeItem","addEventListener","BaseService","client","SettingsService","getAll","method","send","update","bodyParams","body","testS3","filesystem","then","testEmail","collectionIdOrName","toEmail","emailTemplate","template","collection","generateAppleClientSecret","clientId","teamId","keyId","privateKey","duration","knownSendOptionsKeys","normalizeUnknownQueryParams","query","serializeQueryParams","params","encodedKey","arrValue","v","prepareQueryParamValue","toISOString","RealtimeService","eventSource","subscriptions","lastSentSubscriptions","maxConnectTimeout","reconnectAttempts","maxReconnectAttempts","Infinity","predefinedReconnectIntervals","pendingConnects","isConnected","subscribe","topic","serialized","headers","listener","msgEvent","submitSubscriptions","connect","async","unsubscribeByTopicAndListener","unsubscribe","needToSubmit","subs","getSubscriptionsByTopic","hasSubscriptionListeners","removeEventListener","disconnect","unsubscribeByPrefix","keyPrefix","hasAtleastOneTopic","startsWith","exist","keyToCheck","addAllSubscriptionListeners","getNonEmptySubscriptionKeys","requestKey","getSubscriptionsCancelKey","catch","err","removeAllSubscriptionListeners","Promise","resolve","reject","initConnect","clearTimeout","connectTimeoutId","setTimeout","connectErrorHandler","EventSource","buildURL","onerror","lastEventId","retries","hasUnsentSubscriptions","p","reconnectTimeoutId","connectSubs","latestTopics","t","timeout","fromReconnect","onDisconnect","cancelRequest","close","CrudService","getFullList","batchOrqueryParams","_getFullList","batch","getList","page","perPage","baseCrudPath","responseData","items","item","getFirstListItem","filter","skipTotal","code","getOne","create","batchSize","request","list","concat","normalizeLegacyOptionsArgs","legacyWarn","baseOptions","bodyOrOptions","hasQuery","resetAutoRefresh","_resetAutoRefresh","RecordService","baseCollectionPath","isSuperusers","realtime","batchOrOptions","authStore","authExpand","expand","authRecord","delete","success","authResponse","listAuthMethods","fields","authWithPassword","usernameOrEmail","password","autoRefreshThreshold","identity","autoRefresh","authData","registerAutoRefresh","threshold","refreshFunc","reauthenticateFunc","oldBeforeSend","beforeSend","oldModel","unsubStoreChange","newToken","sendOptions","oldToken","authRefresh","authWithOAuth2Code","provider","codeVerifier","redirectURL","createData","authWithOAuth2","args","config","eagerDefaultPopup","urlCallback","openBrowserPopup","cleanup","requestKeyOptions","authMethods","oauth2","providers","find","cancelController","signal","onabort","activeSubscriptions","oldState","state","error","scopes","replacements","_replaceQueryParams","authURL","location","href","requestPasswordReset","confirmPasswordReset","passwordResetToken","passwordConfirm","requestVerification","confirmVerification","verificationToken","verified","requestEmailChange","newEmail","confirmEmailChange","emailChangeToken","listExternalAuths","recordId","unlinkExternalAuth","ea","requestOTP","authWithOTP","otpId","impersonate","Authorization","Client","baseURL","lang","urlPath","substring","parsedParams","rawParams","param","pair","hasOwnProperty","open","width","height","windowWidth","innerWidth","windowHeight","innerHeight","left","top","CollectionService","import","collections","deleteMissing","getScaffolds","truncate","LogService","getStats","HealthService","check","FileService","getUrl","filename","queryParams","getURL","parts","download","getToken","BackupService","basename","upload","restore","getDownloadUrl","getDownloadURL","CronService","run","jobId","isFile","File","uri","isFormData","FormData","hasFileField","values","inferNumberCharsRegex","inferFormDataValue","num","BatchService","requests","SubBatchService","formData","jsonData","req","json","files","file","append","upsert","prepareRequest","convertFormDataToObject","forEach","k","parsed","foundFiles","foundRegular","fileKey","endsWith","baseUrl","cancelControllers","recordServices","enableAutoCancellation","Deno","logs","settings","health","backups","crons","admins","createBatch","idOrName","autoCancellation","enable","abort","cancelAllRequests","raw","replaceAll","getFileUrl","buildUrl","origin","pathname","initSendOptions","getHeader","fetch","aborted","afterSend","convertToFormDataIfNeeded","form","$autoCancel","$cancelKey","controller","AbortController"],"mappings":"uCAIM,MAAOA,4BAA4BC,MAOrC,WAAAC,CAAYC,GACRC,MAAM,uBAPVC,KAAGC,IAAW,GACdD,KAAME,OAAW,EACjBF,KAAQG,SAA2B,GACnCH,KAAOI,SAAY,EACnBJ,KAAaK,cAAQ,KAOjBC,OAAOC,eAAeP,KAAML,oBAAoBa,WAEhC,OAAZV,GAAuC,iBAAZA,IAC3BE,KAAKK,cAAgBP,EAAQO,cAC7BL,KAAKC,IAA6B,iBAAhBH,EAAQG,IAAmBH,EAAQG,IAAM,GAC3DD,KAAKE,OAAmC,iBAAnBJ,EAAQI,OAAsBJ,EAAQI,OAAS,EAIpEF,KAAKI,UACCN,EAAQM,SACO,eAAjBN,EAAQW,MACY,YAApBX,EAAQY,QAEa,OAArBZ,EAAQK,UAAiD,iBAArBL,EAAQK,SAC5CH,KAAKG,SAAWL,EAAQK,SACA,OAAjBL,EAAQa,MAAyC,iBAAjBb,EAAQa,KAC/CX,KAAKG,SAAWL,EAAQa,KAExBX,KAAKG,SAAW,IAInBH,KAAKK,eAAmBP,aAAmBH,sBAC5CK,KAAKK,cAAgBP,GAGzBE,KAAKS,KAAO,uBAAyBT,KAAKE,OAC1CF,KAAKU,QAAUV,KAAKG,UAAUO,QACzBV,KAAKU,UACFV,KAAKI,QACLJ,KAAKU,QACD,yIACGV,KAAKK,eAAeO,OAAOF,SAASG,SAAS,oBACpDb,KAAKU,QACD,qJAEJV,KAAKU,QAAU,yBAMvBV,KAAKY,MAAQZ,KAAKK,aACrB,CAKD,QAAIM,GACA,OAAOX,KAAKG,QACf,CAMD,MAAAW,GACI,MAAO,IAAKd,KACf,EC7DL,MAAMe,EAAqB,iDAqFXC,gBACZP,EACAQ,EACAC,GAEA,MAAMC,EAAMb,OAAOc,OAAO,CAAA,EAAIF,GAAW,CAAA,GACnCG,EAASF,EAAIE,QAAUC,cAE7B,IAAKP,EAAmBQ,KAAKd,GACzB,MAAM,IAAIe,UAAU,4BAGxB,MAAMC,EAAQJ,EAAOJ,GAErB,GAAIQ,IAAUV,EAAmBQ,KAAKE,GAClC,MAAM,IAAID,UAAU,2BAGxB,IAAIE,EAASjB,EAAO,IAAMgB,EAE1B,GAAkB,MAAdN,EAAIQ,OAAgB,CACpB,MAAMA,EAASR,EAAIQ,OAAS,EAE5B,GAAIC,MAAMD,KAAYE,SAASF,GAC3B,MAAM,IAAIH,UAAU,4BAGxBE,GAAU,aAAeI,KAAKC,MAAMJ,EACvC,CAED,GAAIR,EAAIa,OAAQ,CACZ,IAAKjB,EAAmBQ,KAAKJ,EAAIa,QAC7B,MAAM,IAAIR,UAAU,4BAGxBE,GAAU,YAAcP,EAAIa,MAC/B,CAED,GAAIb,EAAIc,KAAM,CACV,IAAKlB,EAAmBQ,KAAKJ,EAAIc,MAC7B,MAAM,IAAIT,UAAU,0BAGxBE,GAAU,UAAYP,EAAIc,IAC7B,CAED,GAAId,EAAIe,QAAS,CACb,IA6ER,SAASC,OAAOlB,GACZ,MAA+C,kBAAxCX,OAAOE,UAAU4B,SAASC,KAAKpB,IAA4BA,aAAeqB,IACrF,CA/EaH,CAAOhB,EAAIe,UAAYN,MAAMT,EAAIe,QAAQK,WAC1C,MAAM,IAAIf,UAAU,6BAGxBE,GAAU,aAAeP,EAAIe,QAAQM,aACxC,CAUD,GARIrB,EAAIsB,WACJf,GAAU,cAGVP,EAAIuB,SACJhB,GAAU,YAGVP,EAAIwB,SAAU,CAId,OAF4B,iBAAjBxB,EAAIwB,SAAwBxB,EAAIwB,SAASC,cAAgBzB,EAAIwB,UAGpE,IAAK,MACDjB,GAAU,iBACV,MACJ,IAAK,SACDA,GAAU,oBACV,MACJ,IAAK,OACDA,GAAU,kBACV,MACJ,QACI,MAAM,IAAIF,UAAU,8BAE/B,CAED,GAAIL,EAAI0B,SAAU,CAId,OAF4B,iBAAjB1B,EAAI0B,SAAwB1B,EAAI0B,SAASD,cAAgBzB,EAAI0B,UAGpE,KAAK,EACDnB,GAAU,oBACV,MACJ,IAAK,MACDA,GAAU,iBACV,MACJ,IAAK,SACDA,GAAU,oBACV,MACJ,IAAK,OACDA,GAAU,kBACV,MACJ,QACI,MAAM,IAAIF,UAAU,8BAE/B,CAED,OAAOE,CACX,CAMA,SAASoB,cAAc7B,GACnB,OAA6B,IAAtBA,EAAI8B,QAAQ,KAAcC,mBAAmB/B,GAAOA,CAC/D,CAKA,SAASK,cAAcL,GACnB,OAAOgC,mBAAmBhC,EAC9B,CCzNA,MAAMiC,EACoB,oBAAdC,WAAmD,gBAAtBA,UAAUC,SAC5B,oBAAXC,QAA2BA,OAAeC,eAEtD,IAAIC,EA2CE,SAAUC,gBAAgBC,GAC5B,GAAIA,EACA,IACI,MAAMC,EAAiBV,mBACnBO,EAAaE,EAAME,MAAM,KAAK,IACzBA,MAAM,IACNC,KAAI,SAAUC,GACX,MAAO,KAAO,KAAOA,EAAEC,WAAW,GAAG1B,SAAS,KAAK2B,OAAO,EAC9D,IACCC,KAAK,KAGd,OAAOC,KAAKC,MAAMR,IAAmB,CAAA,CACxC,CAAC,MAAOS,GAAK,CAGlB,MAAO,EACX,UAUgBC,eAAeX,EAAeY,EAAsB,GAChE,IAAIC,EAAUd,gBAAgBC,GAE9B,QACInD,OAAOiE,KAAKD,GAASE,OAAS,KAC5BF,EAAQG,KAAOH,EAAQG,IAAMJ,EAAsB/B,KAAKoC,MAAQ,KAM1E,CAzEInB,EAPgB,mBAAToB,MAAwBzB,EAOf0B,IAGZ,IAAIC,EAAMC,OAAOF,GAAOG,QAAQ,MAAO,IACvC,GAAIF,EAAIL,OAAS,GAAK,EAClB,MAAM,IAAI5E,MACN,qEAIR,IAEI,IAAYoF,EAAIC,EAAZC,EAAK,EAAeC,EAAM,EAAGC,EAAS,GAEzCH,EAASJ,EAAIQ,OAAOF,MAEpBF,IACCD,EAAKE,EAAK,EAAkB,GAAbF,EAAkBC,EAASA,EAG5CC,IAAO,GACAE,GAAUN,OAAOQ,aAAa,IAAON,KAAS,EAAIE,EAAM,IACzD,EAGND,EAxBU,oEAwBKlC,QAAQkC,GAG3B,OAAOG,CAAM,EAlCFT,KCGnB,MAAMY,EAAmB,gBAQZC,cAAb,WAAA3F,GACcG,KAASyF,UAAW,GACpBzF,KAAS0F,UAAe,KAE1B1F,KAAkB2F,mBAA6B,EAuN1D,CAlNG,SAAIlC,GACA,OAAOzD,KAAKyF,SACf,CAKD,UAAIG,GACA,OAAO5F,KAAK0F,SACf,CAKD,SAAIG,GACA,OAAO7F,KAAK0F,SACf,CAKD,WAAII,GACA,OAAQ1B,eAAepE,KAAKyD,MAC/B,CAOD,eAAIsC,GACA,IAAIzB,EAAUd,gBAAgBxD,KAAKyD,OAEnC,MACoB,QAAhBa,EAAQ0B,OACwB,eAA/BhG,KAAK4F,QAAQK,iBAGRjG,KAAK4F,QAAQK,gBACa,kBAAxB3B,EAAQ4B,aAEvB,CAKD,WAAIC,GAIA,OAHAC,QAAQC,KACJ,sIAEGrG,KAAK+F,WACf,CAKD,gBAAIO,GAIA,OAHAF,QAAQC,KACJ,4IAEuC,QAApC7C,gBAAgBxD,KAAKyD,OAAOuC,OAAmBhG,KAAK+F,WAC9D,CAKD,IAAAQ,CAAK9C,EAAemC,GAChB5F,KAAKyF,UAAYhC,GAAS,GAC1BzD,KAAK0F,UAAYE,GAAU,KAE3B5F,KAAKwG,eACR,CAKD,KAAAC,GACIzG,KAAKyF,UAAY,GACjBzF,KAAK0F,UAAY,KACjB1F,KAAKwG,eACR,CA0BD,cAAAE,CAAeC,EAAgBC,EAAMrB,GACjC,MAAMsB,EF9GE,SAAAC,YAAYjC,EAAa3D,GACrC,MAAMQ,EAAiC,CAAA,EAEvC,GAAmB,iBAARmD,EACP,OAAOnD,EAGX,MACMqF,EADMzG,OAAOc,OAAO,CAAE,EAAa,CAAE,GACxB2F,QAAUjE,cAE7B,IAAIkE,EAAQ,EACZ,KAAOA,EAAQnC,EAAIL,QAAQ,CACvB,MAAMyC,EAAQpC,EAAI9B,QAAQ,IAAKiE,GAG/B,IAAe,IAAXC,EACA,MAGJ,IAAIC,EAASrC,EAAI9B,QAAQ,IAAKiE,GAE9B,IAAgB,IAAZE,EACAA,EAASrC,EAAIL,YACV,GAAI0C,EAASD,EAAO,CAEvBD,EAAQnC,EAAIsC,YAAY,IAAKF,EAAQ,GAAK,EAC1C,QACH,CAED,MAAML,EAAM/B,EAAId,MAAMiD,EAAOC,GAAOG,OAGpC,QAAIC,IAAc3F,EAAOkF,GAAM,CAC3B,IAAI3F,EAAM4D,EAAId,MAAMkD,EAAQ,EAAGC,GAAQE,OAGb,KAAtBnG,EAAI6C,WAAW,KACf7C,EAAMA,EAAI8C,MAAM,GAAI,IAGxB,IACIrC,EAAOkF,GAAOG,EAAO9F,EACxB,CAAC,MAAOqG,GACL5F,EAAOkF,GAAO3F,CACjB,CACJ,CAED+F,EAAQE,EAAS,CACpB,CAED,OAAOxF,CACX,CE2DwBoF,CAAYH,GAAU,IAAIC,IAAQ,GAElD,IAAIjG,EAA+B,CAAA,EACnC,IACIA,EAAOsD,KAAKC,MAAM2C,IAEE,cAATlG,GAAiC,iBAATA,GAAqB4G,MAAMC,QAAQ7G,MAClEA,EAAO,CAAA,EAEd,CAAC,MAAO2G,GAAK,CAEdtH,KAAKuG,KAAK5F,EAAK8C,OAAS,GAAI9C,EAAKiF,QAAUjF,EAAKkF,OAAS,KAC5D,CAgBD,cAAA4B,CAAevG,EAA4B0F,EAAMrB,GAC7C,MAAMmC,EAAmC,CACrChF,QAAQ,EACRG,UAAU,EACVJ,UAAU,EACVR,KAAM,KAIJqC,EAAUd,gBAAgBxD,KAAKyD,OAEjCiE,EAAexF,QADfoC,GAASG,IACgB,IAAInC,KAAmB,IAAdgC,EAAQG,KAEjB,IAAInC,KAAK,cAItCpB,EAAUZ,OAAOc,OAAO,CAAE,EAAEsG,EAAgBxG,GAE5C,MAAM2F,EAAU,CACZpD,MAAOzD,KAAKyD,MACZmC,OAAQ5F,KAAK4F,OAAS3B,KAAKC,MAAMD,KAAK0D,UAAU3H,KAAK4F,SAAW,MAGpE,IAAIlE,EAASV,gBAAgB4F,EAAK3C,KAAK0D,UAAUd,GAAU3F,GAE3D,MAAM0G,EACc,oBAATC,KAAuB,IAAIA,KAAK,CAACnG,IAASoG,KAAOpG,EAAO8C,OAGnE,GAAIqC,EAAQjB,QAAUgC,EAAe,KAAM,CACvCf,EAAQjB,OAAS,CAAEmC,GAAIlB,EAAQjB,QAAQmC,GAAIC,MAAOnB,EAAQjB,QAAQoC,OAClE,MAAMC,EAAa,CAAC,eAAgB,iBAAkB,YACtD,IAAK,MAAMC,KAAQlI,KAAK4F,OAChBqC,EAAWpH,SAASqH,KACpBrB,EAAQjB,OAAOsC,GAAQlI,KAAK4F,OAAOsC,IAG3CxG,EAASV,gBAAgB4F,EAAK3C,KAAK0D,UAAUd,GAAU3F,EAC1D,CAED,OAAOQ,CACV,CAUD,QAAAyG,CAASC,EAA6BC,GAAkB,GAOpD,OANArI,KAAK2F,mBAAmB2C,KAAKF,GAEzBC,GACAD,EAASpI,KAAKyD,MAAOzD,KAAK4F,QAGvB,KACH,IAAK,IAAI2C,EAAIvI,KAAK2F,mBAAmBnB,OAAS,EAAG+D,GAAK,EAAGA,IACrD,GAAIvI,KAAK2F,mBAAmB4C,IAAMH,EAG9B,cAFOpI,KAAK2F,mBAAmB4C,QAC/BvI,KAAK2F,mBAAmB6C,OAAOD,EAAG,EAGzC,CAER,CAES,aAAA/B,GACN,IAAK,MAAM4B,KAAYpI,KAAK2F,mBACxByC,GAAYA,EAASpI,KAAKyD,MAAOzD,KAAK4F,OAE7C,ECtOC,MAAO6C,uBAAuBjD,cAIhC,WAAA3F,CAAY6I,EAAa,mBACrB3I,QAJIC,KAAe2I,gBAA2B,GAM9C3I,KAAK0I,WAAaA,EAElB1I,KAAK4I,mBACR,CAKD,SAAInF,GAGA,OAFazD,KAAK6I,YAAY7I,KAAK0I,aAAe,IAEtCjF,OAAS,EACxB,CAKD,UAAImC,GACA,MAAMjF,EAAOX,KAAK6I,YAAY7I,KAAK0I,aAAe,GAElD,OAAO/H,EAAKiF,QAAUjF,EAAKkF,OAAS,IACvC,CAKD,SAAIA,GACA,OAAO7F,KAAK4F,MACf,CAKD,IAAAW,CAAK9C,EAAemC,GAChB5F,KAAK8I,YAAY9I,KAAK0I,WAAY,CAC9BjF,MAAOA,EACPmC,OAAQA,IAGZ7F,MAAMwG,KAAK9C,EAAOmC,EACrB,CAKD,KAAAa,GACIzG,KAAK+I,eAAe/I,KAAK0I,YAEzB3I,MAAM0G,OACT,CAUO,WAAAoC,CAAYjC,GAChB,GAAsB,oBAAXoC,QAA0BA,QAAQC,aAAc,CACvD,MAAMC,EAAWF,OAAOC,aAAaE,QAAQvC,IAAQ,GACrD,IACI,OAAO3C,KAAKC,MAAMgF,EACrB,CAAC,MAAO/E,GAEL,OAAO+E,CACV,CACJ,CAGD,OAAOlJ,KAAK2I,gBAAgB/B,EAC/B,CAMO,WAAAkC,CAAYlC,EAAanF,GAC7B,GAAsB,oBAAXuH,QAA0BA,QAAQC,aAAc,CAEvD,IAAIG,EAAgB3H,EACC,iBAAVA,IACP2H,EAAgBnF,KAAK0D,UAAUlG,IAEnCuH,OAAOC,aAAaI,QAAQzC,EAAKwC,EACpC,MAEGpJ,KAAK2I,gBAAgB/B,GAAOnF,CAEnC,CAKO,cAAAsH,CAAenC,GAEG,oBAAXoC,QAA0BA,QAAQC,cACzCD,OAAOC,cAAcK,WAAW1C,UAI7B5G,KAAK2I,gBAAgB/B,EAC/B,CAKO,iBAAAgC,GAEkB,oBAAXI,QACNA,QAAQC,cACRD,OAAOO,kBAKZP,OAAOO,iBAAiB,WAAYpF,IAChC,GAAIA,EAAEyC,KAAO5G,KAAK0I,WACd,OAGJ,MAAM/H,EAAOX,KAAK6I,YAAY7I,KAAK0I,aAAe,GAElD3I,MAAMwG,KAAK5F,EAAK8C,OAAS,GAAI9C,EAAKiF,QAAUjF,EAAKkF,OAAS,KAAK,GAEtE,QCtIiB2D,YAGlB,WAAA3J,CAAY4J,GACRzJ,KAAKyJ,OAASA,CACjB,ECHC,MAAOC,wBAAwBF,YAMjC,YAAMG,CAAOzI,GAQT,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,gBAAiB3I,EAC5C,CAOD,YAAM4I,CACFC,EACA7I,GAUA,OARAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,QACRI,KAAMD,GAEV7I,GAGGlB,KAAKyJ,OAAOI,KAAK,gBAAiB3I,EAC5C,CASD,YAAM+I,CACFC,EAAqB,UACrBhJ,GAYA,OAVAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OACRI,KAAM,CACFE,WAAYA,IAGpBhJ,GAGGlB,KAAKyJ,OAAOI,KAAK,wBAAyB3I,GAASiJ,MAAK,KAAM,GACxE,CAYD,eAAMC,CACFC,EACAC,EACAC,EACArJ,GAcA,OAZAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OACRI,KAAM,CACFhC,MAAOsC,EACPE,SAAUD,EACVE,WAAYJ,IAGpBnJ,GAGGlB,KAAKyJ,OAAOI,KAAK,2BAA4B3I,GAASiJ,MAAK,KAAM,GAC3E,CAOD,+BAAMO,CACFC,EACAC,EACAC,EACAC,EACAC,EACA7J,GAgBA,OAdAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OACRI,KAAM,CACFW,WACAC,SACAC,QACAC,aACAC,aAGR7J,GAGGlB,KAAKyJ,OAAOI,KAAK,6CAA8C3I,EACzE,EClBL,MAAM8J,EAAuB,CACzB,aACA,aACA,cACA,QACA,UACA,OACA,QACA,SAEA,QACA,cACA,UACA,YACA,YACA,SACA,OACA,WACA,WACA,iBACA,SACA,UAIE,SAAUC,4BAA4B/J,GACxC,GAAKA,EAAL,CAIAA,EAAQgK,MAAQhK,EAAQgK,OAAS,CAAA,EACjC,IAAK,IAAItE,KAAO1F,EACR8J,EAAqBnK,SAAS+F,KAIlC1F,EAAQgK,MAAMtE,GAAO1F,EAAQ0F,UACtB1F,EAAQ0F,GATlB,CAWL,CAEM,SAAUuE,qBAAqBC,GACjC,MAAM1J,EAAwB,GAE9B,IAAK,MAAMkF,KAAOwE,EAAQ,CACtB,MAAMC,EAAapI,mBAAmB2D,GAChC0E,EAAW/D,MAAMC,QAAQ4D,EAAOxE,IAAQwE,EAAOxE,GAAO,CAACwE,EAAOxE,IAEpE,IAAK,IAAI2E,KAAKD,EACVC,EAAIC,uBAAuBD,GACjB,OAANA,GAGJ7J,EAAO4G,KAAK+C,EAAa,IAAME,EAEtC,CAED,OAAO7J,EAAOsC,KAAK,IACvB,CAGA,SAASwH,uBAAuB/J,GAC5B,OAAIA,QACO,KAGPA,aAAiBa,KACVW,mBAAmBxB,EAAMgK,cAAc1G,QAAQ,IAAK,MAG1C,iBAAVtD,EACAwB,mBAAmBgB,KAAK0D,UAAUlG,IAGtCwB,mBAAmBxB,EAC9B,CC3KM,MAAOiK,wBAAwBlC,YAArC,WAAA3J,uBACIG,KAAQ2K,SAAW,GAEX3K,KAAW2L,YAAuB,KAClC3L,KAAa4L,cAAkB,GAC/B5L,KAAqB6L,sBAAkB,GAEvC7L,KAAiB8L,kBAAW,KAE5B9L,KAAiB+L,kBAAW,EAC5B/L,KAAoBgM,qBAAWC,IAC/BjM,KAAAkM,6BAA8C,CAClD,IAAK,IAAK,IAAK,IAAM,KAAM,KAAM,KAE7BlM,KAAemM,gBAA4B,EAgetD,CA3dG,eAAIC,GACA,QAASpM,KAAK2L,eAAiB3L,KAAK2K,WAAa3K,KAAKmM,gBAAgB3H,MACzE,CAwBD,eAAM6H,CACFC,EACAlE,EACAlH,GAEA,IAAKoL,EACD,MAAM,IAAI1M,MAAM,sBAGpB,IAAIgH,EAAM0F,EAGV,GAAIpL,EAAS,CAET+J,4BADA/J,EAAUZ,OAAOc,OAAO,CAAE,EAAEF,IAE5B,MAAMqL,EACF,WACAtJ,mBACIgB,KAAK0D,UAAU,CAAEuD,MAAOhK,EAAQgK,MAAOsB,QAAStL,EAAQsL,WAEhE5F,IAAQA,EAAI/F,SAAS,KAAO,IAAM,KAAO0L,CAC5C,CAED,MAAME,SAAW,SAAUtI,GACvB,MAAMuI,EAAWvI,EAEjB,IAAIxD,EACJ,IACIA,EAAOsD,KAAKC,MAAMwI,GAAU/L,KAC/B,CAAC,MAAQ,CAEVyH,EAASzH,GAAQ,CAAA,EACrB,EAmBA,OAhBKX,KAAK4L,cAAchF,KACpB5G,KAAK4L,cAAchF,GAAO,IAE9B5G,KAAK4L,cAAchF,GAAK0B,KAAKmE,UAExBzM,KAAKoM,YAGoC,IAAnCpM,KAAK4L,cAAchF,GAAKpC,aAEzBxE,KAAK2M,sBAGX3M,KAAK2L,aAAapC,iBAAiB3C,EAAK6F,gBANlCzM,KAAK4M,UASRC,SACI7M,KAAK8M,8BAA8BR,EAAOG,SAExD,CAaD,iBAAMM,CAAYT,GACd,IAAIU,GAAe,EAEnB,GAAKV,EAGE,CAEH,MAAMW,EAAOjN,KAAKkN,wBAAwBZ,GAC1C,IAAK,IAAI1F,KAAOqG,EACZ,GAAKjN,KAAKmN,yBAAyBvG,GAAnC,CAIA,IAAK,IAAI6F,KAAYzM,KAAK4L,cAAchF,GACpC5G,KAAK2L,aAAayB,oBAAoBxG,EAAK6F,UAExCzM,KAAK4L,cAAchF,GAGrBoG,IACDA,GAAe,EATlB,CAYR,MAnBGhN,KAAK4L,cAAgB,GAqBpB5L,KAAKmN,2BAGCH,SACDhN,KAAK2M,sBAFX3M,KAAKqN,YAIZ,CAUD,yBAAMC,CAAoBC,GACtB,IAAIC,GAAqB,EACzB,IAAK,IAAI5G,KAAO5G,KAAK4L,cAEjB,IAAMhF,EAAM,KAAK6G,WAAWF,GAA5B,CAIAC,GAAqB,EACrB,IAAK,IAAIf,KAAYzM,KAAK4L,cAAchF,GACpC5G,KAAK2L,aAAayB,oBAAoBxG,EAAK6F,UAExCzM,KAAK4L,cAAchF,EANzB,CASA4G,IAIDxN,KAAKmN,iCAECnN,KAAK2M,sBAGX3M,KAAKqN,aAEZ,CAWD,mCAAMP,CACFR,EACAG,GAEA,IAAIO,GAAe,EAEnB,MAAMC,EAAOjN,KAAKkN,wBAAwBZ,GAC1C,IAAK,IAAI1F,KAAOqG,EAAM,CAClB,IACK1F,MAAMC,QAAQxH,KAAK4L,cAAchF,MACjC5G,KAAK4L,cAAchF,GAAKpC,OAEzB,SAGJ,IAAIkJ,GAAQ,EACZ,IAAK,IAAInF,EAAIvI,KAAK4L,cAAchF,GAAKpC,OAAS,EAAG+D,GAAK,EAAGA,IACjDvI,KAAK4L,cAAchF,GAAK2B,KAAOkE,IAInCiB,GAAQ,SACD1N,KAAK4L,cAAchF,GAAK2B,GAC/BvI,KAAK4L,cAAchF,GAAK4B,OAAOD,EAAG,GAClCvI,KAAK2L,aAAayB,oBAAoBxG,EAAK6F,IAE1CiB,IAKA1N,KAAK4L,cAAchF,GAAKpC,eAClBxE,KAAK4L,cAAchF,GAIzBoG,GAAiBhN,KAAKmN,yBAAyBvG,KAChDoG,GAAe,GAEtB,CAEIhN,KAAKmN,2BAGCH,SACDhN,KAAK2M,sBAFX3M,KAAKqN,YAIZ,CAEO,wBAAAF,CAAyBQ,GAI7B,GAHA3N,KAAK4L,cAAgB5L,KAAK4L,eAAiB,CAAA,EAGvC+B,EACA,QAAS3N,KAAK4L,cAAc+B,IAAanJ,OAI7C,IAAK,IAAIoC,KAAO5G,KAAK4L,cACjB,GAAM5L,KAAK4L,cAAchF,IAAMpC,OAC3B,OAAO,EAIf,OAAO,CACV,CAEO,yBAAMmI,GACV,GAAK3M,KAAK2K,SASV,OAJA3K,KAAK4N,8BAEL5N,KAAK6L,sBAAwB7L,KAAK6N,8BAE3B7N,KAAKyJ,OACPI,KAAK,gBAAiB,CACnBD,OAAQ,OACRI,KAAM,CACFW,SAAU3K,KAAK2K,SACfiB,cAAe5L,KAAK6L,uBAExBiC,WAAY9N,KAAK+N,8BAEpBC,OAAOC,IACJ,IAAIA,GAAK7N,QAGT,MAAM6N,CAAG,GAEpB,CAEO,yBAAAF,GACJ,MAAO,YAAc/N,KAAK2K,QAC7B,CAEO,uBAAAuC,CAAwBZ,GAC5B,MAAM5K,EAAwB,CAAA,EAG9B4K,EAAQA,EAAMzL,SAAS,KAAOyL,EAAQA,EAAQ,IAE9C,IAAK,IAAI1F,KAAO5G,KAAK4L,eACZhF,EAAM,KAAK6G,WAAWnB,KACvB5K,EAAOkF,GAAO5G,KAAK4L,cAAchF,IAIzC,OAAOlF,CACV,CAEO,2BAAAmM,GACJ,MAAMnM,EAAwB,GAE9B,IAAK,IAAIkF,KAAO5G,KAAK4L,cACb5L,KAAK4L,cAAchF,GAAKpC,QACxB9C,EAAO4G,KAAK1B,GAIpB,OAAOlF,CACV,CAEO,2BAAAkM,GACJ,GAAK5N,KAAK2L,YAAV,CAIA3L,KAAKkO,iCAEL,IAAK,IAAItH,KAAO5G,KAAK4L,cACjB,IAAK,IAAIa,KAAYzM,KAAK4L,cAAchF,GACpC5G,KAAK2L,YAAYpC,iBAAiB3C,EAAK6F,EAN9C,CASJ,CAEO,8BAAAyB,GACJ,GAAKlO,KAAK2L,YAIV,IAAK,IAAI/E,KAAO5G,KAAK4L,cACjB,IAAK,IAAIa,KAAYzM,KAAK4L,cAAchF,GACpC5G,KAAK2L,YAAYyB,oBAAoBxG,EAAK6F,EAGrD,CAEO,aAAMG,GACV,KAAI5M,KAAK+L,kBAAoB,GAM7B,OAAO,IAAIoC,SAAQ,CAACC,EAASC,KACzBrO,KAAKmM,gBAAgB7D,KAAK,CAAE8F,UAASC,WAEjCrO,KAAKmM,gBAAgB3H,OAAS,GAKlCxE,KAAKsO,aAAa,GAEzB,CAEO,WAAAA,GACJtO,KAAKqN,YAAW,GAGhBkB,aAAavO,KAAKwO,kBAClBxO,KAAKwO,iBAAmBC,YAAW,KAC/BzO,KAAK0O,oBAAoB,IAAI9O,MAAM,sCAAsC,GAC1EI,KAAK8L,mBAER9L,KAAK2L,YAAc,IAAIgD,YAAY3O,KAAKyJ,OAAOmF,SAAS,kBAExD5O,KAAK2L,YAAYkD,QAAWvH,IACxBtH,KAAK0O,oBACD,IAAI9O,MAAM,4CACb,EAGLI,KAAK2L,YAAYpC,iBAAiB,cAAepF,IAC7C,MAAMuI,EAAWvI,EACjBnE,KAAK2K,SAAW+B,GAAUoC,YAE1B9O,KAAK2M,sBACAxC,MAAK0C,UACF,IAAIkC,EAAU,EACd,KAAO/O,KAAKgP,0BAA4BD,EAAU,GAC9CA,UAMM/O,KAAK2M,qBACd,IAEJxC,MAAK,KACF,IAAK,IAAI8E,KAAKjP,KAAKmM,gBACf8C,EAAEb,UAINpO,KAAKmM,gBAAkB,GACvBnM,KAAK+L,kBAAoB,EACzBwC,aAAavO,KAAKkP,oBAClBX,aAAavO,KAAKwO,kBAGlB,MAAMW,EAAcnP,KAAKkN,wBAAwB,cACjD,IAAK,IAAItG,KAAOuI,EACZ,IAAK,IAAI1C,KAAY0C,EAAYvI,GAC7B6F,EAAStI,EAEhB,IAEJ6J,OAAOC,IACJjO,KAAK2K,SAAW,GAChB3K,KAAK0O,oBAAoBT,EAAI,GAC/B,GAEb,CAEO,sBAAAe,GACJ,MAAMI,EAAepP,KAAK6N,8BAC1B,GAAIuB,EAAa5K,QAAUxE,KAAK6L,sBAAsBrH,OAClD,OAAO,EAGX,IAAK,MAAM6K,KAAKD,EACZ,IAAKpP,KAAK6L,sBAAsBhL,SAASwO,GACrC,OAAO,EAIf,OAAO,CACV,CAEO,mBAAAX,CAAoBT,GAIxB,GAHAM,aAAavO,KAAKwO,kBAClBD,aAAavO,KAAKkP,qBAIZlP,KAAK2K,WAAa3K,KAAK+L,mBAEzB/L,KAAK+L,kBAAoB/L,KAAKgM,qBAChC,CACE,IAAK,IAAIiD,KAAKjP,KAAKmM,gBACf8C,EAAEZ,OAAO,IAAI1O,oBAAoBsO,IAIrC,OAFAjO,KAAKmM,gBAAkB,QACvBnM,KAAKqN,YAER,CAGDrN,KAAKqN,YAAW,GAChB,MAAMiC,EACFtP,KAAKkM,6BAA6BlM,KAAK+L,oBACvC/L,KAAKkM,6BACDlM,KAAKkM,6BAA6B1H,OAAS,GAEnDxE,KAAK+L,oBACL/L,KAAKkP,mBAAqBT,YAAW,KACjCzO,KAAKsO,aAAa,GACnBgB,EACN,CAEO,UAAAjC,CAAWkC,GAAgB,GAa/B,GAZIvP,KAAK2K,UAAY3K,KAAKwP,cACtBxP,KAAKwP,aAAalP,OAAOiE,KAAKvE,KAAK4L,gBAGvC2C,aAAavO,KAAKwO,kBAClBD,aAAavO,KAAKkP,oBAClBlP,KAAKkO,iCACLlO,KAAKyJ,OAAOgG,cAAczP,KAAK+N,6BAC/B/N,KAAK2L,aAAa+D,QAClB1P,KAAK2L,YAAc,KACnB3L,KAAK2K,SAAW,IAEX4E,EAAe,CAChBvP,KAAK+L,kBAAoB,EAOzB,IAAK,IAAIkD,KAAKjP,KAAKmM,gBACf8C,EAAEb,UAENpO,KAAKmM,gBAAkB,EAC1B,CACJ,ECrfC,MAAgBwD,oBAAuBnG,YASzC,MAAAzC,CAAcpG,GACV,OAAOA,CACV,CAiBD,iBAAMiP,CACFC,EACA3O,GAEA,GAAiC,iBAAtB2O,EACP,OAAO7P,KAAK8P,aAAgBD,EAAoB3O,GAKpD,IAAI6O,EAAQ,IAMZ,OARA7O,EAAUZ,OAAOc,OAAO,CAAE,EAAEyO,EAAoB3O,IAGpC6O,QACRA,EAAQ7O,EAAQ6O,aACT7O,EAAQ6O,OAGZ/P,KAAK8P,aAAgBC,EAAO7O,EACtC,CASD,aAAM8O,CACFC,EAAO,EACPC,EAAU,GACVhP,GAiBA,OAfAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OAEZ1I,IAGIgK,MAAQ5K,OAAOc,OACnB,CACI6O,KAAMA,EACNC,QAASA,GAEbhP,EAAQgK,OAGLlL,KAAKyJ,OAAOI,KAAK7J,KAAKmQ,aAAcjP,GAASiJ,MAAMiG,IACtDA,EAAaC,MACTD,EAAaC,OAAOzM,KAAK0M,GACdtQ,KAAK+G,OAAUuJ,MACpB,GAEHF,IAEd,CAeD,sBAAMG,CAAwBC,EAAgBtP,GAgB1C,OAfAA,EAAUZ,OAAOc,OACb,CACI0M,WAAY,iBAAmB9N,KAAKmQ,aAAe,IAAMK,GAE7DtP,IAGIgK,MAAQ5K,OAAOc,OACnB,CACIoP,OAAQA,EACRC,UAAW,GAEfvP,EAAQgK,OAGLlL,KAAKgQ,QAAW,EAAG,EAAG9O,GAASiJ,MAAMzI,IACxC,IAAKA,GAAQ2O,OAAO7L,OAChB,MAAM,IAAI7E,oBAAoB,CAC1BO,OAAQ,IACRC,SAAU,CACNuQ,KAAM,IACNhQ,QAAS,uCACTC,KAAM,CAAE,KAKpB,OAAOe,EAAO2O,MAAM,EAAE,GAE7B,CAWD,YAAMM,CAAc5I,EAAY7G,GAC5B,IAAK6G,EACD,MAAM,IAAIpI,oBAAoB,CAC1BM,IAAKD,KAAKyJ,OAAOmF,SAAS5O,KAAKmQ,aAAe,KAC9CjQ,OAAQ,IACRC,SAAU,CACNuQ,KAAM,IACNhQ,QAAS,8BACTC,KAAM,CAAE,KAYpB,OAPAO,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK7J,KAAKmQ,aAAe,IAAMlN,mBAAmB8E,GAAK7G,GACvDiJ,MAAMiG,GAAsBpQ,KAAK+G,OAAUqJ,IACnD,CASD,YAAMQ,CACF7G,EACA7I,GAUA,OARAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OACRI,KAAMD,GAEV7I,GAGGlB,KAAKyJ,OACPI,KAAK7J,KAAKmQ,aAAcjP,GACxBiJ,MAAMiG,GAAsBpQ,KAAK+G,OAAUqJ,IACnD,CASD,YAAMtG,CACF/B,EACAgC,EACA7I,GAUA,OARAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,QACRI,KAAMD,GAEV7I,GAGGlB,KAAKyJ,OACPI,KAAK7J,KAAKmQ,aAAe,IAAMlN,mBAAmB8E,GAAK7G,GACvDiJ,MAAMiG,GAAsBpQ,KAAK+G,OAAUqJ,IACnD,CAOD,YAAM,CAAOrI,EAAY7G,GAQrB,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,UAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK7J,KAAKmQ,aAAe,IAAMlN,mBAAmB8E,GAAK7G,GACvDiJ,MAAK,KAAM,GACnB,CAKS,YAAA2F,CACNe,EAAY,IACZ3P,IAEAA,EAAUA,GAAW,IACbgK,MAAQ5K,OAAOc,OACnB,CACIqP,UAAW,GAEfvP,EAAQgK,OAGZ,IAAIxJ,EAAmB,GAEnBoP,QAAUjE,MAAOoD,GACVjQ,KAAKgQ,QAAQC,EAAMY,GAAa,IAAM3P,GAASiJ,MAAM4G,IACxD,MACMV,EADaU,EACMV,MAIzB,OAFA3O,EAASA,EAAOsP,OAAOX,GAEnBA,EAAM7L,QAAUuM,EAAKb,QACdY,QAAQb,EAAO,GAGnBvO,CAAM,IAIrB,OAAOoP,QAAQ,EAClB,EC1QC,SAAUG,2BACZC,EACAC,EACAC,EACAlG,GAEA,MACMmG,OAA4B,IAAVnG,EAExB,OAAKmG,QAH6C,IAAlBD,EAO5BC,GACAjL,QAAQC,KAAK6K,GACbC,EAAYnH,KAAO1J,OAAOc,OAAO,CAAE,EAAE+P,EAAYnH,KAAMoH,GACvDD,EAAYjG,MAAQ5K,OAAOc,OAAO,CAAE,EAAE+P,EAAYjG,MAAOA,GAElDiG,GAGJ7Q,OAAOc,OAAO+P,EAAaC,GAXvBD,CAYf,CCpBM,SAAUG,iBAAiB7H,GAC5BA,EAAe8H,qBACpB,CCyFM,MAAOC,sBAAuC7B,YAGhD,WAAA9P,CAAY4J,EAAgBY,GACxBtK,MAAM0J,GAENzJ,KAAKqK,mBAAqBA,CAC7B,CAKD,gBAAI8F,GACA,OAAOnQ,KAAKyR,mBAAqB,UACpC,CAKD,sBAAIA,GACA,MAAO,oBAAsBxO,mBAAmBjD,KAAKqK,mBACxD,CAKD,gBAAIqH,GACA,MAC+B,eAA3B1R,KAAKqK,oBACsB,mBAA3BrK,KAAKqK,kBAEZ,CAmBD,eAAMgC,CACFC,EACAlE,EACAlH,GAEA,IAAKoL,EACD,MAAM,IAAI1M,MAAM,kBAGpB,IAAKwI,EACD,MAAM,IAAIxI,MAAM,kCAGpB,OAAOI,KAAKyJ,OAAOkI,SAAStF,UACxBrM,KAAKqK,mBAAqB,IAAMiC,EAChClE,EACAlH,EAEP,CASD,iBAAM6L,CAAYT,GAEd,OAAIA,EACOtM,KAAKyJ,OAAOkI,SAAS5E,YACxB/M,KAAKqK,mBAAqB,IAAMiC,GAKjCtM,KAAKyJ,OAAOkI,SAASrE,oBAAoBtN,KAAKqK,mBACxD,CAqBD,iBAAMuF,CACFgC,EACA1Q,GAEA,GAA6B,iBAAlB0Q,EACP,OAAO7R,MAAM6P,YAAegC,EAAgB1Q,GAGhD,MAAMkK,EAAS9K,OAAOc,OAAO,CAAA,EAAIwQ,EAAgB1Q,GAEjD,OAAOnB,MAAM6P,YAAexE,EAC/B,CAKD,aAAM4E,CACFC,EAAO,EACPC,EAAU,GACVhP,GAEA,OAAOnB,MAAMiQ,QAAWC,EAAMC,EAAShP,EAC1C,CAKD,sBAAMqP,CACFC,EACAtP,GAEA,OAAOnB,MAAMwQ,iBAAoBC,EAAQtP,EAC5C,CAKD,YAAMyP,CAAc5I,EAAY7G,GAC5B,OAAOnB,MAAM4Q,OAAU5I,EAAI7G,EAC9B,CAKD,YAAM0P,CACF7G,EACA7I,GAEA,OAAOnB,MAAM6Q,OAAU7G,EAAY7I,EACtC,CAQD,YAAM4I,CACF/B,EACAgC,EACA7I,GAEA,OAAOnB,MAAM+J,OAAoB/B,EAAIgC,EAAY7I,GAASiJ,MAAMmG,IAC5D,GAEItQ,KAAKyJ,OAAOoI,UAAUjM,QAAQmC,KAAOuI,GAAMvI,KAC1C/H,KAAKyJ,OAAOoI,UAAUjM,QAAQM,eAAiBlG,KAAKqK,oBACjDrK,KAAKyJ,OAAOoI,UAAUjM,QAAQK,iBAC1BjG,KAAKqK,oBACf,CACE,IAAIyH,EAAaxR,OAAOc,OAAO,CAAE,EAAEpB,KAAKyJ,OAAOoI,UAAUjM,OAAOmM,QAC5DC,EAAa1R,OAAOc,OAAO,CAAE,EAAEpB,KAAKyJ,OAAOoI,UAAUjM,OAAQ0K,GAC7DwB,IAEAE,EAAWD,OAASzR,OAAOc,OAAO0Q,EAAYxB,EAAKyB,SAGvD/R,KAAKyJ,OAAOoI,UAAUtL,KAAKvG,KAAKyJ,OAAOoI,UAAUpO,MAAOuO,EAC3D,CAED,OAAO1B,CAAgB,GAE9B,CAQD,YAAM,CAAOvI,EAAY7G,GACrB,OAAOnB,MAAMkS,OAAOlK,EAAI7G,GAASiJ,MAAM+H,KAE/BA,GAEAlS,KAAKyJ,OAAOoI,UAAUjM,QAAQmC,KAAOA,GACpC/H,KAAKyJ,OAAOoI,UAAUjM,QAAQM,eAAiBlG,KAAKqK,oBACjDrK,KAAKyJ,OAAOoI,UAAUjM,QAAQK,iBAC1BjG,KAAKqK,oBAEbrK,KAAKyJ,OAAOoI,UAAUpL,QAGnByL,IAEd,CASS,YAAAC,CAAoB/B,GAC1B,MAAMxK,EAAS5F,KAAK+G,OAAOqJ,GAAcxK,QAAU,CAAA,GAInD,OAFA5F,KAAKyJ,OAAOoI,UAAUtL,KAAK6J,GAAc3M,MAAOmC,GAEzCtF,OAAOc,OAAO,CAAE,EAAEgP,EAAc,CAEnC3M,MAAO2M,GAAc3M,OAAS,GAC9BmC,OAAQA,GAEf,CAOD,qBAAMwM,CAAgBlR,GAUlB,OATAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,MAERyI,OAAQ,2BAEZnR,GAGGlB,KAAKyJ,OAAOI,KAAK7J,KAAKyR,mBAAqB,gBAAiBvQ,EACtE,CAYD,sBAAMoR,CACFC,EACAC,EACAtR,GAcA,IAAIuR,EAZJvR,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OACRI,KAAM,CACF0I,SAAUH,EACVC,SAAUA,IAGlBtR,GAKAlB,KAAK0R,eACLe,EAAuBvR,EAAQuR,4BACxBvR,EAAQuR,qBACVvR,EAAQyR,aACTrB,iBAAiBtR,KAAKyJ,SAI9B,IAAImJ,QAAiB5S,KAAKyJ,OAAOI,KAC7B7J,KAAKyR,mBAAqB,sBAC1BvQ,GAmBJ,OAhBA0R,EAAW5S,KAAKmS,aAAgBS,GAE5BH,GAAwBzS,KAAK0R,cD9XnC,SAAUmB,oBACZpJ,EACAqJ,EACAC,EACAC,GAEA1B,iBAAiB7H,GAEjB,MAAMwJ,EAAgBxJ,EAAOyJ,WACvBC,EAAW1J,EAAOoI,UAAUjM,OAI5BwN,EAAmB3J,EAAOoI,UAAU1J,UAAS,CAACkL,EAAUxN,OAErDwN,GACDxN,GAAOkC,IAAMoL,GAAUpL,KACrBlC,GAAOK,cAAgBiN,GAAUjN,eAC/BL,GAAOK,cAAgBiN,GAAUjN,eAErCoL,iBAAiB7H,EACpB,IAIJA,EAAe8H,kBAAoB,WAChC6B,IACA3J,EAAOyJ,WAAaD,SACZxJ,EAAe8H,iBAC3B,EAEA9H,EAAOyJ,WAAarG,MAAO5M,EAAKqT,KAC5B,MAAMC,EAAW9J,EAAOoI,UAAUpO,MAElC,GAAI6P,EAAYpI,OAAOyH,YACnB,OAAOM,EAAgBA,EAAchT,EAAKqT,GAAe,CAAErT,MAAKqT,eAGpE,IAAIxN,EAAU2D,EAAOoI,UAAU/L,QAC/B,GAEIA,GAEA1B,eAAeqF,EAAOoI,UAAUpO,MAAOqP,GAEvC,UACUC,GACT,CAAC,MAAOzL,GACLxB,GAAU,CACb,CAIAA,SACKkN,IAIV,MAAMxG,EAAU8G,EAAY9G,SAAW,GACvC,IAAK,IAAI5F,KAAO4F,EACZ,GACyB,iBAArB5F,EAAIhE,eAEJ2Q,GAAY/G,EAAQ5F,IACpB6C,EAAOoI,UAAUpO,MACnB,CAEE+I,EAAQ5F,GAAO6C,EAAOoI,UAAUpO,MAChC,KACH,CAIL,OAFA6P,EAAY9G,QAAUA,EAEfyG,EAAgBA,EAAchT,EAAKqT,GAAe,CAAErT,MAAKqT,cAAa,CAErF,CCoTYT,CACI7S,KAAKyJ,OACLgJ,GACA,IAAMzS,KAAKwT,YAAY,CAAEb,aAAa,MACtC,IACI3S,KAAKsS,iBACDC,EACAC,EACAlS,OAAOc,OAAO,CAAEuR,aAAa,GAAQzR,MAK9C0R,CACV,CAsCD,wBAAMa,CACFC,EACAhD,EACAiD,EACAC,EACAC,EACAzC,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACF0J,SAAUA,EACVhD,KAAMA,EACNiD,aAAcA,EACdC,YAAaA,EACbC,WAAYA,IAWpB,OAPA3S,EAAU+P,2BACN,yOACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,oBAAqBvQ,GACpDiJ,MAAMxJ,GAASX,KAAKmS,aAAgBxR,IAC5C,CA2ED,cAAAmT,IAAyBC,GAErB,GAAIA,EAAKvP,OAAS,GAA0B,iBAAduP,IAAO,GAIjC,OAHA3N,QAAQC,KACJ,4PAEGrG,KAAKyT,mBACRM,IAAO,IAAM,GACbA,IAAO,IAAM,GACbA,IAAO,IAAM,GACbA,IAAO,IAAM,GACbA,IAAO,IAAM,CAAA,EACbA,IAAO,IAAM,CAAA,EACbA,IAAO,IAAM,CAAE,GAIvB,MAAMC,EAASD,IAAO,IAAM,CAAA,EAM5B,IAAIE,EAAmC,KAClCD,EAAOE,cACRD,EAAoBE,sBAAiB9M,IAIzC,MAAMsK,EAAW,IAAIjG,gBAAgB1L,KAAKyJ,QAE1C,SAAS2K,UACLH,GAAmBvE,QACnBiC,EAAS5E,aACZ,CAED,MAAMsH,EAAiC,CAAA,EACjCvG,EAAakG,EAAOlG,WAK1B,OAJIA,IACAuG,EAAkBvG,WAAaA,GAG5B9N,KAAKoS,gBAAgBiC,GACvBlK,MAAMmK,IACH,MAAMZ,EAAWY,EAAYC,OAAOC,UAAUC,MACzCxF,GAAMA,EAAExO,OAASuT,EAAON,WAE7B,IAAKA,EACD,MAAM,IAAI/T,oBACN,IAAIC,MAAM,gCAAgCoU,EAAON,eAIzD,MAAME,EAAc5T,KAAKyJ,OAAOmF,SAAS,wBAEzC,OAAO,IAAIT,SAAQtB,MAAOuB,EAASC,KAE/B,MAAMqG,EAAmB5G,EACnB9N,KAAKyJ,OAA0B,oBAAIqE,QACnCzG,EACFqN,IACAA,EAAiBC,OAAOC,QAAU,KAC9BR,UACA/F,EACI,IAAI1O,oBAAoB,CACpBS,SAAS,EACTM,QAAS,uBAEhB,GAKTiR,EAASnC,aAAgBqF,IACjBA,EAAoBrQ,QAAU6J,IAC9B+F,UACA/F,EACI,IAAI1O,oBACA,IAAIC,MAAM,qCAGrB,EAGL,UACU+R,EAAStF,UAAU,WAAWQ,MAAO1I,IACvC,MAAM2Q,EAAWnD,EAAShH,SAE1B,IACI,IAAKxG,EAAE4Q,OAASD,IAAa3Q,EAAE4Q,MAC3B,MAAM,IAAInV,MAAM,iCAGpB,GAAIuE,EAAE6Q,QAAU7Q,EAAEuM,KACd,MAAM,IAAI9Q,MACN,0CACIuE,EAAE6Q,OAKd,MAAM9T,EAAUZ,OAAOc,OAAO,CAAE,EAAE4S,UAC3B9S,EAAQwS,gBACRxS,EAAQ+T,cACR/T,EAAQ2S,kBACR3S,EAAQgT,YAGXQ,GAAkBC,QAAQC,UAC1BF,EAAiBC,OAAOC,QAAU,MAGtC,MAAMhC,QAAiB5S,KAAKyT,mBACxBC,EAASjT,KACT0D,EAAEuM,KACFgD,EAASC,aACTC,EACAI,EAAOH,WACP3S,GAGJkN,EAAQwE,EACX,CAAC,MAAO3E,GACLI,EAAO,IAAI1O,oBAAoBsO,GAClC,CAEDmG,SAAS,IAGb,MAAMc,EAAuC,CACzCH,MAAOpD,EAAShH,UAEhBqJ,EAAOiB,QAAQzQ,SACf0Q,EAAoB,MAAIlB,EAAOiB,OAAOjR,KAAK,MAG/C,MAAM/D,EAAMD,KAAKmV,oBACbzB,EAAS0B,QAAUxB,EACnBsB,GAGJ,IAAIhB,EACAF,EAAOE,aACP,SAAUjU,GACFgU,EACAA,EAAkBoB,SAASC,KAAOrV,EAIlCgU,EAAoBE,iBAAiBlU,EAE7C,QAEEiU,EAAYjU,EACrB,CAAC,MAAOgO,GAEDyG,GAAkBC,QAAQC,UAC1BF,EAAiBC,OAAOC,QAAU,MAGtCR,UACA/F,EAAO,IAAI1O,oBAAoBsO,GAClC,IACH,IAELD,OAAOC,IAEJ,MADAmG,UACMnG,CAAG,GAEpB,CAkBD,iBAAMuF,CACFpC,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,QAUZ,OAPA1I,EAAU+P,2BACN,2GACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,gBAAiBvQ,GAChDiJ,MAAMxJ,GAASX,KAAKmS,aAAgBxR,IAC5C,CAeD,0BAAM4U,CACFvN,EACAoJ,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFhC,MAAOA,IAWf,OAPA9G,EAAU+P,2BACN,2IACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,0BAA2BvQ,GAC1DiJ,MAAK,KAAM,GACnB,CA0BD,0BAAMqL,CACFC,EACAjD,EACAkD,EACAtE,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFvG,MAAOgS,EACPjD,SAAUA,EACVkD,gBAAiBA,IAWzB,OAPAxU,EAAU+P,2BACN,iMACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,0BAA2BvQ,GAC1DiJ,MAAK,KAAM,GACnB,CAeD,yBAAMwL,CACF3N,EACAoJ,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFhC,MAAOA,IAWf,OAPA9G,EAAU+P,2BACN,yIACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,wBAAyBvQ,GACxDiJ,MAAK,KAAM,GACnB,CAyBD,yBAAMyL,CACFC,EACAzE,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFvG,MAAOoS,IAWf,OAPA3U,EAAU+P,2BACN,yIACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,wBAAyBvQ,GACxDiJ,MAAK,KAEF,MAAM7F,EAAUd,gBAAgBqS,GAC1BhQ,EAAQ7F,KAAKyJ,OAAOoI,UAAUjM,OAWpC,OATIC,IACCA,EAAMiQ,UACPjQ,EAAMkC,KAAOzD,EAAQyD,IACrBlC,EAAMK,eAAiB5B,EAAQ4B,eAE/BL,EAAMiQ,UAAW,EACjB9V,KAAKyJ,OAAOoI,UAAUtL,KAAKvG,KAAKyJ,OAAOoI,UAAUpO,MAAOoC,KAGrD,CAAI,GAEtB,CAeD,wBAAMkQ,CACFC,EACA5E,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFgM,SAAUA,IAWlB,OAPA9U,EAAU+P,2BACN,6IACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,wBAAyBvQ,GACxDiJ,MAAK,KAAM,GACnB,CA2BD,wBAAM8L,CACFC,EACA1D,EACApB,EACAlG,GAEA,IAAIhK,EAAe,CACf0I,OAAQ,OACRI,KAAM,CACFvG,MAAOyS,EACP1D,SAAUA,IAWlB,OAPAtR,EAAU+P,2BACN,2JACA/P,EACAkQ,EACAlG,GAGGlL,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,wBAAyBvQ,GACxDiJ,MAAK,KACF,MAAM7F,EAAUd,gBAAgB0S,GAC1BrQ,EAAQ7F,KAAKyJ,OAAOoI,UAAUjM,OASpC,OAPIC,GACAA,EAAMkC,KAAOzD,EAAQyD,IACrBlC,EAAMK,eAAiB5B,EAAQ4B,cAE/BlG,KAAKyJ,OAAOoI,UAAUpL,SAGnB,CAAI,GAEtB,CASD,uBAAM0P,CACFC,EACAlV,GAEA,OAAOlB,KAAKyJ,OAAOgB,WAAW,kBAAkBmF,YAC5CtP,OAAOc,OAAO,CAAE,EAAEF,EAAS,CACvBsP,OAAQxQ,KAAKyJ,OAAO+G,OAAO,oBAAqB,CAAEzI,GAAIqO,MAGjE,CASD,wBAAMC,CACFD,EACA1C,EACAxS,GAEA,MAAMoV,QAAWtW,KAAKyJ,OAAOgB,WAAW,kBAAkB8F,iBACtDvQ,KAAKyJ,OAAO+G,OAAO,oDAAqD,CACpE4F,WACA1C,cAIR,OAAO1T,KAAKyJ,OACPgB,WAAW,kBACXwH,OAAOqE,EAAGvO,GAAI7G,GACdiJ,MAAK,KAAM,GACnB,CAOD,gBAAMoM,CAAWvO,EAAe9G,GAS5B,OARAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OACRI,KAAM,CAAEhC,MAAOA,IAEnB9G,GAGGlB,KAAKyJ,OAAOI,KAAK7J,KAAKyR,mBAAqB,eAAgBvQ,EACrE,CAYD,iBAAMsV,CACFC,EACAjE,EACAtR,GAUA,OARAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OACRI,KAAM,CAAEyM,QAAOjE,aAEnBtR,GAGGlB,KAAKyJ,OACPI,KAAK7J,KAAKyR,mBAAqB,iBAAkBvQ,GACjDiJ,MAAMxJ,GAASX,KAAKmS,aAAgBxR,IAC5C,CAaD,iBAAM+V,CACFN,EACArL,EACA7J,IAEAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OACRI,KAAM,CAAEe,SAAUA,IAEtB7J,IAEIsL,QAAUtL,EAAQsL,SAAW,CAAA,EAChCtL,EAAQsL,QAAQmK,gBACjBzV,EAAQsL,QAAQmK,cAAgB3W,KAAKyJ,OAAOoI,UAAUpO,OAK1D,MAAMgG,EAAS,IAAImN,OACf5W,KAAKyJ,OAAOoN,QACZ,IAAIrR,cACJxF,KAAKyJ,OAAOqN,MAGVlE,QAAiBnJ,EAAOI,KAC1B7J,KAAKyR,mBAAqB,gBAAkBxO,mBAAmBmT,GAC/DlV,GAMJ,OAHAuI,EAAOoI,UAAUtL,KAAKqM,GAAUnP,MAAOzD,KAAK+G,OAAO6L,GAAUhN,QAAU,CAAA,IAGhE6D,CACV,CAQO,mBAAA0L,CACJlV,EACAiV,EAAuC,IAEvC,IAAI6B,EAAU9W,EACViL,EAAQ,GAEOjL,EAAI8C,QAAQ,MACb,IACdgU,EAAU9W,EAAI+W,UAAU,EAAG/W,EAAI8C,QAAQ,MACvCmI,EAAQjL,EAAI+W,UAAU/W,EAAI8C,QAAQ,KAAO,IAG7C,MAAMkU,EAA0C,CAAA,EAG1CC,EAAYhM,EAAMvH,MAAM,KAC9B,IAAK,MAAMwT,KAASD,EAAW,CAC3B,GAAa,IAATC,EACA,SAGJ,MAAMC,EAAOD,EAAMxT,MAAM,KACzBsT,EAAajU,mBAAmBoU,EAAK,GAAGrS,QAAQ,MAAO,OACnD/B,oBAAoBoU,EAAK,IAAM,IAAIrS,QAAQ,MAAO,KACzD,CAGD,IAAK,IAAI6B,KAAOsO,EACPA,EAAamC,eAAezQ,KAIR,MAArBsO,EAAatO,UACNqQ,EAAarQ,GAEpBqQ,EAAarQ,GAAOsO,EAAatO,IAKzCsE,EAAQ,GACR,IAAK,IAAItE,KAAOqQ,EACPA,EAAaI,eAAezQ,KAIpB,IAATsE,IACAA,GAAS,KAGbA,GACIjI,mBAAmB2D,EAAI7B,QAAQ,OAAQ,MACvC,IACA9B,mBAAmBgU,EAAarQ,GAAK7B,QAAQ,OAAQ,OAG7D,MAAgB,IAATmG,EAAc6L,EAAU,IAAM7L,EAAQ6L,CAChD,EAGL,SAAS5C,iBAAiBlU,GACtB,GAAsB,oBAAX+I,SAA2BA,QAAQsO,KAC1C,MAAM,IAAI3X,oBACN,IAAIC,MACA,0EAKZ,IAAI2X,EAAQ,KACRC,EAAS,IAETC,EAAczO,OAAO0O,WACrBC,EAAe3O,OAAO4O,YAG1BL,EAAQA,EAAQE,EAAcA,EAAcF,EAC5CC,EAASA,EAASG,EAAeA,EAAeH,EAEhD,IAAIK,EAAOJ,EAAc,EAAIF,EAAQ,EACjCO,EAAMH,EAAe,EAAIH,EAAS,EAItC,OAAOxO,OAAOsO,KACVrX,EACA,eACA,SACIsX,EACA,WACAC,EACA,QACAM,EACA,SACAD,EACA,wBAEZ,CC9vCM,MAAOE,0BAA0BpI,YAInC,gBAAIQ,GACA,MAAO,kBACV,CAWD,YAAM6H,CACFC,EACAC,GAAyB,EACzBhX,GAaA,OAXAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,MACRI,KAAM,CACFiO,YAAaA,EACbC,cAAeA,IAGvBhX,GAGGlB,KAAKyJ,OAAOI,KAAK7J,KAAKmQ,aAAe,UAAWjP,GAASiJ,MAAK,KAAM,GAC9E,CAQD,kBAAMgO,CACFjX,GASA,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK7J,KAAKmQ,aAAe,kBAAmBjP,EAClE,CAOD,cAAMkX,CAAS/N,EAA4BnJ,GAQvC,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,UAEZ1I,GAGGlB,KAAKyJ,OACPI,KACG7J,KAAKmQ,aACD,IACAlN,mBAAmBoH,GACnB,YACJnJ,GAEHiJ,MAAK,KAAM,GACnB,ECvEC,MAAOkO,mBAAmB7O,YAM5B,aAAMwG,CACFC,EAAO,EACPC,EAAU,GACVhP,GAYA,OAVAA,EAAUZ,OAAOc,OAAO,CAAEwI,OAAQ,OAAS1I,IAEnCgK,MAAQ5K,OAAOc,OACnB,CACI6O,KAAMA,EACNC,QAASA,GAEbhP,EAAQgK,OAGLlL,KAAKyJ,OAAOI,KAAK,YAAa3I,EACxC,CASD,YAAMyP,CAAO5I,EAAY7G,GACrB,IAAK6G,EACD,MAAM,IAAIpI,oBAAoB,CAC1BM,IAAKD,KAAKyJ,OAAOmF,SAAS,cAC1B1O,OAAQ,IACRC,SAAU,CACNuQ,KAAM,IACNhQ,QAAS,2BACTC,KAAM,CAAE,KAYpB,OAPAO,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,aAAe5G,mBAAmB8E,GAAK7G,EAClE,CAOD,cAAMoX,CAASpX,GAQX,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,kBAAmB3I,EAC9C,ECrEC,MAAOqX,sBAAsB/O,YAM/B,WAAMgP,CAAMtX,GAQR,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,cAAe3I,EAC1C,ECrBC,MAAOuX,oBAAoBjP,YAI7B,MAAAkP,CACI9S,EACA+S,EACAC,EAA2B,CAAA,GAG3B,OADAxS,QAAQC,KAAK,2DACNrG,KAAK6Y,OAAOjT,EAAQ+S,EAAUC,EACxC,CAKD,MAAAC,CACIjT,EACA+S,EACAC,EAA2B,CAAA,GAE3B,IACKD,IACA/S,GAAQmC,KACPnC,GAAQM,eAAgBN,GAAQK,eAElC,MAAO,GAGX,MAAM6S,EAAQ,GACdA,EAAMxQ,KAAK,OACXwQ,EAAMxQ,KAAK,SACXwQ,EAAMxQ,KAAKrF,mBAAmB2C,EAAOM,cAAgBN,EAAOK,iBAC5D6S,EAAMxQ,KAAKrF,mBAAmB2C,EAAOmC,KACrC+Q,EAAMxQ,KAAKrF,mBAAmB0V,IAE9B,IAAIjX,EAAS1B,KAAKyJ,OAAOmF,SAASkK,EAAM9U,KAAK,OAGhB,IAAzB4U,EAAYG,iBACLH,EAAYG,SAGvB,MAAM3N,EAASD,qBAAqByN,GAKpC,OAJIxN,IACA1J,IAAWA,EAAOb,SAAS,KAAO,IAAM,KAAOuK,GAG5C1J,CACV,CAOD,cAAMsX,CAAS9X,GAQX,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,QAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK,mBAAoB3I,GACzBiJ,MAAMxJ,GAASA,GAAM8C,OAAS,IACtC,EC7DC,MAAOwV,sBAAsBzP,YAM/B,iBAAMoG,CAAY1O,GAQd,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,eAAgB3I,EAC3C,CAOD,YAAM0P,CAAOsI,EAAkBhY,GAW3B,OAVAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OACRI,KAAM,CACFvJ,KAAMyY,IAGdhY,GAGGlB,KAAKyJ,OAAOI,KAAK,eAAgB3I,GAASiJ,MAAK,KAAM,GAC/D,CAeD,YAAMgP,CACFpP,EACA7I,GAUA,OARAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OACRI,KAAMD,GAEV7I,GAGGlB,KAAKyJ,OAAOI,KAAK,sBAAuB3I,GAASiJ,MAAK,KAAM,GACtE,CAOD,YAAM,CAAOvD,EAAa1F,GAQtB,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,UAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK,gBAAgB5G,mBAAmB2D,KAAQ1F,GAChDiJ,MAAK,KAAM,GACnB,CAOD,aAAMiP,CAAQxS,EAAa1F,GAQvB,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,QAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK,gBAAgB5G,mBAAmB2D,aAAgB1F,GACxDiJ,MAAK,KAAM,GACnB,CAKD,cAAAkP,CAAe5V,EAAemD,GAI1B,OAHAR,QAAQC,KACJ,+EAEGrG,KAAKsZ,eAAe7V,EAAOmD,EACrC,CAQD,cAAA0S,CAAe7V,EAAemD,GAC1B,OAAO5G,KAAKyJ,OAAOmF,SACf,gBAAgB3L,mBAAmB2D,YAAc3D,mBAAmBQ,KAE3E,ECzHC,MAAO8V,oBAAoB/P,YAM7B,iBAAMoG,CAAY1O,GAQd,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OAEZ1I,GAGGlB,KAAKyJ,OAAOI,KAAK,aAAc3I,EACzC,CAOD,SAAMsY,CAAIC,EAAevY,GAQrB,OAPAA,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,QAEZ1I,GAGGlB,KAAKyJ,OACPI,KAAK,cAAc5G,mBAAmBwW,KAAUvY,GAChDiJ,MAAK,KAAM,GACnB,ECtCC,SAAUuP,OAAOzY,GACnB,MACqB,oBAAT4G,MAAwB5G,aAAe4G,MAC9B,oBAAT8R,MAAwB1Y,aAAe0Y,MAGtC,OAAR1Y,GACkB,iBAARA,GACPA,EAAI2Y,MACmB,oBAAdzW,WAAmD,gBAAtBA,UAAUC,SACzB,oBAAXC,QAA2BA,OAAeC,eAElE,CAKM,SAAUuW,WAAW7P,GACvB,OACIA,IAI4B,aAA3BA,EAAKnK,aAAaY,MAIM,oBAAbqZ,UAA4B9P,aAAgB8P,SAEhE,CAKM,SAAUC,aAAa/P,GACzB,IAAK,MAAMpD,KAAOoD,EAAM,CACpB,MAAMgQ,EAASzS,MAAMC,QAAQwC,EAAKpD,IAAQoD,EAAKpD,GAAO,CAACoD,EAAKpD,IAC5D,IAAK,MAAM2E,KAAKyO,EACZ,GAAIN,OAAOnO,GACP,OAAO,CAGlB,CAED,OAAO,CACX,CAoFA,MAAM0O,EAAwB,cAE9B,SAASC,mBAAmBzY,GACxB,GAAoB,iBAATA,EACP,OAAOA,EAGX,GAAa,QAATA,EACA,OAAO,EAGX,GAAa,SAATA,EACA,OAAO,EAIX,IACkB,MAAbA,EAAM,IAAeA,EAAM,IAAM,KAAOA,EAAM,IAAM,MACrDwY,EAAsB1Y,KAAKE,GAC7B,CACE,IAAI0Y,GAAO1Y,EACX,GAAI,GAAK0Y,IAAQ1Y,EACb,OAAO0Y,CAEd,CAED,OAAO1Y,CACX,CCzIM,MAAO2Y,qBAAqB5Q,YAAlC,WAAA3J,uBACYG,KAAQqa,SAAwB,GAChCra,KAAIiN,KAAuC,EA0DtD,CArDG,UAAAxC,CAAWJ,GAQP,OAPKrK,KAAKiN,KAAK5C,KACXrK,KAAKiN,KAAK5C,GAAsB,IAAIiQ,gBAChCta,KAAKqa,SACLhQ,IAIDrK,KAAKiN,KAAK5C,EACpB,CAOD,UAAMR,CAAK3I,GACP,MAAMqZ,EAAW,IAAIT,SAEfU,EAAW,GAEjB,IAAK,IAAIjS,EAAI,EAAGA,EAAIvI,KAAKqa,SAAS7V,OAAQ+D,IAAK,CAC3C,MAAMkS,EAAMza,KAAKqa,SAAS9R,GAS1B,GAPAiS,EAASlS,KAAK,CACVsB,OAAQ6Q,EAAI7Q,OACZ3J,IAAKwa,EAAIxa,IACTuM,QAASiO,EAAIjO,QACbxC,KAAMyQ,EAAIC,OAGVD,EAAIE,MACJ,IAAK,IAAI/T,KAAO6T,EAAIE,MAAO,CACvB,MAAMA,EAAQF,EAAIE,MAAM/T,IAAQ,GAChC,IAAK,IAAIgU,KAAQD,EACbJ,EAASM,OAAO,YAActS,EAAI,IAAM3B,EAAKgU,EAEpD,CAER,CAYD,OAVAL,EAASM,OAAO,eAAgB5W,KAAK0D,UAAU,CAAE0S,SAAUG,KAE3DtZ,EAAUZ,OAAOc,OACb,CACIwI,OAAQ,OACRI,KAAMuQ,GAEVrZ,GAGGlB,KAAKyJ,OAAOI,KAAK,aAAc3I,EACzC,QAGQoZ,gBAIT,WAAAza,CAAYwa,EAA+BhQ,GAHnCrK,KAAQqa,SAAwB,GAIpCra,KAAKqa,SAAWA,EAChBra,KAAKqK,mBAAqBA,CAC7B,CAOD,MAAAyQ,CACI/Q,EACA7I,GAEAA,EAAUZ,OAAOc,OACb,CACI4I,KAAMD,GAAc,CAAE,GAE1B7I,GAGJ,MAAM4P,EAAwB,CAC1BlH,OAAQ,MACR3J,IACI,oBACAgD,mBAAmBjD,KAAKqK,oBACxB,YAGRrK,KAAK+a,eAAejK,EAAS5P,GAE7BlB,KAAKqa,SAAS/R,KAAKwI,EACtB,CAKD,MAAAF,CACI7G,EACA7I,GAEAA,EAAUZ,OAAOc,OACb,CACI4I,KAAMD,GAAc,CAAE,GAE1B7I,GAGJ,MAAM4P,EAAwB,CAC1BlH,OAAQ,OACR3J,IACI,oBACAgD,mBAAmBjD,KAAKqK,oBACxB,YAGRrK,KAAK+a,eAAejK,EAAS5P,GAE7BlB,KAAKqa,SAAS/R,KAAKwI,EACtB,CAKD,MAAAhH,CACI/B,EACAgC,EACA7I,GAEAA,EAAUZ,OAAOc,OACb,CACI4I,KAAMD,GAAc,CAAE,GAE1B7I,GAGJ,MAAM4P,EAAwB,CAC1BlH,OAAQ,QACR3J,IACI,oBACAgD,mBAAmBjD,KAAKqK,oBACxB,YACApH,mBAAmB8E,IAG3B/H,KAAK+a,eAAejK,EAAS5P,GAE7BlB,KAAKqa,SAAS/R,KAAKwI,EACtB,CAKD,OAAO/I,EAAY7G,GACfA,EAAUZ,OAAOc,OAAO,CAAE,EAAEF,GAE5B,MAAM4P,EAAwB,CAC1BlH,OAAQ,SACR3J,IACI,oBACAgD,mBAAmBjD,KAAKqK,oBACxB,YACApH,mBAAmB8E,IAG3B/H,KAAK+a,eAAejK,EAAS5P,GAE7BlB,KAAKqa,SAAS/R,KAAKwI,EACtB,CAEO,cAAAiK,CAAejK,EAAuB5P,GAS1C,GARA+J,4BAA4B/J,GAE5B4P,EAAQtE,QAAUtL,EAAQsL,QAC1BsE,EAAQ4J,KAAO,GACf5J,EAAQ6J,MAAQ,QAIa,IAAlBzZ,EAAQgK,MAAuB,CACtC,MAAMA,EAAQC,qBAAqBjK,EAAQgK,OACvCA,IACA4F,EAAQ7Q,MAAQ6Q,EAAQ7Q,IAAIY,SAAS,KAAO,IAAM,KAAOqK,EAEhE,CAID,IAAIlB,EAAO9I,EAAQ8I,KACf6P,WAAW7P,KACXA,EDhHN,SAAUgR,wBAAwBT,GACpC,IAAI7Y,EAAiC,CAAA,EAsBrC,OApBA6Y,EAASU,SAAQ,CAAC1P,EAAG2P,KACjB,GAAU,iBAANA,GAAoC,iBAAL3P,EAC/B,IACI,IAAI4P,EAASlX,KAAKC,MAAMqH,GACxBjL,OAAOc,OAAOM,EAAQyZ,EACzB,CAAC,MAAOlN,GACL7H,QAAQC,KAAK,sBAAuB4H,EACvC,WAEwB,IAAdvM,EAAOwZ,IACT3T,MAAMC,QAAQ9F,EAAOwZ,MACtBxZ,EAAOwZ,GAAK,CAACxZ,EAAOwZ,KAExBxZ,EAAOwZ,GAAG5S,KAAK4R,mBAAmB3O,KAElC7J,EAAOwZ,GAAKhB,mBAAmB3O,EAEtC,IAGE7J,CACX,CCwFmBsZ,CAAwBhR,IAGnC,IAAK,MAAMpD,KAAOoD,EAAM,CACpB,MAAM/I,EAAM+I,EAAKpD,GAEjB,GAAI8S,OAAOzY,GACP6P,EAAQ6J,MAAM/T,GAAOkK,EAAQ6J,MAAM/T,IAAQ,GAC3CkK,EAAQ6J,MAAM/T,GAAK0B,KAAKrH,QACrB,GAAIsG,MAAMC,QAAQvG,GAAM,CAC3B,MAAMma,EAAa,GACbC,EAAe,GACrB,IAAK,MAAM9P,KAAKtK,EACRyY,OAAOnO,GACP6P,EAAW9S,KAAKiD,GAEhB8P,EAAa/S,KAAKiD,GAI1B,GAAI6P,EAAW5W,OAAS,GAAK4W,EAAW5W,QAAUvD,EAAIuD,OAAQ,CAG1DsM,EAAQ6J,MAAM/T,GAAOkK,EAAQ6J,MAAM/T,IAAQ,GAC3C,IAAK,IAAIgU,KAAQQ,EACbtK,EAAQ6J,MAAM/T,GAAK0B,KAAKsS,EAE/B,MAKG,GAFA9J,EAAQ4J,KAAK9T,GAAOyU,EAEhBD,EAAW5W,OAAS,EAAG,CAIvB,IAAI8W,EAAU1U,EACTA,EAAI6G,WAAW,MAAS7G,EAAI2U,SAAS,OACtCD,GAAW,KAGfxK,EAAQ6J,MAAMW,GAAWxK,EAAQ6J,MAAMW,IAAY,GACnD,IAAK,IAAIV,KAAQQ,EACbtK,EAAQ6J,MAAMW,GAAShT,KAAKsS,EAEnC,CAER,MACG9J,EAAQ4J,KAAK9T,GAAO3F,CAE3B,CACJ,EC9OS,MAAO2V,OAUjB,WAAI4E,GACA,OAAOxb,KAAK6W,OACf,CAMD,WAAI2E,CAAQjQ,GACRvL,KAAK6W,QAAUtL,CAClB,CAiHD,WAAA1L,CAAYgX,EAAU,IAAKhF,EAAkCiF,EAAO,SAJ5D9W,KAAiByb,kBAAuC,GACxDzb,KAAc0b,eAAqC,GACnD1b,KAAsB2b,wBAAY,EAGtC3b,KAAK6W,QAAUA,EACf7W,KAAK8W,KAAOA,EAERjF,EACA7R,KAAK6R,UAAYA,EACO,oBAAV7I,QAA4BA,OAAe4S,KAEzD5b,KAAK6R,UAAY,IAAIrM,cAErBxF,KAAK6R,UAAY,IAAIpJ,eAIzBzI,KAAKiY,YAAc,IAAIF,kBAAkB/X,MACzCA,KAAK2a,MAAQ,IAAIlC,YAAYzY,MAC7BA,KAAK6b,KAAO,IAAIxD,WAAWrY,MAC3BA,KAAK8b,SAAW,IAAIpS,gBAAgB1J,MACpCA,KAAK2R,SAAW,IAAIjG,gBAAgB1L,MACpCA,KAAK+b,OAAS,IAAIxD,cAAcvY,MAChCA,KAAKgc,QAAU,IAAI/C,cAAcjZ,MACjCA,KAAKic,MAAQ,IAAI1C,YAAYvZ,KAChC,CAOD,UAAIkc,GACA,OAAOlc,KAAKyK,WAAW,cAC1B,CAkBD,WAAA0R,GACI,OAAO,IAAI/B,aAAapa,KAC3B,CAKD,UAAAyK,CAA4B2R,GAKxB,OAJKpc,KAAK0b,eAAeU,KACrBpc,KAAK0b,eAAeU,GAAY,IAAI5K,cAAcxR,KAAMoc,IAGrDpc,KAAK0b,eAAeU,EAC9B,CAKD,gBAAAC,CAAiBC,GAGb,OAFAtc,KAAK2b,yBAA2BW,EAEzBtc,IACV,CAKD,aAAAyP,CAAc3B,GAMV,OALI9N,KAAKyb,kBAAkB3N,KACvB9N,KAAKyb,kBAAkB3N,GAAYyO,eAC5Bvc,KAAKyb,kBAAkB3N,IAG3B9N,IACV,CAKD,iBAAAwc,GACI,IAAK,IAAItB,KAAKlb,KAAKyb,kBACfzb,KAAKyb,kBAAkBP,GAAGqB,QAK9B,OAFAvc,KAAKyb,kBAAoB,GAElBzb,IACV,CAyBD,MAAAwQ,CAAOiM,EAAarR,GAChB,IAAKA,EACD,OAAOqR,EAGX,IAAK,IAAI7V,KAAOwE,EAAQ,CACpB,IAAInK,EAAMmK,EAAOxE,GACjB,cAAe3F,GACX,IAAK,UACL,IAAK,SACDA,EAAM,GAAKA,EACX,MACJ,IAAK,SACDA,EAAM,IAAMA,EAAI8D,QAAQ,KAAM,OAAS,IACvC,MACJ,QAEQ9D,EADQ,OAARA,EACM,OACCA,aAAeqB,KAChB,IAAMrB,EAAIwK,cAAc1G,QAAQ,IAAK,KAAO,IAE5C,IAAMd,KAAK0D,UAAU1G,GAAK8D,QAAQ,KAAM,OAAS,IAGnE0X,EAAMA,EAAIC,WAAW,KAAO9V,EAAM,IAAK3F,EAC1C,CAED,OAAOwb,CACV,CAKD,UAAAE,CACI/W,EACA+S,EACAC,EAA2B,CAAA,GAG3B,OADAxS,QAAQC,KAAK,yDACNrG,KAAK2a,MAAM9B,OAAOjT,EAAQ+S,EAAUC,EAC9C,CAKD,QAAAgE,CAAS3a,GAEL,OADAmE,QAAQC,KAAK,mDACNrG,KAAK4O,SAAS3M,EACxB,CAKD,QAAA2M,CAAS3M,GACL,IAAIhC,EAAMD,KAAK6W,QA2Bf,MAvBsB,oBAAX7N,SACLA,OAAOqM,UACRpV,EAAIwN,WAAW,aACfxN,EAAIwN,WAAW,aAEhBxN,EAAM+I,OAAOqM,SAASwH,QAAQtB,SAAS,KACjCvS,OAAOqM,SAASwH,OAAO7F,UAAU,EAAGhO,OAAOqM,SAASwH,OAAOrY,OAAS,GACpEwE,OAAOqM,SAASwH,QAAU,GAE3B7c,KAAK6W,QAAQpJ,WAAW,OACzBxN,GAAO+I,OAAOqM,SAASyH,UAAY,IACnC7c,GAAOA,EAAIsb,SAAS,KAAO,GAAK,KAGpCtb,GAAOD,KAAK6W,SAIZ5U,IACAhC,GAAOA,EAAIsb,SAAS,KAAO,GAAK,IAChCtb,GAAOgC,EAAKwL,WAAW,KAAOxL,EAAK+U,UAAU,GAAK/U,GAG/ChC,CACV,CAOD,UAAM4J,CAAc5H,EAAcf,GAC9BA,EAAUlB,KAAK+c,gBAAgB9a,EAAMf,GAGrC,IAAIjB,EAAMD,KAAK4O,SAAS3M,GAExB,GAAIjC,KAAKkT,WAAY,CACjB,MAAMxR,EAASpB,OAAOc,OAAO,CAAE,QAAQpB,KAAKkT,WAAWjT,EAAKiB,SAElC,IAAfQ,EAAOzB,UACY,IAAnByB,EAAOR,SAEdjB,EAAMyB,EAAOzB,KAAOA,EACpBiB,EAAUQ,EAAOR,SAAWA,GACrBZ,OAAOiE,KAAK7C,GAAQ8C,SAE3BtD,EAAUQ,EACV0E,SAASC,MACLD,QAAQC,KACJ,8GAGf,CAGD,QAA6B,IAAlBnF,EAAQgK,MAAuB,CACtC,MAAMA,EAAQC,qBAAqBjK,EAAQgK,OACvCA,IACAjL,IAAQA,EAAIY,SAAS,KAAO,IAAM,KAAOqK,UAEtChK,EAAQgK,KAClB,CAIsD,oBAAnDlL,KAAKgd,UAAU9b,EAAQsL,QAAS,iBAChCtL,EAAQ8I,MACgB,iBAAjB9I,EAAQ8I,OAEf9I,EAAQ8I,KAAO/F,KAAK0D,UAAUzG,EAAQ8I,OAO1C,OAHkB9I,EAAQ+b,OAASA,OAGlBhd,EAAKiB,GACjBiJ,MAAK0C,MAAO1M,IACT,IAAIQ,EAAY,CAAA,EAEhB,IACIA,QAAaR,EAASua,MACzB,CAAC,MAAOzM,GAIL,GACI/M,EAAQyT,QAAQuI,SACH,cAAbjP,GAAKxN,MACW,WAAhBwN,GAAKvN,QAEL,MAAMuN,CAEb,CAMD,GAJIjO,KAAKmd,YACLxc,QAAaX,KAAKmd,UAAUhd,EAAUQ,EAAMO,IAG5Cf,EAASD,QAAU,IACnB,MAAM,IAAIP,oBAAoB,CAC1BM,IAAKE,EAASF,IACdC,OAAQC,EAASD,OACjBS,KAAMA,IAId,OAAOA,CAAS,IAEnBqN,OAAOC,IAEJ,MAAM,IAAItO,oBAAoBsO,EAAI,GAE7C,CASO,eAAA8O,CAAgB9a,EAAcf,GAyDlC,IAxDAA,EAAUZ,OAAOc,OAAO,CAAEwI,OAAQ,OAAwB1I,IAGlD8I,KFhaV,SAAUoT,0BAA0BpT,GACtC,GACwB,oBAAb8P,eACS,IAAT9P,GACS,iBAATA,GACE,OAATA,GACA6P,WAAW7P,KACV+P,aAAa/P,GAEd,OAAOA,EAGX,MAAMqT,EAAO,IAAIvD,SAEjB,IAAK,MAAMlT,KAAOoD,EAAM,CACpB,MAAM/I,EAAM+I,EAAKpD,GAIjB,QAAmB,IAAR3F,EAIX,GAAmB,iBAARA,GAAqB8Y,aAAa,CAAEpZ,KAAMM,IAK9C,CAEH,MAAMmI,EAAgB7B,MAAMC,QAAQvG,GAAOA,EAAM,CAACA,GAClD,IAAK,IAAIsK,KAAKnC,EACViU,EAAKxC,OAAOjU,EAAK2E,EAExB,KAX4D,CAEzD,IAAIjH,EAAkC,CAAA,EACtCA,EAAQsC,GAAO3F,EACfoc,EAAKxC,OAAO,eAAgB5W,KAAK0D,UAAUrD,GAC9C,CAOJ,CAED,OAAO+Y,CACX,CE0XuBD,CAA0Blc,EAAQ8I,MAGjDiB,4BAA4B/J,GAI5BA,EAAQgK,MAAQ5K,OAAOc,OAAO,CAAA,EAAIF,EAAQkK,OAAQlK,EAAQgK,YACxB,IAAvBhK,EAAQ4M,cACa,IAAxB5M,EAAQoc,cAAuD,IAA9Bpc,EAAQgK,MAAMoS,YAC/Cpc,EAAQ4M,WAAa,MACd5M,EAAQqc,YAAcrc,EAAQgK,MAAMqS,cAC3Crc,EAAQ4M,WAAa5M,EAAQqc,YAAcrc,EAAQgK,MAAMqS,oBAI1Drc,EAAQoc,mBACRpc,EAAQgK,MAAMoS,mBACdpc,EAAQqc,kBACRrc,EAAQgK,MAAMqS,WAMmC,OAApDvd,KAAKgd,UAAU9b,EAAQsL,QAAS,iBAC/BqN,WAAW3Y,EAAQ8I,QAEpB9I,EAAQsL,QAAUlM,OAAOc,OAAO,CAAE,EAAEF,EAAQsL,QAAS,CACjD,eAAgB,sBAKmC,OAAvDxM,KAAKgd,UAAU9b,EAAQsL,QAAS,qBAChCtL,EAAQsL,QAAUlM,OAAOc,OAAO,CAAE,EAAEF,EAAQsL,QAAS,CACjD,kBAAmBxM,KAAK8W,QAO5B9W,KAAK6R,UAAUpO,OAEsC,OAArDzD,KAAKgd,UAAU9b,EAAQsL,QAAS,mBAEhCtL,EAAQsL,QAAUlM,OAAOc,OAAO,CAAE,EAAEF,EAAQsL,QAAS,CACjDmK,cAAe3W,KAAK6R,UAAUpO,SAKlCzD,KAAK2b,wBAAiD,OAAvBza,EAAQ4M,WAAqB,CAC5D,MAAMA,EAAa5M,EAAQ4M,aAAe5M,EAAQ0I,QAAU,OAAS3H,SAE9Df,EAAQ4M,WAGf9N,KAAKyP,cAAc3B,GAInB,MAAM0P,EAAa,IAAIC,gBACvBzd,KAAKyb,kBAAkB3N,GAAc0P,EACrCtc,EAAQyT,OAAS6I,EAAW7I,MAC/B,CAED,OAAOzT,CACV,CAMO,SAAA8b,CACJxQ,EACA/L,GAEA+L,EAAUA,GAAW,GACrB/L,EAAOA,EAAKmC,cAEZ,IAAK,IAAIgE,KAAO4F,EACZ,GAAI5F,EAAIhE,eAAiBnC,EACrB,OAAO+L,EAAQ5F,GAIvB,OAAO,IACV"} \ No newline at end of file diff --git a/script/node_modules/pocketbase/dist/pocketbase.umd.d.ts b/script/node_modules/pocketbase/dist/pocketbase.umd.d.ts new file mode 100644 index 0000000..beeecc0 --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.umd.d.ts @@ -0,0 +1,1468 @@ +interface SerializeOptions { + encode?: (val: string | number | boolean) => string; + maxAge?: number; + domain?: string; + path?: string; + expires?: Date; + httpOnly?: boolean; + secure?: boolean; + priority?: string; + sameSite?: boolean | string; +} +interface ListResult { + page: number; + perPage: number; + totalItems: number; + totalPages: number; + items: Array; +} +interface BaseModel { + [key: string]: any; + id: string; +} +interface LogModel extends BaseModel { + level: string; + message: string; + created: string; + updated: string; + data: { + [key: string]: any; + }; +} +interface RecordModel extends BaseModel { + collectionId: string; + collectionName: string; + expand?: { + [key: string]: any; + }; +} +// ------------------------------------------------------------------- +// Collection types +// ------------------------------------------------------------------- +interface CollectionField { + [key: string]: any; + id: string; + name: string; + type: string; + system: boolean; + hidden: boolean; + presentable: boolean; +} +interface TokenConfig { + duration: number; + secret?: string; +} +interface AuthAlertConfig { + enabled: boolean; + emailTemplate: EmailTemplate; +} +interface OTPConfig { + enabled: boolean; + duration: number; + length: number; + emailTemplate: EmailTemplate; +} +interface MFAConfig { + enabled: boolean; + duration: number; + rule: string; +} +interface PasswordAuthConfig { + enabled: boolean; + identityFields: Array; +} +interface OAuth2Provider { + pkce?: boolean; + clientId: string; + name: string; + clientSecret: string; + authURL: string; + tokenURL: string; + userInfoURL: string; + displayName: string; + extra?: { + [key: string]: any; + }; +} +interface OAuth2Config { + enabled: boolean; + mappedFields: { + [key: string]: string; + }; + providers: Array; +} +interface EmailTemplate { + subject: string; + body: string; +} +interface collection extends BaseModel { + name: string; + fields: Array; + indexes: Array; + system: boolean; + listRule?: string; + viewRule?: string; + createRule?: string; + updateRule?: string; + deleteRule?: string; +} +interface BaseCollectionModel extends collection { + type: "base"; +} +interface ViewCollectionModel extends collection { + type: "view"; + viewQuery: string; +} +interface AuthCollectionModel extends collection { + type: "auth"; + authRule?: string; + manageRule?: string; + authAlert: AuthAlertConfig; + oauth2: OAuth2Config; + passwordAuth: PasswordAuthConfig; + mfa: MFAConfig; + otp: OTPConfig; + authToken: TokenConfig; + passwordResetToken: TokenConfig; + emailChangeToken: TokenConfig; + verificationToken: TokenConfig; + fileToken: TokenConfig; + verificationTemplate: EmailTemplate; + resetPasswordTemplate: EmailTemplate; + confirmEmailChangeTemplate: EmailTemplate; +} +type CollectionModel = BaseCollectionModel | ViewCollectionModel | AuthCollectionModel; +type AuthRecord = RecordModel | null; +// for backward compatibility +type OnStoreChangeFunc = (token: string, record: AuthRecord) => void; +/** + * Base AuthStore class that stores the auth state in runtime memory (aka. only for the duration of the store instane). + * + * Usually you wouldn't use it directly and instead use the builtin LocalAuthStore, AsyncAuthStore + * or extend it with your own custom implementation. + */ +declare class BaseAuthStore { + protected baseToken: string; + protected baseModel: AuthRecord; + private _onChangeCallbacks; + /** + * Retrieves the stored token (if any). + */ + get token(): string; + /** + * Retrieves the stored model data (if any). + */ + get record(): AuthRecord; + /** + * @deprecated use `record` instead. + */ + get model(): AuthRecord; + /** + * Loosely checks if the store has valid token (aka. existing and unexpired exp claim). + */ + get isValid(): boolean; + /** + * Loosely checks whether the currently loaded store state is for superuser. + * + * Alternatively you can also compare directly `pb.authStore.record?.collectionName`. + */ + get isSuperuser(): boolean; + /** + * @deprecated use `isSuperuser` instead or simply check the record.collectionName property. + */ + get isAdmin(): boolean; + /** + * @deprecated use `!isSuperuser` instead or simply check the record.collectionName property. + */ + get isAuthRecord(): boolean; + /** + * Saves the provided new token and model data in the auth store. + */ + save(token: string, record?: AuthRecord): void; + /** + * Removes the stored token and model data form the auth store. + */ + clear(): void; + /** + * Parses the provided cookie string and updates the store state + * with the cookie's token and model data. + * + * NB! This function doesn't validate the token or its data. + * Usually this isn't a concern if you are interacting only with the + * PocketBase API because it has the proper server-side security checks in place, + * but if you are using the store `isValid` state for permission controls + * in a node server (eg. SSR), then it is recommended to call `authRefresh()` + * after loading the cookie to ensure an up-to-date token and model state. + * For example: + * + * ```js + * pb.authStore.loadFromCookie("cookie string..."); + * + * try { + * // get an up-to-date auth store state by veryfing and refreshing the loaded auth model (if any) + * pb.authStore.isValid && await pb.collection('users').authRefresh(); + * } catch (_) { + * // clear the auth store on failed refresh + * pb.authStore.clear(); + * } + * ``` + */ + loadFromCookie(cookie: string, key?: string): void; + /** + * Exports the current store state as cookie string. + * + * By default the following optional attributes are added: + * - Secure + * - HttpOnly + * - SameSite=Strict + * - Path=/ + * - Expires={the token expiration date} + * + * NB! If the generated cookie exceeds 4096 bytes, this method will + * strip the model data to the bare minimum to try to fit within the + * recommended size in https://www.rfc-editor.org/rfc/rfc6265#section-6.1. + */ + exportToCookie(options?: SerializeOptions, key?: string): string; + /** + * Register a callback function that will be called on store change. + * + * You can set the `fireImmediately` argument to true in order to invoke + * the provided callback right after registration. + * + * Returns a removal function that you could call to "unsubscribe" from the changes. + */ + onChange(callback: OnStoreChangeFunc, fireImmediately?: boolean): () => void; + protected triggerChange(): void; +} +/** + * BaseService class that should be inherited from all API services. + */ +declare abstract class BaseService { + readonly client: Client; + constructor(client: Client); +} +interface SendOptions extends RequestInit { + // for backward compatibility and to minimize the verbosity, + // any top-level field that doesn't exist in RequestInit or the + // fields below will be treated as query parameter. + [key: string]: any; + /** + * Optional custom fetch function to use for sending the request. + */ + fetch?: (url: RequestInfo | URL, config?: RequestInit) => Promise; + /** + * Custom headers to send with the requests. + */ + headers?: { + [key: string]: string; + }; + /** + * The body of the request (serialized automatically for json requests). + */ + body?: any; + /** + * Query parameters that will be appended to the request url. + */ + query?: { + [key: string]: any; + }; + /** + * @deprecated use `query` instead + * + * for backward-compatibility `params` values are merged with `query`, + * but this option may get removed in the final v1 release + */ + params?: { + [key: string]: any; + }; + /** + * The request identifier that can be used to cancel pending requests. + */ + requestKey?: string | null; + /** + * @deprecated use `requestKey:string` instead + */ + $cancelKey?: string; + /** + * @deprecated use `requestKey:null` instead + */ + $autoCancel?: boolean; +} +interface CommonOptions extends SendOptions { + fields?: string; +} +interface ListOptions extends CommonOptions { + page?: number; + perPage?: number; + sort?: string; + filter?: string; + skipTotal?: boolean; +} +interface FullListOptions extends ListOptions { + batch?: number; +} +interface RecordOptions extends CommonOptions { + expand?: string; +} +interface RecordListOptions extends ListOptions, RecordOptions { +} +interface RecordFullListOptions extends FullListOptions, RecordOptions { +} +interface RecordSubscribeOptions extends SendOptions { + fields?: string; + filter?: string; + expand?: string; +} +interface LogStatsOptions extends CommonOptions { + filter?: string; +} +interface FileOptions extends CommonOptions { + thumb?: string; + download?: boolean; +} +interface appleClientSecret { + secret: string; +} +declare class SettingsService extends BaseService { + /** + * Fetch all available app settings. + * + * @throws {ClientResponseError} + */ + getAll(options?: CommonOptions): Promise<{ + [key: string]: any; + }>; + /** + * Bulk updates app settings. + * + * @throws {ClientResponseError} + */ + update(bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise<{ + [key: string]: any; + }>; + /** + * Performs a S3 filesystem connection test. + * + * The currently supported `filesystem` are "storage" and "backups". + * + * @throws {ClientResponseError} + */ + testS3(filesystem?: string, options?: CommonOptions): Promise; + /** + * Sends a test email. + * + * The possible `emailTemplate` values are: + * - verification + * - password-reset + * - email-change + * + * @throws {ClientResponseError} + */ + testEmail(collectionIdOrName: string, toEmail: string, emailTemplate: string, options?: CommonOptions): Promise; + /** + * Generates a new Apple OAuth2 client secret. + * + * @throws {ClientResponseError} + */ + generateAppleClientSecret(clientId: string, teamId: string, keyId: string, privateKey: string, duration: number, options?: CommonOptions): Promise; +} +type UnsubscribeFunc = () => Promise; +declare class RealtimeService extends BaseService { + clientId: string; + private eventSource; + private subscriptions; + private lastSentSubscriptions; + private connectTimeoutId; + private maxConnectTimeout; + private reconnectTimeoutId; + private reconnectAttempts; + private maxReconnectAttempts; + private predefinedReconnectIntervals; + private pendingConnects; + /** + * Returns whether the realtime connection has been established. + */ + get isConnected(): boolean; + /** + * An optional hook that is invoked when the realtime client disconnects + * either when unsubscribing from all subscriptions or when the + * connection was interrupted or closed by the server. + * + * The received argument could be used to determine whether the disconnect + * is a result from unsubscribing (`activeSubscriptions.length == 0`) + * or because of network/server error (`activeSubscriptions.length > 0`). + * + * If you want to listen for the opposite, aka. when the client connection is established, + * subscribe to the `PB_CONNECT` event. + */ + onDisconnect?: (activeSubscriptions: Array) => void; + /** + * Register the subscription listener. + * + * You can subscribe multiple times to the same topic. + * + * If the SSE connection is not started yet, + * this method will also initialize it. + */ + subscribe(topic: string, callback: (data: any) => void, options?: SendOptions): Promise; + /** + * Unsubscribe from all subscription listeners with the specified topic. + * + * If `topic` is not provided, then this method will unsubscribe + * from all active subscriptions. + * + * This method is no-op if there are no active subscriptions. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribe(topic?: string): Promise; + /** + * Unsubscribe from all subscription listeners starting with the specified topic prefix. + * + * This method is no-op if there are no active subscriptions with the specified topic prefix. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribeByPrefix(keyPrefix: string): Promise; + /** + * Unsubscribe from all subscriptions matching the specified topic and listener function. + * + * This method is no-op if there are no active subscription with + * the specified topic and listener. + * + * The related sse connection will be autoclosed if after the + * unsubscribe operation there are no active subscriptions left. + */ + unsubscribeByTopicAndListener(topic: string, listener: EventListener): Promise; + private hasSubscriptionListeners; + private submitSubscriptions; + private getSubscriptionsCancelKey; + private getSubscriptionsByTopic; + private getNonEmptySubscriptionKeys; + private addAllSubscriptionListeners; + private removeAllSubscriptionListeners; + private connect; + private initConnect; + private hasUnsentSubscriptions; + private connectErrorHandler; + private disconnect; +} +declare abstract class CrudService extends BaseService { + /** + * Base path for the crud actions (without trailing slash, eg. '/admins'). + */ + abstract get baseCrudPath(): string; + /** + * Response data decoder. + */ + decode(data: { + [key: string]: any; + }): T; + /** + * Returns a promise with all list items batch fetched at once + * (by default 1000 items per request; to change it set the `batch` query param). + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + getFullList(options?: FullListOptions): Promise>; + /** + * Legacy version of getFullList with explicitly specified batch size. + */ + getFullList(batch?: number, options?: ListOptions): Promise>; + /** + * Returns paginated items list. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + getList(page?: number, perPage?: number, options?: ListOptions): Promise>; + /** + * Returns the first found item by the specified filter. + * + * Internally it calls `getList(1, 1, { filter, skipTotal })` and + * returns the first found item. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * For consistency with `getOne`, this method will throw a 404 + * ClientResponseError if no item was found. + * + * @throws {ClientResponseError} + */ + getFirstListItem(filter: string, options?: CommonOptions): Promise; + /** + * Returns single item by its id. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * If `id` is empty it will throw a 404 error. + * + * @throws {ClientResponseError} + */ + getOne(id: string, options?: CommonOptions): Promise; + /** + * Creates a new item. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Updates an existing item by its id. + * + * You can use the generic T to supply a wrapper type of the crud model. + * + * @throws {ClientResponseError} + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Deletes an existing item by its id. + * + * @throws {ClientResponseError} + */ + delete(id: string, options?: CommonOptions): Promise; + /** + * Returns a promise with all list items batch fetched at once. + */ + protected _getFullList(batchSize?: number, options?: ListOptions): Promise>; +} +interface RecordAuthResponse { + /** + * The signed PocketBase auth record. + */ + record: T; + /** + * The PocketBase record auth token. + * + * If you are looking for the OAuth2 access and refresh tokens + * they are available under the `meta.accessToken` and `meta.refreshToken` props. + */ + token: string; + /** + * Auth meta data usually filled when OAuth2 is used. + */ + meta?: { + [key: string]: any; + }; +} +interface AuthProviderInfo { + name: string; + displayName: string; + state: string; + authURL: string; + codeVerifier: string; + codeChallenge: string; + codeChallengeMethod: string; +} +interface AuthMethodsList { + mfa: { + enabled: boolean; + duration: number; + }; + otp: { + enabled: boolean; + duration: number; + }; + password: { + enabled: boolean; + identityFields: Array; + }; + oauth2: { + enabled: boolean; + providers: Array; + }; +} +interface RecordSubscription { + action: string; // eg. create, update, delete + record: T; +} +type OAuth2UrlCallback = (url: string) => void | Promise; +interface OAuth2AuthConfig extends SendOptions { + // the name of the OAuth2 provider (eg. "google") + provider: string; + // custom scopes to overwrite the default ones + scopes?: Array; + // optional record create data + createData?: { + [key: string]: any; + }; + // optional callback that is triggered after the OAuth2 sign-in/sign-up url generation + urlCallback?: OAuth2UrlCallback; + // optional query params to send with the PocketBase auth request (eg. fields, expand, etc.) + query?: RecordOptions; +} +interface OTPResponse { + otpId: string; +} +declare class RecordService extends CrudService { + readonly collectionIdOrName: string; + constructor(client: Client, collectionIdOrName: string); + /** + * @inheritdoc + */ + get baseCrudPath(): string; + /** + * Returns the current collection service base path. + */ + get baseCollectionPath(): string; + /** + * Returns whether the current service collection is superusers. + */ + get isSuperusers(): boolean; + // --------------------------------------------------------------- + // Realtime handlers + // --------------------------------------------------------------- + /** + * Subscribe to realtime changes to the specified topic ("*" or record id). + * + * If `topic` is the wildcard "*", then this method will subscribe to + * any record changes in the collection. + * + * If `topic` is a record id, then this method will subscribe only + * to changes of the specified record id. + * + * It's OK to subscribe multiple times to the same topic. + * You can use the returned `UnsubscribeFunc` to remove only a single subscription. + * Or use `unsubscribe(topic)` if you want to remove all subscriptions attached to the topic. + */ + subscribe(topic: string, callback: (data: RecordSubscription) => void, options?: RecordSubscribeOptions): Promise; + /** + * Unsubscribe from all subscriptions of the specified topic + * ("*" or record id). + * + * If `topic` is not set, then this method will unsubscribe from + * all subscriptions associated to the current collection. + */ + unsubscribe(topic?: string): Promise; + // --------------------------------------------------------------- + // Crud handers + // --------------------------------------------------------------- + /** + * @inheritdoc + */ + getFullList(options?: RecordFullListOptions): Promise>; + /** + * @inheritdoc + */ + getFullList(batch?: number, options?: RecordListOptions): Promise>; + /** + * @inheritdoc + */ + getList(page?: number, perPage?: number, options?: RecordListOptions): Promise>; + /** + * @inheritdoc + */ + getFirstListItem(filter: string, options?: RecordListOptions): Promise; + /** + * @inheritdoc + */ + getOne(id: string, options?: RecordOptions): Promise; + /** + * @inheritdoc + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): Promise; + /** + * @inheritdoc + * + * If the current `client.authStore.record` matches with the updated id, then + * on success the `client.authStore.record` will be updated with the new response record fields. + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): Promise; + /** + * @inheritdoc + * + * If the current `client.authStore.record` matches with the deleted id, + * then on success the `client.authStore` will be cleared. + */ + delete(id: string, options?: CommonOptions): Promise; + // --------------------------------------------------------------- + // Auth handlers + // --------------------------------------------------------------- + /** + * Prepare successful collection authorization response. + */ + protected authResponse(responseData: any): RecordAuthResponse; + /** + * Returns all available collection auth methods. + * + * @throws {ClientResponseError} + */ + listAuthMethods(options?: CommonOptions): Promise; + /** + * Authenticate a single auth collection record via its username/email and password. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * + * @throws {ClientResponseError} + */ + authWithPassword(usernameOrEmail: string, password: string, options?: RecordOptions): Promise>; + /** + * Authenticate a single auth collection record with OAuth2 code. + * + * If you don't have an OAuth2 code you may also want to check `authWithOAuth2` method. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * - the OAuth2 account data (eg. name, email, avatar, etc.) + * + * @throws {ClientResponseError} + */ + authWithOAuth2Code(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, options?: RecordOptions): Promise>; + /** + * @deprecated + * Consider using authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createdData, options?). + */ + authWithOAuth2Code(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, body?: any, query?: any): Promise>; + /** + * @deprecated This form of authWithOAuth2 is deprecated. + * + * Please use `authWithOAuth2Code()` OR its simplified realtime version + * as shown in https://pocketbase.io/docs/authentication/#oauth2-integration. + */ + authWithOAuth2(provider: string, code: string, codeVerifier: string, redirectURL: string, createData?: { + [key: string]: any; + }, bodyParams?: { + [key: string]: any; + }, queryParams?: RecordOptions): Promise>; + /** + * Authenticate a single auth collection record with OAuth2 + * **without custom redirects, deeplinks or even page reload**. + * + * This method initializes a one-off realtime subscription and will + * open a popup window with the OAuth2 vendor page to authenticate. + * Once the external OAuth2 sign-in/sign-up flow is completed, the popup + * window will be automatically closed and the OAuth2 data sent back + * to the user through the previously established realtime connection. + * + * You can specify an optional `urlCallback` prop to customize + * the default url `window.open` behavior. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * - the OAuth2 account data (eg. name, email, avatar, etc.) + * + * Example: + * + * ```js + * const authData = await pb.collection("users").authWithOAuth2({ + * provider: "google", + * }) + * ``` + * + * Note1: When creating the OAuth2 app in the provider dashboard + * you have to configure `https://yourdomain.com/api/oauth2-redirect` + * as redirect URL. + * + * Note2: Safari may block the default `urlCallback` popup because + * it doesn't allow `window.open` calls as part of an `async` click functions. + * To workaround this you can either change your click handler to not be marked as `async` + * OR manually call `window.open` before your `async` function and use the + * window reference in your own custom `urlCallback` (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061). + * For example: + * ```js + * + * ... + * document.getElementById("btn").addEventListener("click", () => { + * pb.collection("users").authWithOAuth2({ + * provider: "gitlab", + * }).then((authData) => { + * console.log(authData) + * }).catch((err) => { + * console.log(err, err.originalError); + * }); + * }) + * ``` + * + * @throws {ClientResponseError} + */ + authWithOAuth2(options: OAuth2AuthConfig): Promise>; + /** + * Refreshes the current authenticated record instance and + * returns a new token and record data. + * + * On success this method also automatically updates the client's AuthStore. + * + * @throws {ClientResponseError} + */ + authRefresh(options?: RecordOptions): Promise>; + /** + * @deprecated + * Consider using authRefresh(options?). + */ + authRefresh(body?: any, query?: any): Promise>; + /** + * Sends auth record password reset request. + * + * @throws {ClientResponseError} + */ + requestPasswordReset(email: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestPasswordReset(email, options?). + */ + requestPasswordReset(email: string, body?: any, query?: any): Promise; + /** + * Confirms auth record password reset request. + * + * @throws {ClientResponseError} + */ + confirmPasswordReset(passwordResetToken: string, password: string, passwordConfirm: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmPasswordReset(passwordResetToken, password, passwordConfirm, options?). + */ + confirmPasswordReset(passwordResetToken: string, password: string, passwordConfirm: string, body?: any, query?: any): Promise; + /** + * Sends auth record verification email request. + * + * @throws {ClientResponseError} + */ + requestVerification(email: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestVerification(email, options?). + */ + requestVerification(email: string, body?: any, query?: any): Promise; + /** + * Confirms auth record email verification request. + * + * If the current `client.authStore.record` matches with the auth record from the token, + * then on success the `client.authStore.record.verified` will be updated to `true`. + * + * @throws {ClientResponseError} + */ + confirmVerification(verificationToken: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmVerification(verificationToken, options?). + */ + confirmVerification(verificationToken: string, body?: any, query?: any): Promise; + /** + * Sends an email change request to the authenticated record model. + * + * @throws {ClientResponseError} + */ + requestEmailChange(newEmail: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using requestEmailChange(newEmail, options?). + */ + requestEmailChange(newEmail: string, body?: any, query?: any): Promise; + /** + * Confirms auth record's new email address. + * + * If the current `client.authStore.record` matches with the auth record from the token, + * then on success the `client.authStore` will be cleared. + * + * @throws {ClientResponseError} + */ + confirmEmailChange(emailChangeToken: string, password: string, options?: CommonOptions): Promise; + /** + * @deprecated + * Consider using confirmEmailChange(emailChangeToken, password, options?). + */ + confirmEmailChange(emailChangeToken: string, password: string, body?: any, query?: any): Promise; + /** + * @deprecated use collection("_externalAuths").* + * + * Lists all linked external auth providers for the specified auth record. + * + * @throws {ClientResponseError} + */ + listExternalAuths(recordId: string, options?: CommonOptions): Promise>; + /** + * @deprecated use collection("_externalAuths").* + * + * Unlink a single external auth provider from the specified auth record. + * + * @throws {ClientResponseError} + */ + unlinkExternalAuth(recordId: string, provider: string, options?: CommonOptions): Promise; + /** + * Sends auth record OTP to the provided email. + * + * @throws {ClientResponseError} + */ + requestOTP(email: string, options?: CommonOptions): Promise; + /** + * Authenticate a single auth collection record via OTP. + * + * On success, this method also automatically updates + * the client's AuthStore data and returns: + * - the authentication token + * - the authenticated record model + * + * @throws {ClientResponseError} + */ + authWithOTP(otpId: string, password: string, options?: CommonOptions): Promise>; + /** + * Impersonate authenticates with the specified recordId and + * returns a new client with the received auth token in a memory store. + * + * If `duration` is 0 the generated auth token will fallback + * to the default collection auth token duration. + * + * This action currently requires superusers privileges. + * + * @throws {ClientResponseError} + */ + impersonate(recordId: string, duration: number, options?: CommonOptions): Promise; + // --------------------------------------------------------------- + // very rudimentary url query params replacement because at the moment + // URL (and URLSearchParams) doesn't seem to be fully supported in React Native + // + // note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html + private _replaceQueryParams; +} +declare class CollectionService extends CrudService { + /** + * @inheritdoc + */ + get baseCrudPath(): string; + /** + * Imports the provided collections. + * + * If `deleteMissing` is `true`, all local collections and their fields, + * that are not present in the imported configuration, WILL BE DELETED + * (including their related records data)! + * + * @throws {ClientResponseError} + */ + import(collections: Array, deleteMissing?: boolean, options?: CommonOptions): Promise; + /** + * Returns type indexed map with scaffolded collection models + * populated with their default field values. + * + * @throws {ClientResponseError} + */ + getScaffolds(options?: CommonOptions): Promise<{ + [key: string]: CollectionModel; + }>; + /** + * Deletes all records associated with the specified collection. + * + * @throws {ClientResponseError} + */ + truncate(collectionIdOrName: string, options?: CommonOptions): Promise; +} +interface HourlyStats { + total: number; + date: string; +} +declare class LogService extends BaseService { + /** + * Returns paginated logs list. + * + * @throws {ClientResponseError} + */ + getList(page?: number, perPage?: number, options?: ListOptions): Promise>; + /** + * Returns a single log by its id. + * + * If `id` is empty it will throw a 404 error. + * + * @throws {ClientResponseError} + */ + getOne(id: string, options?: CommonOptions): Promise; + /** + * Returns logs statistics. + * + * @throws {ClientResponseError} + */ + getStats(options?: LogStatsOptions): Promise>; +} +interface HealthCheckResponse { + code: number; + message: string; + data: { + [key: string]: any; + }; +} +declare class HealthService extends BaseService { + /** + * Checks the health status of the api. + * + * @throws {ClientResponseError} + */ + check(options?: CommonOptions): Promise; +} +declare class FileService extends BaseService { + /** + * @deprecated Please replace with `pb.files.getURL()`. + */ + getUrl(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * Builds and returns an absolute record file url for the provided filename. + */ + getURL(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * Requests a new private file access token for the current auth model. + * + * @throws {ClientResponseError} + */ + getToken(options?: CommonOptions): Promise; +} +interface BackupFileInfo { + key: string; + size: number; + modified: string; +} +declare class BackupService extends BaseService { + /** + * Returns list with all available backup files. + * + * @throws {ClientResponseError} + */ + getFullList(options?: CommonOptions): Promise>; + /** + * Initializes a new backup. + * + * @throws {ClientResponseError} + */ + create(basename: string, options?: CommonOptions): Promise; + /** + * Uploads an existing backup file. + * + * Example: + * + * ```js + * await pb.backups.upload({ + * file: new Blob([...]), + * }); + * ``` + * + * @throws {ClientResponseError} + */ + upload(bodyParams: { + [key: string]: any; + } | FormData, options?: CommonOptions): Promise; + /** + * Deletes a single backup file. + * + * @throws {ClientResponseError} + */ + delete(key: string, options?: CommonOptions): Promise; + /** + * Initializes an app data restore from an existing backup. + * + * @throws {ClientResponseError} + */ + restore(key: string, options?: CommonOptions): Promise; + /** + * @deprecated Please use `getDownloadURL()`. + */ + getDownloadUrl(token: string, key: string): string; + /** + * Builds a download url for a single existing backup using a + * superuser file token and the backup file key. + * + * The file token can be generated via `pb.files.getToken()`. + */ + getDownloadURL(token: string, key: string): string; +} +interface CronJob { + id: string; + expression: string; +} +declare class CronService extends BaseService { + /** + * Returns list with all registered cron jobs. + * + * @throws {ClientResponseError} + */ + getFullList(options?: CommonOptions): Promise>; + /** + * Runs the specified cron job. + * + * @throws {ClientResponseError} + */ + run(jobId: string, options?: CommonOptions): Promise; +} +interface BatchRequest { + method: string; + url: string; + json?: { + [key: string]: any; + }; + files?: { + [key: string]: Array; + }; + headers?: { + [key: string]: string; + }; +} +interface BatchRequestResult { + status: number; + body: any; +} +declare class BatchService extends BaseService { + private requests; + private subs; + /** + * Starts constructing a batch request entry for the specified collection. + */ + collection(collectionIdOrName: string): SubBatchService; + /** + * Sends the batch requests. + * + * @throws {ClientResponseError} + */ + send(options?: SendOptions): Promise>; +} +declare class SubBatchService { + private requests; + private readonly collectionIdOrName; + constructor(requests: Array, collectionIdOrName: string); + /** + * Registers a record upsert request into the current batch queue. + * + * The request will be executed as update if `bodyParams` have a valid existing record `id` value, otherwise - create. + */ + upsert(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record create request into the current batch queue. + */ + create(bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record update request into the current batch queue. + */ + update(id: string, bodyParams?: { + [key: string]: any; + } | FormData, options?: RecordOptions): void; + /** + * Registers a record delete request into the current batch queue. + */ + delete(id: string, options?: SendOptions): void; + private prepareRequest; +} +interface BeforeSendResult { + [key: string]: any; + url?: string; + options?: { + [key: string]: any; + }; +} +/** + * PocketBase JS Client. + */ +declare class Client { + /** + * The base PocketBase backend url address (eg. 'http://127.0.0.1.8090'). + */ + baseURL: string; + /** + * Legacy getter alias for baseURL. + * @deprecated Please replace with baseURL. + */ + get baseUrl(): string; + /** + * Legacy setter alias for baseURL. + * @deprecated Please replace with baseURL. + */ + set baseUrl(v: string); + /** + * Hook that get triggered right before sending the fetch request, + * allowing you to inspect and modify the url and request options. + * + * For list of the possible options check https://developer.mozilla.org/en-US/docs/Web/API/fetch#options + * + * You can return a non-empty result object `{ url, options }` to replace the url and request options entirely. + * + * Example: + * ```js + * const pb = new PocketBase("https://example.com") + * + * pb.beforeSend = function (url, options) { + * options.headers = Object.assign({}, options.headers, { + * 'X-Custom-Header': 'example', + * }) + * + * return { url, options } + * } + * + * // use the created client as usual... + * ``` + */ + beforeSend?: (url: string, options: SendOptions) => BeforeSendResult | Promise; + /** + * Hook that get triggered after successfully sending the fetch request, + * allowing you to inspect/modify the response object and its parsed data. + * + * Returns the new Promise resolved `data` that will be returned to the client. + * + * Example: + * ```js + * const pb = new PocketBase("https://example.com") + * + * pb.afterSend = function (response, data, options) { + * if (response.status != 200) { + * throw new ClientResponseError({ + * url: response.url, + * status: response.status, + * response: { ... }, + * }) + * } + * + * return data; + * } + * + * // use the created client as usual... + * ``` + */ + afterSend?: ((response: Response, data: any) => any) & ((response: Response, data: any, options: SendOptions) => any); + /** + * Optional language code (default to `en-US`) that will be sent + * with the requests to the server as `Accept-Language` header. + */ + lang: string; + /** + * A replaceable instance of the local auth store service. + */ + authStore: BaseAuthStore; + /** + * An instance of the service that handles the **Settings APIs**. + */ + readonly settings: SettingsService; + /** + * An instance of the service that handles the **Collection APIs**. + */ + readonly collections: CollectionService; + /** + * An instance of the service that handles the **File APIs**. + */ + readonly files: FileService; + /** + * An instance of the service that handles the **Log APIs**. + */ + readonly logs: LogService; + /** + * An instance of the service that handles the **Realtime APIs**. + */ + readonly realtime: RealtimeService; + /** + * An instance of the service that handles the **Health APIs**. + */ + readonly health: HealthService; + /** + * An instance of the service that handles the **Backup APIs**. + */ + readonly backups: BackupService; + /** + * An instance of the service that handles the **Cron APIs**. + */ + readonly crons: CronService; + private cancelControllers; + private recordServices; + private enableAutoCancellation; + constructor(baseURL?: string, authStore?: BaseAuthStore | null, lang?: string); + /** + * @deprecated + * With PocketBase v0.23.0 admins are converted to a regular auth + * collection named "_superusers", aka. you can use directly collection("_superusers"). + */ + get admins(): RecordService; + /** + * Creates a new batch handler for sending multiple transactional + * create/update/upsert/delete collection requests in one network call. + * + * Example: + * ```js + * const batch = pb.createBatch(); + * + * batch.collection("example1").create({ ... }) + * batch.collection("example2").update("RECORD_ID", { ... }) + * batch.collection("example3").delete("RECORD_ID") + * batch.collection("example4").upsert({ ... }) + * + * await batch.send() + * ``` + */ + /** + * Creates a new batch handler for sending multiple transactional + * create/update/upsert/delete collection requests in one network call. + * + * Example: + * ```js + * const batch = pb.createBatch(); + * + * batch.collection("example1").create({ ... }) + * batch.collection("example2").update("RECORD_ID", { ... }) + * batch.collection("example3").delete("RECORD_ID") + * batch.collection("example4").upsert({ ... }) + * + * await batch.send() + * ``` + */ + createBatch(): BatchService; + /** + * Returns the RecordService associated to the specified collection. + */ + /** + * Returns the RecordService associated to the specified collection. + */ + collection(idOrName: string): RecordService; + /** + * Globally enable or disable auto cancellation for pending duplicated requests. + */ + /** + * Globally enable or disable auto cancellation for pending duplicated requests. + */ + autoCancellation(enable: boolean): Client; + /** + * Cancels single request by its cancellation key. + */ + /** + * Cancels single request by its cancellation key. + */ + cancelRequest(requestKey: string): Client; + /** + * Cancels all pending requests. + */ + /** + * Cancels all pending requests. + */ + cancelAllRequests(): Client; + /** + * Constructs a filter expression with placeholders populated from a parameters object. + * + * Placeholder parameters are defined with the `{:paramName}` notation. + * + * The following parameter values are supported: + * + * - `string` (_single quotes are autoescaped_) + * - `number` + * - `boolean` + * - `Date` object (_stringified into the PocketBase datetime format_) + * - `null` + * - everything else is converted to a string using `JSON.stringify()` + * + * Example: + * + * ```js + * pb.collection("example").getFirstListItem(pb.filter( + * 'title ~ {:title} && created >= {:created}', + * { title: "example", created: new Date()} + * )) + * ``` + */ + /** + * Constructs a filter expression with placeholders populated from a parameters object. + * + * Placeholder parameters are defined with the `{:paramName}` notation. + * + * The following parameter values are supported: + * + * - `string` (_single quotes are autoescaped_) + * - `number` + * - `boolean` + * - `Date` object (_stringified into the PocketBase datetime format_) + * - `null` + * - everything else is converted to a string using `JSON.stringify()` + * + * Example: + * + * ```js + * pb.collection("example").getFirstListItem(pb.filter( + * 'title ~ {:title} && created >= {:created}', + * { title: "example", created: new Date()} + * )) + * ``` + */ + filter(raw: string, params?: { + [key: string]: any; + }): string; + /** + * @deprecated Please use `pb.files.getURL()`. + */ + /** + * @deprecated Please use `pb.files.getURL()`. + */ + getFileUrl(record: { + [key: string]: any; + }, filename: string, queryParams?: FileOptions): string; + /** + * @deprecated Please use `pb.buildURL()`. + */ + /** + * @deprecated Please use `pb.buildURL()`. + */ + buildUrl(path: string): string; + /** + * Builds a full client url by safely concatenating the provided path. + */ + /** + * Builds a full client url by safely concatenating the provided path. + */ + buildURL(path: string): string; + /** + * Sends an api http request. + * + * @throws {ClientResponseError} + */ + /** + * Sends an api http request. + * + * @throws {ClientResponseError} + */ + send(path: string, options: SendOptions): Promise; + /** + * Shallow copy the provided object and takes care to initialize + * any options required to preserve the backward compatability. + * + * @param {SendOptions} options + * @return {SendOptions} + */ + /** + * Shallow copy the provided object and takes care to initialize + * any options required to preserve the backward compatability. + * + * @param {SendOptions} options + * @return {SendOptions} + */ + private initSendOptions; + /** + * Extracts the header with the provided name in case-insensitive manner. + * Returns `null` if no header matching the name is found. + */ + /** + * Extracts the header with the provided name in case-insensitive manner. + * Returns `null` if no header matching the name is found. + */ + private getHeader; +} +export { BeforeSendResult, Client as default }; diff --git a/script/node_modules/pocketbase/dist/pocketbase.umd.js b/script/node_modules/pocketbase/dist/pocketbase.umd.js new file mode 100644 index 0000000..5eac5ad --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.umd.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).PocketBase=t()}(this,(function(){"use strict";class ClientResponseError extends Error{constructor(e){super("ClientResponseError"),this.url="",this.status=0,this.response={},this.isAbort=!1,this.originalError=null,Object.setPrototypeOf(this,ClientResponseError.prototype),null!==e&&"object"==typeof e&&(this.originalError=e.originalError,this.url="string"==typeof e.url?e.url:"",this.status="number"==typeof e.status?e.status:0,this.isAbort=!!e.isAbort||"AbortError"===e.name||"Aborted"===e.message,null!==e.response&&"object"==typeof e.response?this.response=e.response:null!==e.data&&"object"==typeof e.data?this.response=e.data:this.response={}),this.originalError||e instanceof ClientResponseError||(this.originalError=e),this.name="ClientResponseError "+this.status,this.message=this.response?.message,this.message||(this.isAbort?this.message="The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).":this.originalError?.cause?.message?.includes("ECONNREFUSED ::1")?this.message="Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).":this.message="Something went wrong."),this.cause=this.originalError}get data(){return this.response}toJSON(){return{...this}}}const e=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function cookieSerialize(t,s,i){const n=Object.assign({},i||{}),r=n.encode||defaultEncode;if(!e.test(t))throw new TypeError("argument name is invalid");const o=r(s);if(o&&!e.test(o))throw new TypeError("argument val is invalid");let a=t+"="+o;if(null!=n.maxAge){const e=n.maxAge-0;if(isNaN(e)||!isFinite(e))throw new TypeError("option maxAge is invalid");a+="; Max-Age="+Math.floor(e)}if(n.domain){if(!e.test(n.domain))throw new TypeError("option domain is invalid");a+="; Domain="+n.domain}if(n.path){if(!e.test(n.path))throw new TypeError("option path is invalid");a+="; Path="+n.path}if(n.expires){if(!function isDate(e){return"[object Date]"===Object.prototype.toString.call(e)||e instanceof Date}(n.expires)||isNaN(n.expires.valueOf()))throw new TypeError("option expires is invalid");a+="; Expires="+n.expires.toUTCString()}if(n.httpOnly&&(a+="; HttpOnly"),n.secure&&(a+="; Secure"),n.priority){switch("string"==typeof n.priority?n.priority.toLowerCase():n.priority){case"low":a+="; Priority=Low";break;case"medium":a+="; Priority=Medium";break;case"high":a+="; Priority=High";break;default:throw new TypeError("option priority is invalid")}}if(n.sameSite){switch("string"==typeof n.sameSite?n.sameSite.toLowerCase():n.sameSite){case!0:a+="; SameSite=Strict";break;case"lax":a+="; SameSite=Lax";break;case"strict":a+="; SameSite=Strict";break;case"none":a+="; SameSite=None";break;default:throw new TypeError("option sameSite is invalid")}}return a}function defaultDecode(e){return-1!==e.indexOf("%")?decodeURIComponent(e):e}function defaultEncode(e){return encodeURIComponent(e)}const t="undefined"!=typeof navigator&&"ReactNative"===navigator.product||"undefined"!=typeof global&&global.HermesInternal;let s;function getTokenPayload(e){if(e)try{const t=decodeURIComponent(s(e.split(".")[1]).split("").map((function(e){return"%"+("00"+e.charCodeAt(0).toString(16)).slice(-2)})).join(""));return JSON.parse(t)||{}}catch(e){}return{}}function isTokenExpired(e,t=0){let s=getTokenPayload(e);return!(Object.keys(s).length>0&&(!s.exp||s.exp-t>Date.now()/1e3))}s="function"!=typeof atob||t?e=>{let t=String(e).replace(/=+$/,"");if(t.length%4==1)throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");for(var s,i,n=0,r=0,o="";i=t.charAt(r++);~i&&(s=n%4?64*s+i:i,n++%4)?o+=String.fromCharCode(255&s>>(-2*n&6)):0)i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(i);return o}:atob;const i="pb_auth";class BaseAuthStore{constructor(){this.baseToken="",this.baseModel=null,this._onChangeCallbacks=[]}get token(){return this.baseToken}get record(){return this.baseModel}get model(){return this.baseModel}get isValid(){return!isTokenExpired(this.token)}get isSuperuser(){let e=getTokenPayload(this.token);return"auth"==e.type&&("_superusers"==this.record?.collectionName||!this.record?.collectionName&&"pbc_3142635823"==e.collectionId)}get isAdmin(){return console.warn("Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName"),this.isSuperuser}get isAuthRecord(){return console.warn("Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName"),"auth"==getTokenPayload(this.token).type&&!this.isSuperuser}save(e,t){this.baseToken=e||"",this.baseModel=t||null,this.triggerChange()}clear(){this.baseToken="",this.baseModel=null,this.triggerChange()}loadFromCookie(e,t=i){const s=function cookieParse(e,t){const s={};if("string"!=typeof e)return s;const i=Object.assign({},{}).decode||defaultDecode;let n=0;for(;n4096){r.record={id:r.record?.id,email:r.record?.email};const s=["collectionId","collectionName","verified"];for(const e in this.record)s.includes(e)&&(r.record[e]=this.record[e]);o=cookieSerialize(t,JSON.stringify(r),e)}return o}onChange(e,t=!1){return this._onChangeCallbacks.push(e),t&&e(this.token,this.record),()=>{for(let t=this._onChangeCallbacks.length-1;t>=0;t--)if(this._onChangeCallbacks[t]==e)return delete this._onChangeCallbacks[t],void this._onChangeCallbacks.splice(t,1)}}triggerChange(){for(const e of this._onChangeCallbacks)e&&e(this.token,this.record)}}class LocalAuthStore extends BaseAuthStore{constructor(e="pocketbase_auth"){super(),this.storageFallback={},this.storageKey=e,this._bindStorageEvent()}get token(){return(this._storageGet(this.storageKey)||{}).token||""}get record(){const e=this._storageGet(this.storageKey)||{};return e.record||e.model||null}get model(){return this.record}save(e,t){this._storageSet(this.storageKey,{token:e,record:t}),super.save(e,t)}clear(){this._storageRemove(this.storageKey),super.clear()}_storageGet(e){if("undefined"!=typeof window&&window?.localStorage){const t=window.localStorage.getItem(e)||"";try{return JSON.parse(t)}catch(e){return t}}return this.storageFallback[e]}_storageSet(e,t){if("undefined"!=typeof window&&window?.localStorage){let s=t;"string"!=typeof t&&(s=JSON.stringify(t)),window.localStorage.setItem(e,s)}else this.storageFallback[e]=t}_storageRemove(e){"undefined"!=typeof window&&window?.localStorage&&window.localStorage?.removeItem(e),delete this.storageFallback[e]}_bindStorageEvent(){"undefined"!=typeof window&&window?.localStorage&&window.addEventListener&&window.addEventListener("storage",(e=>{if(e.key!=this.storageKey)return;const t=this._storageGet(this.storageKey)||{};super.save(t.token||"",t.record||t.model||null)}))}}class BaseService{constructor(e){this.client=e}}class SettingsService extends BaseService{async getAll(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/settings",e)}async update(e,t){return t=Object.assign({method:"PATCH",body:e},t),this.client.send("/api/settings",t)}async testS3(e="storage",t){return t=Object.assign({method:"POST",body:{filesystem:e}},t),this.client.send("/api/settings/test/s3",t).then((()=>!0))}async testEmail(e,t,s,i){return i=Object.assign({method:"POST",body:{email:t,template:s,collection:e}},i),this.client.send("/api/settings/test/email",i).then((()=>!0))}async generateAppleClientSecret(e,t,s,i,n,r){return r=Object.assign({method:"POST",body:{clientId:e,teamId:t,keyId:s,privateKey:i,duration:n}},r),this.client.send("/api/settings/apple/generate-client-secret",r)}}const n=["requestKey","$cancelKey","$autoCancel","fetch","headers","body","query","params","cache","credentials","headers","integrity","keepalive","method","mode","redirect","referrer","referrerPolicy","signal","window"];function normalizeUnknownQueryParams(e){if(e){e.query=e.query||{};for(let t in e)n.includes(t)||(e.query[t]=e[t],delete e[t])}}function serializeQueryParams(e){const t=[];for(const s in e){const i=encodeURIComponent(s),n=Array.isArray(e[s])?e[s]:[e[s]];for(let e of n)e=prepareQueryParamValue(e),null!==e&&t.push(i+"="+e)}return t.join("&")}function prepareQueryParamValue(e){return null==e?null:e instanceof Date?encodeURIComponent(e.toISOString().replace("T"," ")):"object"==typeof e?encodeURIComponent(JSON.stringify(e)):encodeURIComponent(e)}class RealtimeService extends BaseService{constructor(){super(...arguments),this.clientId="",this.eventSource=null,this.subscriptions={},this.lastSentSubscriptions=[],this.maxConnectTimeout=15e3,this.reconnectAttempts=0,this.maxReconnectAttempts=1/0,this.predefinedReconnectIntervals=[200,300,500,1e3,1200,1500,2e3],this.pendingConnects=[]}get isConnected(){return!!this.eventSource&&!!this.clientId&&!this.pendingConnects.length}async subscribe(e,t,s){if(!e)throw new Error("topic must be set.");let i=e;if(s){normalizeUnknownQueryParams(s=Object.assign({},s));const e="options="+encodeURIComponent(JSON.stringify({query:s.query,headers:s.headers}));i+=(i.includes("?")?"&":"?")+e}const listener=function(e){const s=e;let i;try{i=JSON.parse(s?.data)}catch{}t(i||{})};return this.subscriptions[i]||(this.subscriptions[i]=[]),this.subscriptions[i].push(listener),this.isConnected?1===this.subscriptions[i].length?await this.submitSubscriptions():this.eventSource?.addEventListener(i,listener):await this.connect(),async()=>this.unsubscribeByTopicAndListener(e,listener)}async unsubscribe(e){let t=!1;if(e){const s=this.getSubscriptionsByTopic(e);for(let e in s)if(this.hasSubscriptionListeners(e)){for(let t of this.subscriptions[e])this.eventSource?.removeEventListener(e,t);delete this.subscriptions[e],t||(t=!0)}}else this.subscriptions={};this.hasSubscriptionListeners()?t&&await this.submitSubscriptions():this.disconnect()}async unsubscribeByPrefix(e){let t=!1;for(let s in this.subscriptions)if((s+"?").startsWith(e)){t=!0;for(let e of this.subscriptions[s])this.eventSource?.removeEventListener(s,e);delete this.subscriptions[s]}t&&(this.hasSubscriptionListeners()?await this.submitSubscriptions():this.disconnect())}async unsubscribeByTopicAndListener(e,t){let s=!1;const i=this.getSubscriptionsByTopic(e);for(let e in i){if(!Array.isArray(this.subscriptions[e])||!this.subscriptions[e].length)continue;let i=!1;for(let s=this.subscriptions[e].length-1;s>=0;s--)this.subscriptions[e][s]===t&&(i=!0,delete this.subscriptions[e][s],this.subscriptions[e].splice(s,1),this.eventSource?.removeEventListener(e,t));i&&(this.subscriptions[e].length||delete this.subscriptions[e],s||this.hasSubscriptionListeners(e)||(s=!0))}this.hasSubscriptionListeners()?s&&await this.submitSubscriptions():this.disconnect()}hasSubscriptionListeners(e){if(this.subscriptions=this.subscriptions||{},e)return!!this.subscriptions[e]?.length;for(let e in this.subscriptions)if(this.subscriptions[e]?.length)return!0;return!1}async submitSubscriptions(){if(this.clientId)return this.addAllSubscriptionListeners(),this.lastSentSubscriptions=this.getNonEmptySubscriptionKeys(),this.client.send("/api/realtime",{method:"POST",body:{clientId:this.clientId,subscriptions:this.lastSentSubscriptions},requestKey:this.getSubscriptionsCancelKey()}).catch((e=>{if(!e?.isAbort)throw e}))}getSubscriptionsCancelKey(){return"realtime_"+this.clientId}getSubscriptionsByTopic(e){const t={};e=e.includes("?")?e:e+"?";for(let s in this.subscriptions)(s+"?").startsWith(e)&&(t[s]=this.subscriptions[s]);return t}getNonEmptySubscriptionKeys(){const e=[];for(let t in this.subscriptions)this.subscriptions[t].length&&e.push(t);return e}addAllSubscriptionListeners(){if(this.eventSource){this.removeAllSubscriptionListeners();for(let e in this.subscriptions)for(let t of this.subscriptions[e])this.eventSource.addEventListener(e,t)}}removeAllSubscriptionListeners(){if(this.eventSource)for(let e in this.subscriptions)for(let t of this.subscriptions[e])this.eventSource.removeEventListener(e,t)}async connect(){if(!(this.reconnectAttempts>0))return new Promise(((e,t)=>{this.pendingConnects.push({resolve:e,reject:t}),this.pendingConnects.length>1||this.initConnect()}))}initConnect(){this.disconnect(!0),clearTimeout(this.connectTimeoutId),this.connectTimeoutId=setTimeout((()=>{this.connectErrorHandler(new Error("EventSource connect took too long."))}),this.maxConnectTimeout),this.eventSource=new EventSource(this.client.buildURL("/api/realtime")),this.eventSource.onerror=e=>{this.connectErrorHandler(new Error("Failed to establish realtime connection."))},this.eventSource.addEventListener("PB_CONNECT",(e=>{const t=e;this.clientId=t?.lastEventId,this.submitSubscriptions().then((async()=>{let e=3;for(;this.hasUnsentSubscriptions()&&e>0;)e--,await this.submitSubscriptions()})).then((()=>{for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[],this.reconnectAttempts=0,clearTimeout(this.reconnectTimeoutId),clearTimeout(this.connectTimeoutId);const t=this.getSubscriptionsByTopic("PB_CONNECT");for(let s in t)for(let i of t[s])i(e)})).catch((e=>{this.clientId="",this.connectErrorHandler(e)}))}))}hasUnsentSubscriptions(){const e=this.getNonEmptySubscriptionKeys();if(e.length!=this.lastSentSubscriptions.length)return!0;for(const t of e)if(!this.lastSentSubscriptions.includes(t))return!0;return!1}connectErrorHandler(e){if(clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),!this.clientId&&!this.reconnectAttempts||this.reconnectAttempts>this.maxReconnectAttempts){for(let t of this.pendingConnects)t.reject(new ClientResponseError(e));return this.pendingConnects=[],void this.disconnect()}this.disconnect(!0);const t=this.predefinedReconnectIntervals[this.reconnectAttempts]||this.predefinedReconnectIntervals[this.predefinedReconnectIntervals.length-1];this.reconnectAttempts++,this.reconnectTimeoutId=setTimeout((()=>{this.initConnect()}),t)}disconnect(e=!1){if(this.clientId&&this.onDisconnect&&this.onDisconnect(Object.keys(this.subscriptions)),clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),this.removeAllSubscriptionListeners(),this.client.cancelRequest(this.getSubscriptionsCancelKey()),this.eventSource?.close(),this.eventSource=null,this.clientId="",!e){this.reconnectAttempts=0;for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[]}}}class CrudService extends BaseService{decode(e){return e}async getFullList(e,t){if("number"==typeof e)return this._getFullList(e,t);let s=1e3;return(t=Object.assign({},e,t)).batch&&(s=t.batch,delete t.batch),this._getFullList(s,t)}async getList(e=1,t=30,s){return(s=Object.assign({method:"GET"},s)).query=Object.assign({page:e,perPage:t},s.query),this.client.send(this.baseCrudPath,s).then((e=>(e.items=e.items?.map((e=>this.decode(e)))||[],e)))}async getFirstListItem(e,t){return(t=Object.assign({requestKey:"one_by_filter_"+this.baseCrudPath+"_"+e},t)).query=Object.assign({filter:e,skipTotal:1},t.query),this.getList(1,1,t).then((e=>{if(!e?.items?.length)throw new ClientResponseError({status:404,response:{code:404,message:"The requested resource wasn't found.",data:{}}});return e.items[0]}))}async getOne(e,t){if(!e)throw new ClientResponseError({url:this.client.buildURL(this.baseCrudPath+"/"),status:404,response:{code:404,message:"Missing required record id.",data:{}}});return t=Object.assign({method:"GET"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),t).then((e=>this.decode(e)))}async create(e,t){return t=Object.assign({method:"POST",body:e},t),this.client.send(this.baseCrudPath,t).then((e=>this.decode(e)))}async update(e,t,s){return s=Object.assign({method:"PATCH",body:t},s),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),s).then((e=>this.decode(e)))}async delete(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e),t).then((()=>!0))}_getFullList(e=1e3,t){(t=t||{}).query=Object.assign({skipTotal:1},t.query);let s=[],request=async i=>this.getList(i,e||1e3,t).then((e=>{const t=e.items;return s=s.concat(t),t.length==e.perPage?request(i+1):s}));return request(1)}}function normalizeLegacyOptionsArgs(e,t,s,i){const n=void 0!==i;return n||void 0!==s?n?(console.warn(e),t.body=Object.assign({},t.body,s),t.query=Object.assign({},t.query,i),t):Object.assign(t,s):t}function resetAutoRefresh(e){e._resetAutoRefresh?.()}class RecordService extends CrudService{constructor(e,t){super(e),this.collectionIdOrName=t}get baseCrudPath(){return this.baseCollectionPath+"/records"}get baseCollectionPath(){return"/api/collections/"+encodeURIComponent(this.collectionIdOrName)}get isSuperusers(){return"_superusers"==this.collectionIdOrName||"_pbc_2773867675"==this.collectionIdOrName}async subscribe(e,t,s){if(!e)throw new Error("Missing topic.");if(!t)throw new Error("Missing subscription callback.");return this.client.realtime.subscribe(this.collectionIdOrName+"/"+e,t,s)}async unsubscribe(e){return e?this.client.realtime.unsubscribe(this.collectionIdOrName+"/"+e):this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName)}async getFullList(e,t){if("number"==typeof e)return super.getFullList(e,t);const s=Object.assign({},e,t);return super.getFullList(s)}async getList(e=1,t=30,s){return super.getList(e,t,s)}async getFirstListItem(e,t){return super.getFirstListItem(e,t)}async getOne(e,t){return super.getOne(e,t)}async create(e,t){return super.create(e,t)}async update(e,t,s){return super.update(e,t,s).then((e=>{if(this.client.authStore.record?.id===e?.id&&(this.client.authStore.record?.collectionId===this.collectionIdOrName||this.client.authStore.record?.collectionName===this.collectionIdOrName)){let t=Object.assign({},this.client.authStore.record.expand),s=Object.assign({},this.client.authStore.record,e);t&&(s.expand=Object.assign(t,e.expand)),this.client.authStore.save(this.client.authStore.token,s)}return e}))}async delete(e,t){return super.delete(e,t).then((t=>(!t||this.client.authStore.record?.id!==e||this.client.authStore.record?.collectionId!==this.collectionIdOrName&&this.client.authStore.record?.collectionName!==this.collectionIdOrName||this.client.authStore.clear(),t)))}authResponse(e){const t=this.decode(e?.record||{});return this.client.authStore.save(e?.token,t),Object.assign({},e,{token:e?.token||"",record:t})}async listAuthMethods(e){return e=Object.assign({method:"GET",fields:"mfa,otp,password,oauth2"},e),this.client.send(this.baseCollectionPath+"/auth-methods",e)}async authWithPassword(e,t,s){let i;s=Object.assign({method:"POST",body:{identity:e,password:t}},s),this.isSuperusers&&(i=s.autoRefreshThreshold,delete s.autoRefreshThreshold,s.autoRefresh||resetAutoRefresh(this.client));let n=await this.client.send(this.baseCollectionPath+"/auth-with-password",s);return n=this.authResponse(n),i&&this.isSuperusers&&function registerAutoRefresh(e,t,s,i){resetAutoRefresh(e);const n=e.beforeSend,r=e.authStore.record,o=e.authStore.onChange(((t,s)=>{(!t||s?.id!=r?.id||(s?.collectionId||r?.collectionId)&&s?.collectionId!=r?.collectionId)&&resetAutoRefresh(e)}));e._resetAutoRefresh=function(){o(),e.beforeSend=n,delete e._resetAutoRefresh},e.beforeSend=async(r,o)=>{const a=e.authStore.token;if(o.query?.autoRefresh)return n?n(r,o):{url:r,sendOptions:o};let c=e.authStore.isValid;if(c&&isTokenExpired(e.authStore.token,t))try{await s()}catch(e){c=!1}c||await i();const l=o.headers||{};for(let t in l)if("authorization"==t.toLowerCase()&&a==l[t]&&e.authStore.token){l[t]=e.authStore.token;break}return o.headers=l,n?n(r,o):{url:r,sendOptions:o}}}(this.client,i,(()=>this.authRefresh({autoRefresh:!0})),(()=>this.authWithPassword(e,t,Object.assign({autoRefresh:!0},s)))),n}async authWithOAuth2Code(e,t,s,i,n,r,o){let a={method:"POST",body:{provider:e,code:t,codeVerifier:s,redirectURL:i,createData:n}};return a=normalizeLegacyOptionsArgs("This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).",a,r,o),this.client.send(this.baseCollectionPath+"/auth-with-oauth2",a).then((e=>this.authResponse(e)))}authWithOAuth2(...e){if(e.length>1||"string"==typeof e?.[0])return console.warn("PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration."),this.authWithOAuth2Code(e?.[0]||"",e?.[1]||"",e?.[2]||"",e?.[3]||"",e?.[4]||{},e?.[5]||{},e?.[6]||{});const t=e?.[0]||{};let s=null;t.urlCallback||(s=openBrowserPopup(void 0));const i=new RealtimeService(this.client);function cleanup(){s?.close(),i.unsubscribe()}const n={},r=t.requestKey;return r&&(n.requestKey=r),this.listAuthMethods(n).then((e=>{const n=e.oauth2.providers.find((e=>e.name===t.provider));if(!n)throw new ClientResponseError(new Error(`Missing or invalid provider "${t.provider}".`));const o=this.client.buildURL("/api/oauth2-redirect");return new Promise((async(e,a)=>{const c=r?this.client.cancelControllers?.[r]:void 0;c&&(c.signal.onabort=()=>{cleanup(),a(new ClientResponseError({isAbort:!0,message:"manually cancelled"}))}),i.onDisconnect=e=>{e.length&&a&&(cleanup(),a(new ClientResponseError(new Error("realtime connection interrupted"))))};try{await i.subscribe("@oauth2",(async s=>{const r=i.clientId;try{if(!s.state||r!==s.state)throw new Error("State parameters don't match.");if(s.error||!s.code)throw new Error("OAuth2 redirect error or missing code: "+s.error);const i=Object.assign({},t);delete i.provider,delete i.scopes,delete i.createData,delete i.urlCallback,c?.signal?.onabort&&(c.signal.onabort=null);const a=await this.authWithOAuth2Code(n.name,s.code,n.codeVerifier,o,t.createData,i);e(a)}catch(e){a(new ClientResponseError(e))}cleanup()}));const r={state:i.clientId};t.scopes?.length&&(r.scope=t.scopes.join(" "));const l=this._replaceQueryParams(n.authURL+o,r);let h=t.urlCallback||function(e){s?s.location.href=e:s=openBrowserPopup(e)};await h(l)}catch(e){c?.signal?.onabort&&(c.signal.onabort=null),cleanup(),a(new ClientResponseError(e))}}))})).catch((e=>{throw cleanup(),e}))}async authRefresh(e,t){let s={method:"POST"};return s=normalizeLegacyOptionsArgs("This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).",s,e,t),this.client.send(this.baseCollectionPath+"/auth-refresh",s).then((e=>this.authResponse(e)))}async requestPasswordReset(e,t,s){let i={method:"POST",body:{email:e}};return i=normalizeLegacyOptionsArgs("This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-password-reset",i).then((()=>!0))}async confirmPasswordReset(e,t,s,i,n){let r={method:"POST",body:{token:e,password:t,passwordConfirm:s}};return r=normalizeLegacyOptionsArgs("This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).",r,i,n),this.client.send(this.baseCollectionPath+"/confirm-password-reset",r).then((()=>!0))}async requestVerification(e,t,s){let i={method:"POST",body:{email:e}};return i=normalizeLegacyOptionsArgs("This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-verification",i).then((()=>!0))}async confirmVerification(e,t,s){let i={method:"POST",body:{token:e}};return i=normalizeLegacyOptionsArgs("This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/confirm-verification",i).then((()=>{const t=getTokenPayload(e),s=this.client.authStore.record;return s&&!s.verified&&s.id===t.id&&s.collectionId===t.collectionId&&(s.verified=!0,this.client.authStore.save(this.client.authStore.token,s)),!0}))}async requestEmailChange(e,t,s){let i={method:"POST",body:{newEmail:e}};return i=normalizeLegacyOptionsArgs("This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).",i,t,s),this.client.send(this.baseCollectionPath+"/request-email-change",i).then((()=>!0))}async confirmEmailChange(e,t,s,i){let n={method:"POST",body:{token:e,password:t}};return n=normalizeLegacyOptionsArgs("This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).",n,s,i),this.client.send(this.baseCollectionPath+"/confirm-email-change",n).then((()=>{const t=getTokenPayload(e),s=this.client.authStore.record;return s&&s.id===t.id&&s.collectionId===t.collectionId&&this.client.authStore.clear(),!0}))}async listExternalAuths(e,t){return this.client.collection("_externalAuths").getFullList(Object.assign({},t,{filter:this.client.filter("recordRef = {:id}",{id:e})}))}async unlinkExternalAuth(e,t,s){const i=await this.client.collection("_externalAuths").getFirstListItem(this.client.filter("recordRef = {:recordId} && provider = {:provider}",{recordId:e,provider:t}));return this.client.collection("_externalAuths").delete(i.id,s).then((()=>!0))}async requestOTP(e,t){return t=Object.assign({method:"POST",body:{email:e}},t),this.client.send(this.baseCollectionPath+"/request-otp",t)}async authWithOTP(e,t,s){return s=Object.assign({method:"POST",body:{otpId:e,password:t}},s),this.client.send(this.baseCollectionPath+"/auth-with-otp",s).then((e=>this.authResponse(e)))}async impersonate(e,t,s){(s=Object.assign({method:"POST",body:{duration:t}},s)).headers=s.headers||{},s.headers.Authorization||(s.headers.Authorization=this.client.authStore.token);const i=new Client(this.client.baseURL,new BaseAuthStore,this.client.lang),n=await i.send(this.baseCollectionPath+"/impersonate/"+encodeURIComponent(e),s);return i.authStore.save(n?.token,this.decode(n?.record||{})),i}_replaceQueryParams(e,t={}){let s=e,i="";e.indexOf("?")>=0&&(s=e.substring(0,e.indexOf("?")),i=e.substring(e.indexOf("?")+1));const n={},r=i.split("&");for(const e of r){if(""==e)continue;const t=e.split("=");n[decodeURIComponent(t[0].replace(/\+/g," "))]=decodeURIComponent((t[1]||"").replace(/\+/g," "))}for(let e in t)t.hasOwnProperty(e)&&(null==t[e]?delete n[e]:n[e]=t[e]);i="";for(let e in n)n.hasOwnProperty(e)&&(""!=i&&(i+="&"),i+=encodeURIComponent(e.replace(/%20/g,"+"))+"="+encodeURIComponent(n[e].replace(/%20/g,"+")));return""!=i?s+"?"+i:s}}function openBrowserPopup(e){if("undefined"==typeof window||!window?.open)throw new ClientResponseError(new Error("Not in a browser context - please pass a custom urlCallback function."));let t=1024,s=768,i=window.innerWidth,n=window.innerHeight;t=t>i?i:t,s=s>n?n:s;let r=i/2-t/2,o=n/2-s/2;return window.open(e,"popup_window","width="+t+",height="+s+",top="+o+",left="+r+",resizable,menubar=no")}class CollectionService extends CrudService{get baseCrudPath(){return"/api/collections"}async import(e,t=!1,s){return s=Object.assign({method:"PUT",body:{collections:e,deleteMissing:t}},s),this.client.send(this.baseCrudPath+"/import",s).then((()=>!0))}async getScaffolds(e){return e=Object.assign({method:"GET"},e),this.client.send(this.baseCrudPath+"/meta/scaffolds",e)}async truncate(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(this.baseCrudPath+"/"+encodeURIComponent(e)+"/truncate",t).then((()=>!0))}}class LogService extends BaseService{async getList(e=1,t=30,s){return(s=Object.assign({method:"GET"},s)).query=Object.assign({page:e,perPage:t},s.query),this.client.send("/api/logs",s)}async getOne(e,t){if(!e)throw new ClientResponseError({url:this.client.buildURL("/api/logs/"),status:404,response:{code:404,message:"Missing required log id.",data:{}}});return t=Object.assign({method:"GET"},t),this.client.send("/api/logs/"+encodeURIComponent(e),t)}async getStats(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/logs/stats",e)}}class HealthService extends BaseService{async check(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/health",e)}}class FileService extends BaseService{getUrl(e,t,s={}){return console.warn("Please replace pb.files.getUrl() with pb.files.getURL()"),this.getURL(e,t,s)}getURL(e,t,s={}){if(!t||!e?.id||!e?.collectionId&&!e?.collectionName)return"";const i=[];i.push("api"),i.push("files"),i.push(encodeURIComponent(e.collectionId||e.collectionName)),i.push(encodeURIComponent(e.id)),i.push(encodeURIComponent(t));let n=this.client.buildURL(i.join("/"));!1===s.download&&delete s.download;const r=serializeQueryParams(s);return r&&(n+=(n.includes("?")?"&":"?")+r),n}async getToken(e){return e=Object.assign({method:"POST"},e),this.client.send("/api/files/token",e).then((e=>e?.token||""))}}class BackupService extends BaseService{async getFullList(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/backups",e)}async create(e,t){return t=Object.assign({method:"POST",body:{name:e}},t),this.client.send("/api/backups",t).then((()=>!0))}async upload(e,t){return t=Object.assign({method:"POST",body:e},t),this.client.send("/api/backups/upload",t).then((()=>!0))}async delete(e,t){return t=Object.assign({method:"DELETE"},t),this.client.send(`/api/backups/${encodeURIComponent(e)}`,t).then((()=>!0))}async restore(e,t){return t=Object.assign({method:"POST"},t),this.client.send(`/api/backups/${encodeURIComponent(e)}/restore`,t).then((()=>!0))}getDownloadUrl(e,t){return console.warn("Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()"),this.getDownloadURL(e,t)}getDownloadURL(e,t){return this.client.buildURL(`/api/backups/${encodeURIComponent(t)}?token=${encodeURIComponent(e)}`)}}class CronService extends BaseService{async getFullList(e){return e=Object.assign({method:"GET"},e),this.client.send("/api/crons",e)}async run(e,t){return t=Object.assign({method:"POST"},t),this.client.send(`/api/crons/${encodeURIComponent(e)}`,t).then((()=>!0))}}function isFile(e){return"undefined"!=typeof Blob&&e instanceof Blob||"undefined"!=typeof File&&e instanceof File||null!==e&&"object"==typeof e&&e.uri&&("undefined"!=typeof navigator&&"ReactNative"===navigator.product||"undefined"!=typeof global&&global.HermesInternal)}function isFormData(e){return e&&("FormData"===e.constructor?.name||"undefined"!=typeof FormData&&e instanceof FormData)}function hasFileField(e){for(const t in e){const s=Array.isArray(e[t])?e[t]:[e[t]];for(const e of s)if(isFile(e))return!0}return!1}const r=/^[\-\.\d]+$/;function inferFormDataValue(e){if("string"!=typeof e)return e;if("true"==e)return!0;if("false"==e)return!1;if(("-"===e[0]||e[0]>="0"&&e[0]<="9")&&r.test(e)){let t=+e;if(""+t===e)return t}return e}class BatchService extends BaseService{constructor(){super(...arguments),this.requests=[],this.subs={}}collection(e){return this.subs[e]||(this.subs[e]=new SubBatchService(this.requests,e)),this.subs[e]}async send(e){const t=new FormData,s=[];for(let e=0;e{if("@jsonPayload"===s&&"string"==typeof e)try{let s=JSON.parse(e);Object.assign(t,s)}catch(e){console.warn("@jsonPayload error:",e)}else void 0!==t[s]?(Array.isArray(t[s])||(t[s]=[t[s]]),t[s].push(inferFormDataValue(e))):t[s]=inferFormDataValue(e)})),t}(s));for(const t in s){const i=s[t];if(isFile(i))e.files[t]=e.files[t]||[],e.files[t].push(i);else if(Array.isArray(i)){const s=[],n=[];for(const e of i)isFile(e)?s.push(e):n.push(e);if(s.length>0&&s.length==i.length){e.files[t]=e.files[t]||[];for(let i of s)e.files[t].push(i)}else if(e.json[t]=n,s.length>0){let i=t;t.startsWith("+")||t.endsWith("+")||(i+="+"),e.files[i]=e.files[i]||[];for(let t of s)e.files[i].push(t)}}else e.json[t]=i}}}class Client{get baseUrl(){return this.baseURL}set baseUrl(e){this.baseURL=e}constructor(e="/",t,s="en-US"){this.cancelControllers={},this.recordServices={},this.enableAutoCancellation=!0,this.baseURL=e,this.lang=s,t?this.authStore=t:"undefined"!=typeof window&&window.Deno?this.authStore=new BaseAuthStore:this.authStore=new LocalAuthStore,this.collections=new CollectionService(this),this.files=new FileService(this),this.logs=new LogService(this),this.settings=new SettingsService(this),this.realtime=new RealtimeService(this),this.health=new HealthService(this),this.backups=new BackupService(this),this.crons=new CronService(this)}get admins(){return this.collection("_superusers")}createBatch(){return new BatchService(this)}collection(e){return this.recordServices[e]||(this.recordServices[e]=new RecordService(this,e)),this.recordServices[e]}autoCancellation(e){return this.enableAutoCancellation=!!e,this}cancelRequest(e){return this.cancelControllers[e]&&(this.cancelControllers[e].abort(),delete this.cancelControllers[e]),this}cancelAllRequests(){for(let e in this.cancelControllers)this.cancelControllers[e].abort();return this.cancelControllers={},this}filter(e,t){if(!t)return e;for(let s in t){let i=t[s];switch(typeof i){case"boolean":case"number":i=""+i;break;case"string":i="'"+i.replace(/'/g,"\\'")+"'";break;default:i=null===i?"null":i instanceof Date?"'"+i.toISOString().replace("T"," ")+"'":"'"+JSON.stringify(i).replace(/'/g,"\\'")+"'"}e=e.replaceAll("{:"+s+"}",i)}return e}getFileUrl(e,t,s={}){return console.warn("Please replace pb.getFileUrl() with pb.files.getURL()"),this.files.getURL(e,t,s)}buildUrl(e){return console.warn("Please replace pb.buildUrl() with pb.buildURL()"),this.buildURL(e)}buildURL(e){let t=this.baseURL;return"undefined"==typeof window||!window.location||t.startsWith("https://")||t.startsWith("http://")||(t=window.location.origin?.endsWith("/")?window.location.origin.substring(0,window.location.origin.length-1):window.location.origin||"",this.baseURL.startsWith("/")||(t+=window.location.pathname||"/",t+=t.endsWith("/")?"":"/"),t+=this.baseURL),e&&(t+=t.endsWith("/")?"":"/",t+=e.startsWith("/")?e.substring(1):e),t}async send(e,t){t=this.initSendOptions(e,t);let s=this.buildURL(e);if(this.beforeSend){const e=Object.assign({},await this.beforeSend(s,t));void 0!==e.url||void 0!==e.options?(s=e.url||s,t=e.options||t):Object.keys(e).length&&(t=e,console?.warn&&console.warn("Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`."))}if(void 0!==t.query){const e=serializeQueryParams(t.query);e&&(s+=(s.includes("?")?"&":"?")+e),delete t.query}"application/json"==this.getHeader(t.headers,"Content-Type")&&t.body&&"string"!=typeof t.body&&(t.body=JSON.stringify(t.body));return(t.fetch||fetch)(s,t).then((async e=>{let s={};try{s=await e.json()}catch(e){if(t.signal?.aborted||"AbortError"==e?.name||"Aborted"==e?.message)throw e}if(this.afterSend&&(s=await this.afterSend(e,s,t)),e.status>=400)throw new ClientResponseError({url:e.url,status:e.status,data:s});return s})).catch((e=>{throw new ClientResponseError(e)}))}initSendOptions(e,t){if((t=Object.assign({method:"GET"},t)).body=function convertToFormDataIfNeeded(e){if("undefined"==typeof FormData||void 0===e||"object"!=typeof e||null===e||isFormData(e)||!hasFileField(e))return e;const t=new FormData;for(const s in e){const i=e[s];if(void 0!==i)if("object"!=typeof i||hasFileField({data:i})){const e=Array.isArray(i)?i:[i];for(let i of e)t.append(s,i)}else{let e={};e[s]=i,t.append("@jsonPayload",JSON.stringify(e))}}return t}(t.body),normalizeUnknownQueryParams(t),t.query=Object.assign({},t.params,t.query),void 0===t.requestKey&&(!1===t.$autoCancel||!1===t.query.$autoCancel?t.requestKey=null:(t.$cancelKey||t.query.$cancelKey)&&(t.requestKey=t.$cancelKey||t.query.$cancelKey)),delete t.$autoCancel,delete t.query.$autoCancel,delete t.$cancelKey,delete t.query.$cancelKey,null!==this.getHeader(t.headers,"Content-Type")||isFormData(t.body)||(t.headers=Object.assign({},t.headers,{"Content-Type":"application/json"})),null===this.getHeader(t.headers,"Accept-Language")&&(t.headers=Object.assign({},t.headers,{"Accept-Language":this.lang})),this.authStore.token&&null===this.getHeader(t.headers,"Authorization")&&(t.headers=Object.assign({},t.headers,{Authorization:this.authStore.token})),this.enableAutoCancellation&&null!==t.requestKey){const s=t.requestKey||(t.method||"GET")+e;delete t.requestKey,this.cancelRequest(s);const i=new AbortController;this.cancelControllers[s]=i,t.signal=i.signal}return t}getHeader(e,t){e=e||{},t=t.toLowerCase();for(let s in e)if(s.toLowerCase()==t)return e[s];return null}}return Client})); +//# sourceMappingURL=pocketbase.umd.js.map diff --git a/script/node_modules/pocketbase/dist/pocketbase.umd.js.map b/script/node_modules/pocketbase/dist/pocketbase.umd.js.map new file mode 100644 index 0000000..ec14b5b --- /dev/null +++ b/script/node_modules/pocketbase/dist/pocketbase.umd.js.map @@ -0,0 +1 @@ +{"version":3,"file":"pocketbase.umd.js","sources":["../src/ClientResponseError.ts","../src/tools/cookie.ts","../src/tools/jwt.ts","../src/stores/BaseAuthStore.ts","../src/stores/LocalAuthStore.ts","../src/services/BaseService.ts","../src/services/SettingsService.ts","../src/tools/options.ts","../src/services/RealtimeService.ts","../src/services/CrudService.ts","../src/tools/legacy.ts","../src/tools/refresh.ts","../src/services/RecordService.ts","../src/services/CollectionService.ts","../src/services/LogService.ts","../src/services/HealthService.ts","../src/services/FileService.ts","../src/services/BackupService.ts","../src/services/CronService.ts","../src/tools/formdata.ts","../src/services/BatchService.ts","../src/Client.ts"],"sourcesContent":["/**\n * ClientResponseError is a custom Error class that is intended to wrap\n * and normalize any error thrown by `Client.send()`.\n */\nexport class ClientResponseError extends Error {\n url: string = \"\";\n status: number = 0;\n response: { [key: string]: any } = {};\n isAbort: boolean = false;\n originalError: any = null;\n\n constructor(errData?: any) {\n super(\"ClientResponseError\");\n\n // Set the prototype explicitly.\n // https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, ClientResponseError.prototype);\n\n if (errData !== null && typeof errData === \"object\") {\n this.originalError = errData.originalError;\n this.url = typeof errData.url === \"string\" ? errData.url : \"\";\n this.status = typeof errData.status === \"number\" ? errData.status : 0;\n\n // note: DOMException is not implemented yet in React Native\n // and aborting a fetch throws a plain Error with message \"Aborted\".\n this.isAbort =\n !!errData.isAbort ||\n errData.name === \"AbortError\" ||\n errData.message === \"Aborted\";\n\n if (errData.response !== null && typeof errData.response === \"object\") {\n this.response = errData.response;\n } else if (errData.data !== null && typeof errData.data === \"object\") {\n this.response = errData.data;\n } else {\n this.response = {};\n }\n }\n\n if (!this.originalError && !(errData instanceof ClientResponseError)) {\n this.originalError = errData;\n }\n\n this.name = \"ClientResponseError \" + this.status;\n this.message = this.response?.message;\n if (!this.message) {\n if (this.isAbort) {\n this.message =\n \"The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).\";\n } else if (this.originalError?.cause?.message?.includes(\"ECONNREFUSED ::1\")) {\n this.message =\n \"Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).\";\n } else {\n this.message = \"Something went wrong.\";\n }\n }\n\n // set this.cause so that JS debugging tools can automatically connect\n // the dots between the original error and the wrapped one\n this.cause = this.originalError;\n }\n\n /**\n * Alias for `this.response` for backward compatibility.\n */\n get data() {\n return this.response;\n }\n\n /**\n * Make a POJO's copy of the current error class instance.\n * @see https://github.com/vuex-orm/vuex-orm/issues/255\n */\n toJSON() {\n return { ...this };\n }\n}\n","/**\n * -------------------------------------------------------------------\n * Simple cookie parse and serialize utilities mostly based on the\n * node module https://github.com/jshttp/cookie.\n * -------------------------------------------------------------------\n */\n\n/**\n * RegExp to match field-content in RFC 7230 sec 3.2\n *\n * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]\n * field-vchar = VCHAR / obs-text\n * obs-text = %x80-FF\n */\nconst fieldContentRegExp = /^[\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+$/;\n\nexport interface ParseOptions {\n decode?: (val: string) => string;\n}\n\n/**\n * Parses the given cookie header string into an object\n * The object has the various cookies as keys(names) => values\n */\nexport function cookieParse(str: string, options?: ParseOptions): { [key: string]: any } {\n const result: { [key: string]: any } = {};\n\n if (typeof str !== \"string\") {\n return result;\n }\n\n const opt = Object.assign({}, options || {});\n const decode = opt.decode || defaultDecode;\n\n let index = 0;\n while (index < str.length) {\n const eqIdx = str.indexOf(\"=\", index);\n\n // no more cookie pairs\n if (eqIdx === -1) {\n break;\n }\n\n let endIdx = str.indexOf(\";\", index);\n\n if (endIdx === -1) {\n endIdx = str.length;\n } else if (endIdx < eqIdx) {\n // backtrack on prior semicolon\n index = str.lastIndexOf(\";\", eqIdx - 1) + 1;\n continue;\n }\n\n const key = str.slice(index, eqIdx).trim();\n\n // only assign once\n if (undefined === result[key]) {\n let val = str.slice(eqIdx + 1, endIdx).trim();\n\n // quoted values\n if (val.charCodeAt(0) === 0x22) {\n val = val.slice(1, -1);\n }\n\n try {\n result[key] = decode(val);\n } catch (_) {\n result[key] = val; // no decoding\n }\n }\n\n index = endIdx + 1;\n }\n\n return result;\n}\n\nexport interface SerializeOptions {\n encode?: (val: string | number | boolean) => string;\n maxAge?: number;\n domain?: string;\n path?: string;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n priority?: string;\n sameSite?: boolean | string;\n}\n\n/**\n * Serialize data into a cookie header.\n *\n * Serialize the a name value pair into a cookie string suitable for\n * http headers. An optional options object specified cookie parameters.\n *\n * ```js\n * cookieSerialize('foo', 'bar', { httpOnly: true }) // \"foo=bar; httpOnly\"\n * ```\n */\nexport function cookieSerialize(\n name: string,\n val: string,\n options?: SerializeOptions,\n): string {\n const opt = Object.assign({}, options || {});\n const encode = opt.encode || defaultEncode;\n\n if (!fieldContentRegExp.test(name)) {\n throw new TypeError(\"argument name is invalid\");\n }\n\n const value = encode(val);\n\n if (value && !fieldContentRegExp.test(value)) {\n throw new TypeError(\"argument val is invalid\");\n }\n\n let result = name + \"=\" + value;\n\n if (opt.maxAge != null) {\n const maxAge = opt.maxAge - 0;\n\n if (isNaN(maxAge) || !isFinite(maxAge)) {\n throw new TypeError(\"option maxAge is invalid\");\n }\n\n result += \"; Max-Age=\" + Math.floor(maxAge);\n }\n\n if (opt.domain) {\n if (!fieldContentRegExp.test(opt.domain)) {\n throw new TypeError(\"option domain is invalid\");\n }\n\n result += \"; Domain=\" + opt.domain;\n }\n\n if (opt.path) {\n if (!fieldContentRegExp.test(opt.path)) {\n throw new TypeError(\"option path is invalid\");\n }\n\n result += \"; Path=\" + opt.path;\n }\n\n if (opt.expires) {\n if (!isDate(opt.expires) || isNaN(opt.expires.valueOf())) {\n throw new TypeError(\"option expires is invalid\");\n }\n\n result += \"; Expires=\" + opt.expires.toUTCString();\n }\n\n if (opt.httpOnly) {\n result += \"; HttpOnly\";\n }\n\n if (opt.secure) {\n result += \"; Secure\";\n }\n\n if (opt.priority) {\n const priority =\n typeof opt.priority === \"string\" ? opt.priority.toLowerCase() : opt.priority;\n\n switch (priority) {\n case \"low\":\n result += \"; Priority=Low\";\n break;\n case \"medium\":\n result += \"; Priority=Medium\";\n break;\n case \"high\":\n result += \"; Priority=High\";\n break;\n default:\n throw new TypeError(\"option priority is invalid\");\n }\n }\n\n if (opt.sameSite) {\n const sameSite =\n typeof opt.sameSite === \"string\" ? opt.sameSite.toLowerCase() : opt.sameSite;\n\n switch (sameSite) {\n case true:\n result += \"; SameSite=Strict\";\n break;\n case \"lax\":\n result += \"; SameSite=Lax\";\n break;\n case \"strict\":\n result += \"; SameSite=Strict\";\n break;\n case \"none\":\n result += \"; SameSite=None\";\n break;\n default:\n throw new TypeError(\"option sameSite is invalid\");\n }\n }\n\n return result;\n}\n\n/**\n * Default URL-decode string value function.\n * Optimized to skip native call when no `%`.\n */\nfunction defaultDecode(val: string): string {\n return val.indexOf(\"%\") !== -1 ? decodeURIComponent(val) : val;\n}\n\n/**\n * Default URL-encode value function.\n */\nfunction defaultEncode(val: string | number | boolean): string {\n return encodeURIComponent(val);\n}\n\n/**\n * Determines if value is a Date.\n */\nfunction isDate(val: any): boolean {\n return Object.prototype.toString.call(val) === \"[object Date]\" || val instanceof Date;\n}\n","// @todo remove after https://github.com/reactwg/react-native-releases/issues/287\nconst isReactNative =\n (typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\") ||\n (typeof global !== \"undefined\" && (global as any).HermesInternal);\n\nlet atobPolyfill: Function;\nif (typeof atob === \"function\" && !isReactNative) {\n atobPolyfill = atob;\n} else {\n /**\n * The code was extracted from:\n * https://github.com/davidchambers/Base64.js\n */\n atobPolyfill = (input: any) => {\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\";\n\n let str = String(input).replace(/=+$/, \"\");\n if (str.length % 4 == 1) {\n throw new Error(\n \"'atob' failed: The string to be decoded is not correctly encoded.\",\n );\n }\n\n for (\n // initialize result and counters\n var bc = 0, bs, buffer, idx = 0, output = \"\";\n // get next character\n (buffer = str.charAt(idx++));\n // character found in table? initialize bit storage and add its ascii value;\n ~buffer &&\n ((bs = bc % 4 ? (bs as any) * 64 + buffer : buffer),\n // and if not first of each 4 characters,\n // convert the first 8 bits to one ascii character\n bc++ % 4)\n ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))\n : 0\n ) {\n // try to find character in table (0-63, not found => -1)\n buffer = chars.indexOf(buffer);\n }\n\n return output;\n };\n}\n\n/**\n * Returns JWT token's payload data.\n */\nexport function getTokenPayload(token: string): { [key: string]: any } {\n if (token) {\n try {\n const encodedPayload = decodeURIComponent(\n atobPolyfill(token.split(\".\")[1])\n .split(\"\")\n .map(function (c: string) {\n return \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2);\n })\n .join(\"\"),\n );\n\n return JSON.parse(encodedPayload) || {};\n } catch (e) {}\n }\n\n return {};\n}\n\n/**\n * Checks whether a JWT token is expired or not.\n * Tokens without `exp` payload key are considered valid.\n * Tokens with empty payload (eg. invalid token strings) are considered expired.\n *\n * @param token The token to check.\n * @param [expirationThreshold] Time in seconds that will be subtracted from the token `exp` property.\n */\nexport function isTokenExpired(token: string, expirationThreshold = 0): boolean {\n let payload = getTokenPayload(token);\n\n if (\n Object.keys(payload).length > 0 &&\n (!payload.exp || payload.exp - expirationThreshold > Date.now() / 1000)\n ) {\n return false;\n }\n\n return true;\n}\n","import { cookieParse, cookieSerialize, SerializeOptions } from \"@/tools/cookie\";\nimport { isTokenExpired, getTokenPayload } from \"@/tools/jwt\";\nimport { RecordModel } from \"@/tools/dtos\";\n\nexport type AuthRecord = RecordModel | null;\n\nexport type AuthModel = AuthRecord; // for backward compatibility\n\nexport type OnStoreChangeFunc = (token: string, record: AuthRecord) => void;\n\nconst defaultCookieKey = \"pb_auth\";\n\n/**\n * Base AuthStore class that stores the auth state in runtime memory (aka. only for the duration of the store instane).\n *\n * Usually you wouldn't use it directly and instead use the builtin LocalAuthStore, AsyncAuthStore\n * or extend it with your own custom implementation.\n */\nexport class BaseAuthStore {\n protected baseToken: string = \"\";\n protected baseModel: AuthRecord = null;\n\n private _onChangeCallbacks: Array = [];\n\n /**\n * Retrieves the stored token (if any).\n */\n get token(): string {\n return this.baseToken;\n }\n\n /**\n * Retrieves the stored model data (if any).\n */\n get record(): AuthRecord {\n return this.baseModel;\n }\n\n /**\n * @deprecated use `record` instead.\n */\n get model(): AuthRecord {\n return this.baseModel;\n }\n\n /**\n * Loosely checks if the store has valid token (aka. existing and unexpired exp claim).\n */\n get isValid(): boolean {\n return !isTokenExpired(this.token);\n }\n\n /**\n * Loosely checks whether the currently loaded store state is for superuser.\n *\n * Alternatively you can also compare directly `pb.authStore.record?.collectionName`.\n */\n get isSuperuser(): boolean {\n let payload = getTokenPayload(this.token);\n\n return (\n payload.type == \"auth\" &&\n (this.record?.collectionName == \"_superusers\" ||\n // fallback in case the record field is not populated and assuming\n // that the collection crc32 checksum id wasn't manually changed\n (!this.record?.collectionName &&\n payload.collectionId == \"pbc_3142635823\"))\n );\n }\n\n /**\n * @deprecated use `isSuperuser` instead or simply check the record.collectionName property.\n */\n get isAdmin(): boolean {\n console.warn(\n \"Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName\",\n );\n return this.isSuperuser;\n }\n\n /**\n * @deprecated use `!isSuperuser` instead or simply check the record.collectionName property.\n */\n get isAuthRecord(): boolean {\n console.warn(\n \"Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName\",\n );\n return getTokenPayload(this.token).type == \"auth\" && !this.isSuperuser;\n }\n\n /**\n * Saves the provided new token and model data in the auth store.\n */\n save(token: string, record?: AuthRecord): void {\n this.baseToken = token || \"\";\n this.baseModel = record || null;\n\n this.triggerChange();\n }\n\n /**\n * Removes the stored token and model data form the auth store.\n */\n clear(): void {\n this.baseToken = \"\";\n this.baseModel = null;\n this.triggerChange();\n }\n\n /**\n * Parses the provided cookie string and updates the store state\n * with the cookie's token and model data.\n *\n * NB! This function doesn't validate the token or its data.\n * Usually this isn't a concern if you are interacting only with the\n * PocketBase API because it has the proper server-side security checks in place,\n * but if you are using the store `isValid` state for permission controls\n * in a node server (eg. SSR), then it is recommended to call `authRefresh()`\n * after loading the cookie to ensure an up-to-date token and model state.\n * For example:\n *\n * ```js\n * pb.authStore.loadFromCookie(\"cookie string...\");\n *\n * try {\n * // get an up-to-date auth store state by veryfing and refreshing the loaded auth model (if any)\n * pb.authStore.isValid && await pb.collection('users').authRefresh();\n * } catch (_) {\n * // clear the auth store on failed refresh\n * pb.authStore.clear();\n * }\n * ```\n */\n loadFromCookie(cookie: string, key = defaultCookieKey): void {\n const rawData = cookieParse(cookie || \"\")[key] || \"\";\n\n let data: { [key: string]: any } = {};\n try {\n data = JSON.parse(rawData);\n // normalize\n if (typeof data === null || typeof data !== \"object\" || Array.isArray(data)) {\n data = {};\n }\n } catch (_) {}\n\n this.save(data.token || \"\", data.record || data.model || null);\n }\n\n /**\n * Exports the current store state as cookie string.\n *\n * By default the following optional attributes are added:\n * - Secure\n * - HttpOnly\n * - SameSite=Strict\n * - Path=/\n * - Expires={the token expiration date}\n *\n * NB! If the generated cookie exceeds 4096 bytes, this method will\n * strip the model data to the bare minimum to try to fit within the\n * recommended size in https://www.rfc-editor.org/rfc/rfc6265#section-6.1.\n */\n exportToCookie(options?: SerializeOptions, key = defaultCookieKey): string {\n const defaultOptions: SerializeOptions = {\n secure: true,\n sameSite: true,\n httpOnly: true,\n path: \"/\",\n };\n\n // extract the token expiration date\n const payload = getTokenPayload(this.token);\n if (payload?.exp) {\n defaultOptions.expires = new Date(payload.exp * 1000);\n } else {\n defaultOptions.expires = new Date(\"1970-01-01\");\n }\n\n // merge with the user defined options\n options = Object.assign({}, defaultOptions, options);\n\n const rawData = {\n token: this.token,\n record: this.record ? JSON.parse(JSON.stringify(this.record)) : null,\n };\n\n let result = cookieSerialize(key, JSON.stringify(rawData), options);\n\n const resultLength =\n typeof Blob !== \"undefined\" ? new Blob([result]).size : result.length;\n\n // strip down the model data to the bare minimum\n if (rawData.record && resultLength > 4096) {\n rawData.record = { id: rawData.record?.id, email: rawData.record?.email };\n const extraProps = [\"collectionId\", \"collectionName\", \"verified\"];\n for (const prop in this.record) {\n if (extraProps.includes(prop)) {\n rawData.record[prop] = this.record[prop];\n }\n }\n result = cookieSerialize(key, JSON.stringify(rawData), options);\n }\n\n return result;\n }\n\n /**\n * Register a callback function that will be called on store change.\n *\n * You can set the `fireImmediately` argument to true in order to invoke\n * the provided callback right after registration.\n *\n * Returns a removal function that you could call to \"unsubscribe\" from the changes.\n */\n onChange(callback: OnStoreChangeFunc, fireImmediately = false): () => void {\n this._onChangeCallbacks.push(callback);\n\n if (fireImmediately) {\n callback(this.token, this.record);\n }\n\n return () => {\n for (let i = this._onChangeCallbacks.length - 1; i >= 0; i--) {\n if (this._onChangeCallbacks[i] == callback) {\n delete this._onChangeCallbacks[i]; // removes the function reference\n this._onChangeCallbacks.splice(i, 1); // reindex the array\n return;\n }\n }\n };\n }\n\n protected triggerChange(): void {\n for (const callback of this._onChangeCallbacks) {\n callback && callback(this.token, this.record);\n }\n }\n}\n","import { BaseAuthStore, AuthRecord } from \"@/stores/BaseAuthStore\";\n\n/**\n * The default token store for browsers with auto fallback\n * to runtime/memory if local storage is undefined (e.g. in node env).\n */\nexport class LocalAuthStore extends BaseAuthStore {\n private storageFallback: { [key: string]: any } = {};\n private storageKey: string;\n\n constructor(storageKey = \"pocketbase_auth\") {\n super();\n\n this.storageKey = storageKey;\n\n this._bindStorageEvent();\n }\n\n /**\n * @inheritdoc\n */\n get token(): string {\n const data = this._storageGet(this.storageKey) || {};\n\n return data.token || \"\";\n }\n\n /**\n * @inheritdoc\n */\n get record(): AuthRecord {\n const data = this._storageGet(this.storageKey) || {};\n\n return data.record || data.model || null;\n }\n\n /**\n * @deprecated use `record` instead.\n */\n get model(): AuthRecord {\n return this.record;\n }\n\n /**\n * @inheritdoc\n */\n save(token: string, record?: AuthRecord) {\n this._storageSet(this.storageKey, {\n token: token,\n record: record,\n });\n\n super.save(token, record);\n }\n\n /**\n * @inheritdoc\n */\n clear() {\n this._storageRemove(this.storageKey);\n\n super.clear();\n }\n\n // ---------------------------------------------------------------\n // Internal helpers:\n // ---------------------------------------------------------------\n\n /**\n * Retrieves `key` from the browser's local storage\n * (or runtime/memory if local storage is undefined).\n */\n private _storageGet(key: string): any {\n if (typeof window !== \"undefined\" && window?.localStorage) {\n const rawValue = window.localStorage.getItem(key) || \"\";\n try {\n return JSON.parse(rawValue);\n } catch (e) {\n // not a json\n return rawValue;\n }\n }\n\n // fallback\n return this.storageFallback[key];\n }\n\n /**\n * Stores a new data in the browser's local storage\n * (or runtime/memory if local storage is undefined).\n */\n private _storageSet(key: string, value: any) {\n if (typeof window !== \"undefined\" && window?.localStorage) {\n // store in local storage\n let normalizedVal = value;\n if (typeof value !== \"string\") {\n normalizedVal = JSON.stringify(value);\n }\n window.localStorage.setItem(key, normalizedVal);\n } else {\n // store in fallback\n this.storageFallback[key] = value;\n }\n }\n\n /**\n * Removes `key` from the browser's local storage and the runtime/memory.\n */\n private _storageRemove(key: string) {\n // delete from local storage\n if (typeof window !== \"undefined\" && window?.localStorage) {\n window.localStorage?.removeItem(key);\n }\n\n // delete from fallback\n delete this.storageFallback[key];\n }\n\n /**\n * Updates the current store state on localStorage change.\n */\n private _bindStorageEvent() {\n if (\n typeof window === \"undefined\" ||\n !window?.localStorage ||\n !window.addEventListener\n ) {\n return;\n }\n\n window.addEventListener(\"storage\", (e) => {\n if (e.key != this.storageKey) {\n return;\n }\n\n const data = this._storageGet(this.storageKey) || {};\n\n super.save(data.token || \"\", data.record || data.model || null);\n });\n }\n}\n","import Client from \"@/Client\";\n\n/**\n * BaseService class that should be inherited from all API services.\n */\nexport abstract class BaseService {\n readonly client: Client;\n\n constructor(client: Client) {\n this.client = client;\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\ninterface appleClientSecret {\n secret: string;\n}\n\nexport class SettingsService extends BaseService {\n /**\n * Fetch all available app settings.\n *\n * @throws {ClientResponseError}\n */\n async getAll(options?: CommonOptions): Promise<{ [key: string]: any }> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/settings\", options);\n }\n\n /**\n * Bulk updates app settings.\n *\n * @throws {ClientResponseError}\n */\n async update(\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise<{ [key: string]: any }> {\n options = Object.assign(\n {\n method: \"PATCH\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client.send(\"/api/settings\", options);\n }\n\n /**\n * Performs a S3 filesystem connection test.\n *\n * The currently supported `filesystem` are \"storage\" and \"backups\".\n *\n * @throws {ClientResponseError}\n */\n async testS3(\n filesystem: string = \"storage\",\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n filesystem: filesystem,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/test/s3\", options).then(() => true);\n }\n\n /**\n * Sends a test email.\n *\n * The possible `emailTemplate` values are:\n * - verification\n * - password-reset\n * - email-change\n *\n * @throws {ClientResponseError}\n */\n async testEmail(\n collectionIdOrName: string,\n toEmail: string,\n emailTemplate: string,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n email: toEmail,\n template: emailTemplate,\n collection: collectionIdOrName,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/test/email\", options).then(() => true);\n }\n\n /**\n * Generates a new Apple OAuth2 client secret.\n *\n * @throws {ClientResponseError}\n */\n async generateAppleClientSecret(\n clientId: string,\n teamId: string,\n keyId: string,\n privateKey: string,\n duration: number,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n clientId,\n teamId,\n keyId,\n privateKey,\n duration,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/settings/apple/generate-client-secret\", options);\n }\n}\n","export interface SendOptions extends RequestInit {\n // for backward compatibility and to minimize the verbosity,\n // any top-level field that doesn't exist in RequestInit or the\n // fields below will be treated as query parameter.\n [key: string]: any;\n\n /**\n * Optional custom fetch function to use for sending the request.\n */\n fetch?: (url: RequestInfo | URL, config?: RequestInit) => Promise;\n\n /**\n * Custom headers to send with the requests.\n */\n headers?: { [key: string]: string };\n\n /**\n * The body of the request (serialized automatically for json requests).\n */\n body?: any;\n\n /**\n * Query parameters that will be appended to the request url.\n */\n query?: { [key: string]: any };\n\n /**\n * @deprecated use `query` instead\n *\n * for backward-compatibility `params` values are merged with `query`,\n * but this option may get removed in the final v1 release\n */\n params?: { [key: string]: any };\n\n /**\n * The request identifier that can be used to cancel pending requests.\n */\n requestKey?: string | null;\n\n /**\n * @deprecated use `requestKey:string` instead\n */\n $cancelKey?: string;\n\n /**\n * @deprecated use `requestKey:null` instead\n */\n $autoCancel?: boolean;\n}\n\nexport interface CommonOptions extends SendOptions {\n fields?: string;\n}\n\nexport interface ListOptions extends CommonOptions {\n page?: number;\n perPage?: number;\n sort?: string;\n filter?: string;\n skipTotal?: boolean;\n}\n\nexport interface FullListOptions extends ListOptions {\n batch?: number;\n}\n\nexport interface RecordOptions extends CommonOptions {\n expand?: string;\n}\n\nexport interface RecordListOptions extends ListOptions, RecordOptions {}\n\nexport interface RecordFullListOptions extends FullListOptions, RecordOptions {}\n\nexport interface RecordSubscribeOptions extends SendOptions {\n fields?: string;\n filter?: string;\n expand?: string;\n}\n\nexport interface LogStatsOptions extends CommonOptions {\n filter?: string;\n}\n\nexport interface FileOptions extends CommonOptions {\n thumb?: string;\n download?: boolean;\n}\n\nexport interface AuthOptions extends CommonOptions {\n /**\n * If autoRefreshThreshold is set it will take care to auto refresh\n * when necessary the auth data before each request to ensure that\n * the auth state is always valid.\n *\n * The value must be in seconds, aka. the amount of seconds\n * that will be subtracted from the current token `exp` claim in order\n * to determine whether it is going to expire within the specified time threshold.\n *\n * For example, if you want to auto refresh the token if it is\n * going to expire in the next 30mins (or already has expired),\n * it can be set to `1800`\n */\n autoRefreshThreshold?: number;\n}\n\n// -------------------------------------------------------------------\n\n// list of known SendOptions keys (everything else is treated as query param)\nconst knownSendOptionsKeys = [\n \"requestKey\",\n \"$cancelKey\",\n \"$autoCancel\",\n \"fetch\",\n \"headers\",\n \"body\",\n \"query\",\n \"params\",\n // ---,\n \"cache\",\n \"credentials\",\n \"headers\",\n \"integrity\",\n \"keepalive\",\n \"method\",\n \"mode\",\n \"redirect\",\n \"referrer\",\n \"referrerPolicy\",\n \"signal\",\n \"window\",\n];\n\n// modifies in place the provided options by moving unknown send options as query parameters.\nexport function normalizeUnknownQueryParams(options?: SendOptions): void {\n if (!options) {\n return;\n }\n\n options.query = options.query || {};\n for (let key in options) {\n if (knownSendOptionsKeys.includes(key)) {\n continue;\n }\n\n options.query[key] = options[key];\n delete options[key];\n }\n}\n\nexport function serializeQueryParams(params: { [key: string]: any }): string {\n const result: Array = [];\n\n for (const key in params) {\n const encodedKey = encodeURIComponent(key);\n const arrValue = Array.isArray(params[key]) ? params[key] : [params[key]];\n\n for (let v of arrValue) {\n v = prepareQueryParamValue(v);\n if (v === null) {\n continue;\n }\n result.push(encodedKey + \"=\" + v);\n }\n }\n\n return result.join(\"&\");\n}\n\n// encodes and normalizes the provided query param value.\nfunction prepareQueryParamValue(value: any): null | string {\n if (value === null || typeof value === \"undefined\") {\n return null;\n }\n\n if (value instanceof Date) {\n return encodeURIComponent(value.toISOString().replace(\"T\", \" \"));\n }\n\n if (typeof value === \"object\") {\n return encodeURIComponent(JSON.stringify(value));\n }\n\n return encodeURIComponent(value);\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseService } from \"@/services/BaseService\";\nimport { SendOptions, normalizeUnknownQueryParams } from \"@/tools/options\";\n\ninterface promiseCallbacks {\n resolve: Function;\n reject: Function;\n}\n\ntype Subscriptions = { [key: string]: Array };\n\nexport type UnsubscribeFunc = () => Promise;\n\nexport class RealtimeService extends BaseService {\n clientId: string = \"\";\n\n private eventSource: EventSource | null = null;\n private subscriptions: Subscriptions = {};\n private lastSentSubscriptions: Array = [];\n private connectTimeoutId: any;\n private maxConnectTimeout: number = 15000;\n private reconnectTimeoutId: any;\n private reconnectAttempts: number = 0;\n private maxReconnectAttempts: number = Infinity;\n private predefinedReconnectIntervals: Array = [\n 200, 300, 500, 1000, 1200, 1500, 2000,\n ];\n private pendingConnects: Array = [];\n\n /**\n * Returns whether the realtime connection has been established.\n */\n get isConnected(): boolean {\n return !!this.eventSource && !!this.clientId && !this.pendingConnects.length;\n }\n\n /**\n * An optional hook that is invoked when the realtime client disconnects\n * either when unsubscribing from all subscriptions or when the\n * connection was interrupted or closed by the server.\n *\n * The received argument could be used to determine whether the disconnect\n * is a result from unsubscribing (`activeSubscriptions.length == 0`)\n * or because of network/server error (`activeSubscriptions.length > 0`).\n *\n * If you want to listen for the opposite, aka. when the client connection is established,\n * subscribe to the `PB_CONNECT` event.\n */\n onDisconnect?: (activeSubscriptions: Array) => void;\n\n /**\n * Register the subscription listener.\n *\n * You can subscribe multiple times to the same topic.\n *\n * If the SSE connection is not started yet,\n * this method will also initialize it.\n */\n async subscribe(\n topic: string,\n callback: (data: any) => void,\n options?: SendOptions,\n ): Promise {\n if (!topic) {\n throw new Error(\"topic must be set.\");\n }\n\n let key = topic;\n\n // serialize and append the topic options (if any)\n if (options) {\n options = Object.assign({}, options); // shallow copy\n normalizeUnknownQueryParams(options);\n const serialized =\n \"options=\" +\n encodeURIComponent(\n JSON.stringify({ query: options.query, headers: options.headers }),\n );\n key += (key.includes(\"?\") ? \"&\" : \"?\") + serialized;\n }\n\n const listener = function (e: Event) {\n const msgEvent = e as MessageEvent;\n\n let data;\n try {\n data = JSON.parse(msgEvent?.data);\n } catch {}\n\n callback(data || {});\n };\n\n // store the listener\n if (!this.subscriptions[key]) {\n this.subscriptions[key] = [];\n }\n this.subscriptions[key].push(listener);\n\n if (!this.isConnected) {\n // initialize sse connection\n await this.connect();\n } else if (this.subscriptions[key].length === 1) {\n // send the updated subscriptions (if it is the first for the key)\n await this.submitSubscriptions();\n } else {\n // only register the listener\n this.eventSource?.addEventListener(key, listener);\n }\n\n return async (): Promise => {\n return this.unsubscribeByTopicAndListener(topic, listener);\n };\n }\n\n /**\n * Unsubscribe from all subscription listeners with the specified topic.\n *\n * If `topic` is not provided, then this method will unsubscribe\n * from all active subscriptions.\n *\n * This method is no-op if there are no active subscriptions.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribe(topic?: string): Promise {\n let needToSubmit = false;\n\n if (!topic) {\n // remove all subscriptions\n this.subscriptions = {};\n } else {\n // remove all listeners related to the topic\n const subs = this.getSubscriptionsByTopic(topic);\n for (let key in subs) {\n if (!this.hasSubscriptionListeners(key)) {\n continue; // already unsubscribed\n }\n\n for (let listener of this.subscriptions[key]) {\n this.eventSource?.removeEventListener(key, listener);\n }\n delete this.subscriptions[key];\n\n // mark for subscriptions change submit if there are no other listeners\n if (!needToSubmit) {\n needToSubmit = true;\n }\n }\n }\n\n if (!this.hasSubscriptionListeners()) {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n } else if (needToSubmit) {\n await this.submitSubscriptions();\n }\n }\n\n /**\n * Unsubscribe from all subscription listeners starting with the specified topic prefix.\n *\n * This method is no-op if there are no active subscriptions with the specified topic prefix.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribeByPrefix(keyPrefix: string): Promise {\n let hasAtleastOneTopic = false;\n for (let key in this.subscriptions) {\n // \"?\" so that it can be used as end delimiter for the prefix\n if (!(key + \"?\").startsWith(keyPrefix)) {\n continue;\n }\n\n hasAtleastOneTopic = true;\n for (let listener of this.subscriptions[key]) {\n this.eventSource?.removeEventListener(key, listener);\n }\n delete this.subscriptions[key];\n }\n\n if (!hasAtleastOneTopic) {\n return; // nothing to unsubscribe from\n }\n\n if (this.hasSubscriptionListeners()) {\n // submit the deleted subscriptions\n await this.submitSubscriptions();\n } else {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n }\n }\n\n /**\n * Unsubscribe from all subscriptions matching the specified topic and listener function.\n *\n * This method is no-op if there are no active subscription with\n * the specified topic and listener.\n *\n * The related sse connection will be autoclosed if after the\n * unsubscribe operation there are no active subscriptions left.\n */\n async unsubscribeByTopicAndListener(\n topic: string,\n listener: EventListener,\n ): Promise {\n let needToSubmit = false;\n\n const subs = this.getSubscriptionsByTopic(topic);\n for (let key in subs) {\n if (\n !Array.isArray(this.subscriptions[key]) ||\n !this.subscriptions[key].length\n ) {\n continue; // already unsubscribed\n }\n\n let exist = false;\n for (let i = this.subscriptions[key].length - 1; i >= 0; i--) {\n if (this.subscriptions[key][i] !== listener) {\n continue;\n }\n\n exist = true; // has at least one matching listener\n delete this.subscriptions[key][i]; // removes the function reference\n this.subscriptions[key].splice(i, 1); // reindex the array\n this.eventSource?.removeEventListener(key, listener);\n }\n if (!exist) {\n continue;\n }\n\n // remove the key from the subscriptions list if there are no other listeners\n if (!this.subscriptions[key].length) {\n delete this.subscriptions[key];\n }\n\n // mark for subscriptions change submit if there are no other listeners\n if (!needToSubmit && !this.hasSubscriptionListeners(key)) {\n needToSubmit = true;\n }\n }\n\n if (!this.hasSubscriptionListeners()) {\n // no other active subscriptions -> close the sse connection\n this.disconnect();\n } else if (needToSubmit) {\n await this.submitSubscriptions();\n }\n }\n\n private hasSubscriptionListeners(keyToCheck?: string): boolean {\n this.subscriptions = this.subscriptions || {};\n\n // check the specified key\n if (keyToCheck) {\n return !!this.subscriptions[keyToCheck]?.length;\n }\n\n // check for at least one non-empty subscription\n for (let key in this.subscriptions) {\n if (!!this.subscriptions[key]?.length) {\n return true;\n }\n }\n\n return false;\n }\n\n private async submitSubscriptions(): Promise {\n if (!this.clientId) {\n return; // no client/subscriber\n }\n\n // optimistic update\n this.addAllSubscriptionListeners();\n\n this.lastSentSubscriptions = this.getNonEmptySubscriptionKeys();\n\n return this.client\n .send(\"/api/realtime\", {\n method: \"POST\",\n body: {\n clientId: this.clientId,\n subscriptions: this.lastSentSubscriptions,\n },\n requestKey: this.getSubscriptionsCancelKey(),\n })\n .catch((err) => {\n if (err?.isAbort) {\n return; // silently ignore aborted pending requests\n }\n throw err;\n });\n }\n\n private getSubscriptionsCancelKey(): string {\n return \"realtime_\" + this.clientId;\n }\n\n private getSubscriptionsByTopic(topic: string): Subscriptions {\n const result: Subscriptions = {};\n\n // \"?\" so that it can be used as end delimiter for the topic\n topic = topic.includes(\"?\") ? topic : topic + \"?\";\n\n for (let key in this.subscriptions) {\n if ((key + \"?\").startsWith(topic)) {\n result[key] = this.subscriptions[key];\n }\n }\n\n return result;\n }\n\n private getNonEmptySubscriptionKeys(): Array {\n const result: Array = [];\n\n for (let key in this.subscriptions) {\n if (this.subscriptions[key].length) {\n result.push(key);\n }\n }\n\n return result;\n }\n\n private addAllSubscriptionListeners(): void {\n if (!this.eventSource) {\n return;\n }\n\n this.removeAllSubscriptionListeners();\n\n for (let key in this.subscriptions) {\n for (let listener of this.subscriptions[key]) {\n this.eventSource.addEventListener(key, listener);\n }\n }\n }\n\n private removeAllSubscriptionListeners(): void {\n if (!this.eventSource) {\n return;\n }\n\n for (let key in this.subscriptions) {\n for (let listener of this.subscriptions[key]) {\n this.eventSource.removeEventListener(key, listener);\n }\n }\n }\n\n private async connect(): Promise {\n if (this.reconnectAttempts > 0) {\n // immediately resolve the promise to avoid indefinitely\n // blocking the client during reconnection\n return;\n }\n\n return new Promise((resolve, reject) => {\n this.pendingConnects.push({ resolve, reject });\n\n if (this.pendingConnects.length > 1) {\n // all promises will be resolved once the connection is established\n return;\n }\n\n this.initConnect();\n });\n }\n\n private initConnect() {\n this.disconnect(true);\n\n // wait up to 15s for connect\n clearTimeout(this.connectTimeoutId);\n this.connectTimeoutId = setTimeout(() => {\n this.connectErrorHandler(new Error(\"EventSource connect took too long.\"));\n }, this.maxConnectTimeout);\n\n this.eventSource = new EventSource(this.client.buildURL(\"/api/realtime\"));\n\n this.eventSource.onerror = (_) => {\n this.connectErrorHandler(\n new Error(\"Failed to establish realtime connection.\"),\n );\n };\n\n this.eventSource.addEventListener(\"PB_CONNECT\", (e) => {\n const msgEvent = e as MessageEvent;\n this.clientId = msgEvent?.lastEventId;\n\n this.submitSubscriptions()\n .then(async () => {\n let retries = 3;\n while (this.hasUnsentSubscriptions() && retries > 0) {\n retries--;\n // resubscribe to ensure that the latest topics are submitted\n //\n // This is needed because missed topics could happen on reconnect\n // if after the pending sent `submitSubscriptions()` call another `subscribe()`\n // was made before the submit was able to complete.\n await this.submitSubscriptions();\n }\n })\n .then(() => {\n for (let p of this.pendingConnects) {\n p.resolve();\n }\n\n // reset connect meta\n this.pendingConnects = [];\n this.reconnectAttempts = 0;\n clearTimeout(this.reconnectTimeoutId);\n clearTimeout(this.connectTimeoutId);\n\n // propagate the PB_CONNECT event\n const connectSubs = this.getSubscriptionsByTopic(\"PB_CONNECT\");\n for (let key in connectSubs) {\n for (let listener of connectSubs[key]) {\n listener(e);\n }\n }\n })\n .catch((err) => {\n this.clientId = \"\";\n this.connectErrorHandler(err);\n });\n });\n }\n\n private hasUnsentSubscriptions(): boolean {\n const latestTopics = this.getNonEmptySubscriptionKeys();\n if (latestTopics.length != this.lastSentSubscriptions.length) {\n return true;\n }\n\n for (const t of latestTopics) {\n if (!this.lastSentSubscriptions.includes(t)) {\n return true;\n }\n }\n\n return false;\n }\n\n private connectErrorHandler(err: any) {\n clearTimeout(this.connectTimeoutId);\n clearTimeout(this.reconnectTimeoutId);\n\n if (\n // wasn't previously connected -> direct reject\n (!this.clientId && !this.reconnectAttempts) ||\n // was previously connected but the max reconnection limit has been reached\n this.reconnectAttempts > this.maxReconnectAttempts\n ) {\n for (let p of this.pendingConnects) {\n p.reject(new ClientResponseError(err));\n }\n this.pendingConnects = [];\n this.disconnect();\n return;\n }\n\n // otherwise -> reconnect in the background\n this.disconnect(true);\n const timeout =\n this.predefinedReconnectIntervals[this.reconnectAttempts] ||\n this.predefinedReconnectIntervals[\n this.predefinedReconnectIntervals.length - 1\n ];\n this.reconnectAttempts++;\n this.reconnectTimeoutId = setTimeout(() => {\n this.initConnect();\n }, timeout);\n }\n\n private disconnect(fromReconnect = false): void {\n if (this.clientId && this.onDisconnect) {\n this.onDisconnect(Object.keys(this.subscriptions));\n }\n\n clearTimeout(this.connectTimeoutId);\n clearTimeout(this.reconnectTimeoutId);\n this.removeAllSubscriptionListeners();\n this.client.cancelRequest(this.getSubscriptionsCancelKey());\n this.eventSource?.close();\n this.eventSource = null;\n this.clientId = \"\";\n\n if (!fromReconnect) {\n this.reconnectAttempts = 0;\n\n // resolve any remaining connect promises\n //\n // this is done to avoid unnecessary throwing errors in case\n // unsubscribe is called before the pending connect promises complete\n // (see https://github.com/pocketbase/pocketbase/discussions/2897#discussioncomment-6423818)\n for (let p of this.pendingConnects) {\n p.resolve();\n }\n this.pendingConnects = [];\n }\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { ClientResponseError } from \"@/ClientResponseError\";\nimport { ListResult } from \"@/tools/dtos\";\nimport { CommonOptions, ListOptions, FullListOptions } from \"@/tools/options\";\n\nexport abstract class CrudService extends BaseService {\n /**\n * Base path for the crud actions (without trailing slash, eg. '/admins').\n */\n abstract get baseCrudPath(): string;\n\n /**\n * Response data decoder.\n */\n decode(data: { [key: string]: any }): T {\n return data as T;\n }\n\n /**\n * Returns a promise with all list items batch fetched at once\n * (by default 1000 items per request; to change it set the `batch` query param).\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: FullListOptions): Promise>;\n\n /**\n * Legacy version of getFullList with explicitly specified batch size.\n */\n async getFullList(batch?: number, options?: ListOptions): Promise>;\n\n async getFullList(\n batchOrqueryParams?: number | FullListOptions,\n options?: ListOptions,\n ): Promise> {\n if (typeof batchOrqueryParams == \"number\") {\n return this._getFullList(batchOrqueryParams, options);\n }\n\n options = Object.assign({}, batchOrqueryParams, options);\n\n let batch = 1000;\n if (options.batch) {\n batch = options.batch;\n delete options.batch;\n }\n\n return this._getFullList(batch, options);\n }\n\n /**\n * Returns paginated items list.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: ListOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n options.query = Object.assign(\n {\n page: page,\n perPage: perPage,\n },\n options.query,\n );\n\n return this.client.send(this.baseCrudPath, options).then((responseData: any) => {\n responseData.items =\n responseData.items?.map((item: any) => {\n return this.decode(item);\n }) || [];\n\n return responseData;\n });\n }\n\n /**\n * Returns the first found item by the specified filter.\n *\n * Internally it calls `getList(1, 1, { filter, skipTotal })` and\n * returns the first found item.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * For consistency with `getOne`, this method will throw a 404\n * ClientResponseError if no item was found.\n *\n * @throws {ClientResponseError}\n */\n async getFirstListItem(filter: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n requestKey: \"one_by_filter_\" + this.baseCrudPath + \"_\" + filter,\n },\n options,\n );\n\n options.query = Object.assign(\n {\n filter: filter,\n skipTotal: 1,\n },\n options.query,\n );\n\n return this.getList(1, 1, options).then((result) => {\n if (!result?.items?.length) {\n throw new ClientResponseError({\n status: 404,\n response: {\n code: 404,\n message: \"The requested resource wasn't found.\",\n data: {},\n },\n });\n }\n\n return result.items[0];\n });\n }\n\n /**\n * Returns single item by its id.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * If `id` is empty it will throw a 404 error.\n *\n * @throws {ClientResponseError}\n */\n async getOne(id: string, options?: CommonOptions): Promise {\n if (!id) {\n throw new ClientResponseError({\n url: this.client.buildURL(this.baseCrudPath + \"/\"),\n status: 404,\n response: {\n code: 404,\n message: \"Missing required record id.\",\n data: {},\n },\n });\n }\n\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Creates a new item.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath, options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Updates an existing item by its id.\n *\n * You can use the generic T to supply a wrapper type of the crud model.\n *\n * @throws {ClientResponseError}\n */\n async update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"PATCH\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then((responseData: any) => this.decode(responseData));\n }\n\n /**\n * Deletes an existing item by its id.\n *\n * @throws {ClientResponseError}\n */\n async delete(id: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(this.baseCrudPath + \"/\" + encodeURIComponent(id), options)\n .then(() => true);\n }\n\n /**\n * Returns a promise with all list items batch fetched at once.\n */\n protected _getFullList(\n batchSize = 1000,\n options?: ListOptions,\n ): Promise> {\n options = options || {};\n options.query = Object.assign(\n {\n skipTotal: 1,\n },\n options.query,\n );\n\n let result: Array = [];\n\n let request = async (page: number): Promise> => {\n return this.getList(page, batchSize || 1000, options).then((list) => {\n const castedList = list as any as ListResult;\n const items = castedList.items;\n\n result = result.concat(items);\n\n if (items.length == list.perPage) {\n return request(page + 1);\n }\n\n return result;\n });\n };\n\n return request(1);\n }\n}\n","import { SendOptions } from \"@/tools/options\";\n\nexport function normalizeLegacyOptionsArgs(\n legacyWarn: string,\n baseOptions: SendOptions,\n bodyOrOptions?: any,\n query?: any,\n): SendOptions {\n const hasBodyOrOptions = typeof bodyOrOptions !== \"undefined\";\n const hasQuery = typeof query !== \"undefined\";\n\n if (!hasQuery && !hasBodyOrOptions) {\n return baseOptions;\n }\n\n if (hasQuery) {\n console.warn(legacyWarn);\n baseOptions.body = Object.assign({}, baseOptions.body, bodyOrOptions);\n baseOptions.query = Object.assign({}, baseOptions.query, query);\n\n return baseOptions;\n }\n\n return Object.assign(baseOptions, bodyOrOptions);\n}\n","import Client from \"@/Client\";\nimport { isTokenExpired } from \"@/tools/jwt\";\n\n// reset previous auto refresh registrations\nexport function resetAutoRefresh(client: Client) {\n (client as any)._resetAutoRefresh?.();\n}\n\nexport function registerAutoRefresh(\n client: Client,\n threshold: number,\n refreshFunc: () => Promise,\n reauthenticateFunc: () => Promise,\n) {\n resetAutoRefresh(client);\n\n const oldBeforeSend = client.beforeSend;\n const oldModel = client.authStore.record;\n\n // unset the auto refresh in case the auth store was cleared\n // OR a new model was authenticated\n const unsubStoreChange = client.authStore.onChange((newToken, model) => {\n if (\n !newToken ||\n model?.id != oldModel?.id ||\n ((model?.collectionId || oldModel?.collectionId) &&\n model?.collectionId != oldModel?.collectionId)\n ) {\n resetAutoRefresh(client);\n }\n });\n\n // initialize a reset function and attach it dynamically to the client\n (client as any)._resetAutoRefresh = function () {\n unsubStoreChange();\n client.beforeSend = oldBeforeSend;\n delete (client as any)._resetAutoRefresh;\n };\n\n client.beforeSend = async (url, sendOptions) => {\n const oldToken = client.authStore.token;\n\n if (sendOptions.query?.autoRefresh) {\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n }\n\n let isValid = client.authStore.isValid;\n if (\n // is loosely valid\n isValid &&\n // but it is going to expire in the next \"threshold\" seconds\n isTokenExpired(client.authStore.token, threshold)\n ) {\n try {\n await refreshFunc();\n } catch (_) {\n isValid = false;\n }\n }\n\n // still invalid -> reauthenticate\n if (!isValid) {\n await reauthenticateFunc();\n }\n\n // the request wasn't sent with a custom token\n const headers = sendOptions.headers || {};\n for (let key in headers) {\n if (\n key.toLowerCase() == \"authorization\" &&\n // the request wasn't sent with a custom token\n oldToken == headers[key] &&\n client.authStore.token\n ) {\n // set the latest store token\n headers[key] = client.authStore.token;\n break;\n }\n }\n sendOptions.headers = headers;\n\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n };\n}\n","import Client from \"@/Client\";\nimport { ClientResponseError } from \"@/ClientResponseError\";\nimport { RealtimeService, UnsubscribeFunc } from \"@/services/RealtimeService\";\nimport { BaseAuthStore } from \"@/stores/BaseAuthStore\";\nimport { CrudService } from \"@/services/CrudService\";\nimport { ListResult, RecordModel } from \"@/tools/dtos\";\nimport { normalizeLegacyOptionsArgs } from \"@/tools/legacy\";\nimport {\n CommonOptions,\n RecordFullListOptions,\n RecordListOptions,\n RecordOptions,\n SendOptions,\n RecordSubscribeOptions,\n} from \"@/tools/options\";\nimport { getTokenPayload } from \"@/tools/jwt\";\nimport { registerAutoRefresh, resetAutoRefresh } from \"@/tools/refresh\";\n\nexport interface RecordAuthResponse {\n /**\n * The signed PocketBase auth record.\n */\n record: T;\n\n /**\n * The PocketBase record auth token.\n *\n * If you are looking for the OAuth2 access and refresh tokens\n * they are available under the `meta.accessToken` and `meta.refreshToken` props.\n */\n token: string;\n\n /**\n * Auth meta data usually filled when OAuth2 is used.\n */\n meta?: { [key: string]: any };\n}\n\nexport interface AuthProviderInfo {\n name: string;\n displayName: string;\n state: string;\n authURL: string;\n codeVerifier: string;\n codeChallenge: string;\n codeChallengeMethod: string;\n}\n\nexport interface AuthMethodsList {\n mfa: {\n enabled: boolean;\n duration: number;\n };\n otp: {\n enabled: boolean;\n duration: number;\n };\n password: {\n enabled: boolean;\n identityFields: Array;\n };\n oauth2: {\n enabled: boolean;\n providers: Array;\n };\n}\n\nexport interface RecordSubscription {\n action: string; // eg. create, update, delete\n record: T;\n}\n\nexport type OAuth2UrlCallback = (url: string) => void | Promise;\n\nexport interface OAuth2AuthConfig extends SendOptions {\n // the name of the OAuth2 provider (eg. \"google\")\n provider: string;\n\n // custom scopes to overwrite the default ones\n scopes?: Array;\n\n // optional record create data\n createData?: { [key: string]: any };\n\n // optional callback that is triggered after the OAuth2 sign-in/sign-up url generation\n urlCallback?: OAuth2UrlCallback;\n\n // optional query params to send with the PocketBase auth request (eg. fields, expand, etc.)\n query?: RecordOptions;\n}\n\nexport interface OTPResponse {\n otpId: string;\n}\n\nexport class RecordService extends CrudService {\n readonly collectionIdOrName: string;\n\n constructor(client: Client, collectionIdOrName: string) {\n super(client);\n\n this.collectionIdOrName = collectionIdOrName;\n }\n\n /**\n * @inheritdoc\n */\n get baseCrudPath(): string {\n return this.baseCollectionPath + \"/records\";\n }\n\n /**\n * Returns the current collection service base path.\n */\n get baseCollectionPath(): string {\n return \"/api/collections/\" + encodeURIComponent(this.collectionIdOrName);\n }\n\n /**\n * Returns whether the current service collection is superusers.\n */\n get isSuperusers(): boolean {\n return (\n this.collectionIdOrName == \"_superusers\" ||\n this.collectionIdOrName == \"_pbc_2773867675\"\n );\n }\n\n // ---------------------------------------------------------------\n // Realtime handlers\n // ---------------------------------------------------------------\n\n /**\n * Subscribe to realtime changes to the specified topic (\"*\" or record id).\n *\n * If `topic` is the wildcard \"*\", then this method will subscribe to\n * any record changes in the collection.\n *\n * If `topic` is a record id, then this method will subscribe only\n * to changes of the specified record id.\n *\n * It's OK to subscribe multiple times to the same topic.\n * You can use the returned `UnsubscribeFunc` to remove only a single subscription.\n * Or use `unsubscribe(topic)` if you want to remove all subscriptions attached to the topic.\n */\n async subscribe(\n topic: string,\n callback: (data: RecordSubscription) => void,\n options?: RecordSubscribeOptions,\n ): Promise {\n if (!topic) {\n throw new Error(\"Missing topic.\");\n }\n\n if (!callback) {\n throw new Error(\"Missing subscription callback.\");\n }\n\n return this.client.realtime.subscribe(\n this.collectionIdOrName + \"/\" + topic,\n callback,\n options,\n );\n }\n\n /**\n * Unsubscribe from all subscriptions of the specified topic\n * (\"*\" or record id).\n *\n * If `topic` is not set, then this method will unsubscribe from\n * all subscriptions associated to the current collection.\n */\n async unsubscribe(topic?: string): Promise {\n // unsubscribe from the specified topic\n if (topic) {\n return this.client.realtime.unsubscribe(\n this.collectionIdOrName + \"/\" + topic,\n );\n }\n\n // unsubscribe from everything related to the collection\n return this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName);\n }\n\n // ---------------------------------------------------------------\n // Crud handers\n // ---------------------------------------------------------------\n /**\n * @inheritdoc\n */\n async getFullList(options?: RecordFullListOptions): Promise>;\n\n /**\n * @inheritdoc\n */\n async getFullList(\n batch?: number,\n options?: RecordListOptions,\n ): Promise>;\n\n /**\n * @inheritdoc\n */\n async getFullList(\n batchOrOptions?: number | RecordFullListOptions,\n options?: RecordListOptions,\n ): Promise> {\n if (typeof batchOrOptions == \"number\") {\n return super.getFullList(batchOrOptions, options);\n }\n\n const params = Object.assign({}, batchOrOptions, options);\n\n return super.getFullList(params);\n }\n\n /**\n * @inheritdoc\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: RecordListOptions,\n ): Promise> {\n return super.getList(page, perPage, options);\n }\n\n /**\n * @inheritdoc\n */\n async getFirstListItem(\n filter: string,\n options?: RecordListOptions,\n ): Promise {\n return super.getFirstListItem(filter, options);\n }\n\n /**\n * @inheritdoc\n */\n async getOne(id: string, options?: RecordOptions): Promise {\n return super.getOne(id, options);\n }\n\n /**\n * @inheritdoc\n */\n async create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): Promise {\n return super.create(bodyParams, options);\n }\n\n /**\n * @inheritdoc\n *\n * If the current `client.authStore.record` matches with the updated id, then\n * on success the `client.authStore.record` will be updated with the new response record fields.\n */\n async update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): Promise {\n return super.update(id, bodyParams, options).then((item) => {\n if (\n // is record auth\n this.client.authStore.record?.id === item?.id &&\n (this.client.authStore.record?.collectionId === this.collectionIdOrName ||\n this.client.authStore.record?.collectionName ===\n this.collectionIdOrName)\n ) {\n let authExpand = Object.assign({}, this.client.authStore.record.expand);\n let authRecord = Object.assign({}, this.client.authStore.record, item);\n if (authExpand) {\n // for now \"merge\" only top-level expand\n authRecord.expand = Object.assign(authExpand, item.expand);\n }\n\n this.client.authStore.save(this.client.authStore.token, authRecord);\n }\n\n return item as any as T;\n });\n }\n\n /**\n * @inheritdoc\n *\n * If the current `client.authStore.record` matches with the deleted id,\n * then on success the `client.authStore` will be cleared.\n */\n async delete(id: string, options?: CommonOptions): Promise {\n return super.delete(id, options).then((success) => {\n if (\n success &&\n // is record auth\n this.client.authStore.record?.id === id &&\n (this.client.authStore.record?.collectionId === this.collectionIdOrName ||\n this.client.authStore.record?.collectionName ===\n this.collectionIdOrName)\n ) {\n this.client.authStore.clear();\n }\n\n return success;\n });\n }\n\n // ---------------------------------------------------------------\n // Auth handlers\n // ---------------------------------------------------------------\n\n /**\n * Prepare successful collection authorization response.\n */\n protected authResponse(responseData: any): RecordAuthResponse {\n const record = this.decode(responseData?.record || {});\n\n this.client.authStore.save(responseData?.token, record as any);\n\n return Object.assign({}, responseData, {\n // normalize common fields\n token: responseData?.token || \"\",\n record: record as any as T,\n });\n }\n\n /**\n * Returns all available collection auth methods.\n *\n * @throws {ClientResponseError}\n */\n async listAuthMethods(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"GET\",\n // @todo remove after deleting the pre v0.23 API response fields\n fields: \"mfa,otp,password,oauth2\",\n },\n options,\n );\n\n return this.client.send(this.baseCollectionPath + \"/auth-methods\", options);\n }\n\n /**\n * Authenticate a single auth collection record via its username/email and password.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n *\n * @throws {ClientResponseError}\n */\n async authWithPassword(\n usernameOrEmail: string,\n password: string,\n options?: RecordOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n identity: usernameOrEmail,\n password: password,\n },\n },\n options,\n );\n\n // note: consider to deprecate\n let autoRefreshThreshold;\n if (this.isSuperusers) {\n autoRefreshThreshold = options.autoRefreshThreshold;\n delete options.autoRefreshThreshold;\n if (!options.autoRefresh) {\n resetAutoRefresh(this.client);\n }\n }\n\n let authData = await this.client.send(\n this.baseCollectionPath + \"/auth-with-password\",\n options,\n );\n\n authData = this.authResponse(authData);\n\n if (autoRefreshThreshold && this.isSuperusers) {\n registerAutoRefresh(\n this.client,\n autoRefreshThreshold,\n () => this.authRefresh({ autoRefresh: true }),\n () =>\n this.authWithPassword(\n usernameOrEmail,\n password,\n Object.assign({ autoRefresh: true }, options),\n ),\n );\n }\n\n return authData;\n }\n\n /**\n * Authenticate a single auth collection record with OAuth2 code.\n *\n * If you don't have an OAuth2 code you may also want to check `authWithOAuth2` method.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n * - the OAuth2 account data (eg. name, email, avatar, etc.)\n *\n * @throws {ClientResponseError}\n */\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n options?: RecordOptions,\n ): Promise>;\n\n /**\n * @deprecated\n * Consider using authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createdData, options?).\n */\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n body?: any,\n query?: any,\n ): Promise>;\n\n async authWithOAuth2Code(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n bodyOrOptions?: any,\n query?: any,\n ): Promise> {\n let options: any = {\n method: \"POST\",\n body: {\n provider: provider,\n code: code,\n codeVerifier: codeVerifier,\n redirectURL: redirectURL,\n createData: createData,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-with-oauth2\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * @deprecated This form of authWithOAuth2 is deprecated.\n *\n * Please use `authWithOAuth2Code()` OR its simplified realtime version\n * as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.\n */\n async authWithOAuth2(\n provider: string,\n code: string,\n codeVerifier: string,\n redirectURL: string,\n createData?: { [key: string]: any },\n bodyParams?: { [key: string]: any },\n queryParams?: RecordOptions,\n ): Promise>;\n\n /**\n * Authenticate a single auth collection record with OAuth2\n * **without custom redirects, deeplinks or even page reload**.\n *\n * This method initializes a one-off realtime subscription and will\n * open a popup window with the OAuth2 vendor page to authenticate.\n * Once the external OAuth2 sign-in/sign-up flow is completed, the popup\n * window will be automatically closed and the OAuth2 data sent back\n * to the user through the previously established realtime connection.\n *\n * You can specify an optional `urlCallback` prop to customize\n * the default url `window.open` behavior.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n * - the OAuth2 account data (eg. name, email, avatar, etc.)\n *\n * Example:\n *\n * ```js\n * const authData = await pb.collection(\"users\").authWithOAuth2({\n * provider: \"google\",\n * })\n * ```\n *\n * Note1: When creating the OAuth2 app in the provider dashboard\n * you have to configure `https://yourdomain.com/api/oauth2-redirect`\n * as redirect URL.\n *\n * Note2: Safari may block the default `urlCallback` popup because\n * it doesn't allow `window.open` calls as part of an `async` click functions.\n * To workaround this you can either change your click handler to not be marked as `async`\n * OR manually call `window.open` before your `async` function and use the\n * window reference in your own custom `urlCallback` (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061).\n * For example:\n * ```js\n * \n * ...\n * document.getElementById(\"btn\").addEventListener(\"click\", () => {\n * pb.collection(\"users\").authWithOAuth2({\n * provider: \"gitlab\",\n * }).then((authData) => {\n * console.log(authData)\n * }).catch((err) => {\n * console.log(err, err.originalError);\n * });\n * })\n * ```\n *\n * @throws {ClientResponseError}\n */\n async authWithOAuth2(\n options: OAuth2AuthConfig,\n ): Promise>;\n\n authWithOAuth2(...args: any): Promise> {\n // fallback to legacy format\n if (args.length > 1 || typeof args?.[0] === \"string\") {\n console.warn(\n \"PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.\",\n );\n return this.authWithOAuth2Code(\n args?.[0] || \"\",\n args?.[1] || \"\",\n args?.[2] || \"\",\n args?.[3] || \"\",\n args?.[4] || {},\n args?.[5] || {},\n args?.[6] || {},\n );\n }\n\n const config = args?.[0] || {};\n\n // open a new popup window in case config.urlCallback is not set\n //\n // note: it is opened before any async calls due to Safari restrictions\n // (see https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061)\n let eagerDefaultPopup: Window | null = null;\n if (!config.urlCallback) {\n eagerDefaultPopup = openBrowserPopup(undefined);\n }\n\n // initialize a one-off realtime service\n const realtime = new RealtimeService(this.client);\n\n function cleanup() {\n eagerDefaultPopup?.close();\n realtime.unsubscribe();\n }\n\n const requestKeyOptions: SendOptions = {};\n const requestKey = config.requestKey;\n if (requestKey) {\n requestKeyOptions.requestKey = requestKey;\n }\n\n return this.listAuthMethods(requestKeyOptions)\n .then((authMethods) => {\n const provider = authMethods.oauth2.providers.find(\n (p) => p.name === config.provider,\n );\n if (!provider) {\n throw new ClientResponseError(\n new Error(`Missing or invalid provider \"${config.provider}\".`),\n );\n }\n\n const redirectURL = this.client.buildURL(\"/api/oauth2-redirect\");\n\n return new Promise(async (resolve, reject) => {\n // find the AbortController associated with the current request key (if any)\n const cancelController = requestKey\n ? this.client[\"cancelControllers\"]?.[requestKey]\n : undefined;\n if (cancelController) {\n cancelController.signal.onabort = () => {\n cleanup();\n reject(\n new ClientResponseError({\n isAbort: true,\n message: \"manually cancelled\",\n }),\n );\n };\n }\n\n // disconnected due to network/server error\n realtime.onDisconnect = (activeSubscriptions: Array) => {\n if (activeSubscriptions.length && reject) {\n cleanup();\n reject(\n new ClientResponseError(\n new Error(\"realtime connection interrupted\"),\n ),\n );\n }\n };\n\n try {\n await realtime.subscribe(\"@oauth2\", async (e) => {\n const oldState = realtime.clientId;\n\n try {\n if (!e.state || oldState !== e.state) {\n throw new Error(\"State parameters don't match.\");\n }\n\n if (e.error || !e.code) {\n throw new Error(\n \"OAuth2 redirect error or missing code: \" +\n e.error,\n );\n }\n\n // clear the non SendOptions props\n const options = Object.assign({}, config);\n delete options.provider;\n delete options.scopes;\n delete options.createData;\n delete options.urlCallback;\n\n // reset the cancelController listener as it will be triggered by the next api call\n if (cancelController?.signal?.onabort) {\n cancelController.signal.onabort = null;\n }\n\n const authData = await this.authWithOAuth2Code(\n provider.name,\n e.code,\n provider.codeVerifier,\n redirectURL,\n config.createData,\n options,\n );\n\n resolve(authData);\n } catch (err) {\n reject(new ClientResponseError(err));\n }\n\n cleanup();\n });\n\n const replacements: { [key: string]: any } = {\n state: realtime.clientId,\n };\n if (config.scopes?.length) {\n replacements[\"scope\"] = config.scopes.join(\" \");\n }\n\n const url = this._replaceQueryParams(\n provider.authURL + redirectURL,\n replacements,\n );\n\n let urlCallback =\n config.urlCallback ||\n function (url: string) {\n if (eagerDefaultPopup) {\n eagerDefaultPopup.location.href = url;\n } else {\n // it could have been blocked due to its empty initial url,\n // try again...\n eagerDefaultPopup = openBrowserPopup(url);\n }\n };\n\n await urlCallback(url);\n } catch (err) {\n // reset the cancelController listener in case the request key is reused\n if (cancelController?.signal?.onabort) {\n cancelController.signal.onabort = null;\n }\n\n cleanup();\n reject(new ClientResponseError(err));\n }\n });\n })\n .catch((err) => {\n cleanup();\n throw err; // rethrow\n }) as Promise>;\n }\n\n /**\n * Refreshes the current authenticated record instance and\n * returns a new token and record data.\n *\n * On success this method also automatically updates the client's AuthStore.\n *\n * @throws {ClientResponseError}\n */\n async authRefresh(options?: RecordOptions): Promise>;\n\n /**\n * @deprecated\n * Consider using authRefresh(options?).\n */\n async authRefresh(body?: any, query?: any): Promise>;\n\n async authRefresh(\n bodyOrOptions?: any,\n query?: any,\n ): Promise> {\n let options: any = {\n method: \"POST\",\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-refresh\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * Sends auth record password reset request.\n *\n * @throws {ClientResponseError}\n */\n async requestPasswordReset(email: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestPasswordReset(email, options?).\n */\n async requestPasswordReset(email: string, body?: any, query?: any): Promise;\n\n async requestPasswordReset(\n email: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n email: email,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-password-reset\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record password reset request.\n *\n * @throws {ClientResponseError}\n */\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmPasswordReset(passwordResetToken, password, passwordConfirm, options?).\n */\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmPasswordReset(\n passwordResetToken: string,\n password: string,\n passwordConfirm: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: passwordResetToken,\n password: password,\n passwordConfirm: passwordConfirm,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-password-reset\", options)\n .then(() => true);\n }\n\n /**\n * Sends auth record verification email request.\n *\n * @throws {ClientResponseError}\n */\n async requestVerification(email: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestVerification(email, options?).\n */\n async requestVerification(email: string, body?: any, query?: any): Promise;\n\n async requestVerification(\n email: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n email: email,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-verification\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record email verification request.\n *\n * If the current `client.authStore.record` matches with the auth record from the token,\n * then on success the `client.authStore.record.verified` will be updated to `true`.\n *\n * @throws {ClientResponseError}\n */\n async confirmVerification(\n verificationToken: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmVerification(verificationToken, options?).\n */\n async confirmVerification(\n verificationToken: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmVerification(\n verificationToken: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: verificationToken,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-verification\", options)\n .then(() => {\n // on success manually update the current auth record verified state\n const payload = getTokenPayload(verificationToken);\n const model = this.client.authStore.record;\n if (\n model &&\n !model.verified &&\n model.id === payload.id &&\n model.collectionId === payload.collectionId\n ) {\n model.verified = true;\n this.client.authStore.save(this.client.authStore.token, model);\n }\n\n return true;\n });\n }\n\n /**\n * Sends an email change request to the authenticated record model.\n *\n * @throws {ClientResponseError}\n */\n async requestEmailChange(newEmail: string, options?: CommonOptions): Promise;\n\n /**\n * @deprecated\n * Consider using requestEmailChange(newEmail, options?).\n */\n async requestEmailChange(newEmail: string, body?: any, query?: any): Promise;\n\n async requestEmailChange(\n newEmail: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n newEmail: newEmail,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/request-email-change\", options)\n .then(() => true);\n }\n\n /**\n * Confirms auth record's new email address.\n *\n * If the current `client.authStore.record` matches with the auth record from the token,\n * then on success the `client.authStore` will be cleared.\n *\n * @throws {ClientResponseError}\n */\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n options?: CommonOptions,\n ): Promise;\n\n /**\n * @deprecated\n * Consider using confirmEmailChange(emailChangeToken, password, options?).\n */\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n body?: any,\n query?: any,\n ): Promise;\n\n async confirmEmailChange(\n emailChangeToken: string,\n password: string,\n bodyOrOptions?: any,\n query?: any,\n ): Promise {\n let options: any = {\n method: \"POST\",\n body: {\n token: emailChangeToken,\n password: password,\n },\n };\n\n options = normalizeLegacyOptionsArgs(\n \"This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).\",\n options,\n bodyOrOptions,\n query,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/confirm-email-change\", options)\n .then(() => {\n const payload = getTokenPayload(emailChangeToken);\n const model = this.client.authStore.record;\n if (\n model &&\n model.id === payload.id &&\n model.collectionId === payload.collectionId\n ) {\n this.client.authStore.clear();\n }\n\n return true;\n });\n }\n\n /**\n * @deprecated use collection(\"_externalAuths\").*\n *\n * Lists all linked external auth providers for the specified auth record.\n *\n * @throws {ClientResponseError}\n */\n async listExternalAuths(\n recordId: string,\n options?: CommonOptions,\n ): Promise> {\n return this.client.collection(\"_externalAuths\").getFullList(\n Object.assign({}, options, {\n filter: this.client.filter(\"recordRef = {:id}\", { id: recordId }),\n }),\n );\n }\n\n /**\n * @deprecated use collection(\"_externalAuths\").*\n *\n * Unlink a single external auth provider from the specified auth record.\n *\n * @throws {ClientResponseError}\n */\n async unlinkExternalAuth(\n recordId: string,\n provider: string,\n options?: CommonOptions,\n ): Promise {\n const ea = await this.client.collection(\"_externalAuths\").getFirstListItem(\n this.client.filter(\"recordRef = {:recordId} && provider = {:provider}\", {\n recordId,\n provider,\n }),\n );\n\n return this.client\n .collection(\"_externalAuths\")\n .delete(ea.id, options)\n .then(() => true);\n }\n\n /**\n * Sends auth record OTP to the provided email.\n *\n * @throws {ClientResponseError}\n */\n async requestOTP(email: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: { email: email },\n },\n options,\n );\n\n return this.client.send(this.baseCollectionPath + \"/request-otp\", options);\n }\n\n /**\n * Authenticate a single auth collection record via OTP.\n *\n * On success, this method also automatically updates\n * the client's AuthStore data and returns:\n * - the authentication token\n * - the authenticated record model\n *\n * @throws {ClientResponseError}\n */\n async authWithOTP(\n otpId: string,\n password: string,\n options?: CommonOptions,\n ): Promise> {\n options = Object.assign(\n {\n method: \"POST\",\n body: { otpId, password },\n },\n options,\n );\n\n return this.client\n .send(this.baseCollectionPath + \"/auth-with-otp\", options)\n .then((data) => this.authResponse(data));\n }\n\n /**\n * Impersonate authenticates with the specified recordId and\n * returns a new client with the received auth token in a memory store.\n *\n * If `duration` is 0 the generated auth token will fallback\n * to the default collection auth token duration.\n *\n * This action currently requires superusers privileges.\n *\n * @throws {ClientResponseError}\n */\n async impersonate(\n recordId: string,\n duration: number,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: { duration: duration },\n },\n options,\n );\n options.headers = options.headers || {};\n if (!options.headers.Authorization) {\n options.headers.Authorization = this.client.authStore.token;\n }\n\n // create a new client loaded with the impersonated auth state\n // ---\n const client = new Client(\n this.client.baseURL,\n new BaseAuthStore(),\n this.client.lang,\n );\n\n const authData = await client.send(\n this.baseCollectionPath + \"/impersonate/\" + encodeURIComponent(recordId),\n options,\n );\n\n client.authStore.save(authData?.token, this.decode(authData?.record || {}));\n // ---\n\n return client;\n }\n\n // ---------------------------------------------------------------\n\n // very rudimentary url query params replacement because at the moment\n // URL (and URLSearchParams) doesn't seem to be fully supported in React Native\n //\n // note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html\n private _replaceQueryParams(\n url: string,\n replacements: { [key: string]: any } = {},\n ): string {\n let urlPath = url;\n let query = \"\";\n\n const queryIndex = url.indexOf(\"?\");\n if (queryIndex >= 0) {\n urlPath = url.substring(0, url.indexOf(\"?\"));\n query = url.substring(url.indexOf(\"?\") + 1);\n }\n\n const parsedParams: { [key: string]: string } = {};\n\n // parse the query parameters\n const rawParams = query.split(\"&\");\n for (const param of rawParams) {\n if (param == \"\") {\n continue;\n }\n\n const pair = param.split(\"=\");\n parsedParams[decodeURIComponent(pair[0].replace(/\\+/g, \" \"))] =\n decodeURIComponent((pair[1] || \"\").replace(/\\+/g, \" \"));\n }\n\n // apply the replacements\n for (let key in replacements) {\n if (!replacements.hasOwnProperty(key)) {\n continue;\n }\n\n if (replacements[key] == null) {\n delete parsedParams[key];\n } else {\n parsedParams[key] = replacements[key];\n }\n }\n\n // construct back the full query string\n query = \"\";\n for (let key in parsedParams) {\n if (!parsedParams.hasOwnProperty(key)) {\n continue;\n }\n\n if (query != \"\") {\n query += \"&\";\n }\n\n query +=\n encodeURIComponent(key.replace(/%20/g, \"+\")) +\n \"=\" +\n encodeURIComponent(parsedParams[key].replace(/%20/g, \"+\"));\n }\n\n return query != \"\" ? urlPath + \"?\" + query : urlPath;\n }\n}\n\nfunction openBrowserPopup(url?: string): Window | null {\n if (typeof window === \"undefined\" || !window?.open) {\n throw new ClientResponseError(\n new Error(\n `Not in a browser context - please pass a custom urlCallback function.`,\n ),\n );\n }\n\n let width = 1024;\n let height = 768;\n\n let windowWidth = window.innerWidth;\n let windowHeight = window.innerHeight;\n\n // normalize window size\n width = width > windowWidth ? windowWidth : width;\n height = height > windowHeight ? windowHeight : height;\n\n let left = windowWidth / 2 - width / 2;\n let top = windowHeight / 2 - height / 2;\n\n // note: we don't use the noopener and noreferrer attributes since\n // for some reason browser blocks such windows then url is undefined/blank\n return window.open(\n url,\n \"popup_window\",\n \"width=\" +\n width +\n \",height=\" +\n height +\n \",top=\" +\n top +\n \",left=\" +\n left +\n \",resizable,menubar=no\",\n );\n}\n","import { CrudService } from \"@/services/CrudService\";\nimport { CollectionModel } from \"@/tools/dtos\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport class CollectionService extends CrudService {\n /**\n * @inheritdoc\n */\n get baseCrudPath(): string {\n return \"/api/collections\";\n }\n\n /**\n * Imports the provided collections.\n *\n * If `deleteMissing` is `true`, all local collections and their fields,\n * that are not present in the imported configuration, WILL BE DELETED\n * (including their related records data)!\n *\n * @throws {ClientResponseError}\n */\n async import(\n collections: Array,\n deleteMissing: boolean = false,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"PUT\",\n body: {\n collections: collections,\n deleteMissing: deleteMissing,\n },\n },\n options,\n );\n\n return this.client.send(this.baseCrudPath + \"/import\", options).then(() => true);\n }\n\n /**\n * Returns type indexed map with scaffolded collection models\n * populated with their default field values.\n *\n * @throws {ClientResponseError}\n */\n async getScaffolds(\n options?: CommonOptions,\n ): Promise<{ [key: string]: CollectionModel }> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(this.baseCrudPath + \"/meta/scaffolds\", options);\n }\n\n /**\n * Deletes all records associated with the specified collection.\n *\n * @throws {ClientResponseError}\n */\n async truncate(collectionIdOrName: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(\n this.baseCrudPath +\n \"/\" +\n encodeURIComponent(collectionIdOrName) +\n \"/truncate\",\n options,\n )\n .then(() => true);\n }\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseService } from \"@/services/BaseService\";\nimport { ListResult, LogModel } from \"@/tools/dtos\";\nimport { CommonOptions, ListOptions, LogStatsOptions } from \"@/tools/options\";\n\nexport interface HourlyStats {\n total: number;\n date: string;\n}\n\nexport class LogService extends BaseService {\n /**\n * Returns paginated logs list.\n *\n * @throws {ClientResponseError}\n */\n async getList(\n page = 1,\n perPage = 30,\n options?: ListOptions,\n ): Promise> {\n options = Object.assign({ method: \"GET\" }, options);\n\n options.query = Object.assign(\n {\n page: page,\n perPage: perPage,\n },\n options.query,\n );\n\n return this.client.send(\"/api/logs\", options);\n }\n\n /**\n * Returns a single log by its id.\n *\n * If `id` is empty it will throw a 404 error.\n *\n * @throws {ClientResponseError}\n */\n async getOne(id: string, options?: CommonOptions): Promise {\n if (!id) {\n throw new ClientResponseError({\n url: this.client.buildURL(\"/api/logs/\"),\n status: 404,\n response: {\n code: 404,\n message: \"Missing required log id.\",\n data: {},\n },\n });\n }\n\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/logs/\" + encodeURIComponent(id), options);\n }\n\n /**\n * Returns logs statistics.\n *\n * @throws {ClientResponseError}\n */\n async getStats(options?: LogStatsOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/logs/stats\", options);\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface HealthCheckResponse {\n code: number;\n message: string;\n data: { [key: string]: any };\n}\n\nexport class HealthService extends BaseService {\n /**\n * Checks the health status of the api.\n *\n * @throws {ClientResponseError}\n */\n async check(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/health\", options);\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions, FileOptions, serializeQueryParams } from \"@/tools/options\";\n\nexport class FileService extends BaseService {\n /**\n * @deprecated Please replace with `pb.files.getURL()`.\n */\n getUrl(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n console.warn(\"Please replace pb.files.getUrl() with pb.files.getURL()\");\n return this.getURL(record, filename, queryParams);\n }\n\n /**\n * Builds and returns an absolute record file url for the provided filename.\n */\n getURL(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n if (\n !filename ||\n !record?.id ||\n !(record?.collectionId || record?.collectionName)\n ) {\n return \"\";\n }\n\n const parts = [];\n parts.push(\"api\");\n parts.push(\"files\");\n parts.push(encodeURIComponent(record.collectionId || record.collectionName));\n parts.push(encodeURIComponent(record.id));\n parts.push(encodeURIComponent(filename));\n\n let result = this.client.buildURL(parts.join(\"/\"));\n\n // normalize the download query param for consistency with the Dart sdk\n if (queryParams.download === false) {\n delete queryParams.download;\n }\n\n const params = serializeQueryParams(queryParams);\n if (params) {\n result += (result.includes(\"?\") ? \"&\" : \"?\") + params;\n }\n\n return result;\n }\n\n /**\n * Requests a new private file access token for the current auth model.\n *\n * @throws {ClientResponseError}\n */\n async getToken(options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(\"/api/files/token\", options)\n .then((data) => data?.token || \"\");\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface BackupFileInfo {\n key: string;\n size: number;\n modified: string;\n}\n\nexport class BackupService extends BaseService {\n /**\n * Returns list with all available backup files.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: CommonOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/backups\", options);\n }\n\n /**\n * Initializes a new backup.\n *\n * @throws {ClientResponseError}\n */\n async create(basename: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: {\n name: basename,\n },\n },\n options,\n );\n\n return this.client.send(\"/api/backups\", options).then(() => true);\n }\n\n /**\n * Uploads an existing backup file.\n *\n * Example:\n *\n * ```js\n * await pb.backups.upload({\n * file: new Blob([...]),\n * });\n * ```\n *\n * @throws {ClientResponseError}\n */\n async upload(\n bodyParams: { [key: string]: any } | FormData,\n options?: CommonOptions,\n ): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n body: bodyParams,\n },\n options,\n );\n\n return this.client.send(\"/api/backups/upload\", options).then(() => true);\n }\n\n /**\n * Deletes a single backup file.\n *\n * @throws {ClientResponseError}\n */\n async delete(key: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"DELETE\",\n },\n options,\n );\n\n return this.client\n .send(`/api/backups/${encodeURIComponent(key)}`, options)\n .then(() => true);\n }\n\n /**\n * Initializes an app data restore from an existing backup.\n *\n * @throws {ClientResponseError}\n */\n async restore(key: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(`/api/backups/${encodeURIComponent(key)}/restore`, options)\n .then(() => true);\n }\n\n /**\n * @deprecated Please use `getDownloadURL()`.\n */\n getDownloadUrl(token: string, key: string): string {\n console.warn(\n \"Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()\",\n );\n return this.getDownloadURL(token, key);\n }\n\n /**\n * Builds a download url for a single existing backup using a\n * superuser file token and the backup file key.\n *\n * The file token can be generated via `pb.files.getToken()`.\n */\n getDownloadURL(token: string, key: string): string {\n return this.client.buildURL(\n `/api/backups/${encodeURIComponent(key)}?token=${encodeURIComponent(token)}`,\n );\n }\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { CommonOptions } from \"@/tools/options\";\n\nexport interface CronJob {\n id: string;\n expression: string;\n}\n\nexport class CronService extends BaseService {\n /**\n * Returns list with all registered cron jobs.\n *\n * @throws {ClientResponseError}\n */\n async getFullList(options?: CommonOptions): Promise> {\n options = Object.assign(\n {\n method: \"GET\",\n },\n options,\n );\n\n return this.client.send(\"/api/crons\", options);\n }\n\n /**\n * Runs the specified cron job.\n *\n * @throws {ClientResponseError}\n */\n async run(jobId: string, options?: CommonOptions): Promise {\n options = Object.assign(\n {\n method: \"POST\",\n },\n options,\n );\n\n return this.client\n .send(`/api/crons/${encodeURIComponent(jobId)}`, options)\n .then(() => true);\n }\n}\n","/**\n * Checks if the specified value is a file (aka. File, Blob, RN file object).\n */\nexport function isFile(val: any): boolean {\n return (\n (typeof Blob !== \"undefined\" && val instanceof Blob) ||\n (typeof File !== \"undefined\" && val instanceof File) ||\n // check for React Native file object format\n // (see https://github.com/pocketbase/pocketbase/discussions/2002#discussioncomment-5254168)\n (val !== null &&\n typeof val === \"object\" &&\n val.uri &&\n ((typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\") ||\n (typeof global !== \"undefined\" && (global as any).HermesInternal)))\n );\n}\n\n/**\n * Loosely checks if the specified body is a FormData instance.\n */\nexport function isFormData(body: any): boolean {\n return (\n body &&\n // we are checking the constructor name because FormData\n // is not available natively in some environments and the\n // polyfill(s) may not be globally accessible\n (body.constructor?.name === \"FormData\" ||\n // fallback to global FormData instance check\n // note: this is needed because the constructor.name could be different in case of\n // custom global FormData implementation, eg. React Native on Android/iOS\n (typeof FormData !== \"undefined\" && body instanceof FormData))\n );\n}\n\n/**\n * Checks if the submitted body object has at least one Blob/File field value.\n */\nexport function hasFileField(body: { [key: string]: any }): boolean {\n for (const key in body) {\n const values = Array.isArray(body[key]) ? body[key] : [body[key]];\n for (const v of values) {\n if (isFile(v)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Converts analyzes the provided body and converts it to FormData\n * in case a plain object with File/Blob values is used.\n */\nexport function convertToFormDataIfNeeded(body: any): any {\n if (\n typeof FormData === \"undefined\" ||\n typeof body === \"undefined\" ||\n typeof body !== \"object\" ||\n body === null ||\n isFormData(body) ||\n !hasFileField(body)\n ) {\n return body;\n }\n\n const form = new FormData();\n\n for (const key in body) {\n const val = body[key];\n\n // skip undefined values for consistency with JSON.stringify\n // (see https://github.com/pocketbase/pocketbase/issues/6731#issuecomment-2812382827)\n if (typeof val === \"undefined\") {\n continue;\n }\n\n if (typeof val === \"object\" && !hasFileField({ data: val })) {\n // send json-like values as jsonPayload to avoid the implicit string value normalization\n let payload: { [key: string]: any } = {};\n payload[key] = val;\n form.append(\"@jsonPayload\", JSON.stringify(payload));\n } else {\n // in case of mixed string and file/blob\n const normalizedVal = Array.isArray(val) ? val : [val];\n for (let v of normalizedVal) {\n form.append(key, v);\n }\n }\n }\n\n return form;\n}\n\n/**\n * Converts the provided FormData instance into a plain object.\n *\n * For consistency with the server multipart/form-data inferring,\n * the following normalization rules are applied for plain multipart string values:\n * - \"true\" is converted to the json \"true\"\n * - \"false\" is converted to the json \"false\"\n * - numeric strings are converted to json number ONLY if the resulted\n * minimal number string representation is the same as the provided raw string\n * (aka. scientific notations, \"Infinity\", \"0.0\", \"0001\", etc. are kept as string)\n * - any other string (empty string too) is left as it is\n */\nexport function convertFormDataToObject(formData: FormData): { [key: string]: any } {\n let result: { [key: string]: any } = {};\n\n formData.forEach((v, k) => {\n if (k === \"@jsonPayload\" && typeof v == \"string\") {\n try {\n let parsed = JSON.parse(v);\n Object.assign(result, parsed);\n } catch (err) {\n console.warn(\"@jsonPayload error:\", err);\n }\n } else {\n if (typeof result[k] !== \"undefined\") {\n if (!Array.isArray(result[k])) {\n result[k] = [result[k]];\n }\n result[k].push(inferFormDataValue(v));\n } else {\n result[k] = inferFormDataValue(v);\n }\n }\n });\n\n return result;\n}\n\nconst inferNumberCharsRegex = /^[\\-\\.\\d]+$/;\n\nfunction inferFormDataValue(value: any): any {\n if (typeof value != \"string\") {\n return value;\n }\n\n if (value == \"true\") {\n return true;\n }\n\n if (value == \"false\") {\n return false;\n }\n\n // note: expects the provided raw string to match exactly with the minimal string representation of the parsed number\n if (\n (value[0] === \"-\" || (value[0] >= \"0\" && value[0] <= \"9\")) &&\n inferNumberCharsRegex.test(value)\n ) {\n let num = +value;\n if (\"\" + num === value) {\n return num;\n }\n }\n\n return value;\n}\n","import { BaseService } from \"@/services/BaseService\";\nimport { isFile, isFormData, convertFormDataToObject } from \"@/tools/formdata\";\nimport {\n SendOptions,\n RecordOptions,\n normalizeUnknownQueryParams,\n serializeQueryParams,\n} from \"@/tools/options\";\n\nexport interface BatchRequest {\n method: string;\n url: string;\n json?: { [key: string]: any };\n files?: { [key: string]: Array };\n headers?: { [key: string]: string };\n}\n\nexport interface BatchRequestResult {\n status: number;\n body: any;\n}\n\nexport class BatchService extends BaseService {\n private requests: Array = [];\n private subs: { [key: string]: SubBatchService } = {};\n\n /**\n * Starts constructing a batch request entry for the specified collection.\n */\n collection(collectionIdOrName: string): SubBatchService {\n if (!this.subs[collectionIdOrName]) {\n this.subs[collectionIdOrName] = new SubBatchService(\n this.requests,\n collectionIdOrName,\n );\n }\n\n return this.subs[collectionIdOrName];\n }\n\n /**\n * Sends the batch requests.\n *\n * @throws {ClientResponseError}\n */\n async send(options?: SendOptions): Promise> {\n const formData = new FormData();\n\n const jsonData = [];\n\n for (let i = 0; i < this.requests.length; i++) {\n const req = this.requests[i];\n\n jsonData.push({\n method: req.method,\n url: req.url,\n headers: req.headers,\n body: req.json,\n });\n\n if (req.files) {\n for (let key in req.files) {\n const files = req.files[key] || [];\n for (let file of files) {\n formData.append(\"requests.\" + i + \".\" + key, file);\n }\n }\n }\n }\n\n formData.append(\"@jsonPayload\", JSON.stringify({ requests: jsonData }));\n\n options = Object.assign(\n {\n method: \"POST\",\n body: formData,\n },\n options,\n );\n\n return this.client.send(\"/api/batch\", options);\n }\n}\n\nexport class SubBatchService {\n private requests: Array = [];\n private readonly collectionIdOrName: string;\n\n constructor(requests: Array, collectionIdOrName: string) {\n this.requests = requests;\n this.collectionIdOrName = collectionIdOrName;\n }\n\n /**\n * Registers a record upsert request into the current batch queue.\n *\n * The request will be executed as update if `bodyParams` have a valid existing record `id` value, otherwise - create.\n */\n upsert(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"PUT\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records\",\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record create request into the current batch queue.\n */\n create(\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"POST\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records\",\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record update request into the current batch queue.\n */\n update(\n id: string,\n bodyParams?: { [key: string]: any } | FormData,\n options?: RecordOptions,\n ): void {\n options = Object.assign(\n {\n body: bodyParams || {},\n },\n options,\n );\n\n const request: BatchRequest = {\n method: \"PATCH\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records/\" +\n encodeURIComponent(id),\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n /**\n * Registers a record delete request into the current batch queue.\n */\n delete(id: string, options?: SendOptions): void {\n options = Object.assign({}, options);\n\n const request: BatchRequest = {\n method: \"DELETE\",\n url:\n \"/api/collections/\" +\n encodeURIComponent(this.collectionIdOrName) +\n \"/records/\" +\n encodeURIComponent(id),\n };\n\n this.prepareRequest(request, options);\n\n this.requests.push(request);\n }\n\n private prepareRequest(request: BatchRequest, options: SendOptions) {\n normalizeUnknownQueryParams(options);\n\n request.headers = options.headers;\n request.json = {};\n request.files = {};\n\n // serialize query parameters\n // -----------------------------------------------------------\n if (typeof options.query !== \"undefined\") {\n const query = serializeQueryParams(options.query);\n if (query) {\n request.url += (request.url.includes(\"?\") ? \"&\" : \"?\") + query;\n }\n }\n\n // extract json and files body data\n // -----------------------------------------------------------\n let body = options.body;\n if (isFormData(body)) {\n body = convertFormDataToObject(body);\n }\n\n for (const key in body) {\n const val = body[key];\n\n if (isFile(val)) {\n request.files[key] = request.files[key] || [];\n request.files[key].push(val);\n } else if (Array.isArray(val)) {\n const foundFiles = [];\n const foundRegular = [];\n for (const v of val) {\n if (isFile(v)) {\n foundFiles.push(v);\n } else {\n foundRegular.push(v);\n }\n }\n\n if (foundFiles.length > 0 && foundFiles.length == val.length) {\n // only files\n // ---\n request.files[key] = request.files[key] || [];\n for (let file of foundFiles) {\n request.files[key].push(file);\n }\n } else {\n // empty or mixed array (both regular and File/Blob values)\n // ---\n request.json[key] = foundRegular;\n\n if (foundFiles.length > 0) {\n // add \"+\" to append if not already since otherwise\n // the existing regular files will be deleted\n // (the mixed values order is preserved only within their corresponding groups)\n let fileKey = key;\n if (!key.startsWith(\"+\") && !key.endsWith(\"+\")) {\n fileKey += \"+\";\n }\n\n request.files[fileKey] = request.files[fileKey] || [];\n for (let file of foundFiles) {\n request.files[fileKey].push(file);\n }\n }\n }\n } else {\n request.json[key] = val;\n }\n }\n }\n}\n","import { ClientResponseError } from \"@/ClientResponseError\";\nimport { BaseAuthStore } from \"@/stores/BaseAuthStore\";\nimport { LocalAuthStore } from \"@/stores/LocalAuthStore\";\nimport { SettingsService } from \"@/services/SettingsService\";\nimport { RecordService } from \"@/services/RecordService\";\nimport { CollectionService } from \"@/services/CollectionService\";\nimport { LogService } from \"@/services/LogService\";\nimport { RealtimeService } from \"@/services/RealtimeService\";\nimport { HealthService } from \"@/services/HealthService\";\nimport { FileService } from \"@/services/FileService\";\nimport { BackupService } from \"@/services/BackupService\";\nimport { CronService } from \"@/services/CronService\";\nimport { BatchService } from \"@/services/BatchService\";\nimport { RecordModel } from \"@/tools/dtos\";\nimport {\n SendOptions,\n FileOptions,\n normalizeUnknownQueryParams,\n serializeQueryParams,\n} from \"@/tools/options\";\nimport { isFormData, convertToFormDataIfNeeded } from \"@/tools/formdata\";\n\nexport interface BeforeSendResult {\n [key: string]: any; // for backward compatibility\n url?: string;\n options?: { [key: string]: any };\n}\n\n/**\n * PocketBase JS Client.\n */\nexport default class Client {\n /**\n * The base PocketBase backend url address (eg. 'http://127.0.0.1.8090').\n */\n baseURL: string;\n\n /**\n * Legacy getter alias for baseURL.\n * @deprecated Please replace with baseURL.\n */\n get baseUrl(): string {\n return this.baseURL;\n }\n\n /**\n * Legacy setter alias for baseURL.\n * @deprecated Please replace with baseURL.\n */\n set baseUrl(v: string) {\n this.baseURL = v;\n }\n\n /**\n * Hook that get triggered right before sending the fetch request,\n * allowing you to inspect and modify the url and request options.\n *\n * For list of the possible options check https://developer.mozilla.org/en-US/docs/Web/API/fetch#options\n *\n * You can return a non-empty result object `{ url, options }` to replace the url and request options entirely.\n *\n * Example:\n * ```js\n * const pb = new PocketBase(\"https://example.com\")\n *\n * pb.beforeSend = function (url, options) {\n * options.headers = Object.assign({}, options.headers, {\n * 'X-Custom-Header': 'example',\n * })\n *\n * return { url, options }\n * }\n *\n * // use the created client as usual...\n * ```\n */\n beforeSend?: (\n url: string,\n options: SendOptions,\n ) => BeforeSendResult | Promise;\n\n /**\n * Hook that get triggered after successfully sending the fetch request,\n * allowing you to inspect/modify the response object and its parsed data.\n *\n * Returns the new Promise resolved `data` that will be returned to the client.\n *\n * Example:\n * ```js\n * const pb = new PocketBase(\"https://example.com\")\n *\n * pb.afterSend = function (response, data, options) {\n * if (response.status != 200) {\n * throw new ClientResponseError({\n * url: response.url,\n * status: response.status,\n * response: { ... },\n * })\n * }\n *\n * return data;\n * }\n *\n * // use the created client as usual...\n * ```\n */\n afterSend?: ((response: Response, data: any) => any) &\n ((response: Response, data: any, options: SendOptions) => any);\n\n /**\n * Optional language code (default to `en-US`) that will be sent\n * with the requests to the server as `Accept-Language` header.\n */\n lang: string;\n\n /**\n * A replaceable instance of the local auth store service.\n */\n authStore: BaseAuthStore;\n\n /**\n * An instance of the service that handles the **Settings APIs**.\n */\n readonly settings: SettingsService;\n\n /**\n * An instance of the service that handles the **Collection APIs**.\n */\n readonly collections: CollectionService;\n\n /**\n * An instance of the service that handles the **File APIs**.\n */\n readonly files: FileService;\n\n /**\n * An instance of the service that handles the **Log APIs**.\n */\n readonly logs: LogService;\n\n /**\n * An instance of the service that handles the **Realtime APIs**.\n */\n readonly realtime: RealtimeService;\n\n /**\n * An instance of the service that handles the **Health APIs**.\n */\n readonly health: HealthService;\n\n /**\n * An instance of the service that handles the **Backup APIs**.\n */\n readonly backups: BackupService;\n\n /**\n * An instance of the service that handles the **Cron APIs**.\n */\n readonly crons: CronService;\n\n private cancelControllers: { [key: string]: AbortController } = {};\n private recordServices: { [key: string]: RecordService } = {};\n private enableAutoCancellation: boolean = true;\n\n constructor(baseURL = \"/\", authStore?: BaseAuthStore | null, lang = \"en-US\") {\n this.baseURL = baseURL;\n this.lang = lang;\n\n if (authStore) {\n this.authStore = authStore;\n } else if (typeof window != \"undefined\" && !!(window as any).Deno) {\n // note: to avoid common security issues we fallback to runtime/memory store in case the code is running in Deno env\n this.authStore = new BaseAuthStore();\n } else {\n this.authStore = new LocalAuthStore();\n }\n\n // common services\n this.collections = new CollectionService(this);\n this.files = new FileService(this);\n this.logs = new LogService(this);\n this.settings = new SettingsService(this);\n this.realtime = new RealtimeService(this);\n this.health = new HealthService(this);\n this.backups = new BackupService(this);\n this.crons = new CronService(this);\n }\n\n /**\n * @deprecated\n * With PocketBase v0.23.0 admins are converted to a regular auth\n * collection named \"_superusers\", aka. you can use directly collection(\"_superusers\").\n */\n get admins(): RecordService {\n return this.collection(\"_superusers\");\n }\n\n /**\n * Creates a new batch handler for sending multiple transactional\n * create/update/upsert/delete collection requests in one network call.\n *\n * Example:\n * ```js\n * const batch = pb.createBatch();\n *\n * batch.collection(\"example1\").create({ ... })\n * batch.collection(\"example2\").update(\"RECORD_ID\", { ... })\n * batch.collection(\"example3\").delete(\"RECORD_ID\")\n * batch.collection(\"example4\").upsert({ ... })\n *\n * await batch.send()\n * ```\n */\n createBatch(): BatchService {\n return new BatchService(this);\n }\n\n /**\n * Returns the RecordService associated to the specified collection.\n */\n collection(idOrName: string): RecordService {\n if (!this.recordServices[idOrName]) {\n this.recordServices[idOrName] = new RecordService(this, idOrName);\n }\n\n return this.recordServices[idOrName];\n }\n\n /**\n * Globally enable or disable auto cancellation for pending duplicated requests.\n */\n autoCancellation(enable: boolean): Client {\n this.enableAutoCancellation = !!enable;\n\n return this;\n }\n\n /**\n * Cancels single request by its cancellation key.\n */\n cancelRequest(requestKey: string): Client {\n if (this.cancelControllers[requestKey]) {\n this.cancelControllers[requestKey].abort();\n delete this.cancelControllers[requestKey];\n }\n\n return this;\n }\n\n /**\n * Cancels all pending requests.\n */\n cancelAllRequests(): Client {\n for (let k in this.cancelControllers) {\n this.cancelControllers[k].abort();\n }\n\n this.cancelControllers = {};\n\n return this;\n }\n\n /**\n * Constructs a filter expression with placeholders populated from a parameters object.\n *\n * Placeholder parameters are defined with the `{:paramName}` notation.\n *\n * The following parameter values are supported:\n *\n * - `string` (_single quotes are autoescaped_)\n * - `number`\n * - `boolean`\n * - `Date` object (_stringified into the PocketBase datetime format_)\n * - `null`\n * - everything else is converted to a string using `JSON.stringify()`\n *\n * Example:\n *\n * ```js\n * pb.collection(\"example\").getFirstListItem(pb.filter(\n * 'title ~ {:title} && created >= {:created}',\n * { title: \"example\", created: new Date()}\n * ))\n * ```\n */\n filter(raw: string, params?: { [key: string]: any }): string {\n if (!params) {\n return raw;\n }\n\n for (let key in params) {\n let val = params[key];\n switch (typeof val) {\n case \"boolean\":\n case \"number\":\n val = \"\" + val;\n break;\n case \"string\":\n val = \"'\" + val.replace(/'/g, \"\\\\'\") + \"'\";\n break;\n default:\n if (val === null) {\n val = \"null\";\n } else if (val instanceof Date) {\n val = \"'\" + val.toISOString().replace(\"T\", \" \") + \"'\";\n } else {\n val = \"'\" + JSON.stringify(val).replace(/'/g, \"\\\\'\") + \"'\";\n }\n }\n raw = raw.replaceAll(\"{:\" + key + \"}\", val);\n }\n\n return raw;\n }\n\n /**\n * @deprecated Please use `pb.files.getURL()`.\n */\n getFileUrl(\n record: { [key: string]: any },\n filename: string,\n queryParams: FileOptions = {},\n ): string {\n console.warn(\"Please replace pb.getFileUrl() with pb.files.getURL()\");\n return this.files.getURL(record, filename, queryParams);\n }\n\n /**\n * @deprecated Please use `pb.buildURL()`.\n */\n buildUrl(path: string): string {\n console.warn(\"Please replace pb.buildUrl() with pb.buildURL()\");\n return this.buildURL(path);\n }\n\n /**\n * Builds a full client url by safely concatenating the provided path.\n */\n buildURL(path: string): string {\n let url = this.baseURL;\n\n // construct an absolute base url if in a browser environment\n if (\n typeof window !== \"undefined\" &&\n !!window.location &&\n !url.startsWith(\"https://\") &&\n !url.startsWith(\"http://\")\n ) {\n url = window.location.origin?.endsWith(\"/\")\n ? window.location.origin.substring(0, window.location.origin.length - 1)\n : window.location.origin || \"\";\n\n if (!this.baseURL.startsWith(\"/\")) {\n url += window.location.pathname || \"/\";\n url += url.endsWith(\"/\") ? \"\" : \"/\";\n }\n\n url += this.baseURL;\n }\n\n // concatenate the path\n if (path) {\n url += url.endsWith(\"/\") ? \"\" : \"/\"; // append trailing slash if missing\n url += path.startsWith(\"/\") ? path.substring(1) : path;\n }\n\n return url;\n }\n\n /**\n * Sends an api http request.\n *\n * @throws {ClientResponseError}\n */\n async send(path: string, options: SendOptions): Promise {\n options = this.initSendOptions(path, options);\n\n // build url + path\n let url = this.buildURL(path);\n\n if (this.beforeSend) {\n const result = Object.assign({}, await this.beforeSend(url, options));\n if (\n typeof result.url !== \"undefined\" ||\n typeof result.options !== \"undefined\"\n ) {\n url = result.url || url;\n options = result.options || options;\n } else if (Object.keys(result).length) {\n // legacy behavior\n options = result as SendOptions;\n console?.warn &&\n console.warn(\n \"Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`.\",\n );\n }\n }\n\n // serialize the query parameters\n if (typeof options.query !== \"undefined\") {\n const query = serializeQueryParams(options.query);\n if (query) {\n url += (url.includes(\"?\") ? \"&\" : \"?\") + query;\n }\n delete options.query;\n }\n\n // ensures that the json body is serialized\n if (\n this.getHeader(options.headers, \"Content-Type\") == \"application/json\" &&\n options.body &&\n typeof options.body !== \"string\"\n ) {\n options.body = JSON.stringify(options.body);\n }\n\n // early throw an abort error in case the request was already cancelled\n const fetchFunc = options.fetch || fetch;\n\n // send the request\n return fetchFunc(url, options)\n .then(async (response) => {\n let data: any = {};\n\n try {\n data = await response.json();\n } catch (err: any) {\n // @todo map against the response content type\n // all api responses are expected to return json\n // with exception of the realtime events and 204\n if (\n options.signal?.aborted ||\n err?.name == \"AbortError\" ||\n err?.message == \"Aborted\"\n ) {\n throw err;\n }\n }\n\n if (this.afterSend) {\n data = await this.afterSend(response, data, options);\n }\n\n if (response.status >= 400) {\n throw new ClientResponseError({\n url: response.url,\n status: response.status,\n data: data,\n });\n }\n\n return data as T;\n })\n .catch((err) => {\n // wrap to normalize all errors\n throw new ClientResponseError(err);\n });\n }\n\n /**\n * Shallow copy the provided object and takes care to initialize\n * any options required to preserve the backward compatability.\n *\n * @param {SendOptions} options\n * @return {SendOptions}\n */\n private initSendOptions(path: string, options: SendOptions): SendOptions {\n options = Object.assign({ method: \"GET\" } as SendOptions, options);\n\n // auto convert the body to FormData, if needed\n options.body = convertToFormDataIfNeeded(options.body);\n\n // move unknown send options as query parameters\n normalizeUnknownQueryParams(options);\n\n // requestKey normalizations for backward-compatibility\n // ---\n options.query = Object.assign({}, options.params, options.query);\n if (typeof options.requestKey === \"undefined\") {\n if (options.$autoCancel === false || options.query.$autoCancel === false) {\n options.requestKey = null;\n } else if (options.$cancelKey || options.query.$cancelKey) {\n options.requestKey = options.$cancelKey || options.query.$cancelKey;\n }\n }\n // remove the deprecated special cancellation params from the other query params\n delete options.$autoCancel;\n delete options.query.$autoCancel;\n delete options.$cancelKey;\n delete options.query.$cancelKey;\n // ---\n\n // add the json header, if not explicitly set\n // (for FormData body the Content-Type header should be skipped since the boundary is autogenerated)\n if (\n this.getHeader(options.headers, \"Content-Type\") === null &&\n !isFormData(options.body)\n ) {\n options.headers = Object.assign({}, options.headers, {\n \"Content-Type\": \"application/json\",\n });\n }\n\n // add Accept-Language header, if not explicitly set\n if (this.getHeader(options.headers, \"Accept-Language\") === null) {\n options.headers = Object.assign({}, options.headers, {\n \"Accept-Language\": this.lang,\n });\n }\n\n // check if Authorization header can be added\n if (\n // has valid token\n this.authStore.token &&\n // auth header is not explicitly set\n this.getHeader(options.headers, \"Authorization\") === null\n ) {\n options.headers = Object.assign({}, options.headers, {\n Authorization: this.authStore.token,\n });\n }\n\n // handle auto cancellation for duplicated pending request\n if (this.enableAutoCancellation && options.requestKey !== null) {\n const requestKey = options.requestKey || (options.method || \"GET\") + path;\n\n delete options.requestKey;\n\n // cancel previous pending requests\n this.cancelRequest(requestKey);\n\n // @todo evaluate if a cleanup after the request is necessary\n // (check also authWithOAuth2 as it currently relies on the controller)\n const controller = new AbortController();\n this.cancelControllers[requestKey] = controller;\n options.signal = controller.signal;\n }\n\n return options;\n }\n\n /**\n * Extracts the header with the provided name in case-insensitive manner.\n * Returns `null` if no header matching the name is found.\n */\n private getHeader(\n headers: { [key: string]: string } | undefined,\n name: string,\n ): string | null {\n headers = headers || {};\n name = name.toLowerCase();\n\n for (let key in headers) {\n if (key.toLowerCase() == name) {\n return headers[key];\n }\n }\n\n return null;\n }\n}\n"],"names":["ClientResponseError","Error","constructor","errData","super","this","url","status","response","isAbort","originalError","Object","setPrototypeOf","prototype","name","message","data","cause","includes","toJSON","fieldContentRegExp","cookieSerialize","val","options","opt","assign","encode","defaultEncode","test","TypeError","value","result","maxAge","isNaN","isFinite","Math","floor","domain","path","expires","isDate","toString","call","Date","valueOf","toUTCString","httpOnly","secure","priority","toLowerCase","sameSite","defaultDecode","indexOf","decodeURIComponent","encodeURIComponent","isReactNative","navigator","product","global","HermesInternal","atobPolyfill","getTokenPayload","token","encodedPayload","split","map","c","charCodeAt","slice","join","JSON","parse","e","isTokenExpired","expirationThreshold","payload","keys","length","exp","now","atob","input","str","String","replace","bs","buffer","bc","idx","output","charAt","fromCharCode","defaultCookieKey","BaseAuthStore","baseToken","baseModel","_onChangeCallbacks","record","model","isValid","isSuperuser","type","collectionName","collectionId","isAdmin","console","warn","isAuthRecord","save","triggerChange","clear","loadFromCookie","cookie","key","rawData","cookieParse","decode","index","eqIdx","endIdx","lastIndexOf","trim","undefined","_","Array","isArray","exportToCookie","defaultOptions","stringify","resultLength","Blob","size","id","email","extraProps","prop","onChange","callback","fireImmediately","push","i","splice","LocalAuthStore","storageKey","storageFallback","_bindStorageEvent","_storageGet","_storageSet","_storageRemove","window","localStorage","rawValue","getItem","normalizedVal","setItem","removeItem","addEventListener","BaseService","client","SettingsService","getAll","method","send","update","bodyParams","body","testS3","filesystem","then","testEmail","collectionIdOrName","toEmail","emailTemplate","template","collection","generateAppleClientSecret","clientId","teamId","keyId","privateKey","duration","knownSendOptionsKeys","normalizeUnknownQueryParams","query","serializeQueryParams","params","encodedKey","arrValue","v","prepareQueryParamValue","toISOString","RealtimeService","eventSource","subscriptions","lastSentSubscriptions","maxConnectTimeout","reconnectAttempts","maxReconnectAttempts","Infinity","predefinedReconnectIntervals","pendingConnects","isConnected","subscribe","topic","serialized","headers","listener","msgEvent","submitSubscriptions","connect","async","unsubscribeByTopicAndListener","unsubscribe","needToSubmit","subs","getSubscriptionsByTopic","hasSubscriptionListeners","removeEventListener","disconnect","unsubscribeByPrefix","keyPrefix","hasAtleastOneTopic","startsWith","exist","keyToCheck","addAllSubscriptionListeners","getNonEmptySubscriptionKeys","requestKey","getSubscriptionsCancelKey","catch","err","removeAllSubscriptionListeners","Promise","resolve","reject","initConnect","clearTimeout","connectTimeoutId","setTimeout","connectErrorHandler","EventSource","buildURL","onerror","lastEventId","retries","hasUnsentSubscriptions","p","reconnectTimeoutId","connectSubs","latestTopics","t","timeout","fromReconnect","onDisconnect","cancelRequest","close","CrudService","getFullList","batchOrqueryParams","_getFullList","batch","getList","page","perPage","baseCrudPath","responseData","items","item","getFirstListItem","filter","skipTotal","code","getOne","create","batchSize","request","list","concat","normalizeLegacyOptionsArgs","legacyWarn","baseOptions","bodyOrOptions","hasQuery","resetAutoRefresh","_resetAutoRefresh","RecordService","baseCollectionPath","isSuperusers","realtime","batchOrOptions","authStore","authExpand","expand","authRecord","delete","success","authResponse","listAuthMethods","fields","authWithPassword","usernameOrEmail","password","autoRefreshThreshold","identity","autoRefresh","authData","registerAutoRefresh","threshold","refreshFunc","reauthenticateFunc","oldBeforeSend","beforeSend","oldModel","unsubStoreChange","newToken","sendOptions","oldToken","authRefresh","authWithOAuth2Code","provider","codeVerifier","redirectURL","createData","authWithOAuth2","args","config","eagerDefaultPopup","urlCallback","openBrowserPopup","cleanup","requestKeyOptions","authMethods","oauth2","providers","find","cancelController","signal","onabort","activeSubscriptions","oldState","state","error","scopes","replacements","_replaceQueryParams","authURL","location","href","requestPasswordReset","confirmPasswordReset","passwordResetToken","passwordConfirm","requestVerification","confirmVerification","verificationToken","verified","requestEmailChange","newEmail","confirmEmailChange","emailChangeToken","listExternalAuths","recordId","unlinkExternalAuth","ea","requestOTP","authWithOTP","otpId","impersonate","Authorization","Client","baseURL","lang","urlPath","substring","parsedParams","rawParams","param","pair","hasOwnProperty","open","width","height","windowWidth","innerWidth","windowHeight","innerHeight","left","top","CollectionService","import","collections","deleteMissing","getScaffolds","truncate","LogService","getStats","HealthService","check","FileService","getUrl","filename","queryParams","getURL","parts","download","getToken","BackupService","basename","upload","restore","getDownloadUrl","getDownloadURL","CronService","run","jobId","isFile","File","uri","isFormData","FormData","hasFileField","values","inferNumberCharsRegex","inferFormDataValue","num","BatchService","requests","SubBatchService","formData","jsonData","req","json","files","file","append","upsert","prepareRequest","convertFormDataToObject","forEach","k","parsed","foundFiles","foundRegular","fileKey","endsWith","baseUrl","cancelControllers","recordServices","enableAutoCancellation","Deno","logs","settings","health","backups","crons","admins","createBatch","idOrName","autoCancellation","enable","abort","cancelAllRequests","raw","replaceAll","getFileUrl","buildUrl","origin","pathname","initSendOptions","getHeader","fetch","aborted","afterSend","convertToFormDataIfNeeded","form","$autoCancel","$cancelKey","controller","AbortController"],"mappings":"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"} \ No newline at end of file diff --git a/script/node_modules/pocketbase/package.json b/script/node_modules/pocketbase/package.json new file mode 100644 index 0000000..1fc1a3b --- /dev/null +++ b/script/node_modules/pocketbase/package.json @@ -0,0 +1,47 @@ +{ + "version": "0.26.8", + "name": "pocketbase", + "description": "PocketBase JavaScript SDK", + "author": "Gani Georgiev", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/pocketbase/js-sdk.git" + }, + "exports": { + ".": "./dist/pocketbase.es.mjs", + "./cjs": "./dist/pocketbase.cjs.js", + "./umd": "./dist/pocketbase.umd.js" + }, + "main": "./dist/pocketbase.es.mjs", + "module": "./dist/pocketbase.es.mjs", + "react-native": "./dist/pocketbase.es.js", + "types": "./dist/pocketbase.es.d.mts", + "keywords": [ + "pocketbase", + "pocketbase-js", + "js-sdk", + "javascript-sdk", + "pocketbase-sdk" + ], + "prettier": { + "tabWidth": 4, + "printWidth": 90, + "bracketSameLine": true + }, + "scripts": { + "format": "npx prettier ./src ./tests --write", + "build": "rm -rf dist && rollup -c", + "dev": "rollup -c -w", + "test": "vitest", + "prepublishOnly": "npm run build" + }, + "devDependencies": { + "@rollup/plugin-terser": "^0.4.3", + "prettier": "3.2.4", + "rollup": "^4.0.0", + "rollup-plugin-ts": "^3.0.0", + "typescript": "^5.1.6", + "vitest": "^2.0.0" + } +} diff --git a/script/package-lock.json b/script/package-lock.json new file mode 100644 index 0000000..df82f5d --- /dev/null +++ b/script/package-lock.json @@ -0,0 +1,22 @@ +{ + "name": "script", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "script", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "pocketbase": "^0.26.8" + } + }, + "node_modules/pocketbase": { + "version": "0.26.8", + "resolved": "https://registry.npmmirror.com/pocketbase/-/pocketbase-0.26.8.tgz", + "integrity": "sha512-aQ/ewvS7ncvAE8wxoW10iAZu6ElgbeFpBhKPnCfvRovNzm2gW8u/sQNPGN6vNgVEagz44kK//C61oKjfa+7Low==", + "license": "MIT" + } + } +} diff --git a/script/package.json b/script/package.json new file mode 100644 index 0000000..f860aa4 --- /dev/null +++ b/script/package.json @@ -0,0 +1,16 @@ +{ + "name": "script", + "version": "1.0.0", + "description": "", + "main": "pocketbase.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "module", + "dependencies": { + "pocketbase": "^0.26.8" + } +} diff --git a/script/pocketbase.js b/script/pocketbase.js new file mode 100644 index 0000000..a3c3d09 --- /dev/null +++ b/script/pocketbase.js @@ -0,0 +1,176 @@ +import PocketBase from 'pocketbase'; + +// ================= 配置区 ================= +// 确保使用不带 /api 的根地址,并且端口是你后台管理的实际端口 +const PB_URL = 'http://new.blv-oa.com:8000'; +const ADMIN_EMAIL = '450481891@qq.com'; // 必须是超级管理员账号,不是普通用户! +const ADMIN_PASSWORD = 'Momo123456'; +// ========================================== + +const pb = new PocketBase(PB_URL); + +const collections = [ + { + name: 'tbl_system_dict', + type: 'base', + fields: [ + { name: 'system_dict_id', type: 'text', required: true }, + { name: 'dict_name', type: 'text' }, + { name: 'dict_word_enum', type: 'text' }, + { name: 'dict_word_description', type: 'text' }, + { name: 'dict_word_is_enabled', type: 'bool' }, + { name: 'dict_word_sort_order', type: 'number' }, + { name: 'dict_word_parent_id', type: 'text' }, + { name: 'dict_word_remark', type: 'text' } + ], + indexes: [ + 'CREATE UNIQUE INDEX idx_system_dict_id ON tbl_system_dict (system_dict_id)', + 'CREATE INDEX idx_dict_word_parent_id ON tbl_system_dict (dict_word_parent_id)' + ] + }, + { + name: 'tbl_company', + type: 'base', + fields: [ + { name: 'company_id', type: 'text', required: true }, + { name: 'company_name', type: 'text' }, + { name: 'company_type', type: 'text' }, + { name: 'company_entity', type: 'text' }, + { name: 'company_usci', type: 'text' }, + { name: 'company_nationality', type: 'text' }, + { name: 'company_province', type: 'text' }, + { name: 'company_city', type: 'text' }, + { name: 'company_postalcode', type: 'text' }, + { name: 'company_add', type: 'text' }, + { name: 'company_status', type: 'text' }, + { name: 'company_level', type: 'text' }, + { name: 'company_remark', type: 'text' } + ], + indexes: [ + 'CREATE UNIQUE INDEX idx_company_id ON tbl_company (company_id)', + 'CREATE INDEX idx_company_usci ON tbl_company (company_usci)' + ] + }, + { + name: 'tbl_user_groups', + type: 'base', + fields: [ + { name: 'usergroups_id', type: 'text', required: true }, + { name: 'usergroups_name', type: 'text' }, + { name: 'usergroups_level', type: 'number' }, + { name: 'usergroups_remark', type: 'text' } + ], + indexes: [ + 'CREATE UNIQUE INDEX idx_usergroups_id ON tbl_user_groups (usergroups_id)' + ] + }, + { + name: 'tbl_users', + type: 'base', + fields: [ + { name: 'users_id', type: 'text', required: true }, + { name: 'users_name', type: 'text' }, + { name: 'users_idtype', type: 'text' }, + { name: 'users_id_number', type: 'text' }, + { name: 'users_phone', type: 'text' }, + { name: 'users_wx_openid', type: 'text' }, + { name: 'users_level', type: 'text' }, + { name: 'users_type', type: 'text' }, + { name: 'users_status', type: 'text' }, + { name: 'company_id', type: 'text' }, + { name: 'users_parent_id', type: 'text' }, + { name: 'users_promo_code', type: 'text' }, + { name: 'users_id_pic_a', type: 'file', options: { maxSelect: 1, mimeTypes: ['image/jpeg', 'image/png', 'image/webp'] } }, + { name: 'users_id_pic_b', type: 'file', options: { maxSelect: 1, mimeTypes: ['image/jpeg', 'image/png', 'image/webp'] } }, + { name: 'users_title_picture', type: 'file', options: { maxSelect: 1, mimeTypes: ['image/jpeg', 'image/png', 'image/webp'] } }, + { name: 'users_picture', type: 'file', options: { maxSelect: 1, mimeTypes: ['image/jpeg', 'image/png', 'image/webp'] } }, + { name: 'usergroups_id', type: 'text' } + ], + indexes: [ + 'CREATE UNIQUE INDEX idx_users_id ON tbl_users (users_id)', + 'CREATE UNIQUE INDEX idx_users_phone ON tbl_users (users_phone)', + 'CREATE UNIQUE INDEX idx_users_wx_openid ON tbl_users (users_wx_openid)', + 'CREATE INDEX idx_users_company_id ON tbl_users (company_id)', + 'CREATE INDEX idx_users_usergroups_id ON tbl_users (usergroups_id)', + 'CREATE INDEX idx_users_parent_id ON tbl_users (users_parent_id)' + ] + } +]; + +async function init() { + try { + console.log('🔄 正在登录管理员账号...'); + await pb.admins.authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD); + console.log('✅ 登录成功!开始初始化表结构与索引...\n'); + + for (const collectionData of collections) { + await createOrUpdateCollection(collectionData); + } + + await verifyCollections(collections); + console.log('\n🎉 所有表结构及索引初始化并校验完成!'); + + } catch (error) { + console.error('❌ 初始化失败:', error.response?.data || error.message); + } +} + +// 幂等创建/更新集合 +async function createOrUpdateCollection(collectionData) { + console.log(`🔄 正在创建表: ${collectionData.name} ...`); + const payload = { + name: collectionData.name, + type: collectionData.type, + fields: collectionData.fields, + indexes: collectionData.indexes + }; + + try { + await pb.collections.create(payload); + console.log(`✅ ${collectionData.name} 表及索引创建完成。`); + } catch (error) { + const nameErrorCode = error.response?.data?.name?.code; + if ( + error.status === 400 + && (nameErrorCode === 'validation_not_unique' || nameErrorCode === 'validation_collection_name_exists') + ) { + const existing = await pb.collections.getOne(collectionData.name); + await pb.collections.update(existing.id, payload); + console.log(`♻️ ${collectionData.name} 表已存在,已按最新结构更新。`); + return; + } + throw error; + } +} + +async function verifyCollections(targetCollections) { + console.log('\n🔍 开始校验表结构与索引...'); + + for (const target of targetCollections) { + const remote = await pb.collections.getOne(target.name); + const remoteFieldNames = new Set((remote.fields || []).map((field) => field.name)); + const missingFields = target.fields + .map((field) => field.name) + .filter((fieldName) => !remoteFieldNames.has(fieldName)); + + const remoteIndexes = new Set(remote.indexes || []); + const missingIndexes = target.indexes.filter((indexSql) => !remoteIndexes.has(indexSql)); + + if (missingFields.length === 0 && missingIndexes.length === 0) { + console.log(`✅ ${target.name} 校验通过。`); + continue; + } + + console.log(`❌ ${target.name} 校验失败:`); + if (missingFields.length > 0) { + console.log(` - 缺失字段: ${missingFields.join(', ')}`); + } + if (missingIndexes.length > 0) { + console.log(` - 缺失索引: ${missingIndexes.join(' | ')}`); + } + + throw new Error(`${target.name} 结构与预期不一致`); + } +} + +init(); \ No newline at end of file