From 6129a9221a34de6f786fcc01184550ac2373c5a3 Mon Sep 17 00:00:00 2001 From: rain Date: Fri, 10 Jul 2026 14:11:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=BA=95=E5=B1=82=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E9=80=BB=E8=BE=91=EF=BC=8C=E5=B0=81=E8=A3=85=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.html | 6 +- article-detail.html | 6 +- config.js | 70 +- create-genealogy.html | 5 +- ...api-page-integration-tracker-2026-07-09.md | 135 +- ...2026-07-10-axios-request-layer-refactor.md | 251 + .../2026-07-10-pc-tracker-reverification.md | 150 + .../2026-07-10-tac-layer-call-correction.md | 84 + .../plans/2026-07-10-tac-layer-center-fix.md | 23 + .../2026-07-10-tac-layer-real-mount-fix.md | 72 + ...26-07-10-tac-layui-content-contract-fix.md | 71 + .../plans/2026-07-10-tac-modal-cleanup.md | 150 + .../2026-07-10-tac-native-style-cleanup.md | 85 + download.html | 6 +- family-detail.html | 6 +- forgot-password.html | 7 +- help.html | 6 +- join-genealogy.html | 6 +- login.html | 7 +- notice-detail.html | 6 +- plaza.html | 6 +- profile-admin-permissions.html | 6 +- profile-album.html | 5 +- profile-article-edit.html | 5 +- profile-article.html | 6 +- profile-content.html | 6 +- profile-create-family.html | 5 +- profile-data.html | 5 +- profile-families.html | 6 +- profile-family-admin.html | 6 +- profile-family-home.html | 6 +- profile-feed-edit.html | 5 +- profile-feed.html | 6 +- profile-feedback.html | 6 +- profile-generation.html | 6 +- profile-gift-edit.html | 5 +- profile-gift.html | 6 +- profile-growth-edit.html | 5 +- profile-growth.html | 6 +- profile-invite.html | 6 +- profile-join-family.html | 6 +- profile-join-review.html | 6 +- profile-memo-edit.html | 5 +- profile-memo.html | 6 +- profile-merit-edit.html | 6 +- profile-merit.html | 6 +- profile-messages.html | 6 +- profile-security.html | 5 +- profile-services.html | 6 +- profile-share.html | 6 +- profile-tree.html | 6 +- profile.html | 5 +- promo-album.html | 6 +- public/css/public.css | 35 - public/js/captcha-pages.js | 76 +- register.html | 9 +- submit-ticket.html | 6 +- tests/api-client.test.js | 79 +- tests/api-script-order.test.js | 45 + tests/auth-page-structure.test.js | 9 +- tests/captcha-pages.test.js | 52 +- tests/config.test.js | 36 +- tests/utils.test.js | 27 +- public/js/api-client.js => utils/ApiClient.js | 219 +- utils/{RequestUtil.js => AxiosRequestUtil.js} | 91 +- utils/axios.js | 4288 +++++++++++++++++ 66 files changed, 5861 insertions(+), 451 deletions(-) create mode 100644 docs/superpowers/plans/2026-07-10-axios-request-layer-refactor.md create mode 100644 docs/superpowers/plans/2026-07-10-pc-tracker-reverification.md create mode 100644 docs/superpowers/plans/2026-07-10-tac-layer-call-correction.md create mode 100644 docs/superpowers/plans/2026-07-10-tac-layer-center-fix.md create mode 100644 docs/superpowers/plans/2026-07-10-tac-layer-real-mount-fix.md create mode 100644 docs/superpowers/plans/2026-07-10-tac-layui-content-contract-fix.md create mode 100644 docs/superpowers/plans/2026-07-10-tac-modal-cleanup.md create mode 100644 docs/superpowers/plans/2026-07-10-tac-native-style-cleanup.md create mode 100644 tests/api-script-order.test.js rename public/js/api-client.js => utils/ApiClient.js (66%) rename utils/{RequestUtil.js => AxiosRequestUtil.js} (54%) create mode 100644 utils/axios.js diff --git a/app.html b/app.html index acfcdeb..0b4925c 100644 --- a/app.html +++ b/app.html @@ -143,7 +143,11 @@ - + + + + + diff --git a/article-detail.html b/article-detail.html index 7ea601b..8cf1d17 100644 --- a/article-detail.html +++ b/article-detail.html @@ -102,7 +102,11 @@ - + + + + + diff --git a/config.js b/config.js index 885fcee..fe969c5 100644 --- a/config.js +++ b/config.js @@ -1,5 +1,5 @@ (function (root, factory) { - // 全局配置同时支持浏览器页面和 Node 测试。 + // 根配置只负责运行环境与接口基础地址。 if (typeof module === 'object' && module.exports) { module.exports = factory; return; @@ -9,69 +9,41 @@ })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; - var DEFAULT_ENV = 'development'; - var API_BASE_URLS = { - development: 'http://test-genealogy-api.ddxcjp.cn', - production: '' - }; - var TEST_API_HTTP_URL = 'http://test-genealogy-api.ddxcjp.cn'; - var TEST_API_HTTPS_URL = 'https://test-genealogy-api.ddxcjp.cn'; - var CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f'; - var TENANT_ID = '000000'; - var TOKEN_KEY = 'genealogy_auth_token'; - var TOKEN_HEADER_NAME = 'Authorization'; - var ENV_KEY = 'genealogy_env'; - var BASE_URL_KEY = 'genealogy_api_base_url'; - - function readStorage(key) { - try { - return root.localStorage && root.localStorage.getItem(key); - } catch (error) { - return null; + var ENVIRONMENTS = { + development: { + apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn' + }, + production: { + // 后端尚未提供正式生产域名,暂时保持用户指定的测试地址。 + apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn' } - } + }; - function getEnv() { - return readStorage(ENV_KEY) || DEFAULT_ENV; - } + function getEnvironment() { + var location = root.location || {}; + var hostname = location.hostname || ''; - function normalizeApiBaseUrl(value) { - // 测试域名当前 HTTPS 握手失败,开发环境统一走可访问的 HTTP 地址。 - var url = String(value || '').replace(/\/+$/, ''); + if (hostname === 'localhost' || hostname === '127.0.0.1' || location.port) { + return 'development'; + } - if (url === TEST_API_HTTPS_URL) return TEST_API_HTTP_URL; - return url; + return 'production'; } function getApiBaseUrl() { - return normalizeApiBaseUrl(readStorage(BASE_URL_KEY) || API_BASE_URLS[getEnv()] || API_BASE_URLS.development); + return ENVIRONMENTS[getEnvironment()].apiBaseUrl; } function getConfig() { return { - env: getEnv(), - apiBaseUrl: getApiBaseUrl(), - clientId: CLIENT_ID, - tenantId: TENANT_ID, - tokenKey: TOKEN_KEY, - tokenHeaderName: TOKEN_HEADER_NAME + environment: getEnvironment(), + apiBaseUrl: getApiBaseUrl() }; } return { - getConfig: getConfig, + getEnvironment: getEnvironment, getApiBaseUrl: getApiBaseUrl, - getClientId: function () { - return CLIENT_ID; - }, - getTenantId: function () { - return TENANT_ID; - }, - getTokenKey: function () { - return TOKEN_KEY; - }, - getTokenHeaderName: function () { - return TOKEN_HEADER_NAME; - } + getConfig: getConfig }; }); diff --git a/create-genealogy.html b/create-genealogy.html index 6de63f4..4916994 100644 --- a/create-genealogy.html +++ b/create-genealogy.html @@ -127,8 +127,9 @@ - - + + + diff --git a/docs/pc-api-page-integration-tracker-2026-07-09.md b/docs/pc-api-page-integration-tracker-2026-07-09.md index b43d2a7..3ebf536 100644 --- a/docs/pc-api-page-integration-tracker-2026-07-09.md +++ b/docs/pc-api-page-integration-tracker-2026-07-09.md @@ -57,18 +57,18 @@ | 编号 | 状态 | 页面范围 | 接口范围 | 计划与验证 | | --- | --- | --- | --- | --- | -| PC-000 | 已完成 | 全项目 | `genealogy-pc-openapi.yaml`、`https://test-genealogy-api.ddxcjp.cn` | 2026-07-09 15:14:58 +08:00 完成。确认旧 APP 接口依据错误,停止继续按 APP 路径推进;确认 PC YAML 当前只明确认证、文件、地区接口。 | -| PC-001 | 已完成 | 全项目配置层 | `config.js`、`https://test-genealogy-api.ddxcjp.cn` | 2026-07-09 15:57:36 +08:00 修正完成。根目录 `config.js` 作为开发/生产环境切换的唯一入口;开发环境基础地址为 `https://test-genealogy-api.ddxcjp.cn`;PC `clientid` 按用户最新截图改为 `ced7e5f0498645c6ec642dcf450b036f`,客户端 Key 为 `web_pc`。验证:`node tests\config.test.js` 输出 `config tests passed`,`node --check config.js` 通过。 | -| PC-002 | 已完成 | `utils` 公共工具层、`public/js/api-client.js` | PC `clientid`、通用请求、token、表单、存储、PC YAML paths | 2026-07-09 15:51:34 +08:00 完成。新增 `utils/StorageUtil.js`、`utils/FormUtil.js`、`utils/RequestUtil.js`;重写 `public/js/api-client.js`,真实请求只保留 PC YAML 中的 `/captcha/*`、`/auth/code`、`/genealogy/pc/auth/*`、`/genealogy/pc/files/*`、`/genealogy/region/*`,PC YAML 未覆盖业务方法改为抛 `PC_API_NOT_AVAILABLE`,不再发旧 APP 请求。验证:`node tests\utils.test.js`、`node tests\api-client.test.js` 均通过;`Select-String -Path public\js\api-client.js -Pattern '/genealogy/app/'` 无输出;`node --check public\js\api-client.js` 通过。 | -| PC-003 | 已完成 | `login.html`、`register.html`、`forgot-password.html`、`profile-security.html`、`profile-data.html`、`profile.html` | `/genealogy/pc/auth/*`、`/captcha/*` | 2026-07-09 16:00:22 +08:00 完成。`login.html` 使用 `WEB_H5_LOGIN`;`register.html` 使用 `WEB_H5_REGISTER`;`forgot-password.html` 使用 `WEB_H5_FORGOT_PASSWORD`;认证/资料/安全页面均已补齐 `config.js`、`utils/*`、`api-client.js` 脚本顺序。资料提交字段按 PC YAML 使用 `provinceCode/cityCode/districtCode`。验证:`node tests\auth-pages.test.js`、`node tests\captcha-pages.test.js`、`node tests\profile-pages.test.js`、`node tests\security-pages.test.js` 均通过;相关 JS `node --check` 通过;旧 `PC_LOGIN/PC_REGISTER/PC_PASSWORD_RESET/APP_*` 场景扫描无输出。 | -| PC-004 | 已完成 | 文件上传相关页面 | `/genealogy/pc/files/*` | 2026-07-09 16:03:23 +08:00 完成。`api-client.js` 文件上传、分片上传、文件引用绑定均走 `/genealogy/pc/files/*`;`profile-album.html`、`profile-article-edit.html`、`profile-feed-edit.html`、`profile-growth-edit.html`、`profile-memo-edit.html`、`profile-gift-edit.html` 已补齐 `config.js`、`utils/*`、`api-client.js`、`upload-pages.js` 顺序。验证:`node tests\upload-pages.test.js`、`node tests\api-client.test.js` 通过;`Select-String -Path public\js\api-client.js,public\js\upload-pages.js -Pattern '/genealogy/app/files'` 无输出。 | -| PC-005 | 已完成 | 使用地区选择的页面 | `/genealogy/region/*` | 2026-07-09 16:05:12 +08:00 完成。`create-genealogy.html`、`profile-create-family.html`、`profile-data.html` 地区选择页面已补齐 `config.js`、`utils/*`、`api-client.js`、`region-pages.js` 顺序;地区请求只走 `/genealogy/region/children`、`/genealogy/region/path/{regionCode}`、`/genealogy/region/search`、`/genealogy/region/{regionCode}`。验证:`node tests\region-pages.test.js`、`node tests\profile-pages.test.js` 通过;`node --check public\js\region-pages.js` 通过;地区旧 APP 路径扫描无输出。 | -| PC-006 | 已修正 | 全项目页面覆盖矩阵 | PC YAML 现有接口覆盖范围与缺口 | 2026-07-09 15:34:26 +08:00 完成修正。该项只作为文档清单任务,不做业务代码。输出页面级覆盖矩阵:完整可闭环、接口存在但验证码待确认、只能保留公共能力、文件能力可切换但主业务待确认、PC YAML 未覆盖。避免把“页面里某个公共接口可用”误标为“整页已可对接”。 | -| PC-007 | 已完成 | 详细实施规划 | `genealogy-pc-openapi.yaml` 的 `paths`、`docs/superpowers/plans/2026-07-09-pc-api-replan.md` | 2026-07-09 15:41:40 +08:00 完成修正。重写详细实施计划:只以 YAML `paths` 为真实接口来源;`api-client.js` 不能保留 APP 路径 fallback;PC YAML 未覆盖页面改为受控“接口待确认/PC_API_NOT_AVAILABLE”;补充资料字段 `provinceCode/cityCode/districtCode`、TAC 登录场景 `WEB_H5_LOGIN`、文件/地区局部能力边界。 | -| PC-008 | 已完成 | PC YAML 未覆盖业务页面 | `PC_API_NOT_AVAILABLE`、旧 APP 网络路径清理 | 2026-07-09 16:06:35 +08:00 完成。`api-client.js` 中家谱主体、成员、字辈、世系、动态、文章、相册、祭祀、族务、VIP、通知、反馈、帮助、推广等 PC YAML 未覆盖业务方法保留方法名但统一抛 `PC_API_NOT_AVAILABLE`,不再发 `/genealogy/app/` 请求。验证:`Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }` 全部通过;`Select-String -Path public\js\api-client.js -Pattern '/genealogy/app/'` 无输出。 | -| PC-009 | 已完成 | `login.html`、`register.html`、`forgot-password.html` | `/genealogy/pc/auth/login`、`/genealogy/pc/auth/login/sms`、`/genealogy/pc/auth/register`、`/genealogy/pc/auth/sms/code`、`/genealogy/pc/auth/password/reset`、`/captcha/*` | 2026-07-09 16:32:17 +08:00 开始,2026-07-09 16:40:02 +08:00 完成。专项计划见 `docs/superpowers/plans/2026-07-09-pc-auth-three-pages.md`。`login.html` 已补齐密码登录/短信登录两种模式,短信登录使用 `WEB_H5_LOGIN` 发送验证码并调用 `/genealogy/pc/auth/login/sms`;注册页继续使用 `WEB_H5_REGISTER` 和 `/genealogy/pc/auth/register`;忘记密码页继续使用 `WEB_H5_FORGOT_PASSWORD`、`/genealogy/pc/auth/sms/code`、`/genealogy/pc/auth/password/reset`。修改文件:`login.html`、`public/css/login.css`、`public/js/auth-pages.js`、`tests/auth-pages.test.js`、`tests/api-client.test.js`。验证:`node tests\auth-pages.test.js`、`node tests\api-client.test.js`、`node tests\captcha-pages.test.js` 通过;全量 `tests/*.test.js` 通过;`node --check public\js\auth-pages.js`、`node --check public\js\api-client.js`、`node --check public\js\captcha-pages.js` 通过;内联样式和旧验证码场景扫描无输出。 | -| PC-010 | 已完成 | `config.js`、`public/js/api-client.js` | 开发环境接口基础地址、`/captcha/require`、`/captcha/challenge` | 2026-07-09 16:45:35 +08:00 开始,2026-07-09 16:48:05 +08:00 完成。根因:浏览器和 `curl.exe` 复现 `https://test-genealogy-api.ddxcjp.cn` TLS 握手失败,错误为 `ERR_SSL_UNRECOGNIZED_NAME_ALERT`/`SEC_E_ILLEGAL_MESSAGE`;同一 `/captcha/require` 请求改用 `http://test-genealogy-api.ddxcjp.cn` 返回 `200 OK`。处理:开发环境基础地址改为 `http://test-genealogy-api.ddxcjp.cn`;`config.js` 对旧的 `https://test-genealogy-api.ddxcjp.cn` 本地覆盖值做归一,避免浏览器 localStorage 残留继续走坏地址;`api-client.js` fallback 默认地址同步改为 HTTP。修改文件:`config.js`、`public/js/api-client.js`、`tests/config.test.js`、`tests/api-client.test.js`。验证:`node tests\config.test.js`、`node tests\api-client.test.js`、`node --check config.js`、`node --check public\js\api-client.js` 通过。 | -| PC-011 | 已完成 | `login.html`、`register.html`、`forgot-password.html`、`public/css/public.css`、`public/js/captcha-pages.js` | `public/tac` 弹窗挂载、`/captcha/challenge`、`/captcha/verify` | 2026-07-09 16:56:22 +08:00 开始,2026-07-09 17:02:35 +08:00 完成。根因:`public/tac/css/tac.css` 中 `#tianai-captcha-parent` 是相对定位,会在绑定元素内渲染;此前把 `[data-captcha-box]` 放进表单内部,导致验证码块直接占据卡片内容区域。处理:三张认证页移除表单内部挂载点,新增页面级 `.auth-captcha-modal[data-captcha-box]`;`public/css/public.css` 增加固定遮罩、居中和移动端宽度约束;`captcha-pages.js` 优先使用页面级挂载点;未修改 `public/tac/js/tac.min.js`。修改文件:`login.html`、`register.html`、`forgot-password.html`、`public/css/public.css`、`public/js/captcha-pages.js`、`tests/auth-page-structure.test.js`、`tests/captcha-pages.test.js`。验证:`node tests\auth-page-structure.test.js`、`node tests\captcha-pages.test.js` 通过;全量 `tests/*.test.js` 通过;`node --check public\js\captcha-pages.js`、`node --check public\js\auth-pages.js` 通过;表单内部 `data-captcha-box` 扫描无输出。 | +| PC-000 | 复核通过 | 全项目 | `genealogy-pc-openapi.yaml` | 历史记录保留;当前 PC YAML `paths` 再次核对通过,真实接口范围仍只有验证码、PC 认证、PC 文件和地区。 | +| PC-001 | 复核通过 | 根目录 `config.js` | 开发/生产环境、API 基础地址 | 当前 `config.js` 已收敛为环境和基础地址唯一入口;`node tests\config.test.js`、`node --check config.js` 通过。 | +| PC-002 | 复核通过 | `utils` 公共工具层 | Axios、请求封装、PC 接口客户端 | 当前公共层为 `utils/axios.js`、`utils/AxiosRequestUtil.js`、`utils/ApiClient.js`;旧 fetch 工具和旧 public API 客户端已删除。 | +| PC-003 | 本地复核通过 | 认证、资料、安全页面 | `/genealogy/pc/auth/*`、`/captcha/*` | 认证、资料、安全测试和脚本顺序检查均通过;真实登录业务仍依赖后端账户数据。 | +| PC-004 | 本地复核通过 | 文件上传页面 | `/genealogy/pc/files/*` | `node tests\upload-pages.test.js`、`node tests\api-client.test.js` 通过;文件上传仍需真实文件和后端授权进行端到端验证。 | +| PC-005 | 本地复核通过 | 地区选择页面 | `/genealogy/region/*` | `node tests\region-pages.test.js`、`node tests\profile-pages.test.js` 通过。 | +| PC-006 | 复核通过 | 页面覆盖矩阵 | PC YAML 覆盖边界 | 保持“公共能力不等于整页业务已对接”的边界,未覆盖业务继续受控失败。 | +| PC-007 | 复核通过 | 实施规划 | `genealogy-pc-openapi.yaml` | 当前计划与 YAML `paths` 一致,不使用旧 APP 路径 fallback。 | +| PC-008 | 复核通过 | PC YAML 未覆盖业务页面 | `PC_API_NOT_AVAILABLE` | `utils/ApiClient.js` 统一抛 `PC_API_NOT_AVAILABLE`,旧 `/genealogy/app/` 请求路径扫描无结果。 | +| PC-009 | 本地复核通过 | 登录、注册、找回密码 | PC auth、`/captcha/*` | 三页场景编码、表单契约、Axios 请求客户端和相关测试均通过;真实账号流程待业务数据联调。 | +| PC-010 | 复核通过(网关可达) | `config.js`、`utils/ApiClient.js` | 测试 API 基础地址 | 开发地址为 `http://test-genealogy-api.ddxcjp.cn`;2026-07-10 10:02:48 +08:00 外部 `curl.exe -I` 返回 `HTTP 200 OK`。 | +| PC-011 | 本地复核通过 | 三张认证页 TAC 弹窗 | `public/tac`、`/captcha/*` | 页面级挂载、移动端约束和结构测试通过;未修改 `tac.min.js`。 | ## PC 页面覆盖矩阵草案 @@ -109,7 +109,7 @@ - 2026-07-09 17:04:21 +08:00:复现用户反馈的 `503 Service Unavailable`。`curl.exe` 请求 `/captcha/require` 和域名根路径 `/` 均返回 `Server: SakuraFrp`、`503 Service Unavailable`,响应页提示检查 SakuraFrp 隧道节点、隧道配置、frpc 是否运行、隧道是否在线。结论:这是测试接口域名/内网穿透服务当前不可用,不是前端字段、TAC 样式或请求参数导致。 ## PC-012 认证页 layui 提示与 TAC 弹窗样式修正 -- 状态:已完成 +- 状态:本地复核通过 - 计划开始时间:2026-07-09 17:14:08 +08:00 - 计划文件:`docs/superpowers/plans/2026-07-09-auth-layer-message.md` - 页面范围:`login.html`、`register.html`、`forgot-password.html` @@ -132,3 +132,112 @@ 3. 已完成:`auth-pages.js`、`captcha-pages.js` 提示统一走 `MessageUtil`/`layui.layer.msg`,不再使用浏览器原生 `alert`。 4. 已完成:`public/css/public.css` 增加认证页 layer 提示皮肤。 5. 已完成:焦点测试、相关测试、语法检查和全量 Node 测试通过。 + +## PC-013 TAC 取消或请求失败后的遮罩清理 + +- 状态:本地复核通过 +- 计划开始时间:2026-07-10 09:10:03 +08:00 +- 计划文件:`docs/superpowers/plans/2026-07-10-tac-modal-cleanup.md` +- 页面范围:`login.html`、`register.html`、`forgot-password.html` +- 接口范围:`/captcha/require`、`/captcha/challenge`、`/captcha/verify` +- 根因记录:现有 `.auth-captcha-modal` 仅依赖 `:empty` 判断隐藏;TAC 在取消、CORS 失败或 503 失败后仍可能保留子节点,导致外层遮罩层不再为空从而不会消失。 +- 处理计划:由 `captcha-pages.js` 显式管理 `is-active` 状态、`aria-hidden` 和残留 DOM;成功、取消、初始化异常、Promise 失败路径全部调用统一清理函数。 +- 完成时间:2026-07-10 09:15:55 +08:00 +- 修改文件:`public/js/captcha-pages.js`、`public/css/captcha-modal.css`、`login.html`、`register.html`、`forgot-password.html`、`tests/captcha-pages.test.js`、`tests/auth-page-structure.test.js`、`docs/superpowers/plans/2026-07-10-tac-modal-cleanup.md`。 +- 完成结果:`openCaptchaBox` 在每次初始化前清理旧 TAC DOM 并显示遮罩;`closeCaptchaBox` 在成功、取消、初始化异常、Promise 失败后清空 DOM、隐藏遮罩并更新 `aria-hidden`;三张认证页加载新的公共验证码弹窗 CSS。 +- 验证结果:`node tests\\captcha-pages.test.js`、`node tests\\auth-page-structure.test.js`、`node --check public\\js\\captcha-pages.js` 和 `Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }` 均通过。 +- 剩余风险:`/captcha/*` 的 503、CORS 或 TLS 错误属于测试网关/服务端可用性问题;本项只保证它们触发取消或异常后,前端不会继续遗留遮罩。 + +## PC-014 已完成记录全量复核 + +- 状态:已完成 +- 开始时间:2026-07-10 09:20:38 +08:00 +- 计划文件:`docs/superpowers/plans/2026-07-10-pc-tracker-reverification.md` +- 复核范围:PC-000 至 PC-013 中所有标记为“已完成”或“已修正”的项目。 +- 复核规则:以当前 `genealogy-pc-openapi.yaml` 的 `paths`、当前代码、当前 Node 测试、语法检查和静态扫描为准;历史记录只作为待核对内容,不作为证明。 +- 状态规则:当前证据完整才保留“已完成”;代码或记录不一致改为“待修正”;仅受测试网关影响的项目标记“外部环境待验证”。 +- 暂停时间:2026-07-10 09:27:30 +08:00 +- 暂停原因:用户要求删除现有 fetch 请求层并整体切换为 Axios;重构前取得的复核结果不能作为重构后的最终证据。 +- 完成时间:2026-07-10 10:03:18 +08:00 +- 当前结论:上方 PC-000 至 PC-011 及下方 PC-012、PC-013 的复核状态为本次有效状态,历史文件名、历史命令和历史时间仅用于追溯。 +- 验证证据:当前 YAML 路径扫描、Axios 公共层测试、认证/验证码/资料/安全/上传/地区测试、45 页脚本顺序测试、全量 Node 测试、语法检查均通过;受限环境外 `curl.exe -I http://test-genealogy-api.ddxcjp.cn/captcha/require` 返回 `HTTP 200 OK`。 + +## PC-015 Axios 公共请求层重构 + +- 状态:已完成 +- 开始时间:2026-07-10 09:27:30 +08:00 +- 计划文件:`docs/superpowers/plans/2026-07-10-axios-request-layer-refactor.md` +- 目录边界:`utils/axios.js` 存第三方 Axios 非压缩浏览器构建;`utils/AxiosRequestUtil.js` 存公共请求封装;`utils/ApiClient.js` 存跨页面 PC 接口客户端;`public/js` 只存页面业务脚本。 +- 清理目标:删除 `utils/RequestUtil.js` 与 `public/js/api-client.js`,并清除 `fetch`、`fetchImpl`、`root.fetch` 和旧页面脚本引用;不提供兼容分支。 +- 范围修正:2026-07-10 09:27:30 +08:00 初始清单只按旧 RequestUtil 引用列出 14 页;本轮静态扫描发现总共有 45 页加载接口客户端,已修正为自动扫描全部相关 HTML 页面,剩余 31 页必须迁移后才能完成 PC-015。 +- 配置职责修正:2026-07-10 09:47:25 +08:00 参考 `C:\Users\Administrator\Desktop\job\3Dyjs\config.js` 后确认,根目录 `config.js` 只负责开发/生产环境与 API 基础地址;PC clientId、tenantId、token key 和 Authorization header 由 `utils/ApiClient.js` 负责。当前未提供生产地址,生产环境暂时使用用户指定的测试地址,待后端提供生产域名后单独替换。 +- 配置单一来源复核:2026-07-10 10:10:56 +08:00 已移除 `utils/ApiClient.js` 的 `DEFAULT_BASE_URL`;客户端现在只能消费根目录 `config.js` 的 `apiBaseUrl` 或调用方显式传入的 `baseUrl`,缺失时立即抛出明确错误。 +- 完成时间:2026-07-10 10:03:18 +08:00 +- 完成结果:下载 Axios 1.7.9 非压缩浏览器构建至 `utils/axios.js`;创建 `utils/AxiosRequestUtil.js` 与 `utils/ApiClient.js`;删除 `utils/RequestUtil.js` 与 `public/js/api-client.js`;45 页统一加载 `config.js`、`StorageUtil.js`、Axios、AxiosRequestUtil、ApiClient 后再加载页面业务脚本。 +- 验证结果:`node tests\config.test.js`、`node tests\utils.test.js`、`node tests\api-client.test.js`、`node tests\api-script-order.test.js`、全量 Node 测试、`node --check` 和旧 fetch/旧脚本扫描均通过;`git diff --check` 无空白错误。2026-07-10 10:10:56 +08:00 再次回归通过,并确认客户端不再导出 `DEFAULT_BASE_URL`。 + +## PC-016 TAC 原生样式调用清理 + +- 状态:已完成(由 PC-017 修正调用位置) +- 开始时间:2026-07-10 10:19:46 +08:00 +- 计划文件:`docs/superpowers/plans/2026-07-10-tac-native-style-cleanup.md` +- 页面范围:`login.html`、`register.html`、`forgot-password.html` +- 约束:只调用 `public/tac` 自带的 CSS 和 JS;移除项目自定义验证码遮罩、尺寸约束与视觉配置,不改动 TAC 第三方文件。 +- 完成时间:2026-07-10 10:32:20 +08:00 +- 修改结果:删除 `public/css/captcha-modal.css`;删除 `public/css/public.css` 中 35 行自定义验证码规则;三页仅保留 `public/tac/css/tac.css`,并使用无 class 的 `[data-captcha-box]`;`captcha-pages.js` 只调用 `new TAC(config)`。 +- 验证结果:`node tests\auth-page-structure.test.js`、`node tests\captcha-pages.test.js`、全量 Node 测试、`node --check public\js\captcha-pages.js`、自定义验证码视觉标识扫描和 `git diff --check` 均通过。 +- 外部边界:本项只恢复 TAC 原生视觉与调用边界;验证码背景尺寸校验失败仍需以后端挑战响应的原始图片尺寸为依据另行修复。 +- 复核时间:2026-07-10 10:38:10 +08:00 +- 复核结论:TAC 的 `init()` 只将组件追加到 `CaptchaConfig.bindEl`,不负责页面级定位;删除静态挂载点定位样式后,组件被追加到 `body` 末尾,因此渲染到页面底部。本项不能保留“已完成”。 + +## PC-017 TAC 默认弹层调用纠正 + +- 状态:已完成 +- 开始时间:2026-07-10 10:38:10 +08:00 +- 计划文件:`docs/superpowers/plans/2026-07-10-tac-layer-call-correction.md` +- 调用位置:`auth-pages.js` 表单提交前调用 `CaptchaPages.ensureToken`;验证码适配层使用 Layui 默认 `layer.open` 创建临时容器,容器传入 `CaptchaConfig.bindEl`,然后调用 `new TAC(config)`。 +- 功能边界:不限制后端下发的 `SLIDER`、`ROTATE`、`CONCAT`、`WORD_IMAGE_CLICK`、`DISABLED` 类型;刷新由 TAC `reloadCaptcha()` 处理;成功、关闭与异常路径由适配层关闭对应 Layui 层。 +- 完成时间:2026-07-10 10:44:11 +08:00 +- 修改结果:删除三页静态 `[data-captcha-box]`;`captcha-pages.js` 用 Layui 默认 `layer.open` 创建临时容器作为 `CaptchaConfig.bindEl`,并保持 `new TAC(config)` 原样调用;TAC 结束后关闭同一 Layui 层。 +- 验证结果:`node tests\auth-page-structure.test.js`、`node tests\captcha-pages.test.js`、全量 Node 测试、`node --check public\js\captcha-pages.js` 和 `git diff --check` 均通过;静态扫描确认三页、公共 CSS 和验证码适配层没有 `data-captcha-box`、`auth-captcha-modal`、`captcha-modal.css`、`bgUrl` 或 `logoUrl`。 +- 浏览器边界:尚未在真实测试网关完成有效轨迹验证;需在浏览器发起一次注册、登录或找回密码操作,确认 Layui 默认层居中和后端 `validToken` 流程。 + +## PC-018 TAC Layui content 契约修复 + +- 状态:已完成(由 PC-019 修正真实挂载点) +- 开始时间:2026-07-10 10:50:06 +08:00 +- 完成时间:2026-07-10 10:54:03 +08:00 +- 计划文件:`docs/superpowers/plans/2026-07-10-tac-layui-content-contract-fix.md` +- 页面范围:`login.html`、`register.html`、`forgot-password.html` +- 接口范围:`/captcha/require`、`/captcha/challenge`、`/captcha/verify` +- 根因记录:浏览器中 `/captcha/require` 已返回 `200`,但点击提交后提示 `d.parents is not a function` 且 TAC 不显示。根因是 `captcha-pages.js` 将原生 DOM 节点直接传入 `layui.layer.open({ content })`;当前 Layui 2.11.5 弹层内部会对 `content` 调用 `.parents()`,原生 DOM 没有该方法,导致弹层创建中断。 +- 修改文件:`public/js/captcha-pages.js`、`tests/captcha-pages.test.js`、`docs/superpowers/plans/2026-07-10-tac-layui-content-contract-fix.md`、`docs/pc-api-page-integration-tracker-2026-07-09.md` +- 修改结果:`createCaptchaLayer` 现在使用 `layui.$`、`$` 或 `jQuery` 包装临时 DOM 后传给 `layer.open`;返回给 TAC 的 `box` 仍是真实 DOM 节点,继续作为 `CaptchaConfig.bindEl`。未修改 `public/tac`,未新增验证码 CSS,未传 TAC 视觉配置。 +- 验证结果:先运行 `node tests\captcha-pages.test.js` 复现失败;修复后 `node tests\auth-page-structure.test.js`、`node tests\captcha-pages.test.js`、`node --check public\js\captcha-pages.js` 和 `git diff --check` 通过。`git diff --check` 仅输出既有 CRLF 行尾提示,没有空白错误。 + +## PC-020 TAC Layui 弹层居中修复 + +- 状态:已完成 +- 开始时间:2026-07-10 11:11:05 +08:00 +- 完成时间:2026-07-10 11:14:31 +08:00 +- 计划文件:`docs/superpowers/plans/2026-07-10-tac-layer-center-fix.md` +- 页面范围:`login.html`、`register.html`、`forgot-password.html` +- 接口范围:`/captcha/require`、`/captcha/challenge`、`/captcha/verify` +- 根因记录:Layui 打开弹层时验证码挂载点仍是空节点,TAC 后续异步创建自己的 `#tianai-captcha-parent`,原生尺寸为 `318px * 318px`,导致 Layui 先按空内容定位后,最终验证码窗口偏离视觉中心。 +- 修改文件:`public/js/captcha-pages.js`、`tests/captcha-pages.test.js`、`docs/superpowers/plans/2026-07-10-tac-layer-center-fix.md`、`docs/pc-api-page-integration-tracker-2026-07-09.md` +- 修改结果:`createCaptchaLayer` 使用 Layui 默认 `layer.open` 时增加 `area: ['318px', '318px']` 与 `offset: 'auto'`,尺寸来自 TAC 原生 `#tianai-captcha-parent`;未修改 `public/tac`,未新增验证码自定义 CSS,未传 TAC 视觉配置。 +- 验证结果:先运行 `node tests\captcha-pages.test.js` 复现 `layerOptions.area` 缺失失败;修复后 `node tests\captcha-pages.test.js`、`node tests\auth-page-structure.test.js`、`node --check public\js\captcha-pages.js` 和 `git diff --check` 通过。`git diff --check` 仅输出既有 LF/CRLF 换行提示,没有空白错误。 +- 复核结论:该项只解决 `d.parents is not a function`;用户 2026-07-10 10:59 左右浏览器复测显示 Layui 遮罩出现、`/captcha/challenge` 返回 200,但 TAC 本体未显示,因此真实挂载点问题进入 PC-019 修正。 + +## PC-019 TAC Layui 实际挂载点修复 + +- 状态:已完成 +- 开始时间:2026-07-10 10:59:52 +08:00 +- 完成时间:2026-07-10 11:04:18 +08:00 +- 计划文件:`docs/superpowers/plans/2026-07-10-tac-layer-real-mount-fix.md` +- 页面范围:`login.html`、`register.html`、`forgot-password.html` +- 接口范围:`/captcha/require`、`/captcha/challenge`、`/captcha/verify` +- 根因记录:用户浏览器截图显示 Layui 遮罩已经出现,`/captcha/require` 与 `/captcha/challenge` 都返回 `200`,但弹层内没有 TAC 验证窗口。根因是 PC-018 仍返回打开前的临时 DOM 给 `CaptchaConfig.bindEl`,不能保证该节点就是 Layui 弹层内容区里的真实节点。 +- 修改文件:`public/js/captcha-pages.js`、`tests/captcha-pages.test.js`、`docs/superpowers/plans/2026-07-10-tac-layer-real-mount-fix.md`、`docs/pc-api-page-integration-tracker-2026-07-09.md` +- 修改结果:`createCaptchaLayer` 现在用字符串 `content` 让 Layui 创建 `
`,弹层打开后再通过 `document.querySelector` 获取这个实际节点,并将它作为 TAC 的 `bindEl`。未修改 `public/tac`,未新增验证码 CSS,未传 TAC 视觉配置。 +- 验证结果:先运行 `node tests\captcha-pages.test.js` 复现失败;修复后 `node tests\auth-page-structure.test.js`、`node tests\captcha-pages.test.js`、`node --check public\js\captcha-pages.js` 和 `git diff --check` 通过。`git diff --check` 仅输出既有 CRLF 行尾提示,没有空白错误。 diff --git a/docs/superpowers/plans/2026-07-10-axios-request-layer-refactor.md b/docs/superpowers/plans/2026-07-10-axios-request-layer-refactor.md new file mode 100644 index 0000000..7e59bdc --- /dev/null +++ b/docs/superpowers/plans/2026-07-10-axios-request-layer-refactor.md @@ -0,0 +1,251 @@ +# Axios 公共请求层重构 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 删除现有 `fetch` 请求实现,以本地 Axios 浏览器版和 `utils/AxiosRequestUtil.js` 替换为唯一公共请求层,页面业务 JS 不再依赖或实现底层网络请求。 + +**Architecture:** `utils/axios.js` 是固定版本的第三方浏览器依赖;`utils/AxiosRequestUtil.js` 负责 Axios 实例、请求头、token、参数、响应解包和错误转换;`utils/ApiClient.js` 负责所有 PC 接口契约与 token 生命周期。所有页面在页面业务脚本前加载三个 utils 文件,旧 `utils/RequestUtil.js` 和 `public/js/api-client.js` 被删除且不保留兼容入口。 + +**Tech Stack:** Axios 1.7.9 浏览器非压缩构建、原生 JavaScript、Node `assert` 测试、PowerShell 静态扫描。 + +## Global Constraints + +- 计划创建时间:2026-07-10 09:27:30 +08:00。 +- `utils` 只存通用依赖和公共请求封装;`public/js` 只存页面业务脚本,不存 API 客户端或底层请求实现。 +- 不保留 `fetch`、`fetchImpl`、`root.fetch`、`RequestUtil`、`public/js/api-client.js` 或旧脚本引用;不做 Axios/fetch 双实现兼容。 +- Axios 使用本地 `utils/axios.js`,页面运行时不依赖 CDN。 +- 普通 JSON 请求传对象给 Axios;文件上传传 `FormData`;不手写 JSON 字符串。 +- 每个请求都带 `clientid`;有 token 且 `auth !== false` 时带 `Authorization: Bearer `。 +- PC-014 在本重构完成后重新执行,重构前的复核结论不作为最终证据。 + +--- + +### Task 0: 收敛环境配置职责 + +**Files:** +- Modify: `config.js` +- Modify: `tests/config.test.js` +- Modify: `utils/ApiClient.js` + +**Interfaces:** +- Produces: `GenealogyConfig.getEnvironment()`,只返回 `development` 或 `production`。 +- Produces: `GenealogyConfig.getApiBaseUrl()`,只返回当前环境的接口基础地址。 +- Produces: `GenealogyConfig.getConfig()`,只返回 `{ environment, apiBaseUrl }`。 +- Consumes: `ApiClient` 自己定义 PC `clientId`、`tenantId`、token key 和 token header。 + +- [x] **Step 1: 写配置职责失败测试** + +```js +assert.deepStrictEqual(config.getConfig(), { + environment: 'development', + apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn' +}); +assert.strictEqual(config.getClientId, undefined); +assert.strictEqual(config.getTenantId, undefined); +assert.strictEqual(config.getTokenKey, undefined); +``` + +- [x] **Step 2: 运行测试确认旧配置职责过宽** + +Run: `node tests\\config.test.js` + +Expected: FAIL,旧配置仍暴露 clientId、tenantId、token key 或 token header。 + +- [x] **Step 3: 重写 config.js 为环境和域名唯一入口** + +```js +var environments = { + development: { apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn' }, + production: { apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn' } +}; + +function getConfig() { + return { + environment: getEnvironment(), + apiBaseUrl: getApiBaseUrl() + }; +} +``` + +- [x] **Step 4: 将 PC 身份和 token 常量保留在 ApiClient** + +```js +var DEFAULT_CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f'; +var DEFAULT_TENANT_ID = '000000'; +var DEFAULT_TOKEN_KEY = 'genealogy_auth_token'; +var DEFAULT_TOKEN_HEADER_NAME = 'Authorization'; +``` + +补充约束:`ApiClient` 不定义接口基础地址;必须由根目录 `config.js` 的 `apiBaseUrl` 或调用方显式 `baseUrl` 提供。 + +- [x] **Step 5: 运行配置与 API 客户端测试** + +Run: `node tests\\config.test.js; node tests\\api-client.test.js; node --check config.js; node --check utils\\ApiClient.js` + +Expected: 所有命令通过。 + +### Task 1: 锁定 Axios 请求契约的失败测试 + +**Files:** +- Modify: `tests/utils.test.js` +- Modify: `tests/api-client.test.js` + +**Interfaces:** +- Produces: `AxiosRequestUtil.createRequester(options)`。 +- Consumes: `options.axiosInstance`,该对象实现 `request(config)`。 +- Produces: `request(method, path, { query, body, headers, auth })`,向 Axios 传入 `{ method, url, params, data, headers }`。 + +- [x] **Step 1: 把工具测试改为 Axios 假实例** + +```js +const axiosCalls = []; +const requester = AxiosRequestUtil.createRequester({ + baseUrl: 'https://api.example.test', + clientId: 'pc-client', + getToken: () => 'token-x', + axiosInstance: { + request: async (config) => { + axiosCalls.push(config); + return { status: 200, data: { code: 200, data: { ok: true } } }; + } + } +}); +``` + +- [x] **Step 2: 运行测试确认因旧 fetch 契约失败** + +Run: `node tests\\utils.test.js; node tests\\api-client.test.js` + +Expected: FAIL,提示缺少 `AxiosRequestUtil` 或仍使用 `fetchImpl`。 + +### Task 2: 创建 Axios 工具层并删除 fetch 工具层 + +**Files:** +- Create: `utils/axios.js` +- Create: `utils/AxiosRequestUtil.js` +- Create: `utils/ApiClient.js` +- Delete: `utils/RequestUtil.js` +- Delete: `public/js/api-client.js` + +**Interfaces:** +- `AxiosRequestUtil.createRequester(options)` 只接受 Axios 实例,不接受 `fetchImpl`。 +- `GenealogyApi.createClient(options)` 由 `utils/ApiClient.js` 提供,只接受 `axiosInstance` 测试注入;生产环境读取 `window.axios`。 + +- [x] **Step 1: 下载固定 Axios 非压缩浏览器构建** + +Run: `curl.exe --fail --location https://cdn.jsdelivr.net/npm/axios@1.7.9/dist/axios.js --output utils\\axios.js` + +Expected: `utils/axios.js` 存在且首行包含 Axios 版本信息。 + +- [x] **Step 2: 实现 AxiosRequestUtil 最小请求流程** + +```js +var response = await axiosInstance.request({ + method: method.toLowerCase(), + url: path, + params: requestOptions.query, + data: requestOptions.body, + headers: headers +}); + +return unwrapResponse(response.data); +``` + +- [x] **Step 3: 从零实现 utils/ApiClient.js 并只依赖 AxiosRequestUtil** + +```js +var requestUtil = getAxiosRequestUtil(); +var requester = requestUtil.createRequester({ + baseUrl: baseUrl, + clientId: clientId, + getToken: getToken, + axiosInstance: settings.axiosInstance || root.axios +}); +``` + +- [x] **Step 4: 删除旧 fetch 请求文件和旧公共 API 文件** + +Run: `Remove-Item -LiteralPath utils\\RequestUtil.js; Remove-Item -LiteralPath public\\js\\api-client.js` + +Expected: 两个文件不存在,后续扫描无 `RequestUtil`、`fetchImpl`、`root.fetch`、`fetch(` 或 `public/js/api-client.js` 匹配。 + +- [x] **Step 5: 运行工具和 API 客户端测试** + +Run: `node tests\\utils.test.js; node tests\\api-client.test.js; node --check utils\\AxiosRequestUtil.js; node --check utils\\ApiClient.js` + +Expected: 所有命令通过。 + +### Task 3: 更新页面公共脚本顺序 + +**Files:** +- Modify: 所有根目录中加载 `public/js/api-client.js` 或 `utils/ApiClient.js` 的 HTML 页面。 +- Test: `tests/auth-page-structure.test.js` +- Test: `tests/api-script-order.test.js` + +- [x] **Step 1: 以自动发现的脚本顺序测试锁定页面依赖** + +```js +assert.strictEqual(pages.length, 45); +assert.ok(axiosIndex < axiosRequestUtilIndex); +assert.ok(axiosRequestUtilIndex < apiClientIndex); +assert.strictEqual(html.includes('utils/RequestUtil.js'), false); +``` + +- [x] **Step 2: 运行测试确认旧脚本顺序不满足新契约** + +Run: `node tests\\auth-page-structure.test.js` + +Expected: FAIL,仍有页面加载旧 API 客户端或未加载 Axios 和 AxiosRequestUtil。 + +- [x] **Step 3: 逐页替换公共脚本引用** + +```html + + + +``` + +- [x] **Step 4: 运行结构测试和静态扫描** + +Run: `node tests\\auth-page-structure.test.js; rg -n "utils/RequestUtil\\.js|public/js/api-client\\.js|fetchImpl|root\\.fetch|fetch\\(" --glob "!public/tac/**" --glob "!utils/axios.js" .` + +Expected: 测试通过,旧请求层标识无匹配。 + +### Task 4: 重跑 PC 复核并更新记录 + +**Files:** +- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md` +- Modify: `docs/superpowers/plans/2026-07-10-axios-request-layer-refactor.md` +- Modify: `docs/superpowers/plans/2026-07-10-pc-tracker-reverification.md` + +- [x] **Step 1: 运行所有 Node 测试与语法检查** + +Run: `Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }; node --check utils\\AxiosRequestUtil.js; node --check utils\\ApiClient.js; git diff --check` + +Expected: 所有测试通过,语法检查和差异检查无错误。 + +- [x] **Step 2: 执行 PC-014 复核命令并记录外部网关状态** + +Run: `curl.exe -I --max-time 15 http://test-genealogy-api.ddxcjp.cn/captcha/require` + +Expected: 记录当次结果;仅本地验证通过的项标为“本地复核通过”,网关不可用时标注“外部环境待验证”。 + +- [x] **Step 3: 写入完成时间和清理结论** + +Record: Axios 文件来源、删除的 `fetch` 文件、页面脚本替换范围、所有验证命令及结果。 + +## 自检 + +- 所有底层网络调用都从 `utils/AxiosRequestUtil.js` 发起。 +- `public/js` 中不含 `fetch`、Axios 实例创建或请求头拼装。 +- 所有页面在页面业务脚本前加载 `utils/axios.js`、`utils/AxiosRequestUtil.js` 和 `utils/ApiClient.js`。 +- `utils/RequestUtil.js`、`public/js/api-client.js`、`fetchImpl`、`root.fetch` 和旧页面脚本引用均不存在。 + +## 完成记录 + +- 完成时间:2026-07-10 10:10:56 +08:00。 +- Axios 来源:`axios@1.7.9/dist/axios.js` 的本地非压缩浏览器构建,存放于 `utils/axios.js`。 +- 请求契约:所有 PC 请求均由 `utils/AxiosRequestUtil.js` 通过 `axios.request` 发起;`utils/ApiClient.js` 仅封装 PC 接口和 token 生命周期。 +- 配置边界:根目录 `config.js` 是接口基础地址唯一来源;`ApiClient` 不再保存 `DEFAULT_BASE_URL`。 +- 页面范围:45 个加载接口客户端的 HTML 页面已统一脚本顺序;旧 `utils/RequestUtil.js` 和 `public/js/api-client.js` 已删除。 +- 验证:全量 Node 测试、语法检查、45 页脚本顺序检查、旧 fetch/旧脚本扫描和 `git diff --check` 均通过。 diff --git a/docs/superpowers/plans/2026-07-10-pc-tracker-reverification.md b/docs/superpowers/plans/2026-07-10-pc-tracker-reverification.md new file mode 100644 index 0000000..864a323 --- /dev/null +++ b/docs/superpowers/plans/2026-07-10-pc-tracker-reverification.md @@ -0,0 +1,150 @@ +# PC 接口对接记录全量复核 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 重新验证 `pc-api-page-integration-tracker-2026-07-09.md` 中 PC-000 至 PC-013 的完成状态,并以当前代码、当前 PC YAML 和本次命令结果修正记录。 + +**Architecture:** `genealogy-pc-openapi.yaml` 的 `paths` 是接口范围唯一来源;代码和 HTML 是当前实现来源;Node 测试、语法检查和静态扫描是可重复验证来源。无法由前端离线验证的测试网关可用性明确标记为“外部环境待验证”,不将其当作页面对接完成证据。 + +**Tech Stack:** OpenAPI YAML、原生 JavaScript、Node `assert` 测试、PowerShell 静态扫描、Git 差异检查。 + +## Global Constraints + +- 计划创建时间:2026-07-10 09:20:38 +08:00。 +- 范围仅限追踪表中状态为“已完成”或“已修正”的 PC-000 至 PC-013;不新增业务接口或页面功能。 +- 复核时不采用旧 APP 路径,不以历史测试文字代替当前命令结果。 +- 每个结论必须记录命令、结果、时间和证据边界;证据不足必须改为“复核失败/待修正”或“外部环境待验证”。 +- 仅在记录与事实不一致时修改文档;不改无关业务代码。 + +--- + +### Task 1: 复核契约和配置项 + +**Files:** +- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md` +- Read: `genealogy-pc-openapi.yaml` +- Read: `config.js` +- Read: `public/js/api-client.js` +- Test: `tests/config.test.js` +- Test: `tests/api-client.test.js` + +**Covers:** PC-000、PC-001、PC-007、PC-010。 + +- [x] **Step 1: 核对 YAML 路径与客户端配置** + +Run: `rg -n "^ /captcha/|^ /auth/code:|^ /genealogy/pc/auth/|^ /genealogy/pc/files/|^ /genealogy/region/" genealogy-pc-openapi.yaml; rg -n "test-genealogy-api|ced7e5f0498645c6ec642dcf450b036f|web_pc" config.js public/js/api-client.js` + +Expected: 当前 YAML 只包含验证码、PC 认证、PC 文件、地区路径;代码使用当前 PC clientId 和开发环境 HTTP 基础地址。 + +- [x] **Step 2: 运行配置与 API 客户端测试** + +Run: `node tests\\config.test.js; node tests\\api-client.test.js; node --check config.js; node --check public\\js\\api-client.js` + +Expected: 四条命令均通过。 + +### Task 2: 复核通用工具与未覆盖业务降级 + +**Files:** +- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md` +- Read: `utils/StorageUtil.js` +- Read: `utils/FormUtil.js` +- Read: `utils/RequestUtil.js` +- Read: `public/js/api-client.js` +- Test: `tests/utils.test.js` +- Test: `tests/api-client.test.js` + +**Covers:** PC-002、PC-008。 + +- [x] **Step 1: 运行工具和 API 降级测试** + +Run: `node tests\\utils.test.js; node tests\\api-client.test.js; rg -n "/genealogy/app/" public\\js\\api-client.js` + +Expected: 两个测试通过,旧 `/genealogy/app/` 请求路径无匹配结果。 + +- [x] **Step 2: 核对未覆盖业务的错误边界** + +Run: `rg -n "PC_API_NOT_AVAILABLE" public\\js\\api-client.js` + +Expected: 未被 YAML `paths` 覆盖的业务方法统一受控失败,不生成旧 APP 网络请求。 + +### Task 3: 复核认证、验证码与资料页面 + +**Files:** +- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md` +- Read: `login.html` +- Read: `register.html` +- Read: `forgot-password.html` +- Read: `public/js/auth-pages.js` +- Read: `public/js/captcha-pages.js` +- Read: `utils/MessageUtil.js` +- Test: `tests/auth-pages.test.js` +- Test: `tests/captcha-pages.test.js` +- Test: `tests/auth-page-structure.test.js` +- Test: `tests/auth-message.test.js` +- Test: `tests/profile-pages.test.js` +- Test: `tests/security-pages.test.js` + +**Covers:** PC-003、PC-009、PC-011、PC-012、PC-013。 + +- [x] **Step 1: 检查认证场景、接口路径、TAC 挂载和 layui 提示** + +Run: `rg -n "WEB_H5_LOGIN|WEB_H5_REGISTER|WEB_H5_FORGOT_PASSWORD|captcha-modal.css|auth-captcha-modal|MessageUtil|layer.msg" login.html register.html forgot-password.html public\\js\\auth-pages.js public\\js\\captcha-pages.js utils\\MessageUtil.js` + +Expected: 三张认证页分别使用正确场景;验证码使用页面级挂载点;提示由 `MessageUtil`/layui 提供。 + +- [x] **Step 2: 运行认证和验证码测试** + +Run: `node tests\\auth-pages.test.js; node tests\\captcha-pages.test.js; node tests\\auth-page-structure.test.js; node tests\\auth-message.test.js; node tests\\profile-pages.test.js; node tests\\security-pages.test.js; node --check public\\js\\auth-pages.js; node --check public\\js\\captcha-pages.js; node --check utils\\MessageUtil.js` + +Expected: 所有测试和语法检查通过。 + +- [x] **Step 3: 记录网关外部依赖边界** + +Run: `curl.exe -I --max-time 15 http://test-genealogy-api.ddxcjp.cn/captcha/require` + +Expected: 仅记录当次 HTTP 结果;无论结果如何,不将测试网关可用性视为前端实现的通过或失败。 + +### Task 4: 复核文件与地区公共能力 + +**Files:** +- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md` +- Read: `public/js/upload-pages.js` +- Read: `public/js/region-pages.js` +- Test: `tests/upload-pages.test.js` +- Test: `tests/region-pages.test.js` + +**Covers:** PC-004、PC-005、PC-006。 + +- [x] **Step 1: 运行文件和地区测试** + +Run: `node tests\\upload-pages.test.js; node tests\\region-pages.test.js; node --check public\\js\\upload-pages.js; node --check public\\js\\region-pages.js` + +Expected: 所有测试和语法检查通过。 + +- [x] **Step 2: 扫描旧路径与页面覆盖边界** + +Run: `rg -n "/genealogy/app/files|/genealogy/app/region" public\\js; rg -n "PC_API_NOT_AVAILABLE" public\\js\\api-client.js` + +Expected: 文件、地区旧 APP 路径无匹配;业务页面覆盖边界仍由 `PC_API_NOT_AVAILABLE` 保护。 + +### Task 5: 全量验证与追踪表修正 + +**Files:** +- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md` +- Modify: `docs/superpowers/plans/2026-07-10-pc-tracker-reverification.md` + +- [x] **Step 1: 运行全量测试和差异检查** + +Run: `Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }; git diff --check` + +Expected: 所有测试通过,差异检查无空白错误。 + +- [x] **Step 2: 写入复核矩阵和最终状态** + +Record: PC-000 至 PC-013 每项的本次复核时间、验证命令、结论和外部环境边界;任何不一致项从“已完成”改为“待修正”。 + +## 自检 + +- 覆盖范围:PC-000 至 PC-013 的所有“已完成/已修正”项均被归入一个复核任务。 +- 来源一致性:接口范围只引用 `genealogy-pc-openapi.yaml` 的 `paths`,不引用旧 APP 文档。 +- 状态规则:只有当前命令和当前代码同时支持的结论才能保留“已完成”。 diff --git a/docs/superpowers/plans/2026-07-10-tac-layer-call-correction.md b/docs/superpowers/plans/2026-07-10-tac-layer-call-correction.md new file mode 100644 index 0000000..5416c8c --- /dev/null +++ b/docs/superpowers/plans/2026-07-10-tac-layer-call-correction.md @@ -0,0 +1,84 @@ +# TAC 默认层调用纠正实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**目标:** 在不写任何验证码 CSS、也不修改 TAC 第三方文件的前提下,将 TAC 挂载到 Layui 默认弹层中,不再渲染到认证页底部。 + +**架构:** `auth-pages.js` 仍在表单提交时调用 `CaptchaPages.ensureToken`;`captcha-pages.js` 通过已加载的 `layui.layer.open` 创建默认容器,将该容器传给 `CaptchaConfig.bindEl`,然后原样调用 `new TAC(config)`。TAC 继续根据后端下发的 `captchaType` 自己选择滑块、旋转、拼图、文字点击或禁用态。 + +**技术栈:** Layui 内置 `layer`、`public/tac/css/tac.css`、`public/tac/js/tac.min.js`、原生 JavaScript、Node `assert` 测试。 + +## 全局约束 + +- 计划创建时间:2026-07-10 10:38:10 +08:00。 +- 不修改 `public/tac/css/tac.css`、`public/tac/js/tac.min.js` 或 Layui 第三方文件。 +- 不创建或注入任何验证码 CSS;不传 `bgUrl`、`logoUrl`、尺寸、皮肤等 TAC 视觉配置。 +- 只使用 Layui 默认 `layer.open` 作为弹层容器,TAC 的内部外观完全由其自带文件负责。 +- 只修改认证三页、验证码适配、对应测试和记录。 + +--- + +### 任务 1:锁定正确的弹层挂载契约 + +**文件:** +- 修改:`tests/auth-page-structure.test.js` +- 修改:`tests/captcha-pages.test.js` + +- [x] **步骤 1:写页面结构失败测试。** + +断言 `login.html`、`register.html`、`forgot-password.html` 不再保留 `[data-captcha-box]` 静态挂载点,且 `public/layui/layui.js` 在 `captcha-pages.js` 前加载。 + +- [x] **步骤 2:写验证码适配失败测试。** + +断言适配层调用 `root.layui.layer.open`、在结束时调用 `root.layui.layer.close`,仍只调用 `new root.TAC(config)`,不存在自定义验证码视觉标识。 + +- [x] **步骤 3:运行测试并确认当前底部挂载实现失败。** + +运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js` + +预期:因静态 `[data-captcha-box]` 与缺少 `layer.open` 而失败。 + +### 任务 2:使用 Layui 默认层承载 TAC + +**文件:** +- 修改:`login.html` +- 修改:`register.html` +- 修改:`forgot-password.html` +- 修改:`public/js/captcha-pages.js` + +- [x] **步骤 1:删除三页静态 `[data-captcha-box]`。** + +- [x] **步骤 2:在 `captcha-pages.js` 创建无样式的 DOM 容器,并以 `layui.layer.open({ type: 1, content: mount })` 打开默认弹层。** + +- [x] **步骤 3:将该 DOM 容器作为 `CaptchaConfig.bindEl`,并保持 `new TAC(config)` 原样调用。** + +- [x] **步骤 4:在 TAC 成功、关闭、初始化异常和 Promise 异常时销毁 TAC 并关闭对应 Layui 层。** + +- [x] **步骤 5:运行聚焦测试和语法检查。** + +运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js` + +预期:全部通过。 + +### 任务 3:全量验证和记录 + +**文件:** +- 修改:`docs/pc-api-page-integration-tracker-2026-07-09.md` +- 修改:`docs/superpowers/plans/2026-07-10-tac-layer-call-correction.md` + +- [x] **步骤 1:运行全量 Node 测试、静态扫描和差异检查。** + +运行:`Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }; rg -n "data-captcha-box|auth-captcha-modal|captcha-modal.css|bgUrl|logoUrl" login.html register.html forgot-password.html public\css public\js; git diff --check` + +预期:测试和差异检查通过;认证页中不再有静态验证码挂载点或自定义视觉标识。 + +- [x] **步骤 2:记录完成时间、TAC 功能边界和浏览器实测边界。** + +## 完成记录 + +- 完成时间:2026-07-10 10:44:11 +08:00。 +- 根因:TAC 的 `init()` 只向 `CaptchaConfig.bindEl` 追加组件;原先静态挂载点位于 `body` 末尾,移除定位样式后自然渲染在页面底部。 +- 修改:删除三页静态 `[data-captcha-box]`;验证码适配层使用 Layui 默认 `layer.open` 创建临时容器并传给 `bindEl`;成功、TAC 关闭、初始化异常与 Promise 异常都会清空容器并关闭同一层。 +- TAC 边界:保留原生 `new TAC(config)`、原生样式、原生刷新和后端类型分发;不传视觉配置,不写验证码 CSS,不改动任何 `public/tac` 文件。 +- 验证:失败测试已复现底部挂载;聚焦测试、全量 Node 测试、`node --check public/js/captcha-pages.js` 和 `git diff --check` 均通过。 +- 浏览器边界:未在真实测试网关完成一次有效轨迹验证;仍需在浏览器点击注册后确认 Layui 默认层居中显示及后端 `validToken` 返回。 diff --git a/docs/superpowers/plans/2026-07-10-tac-layer-center-fix.md b/docs/superpowers/plans/2026-07-10-tac-layer-center-fix.md new file mode 100644 index 0000000..242d043 --- /dev/null +++ b/docs/superpowers/plans/2026-07-10-tac-layer-center-fix.md @@ -0,0 +1,23 @@ +# 2026-07-10 TAC 弹层居中修复计划 + +- 计划编号:PC-020 +- 开始时间:2026-07-10 11:11:05 +08:00 +- 页面范围:`login.html`、`register.html`、`forgot-password.html` +- 接口范围:`/captcha/require`、`/captcha/challenge`、`/captcha/verify` +- 约束:不修改 `public/tac`,不新增验证码自定义样式,不传 TAC 视觉配置,只调整 Layui 弹层调用参数。 + +## 根因 + +Layui 打开页面层时,`content` 里的验证码挂载点还是空节点;TAC 后续异步创建自己的 `#tianai-captcha-parent`,该原生外层尺寸是 `318px * 318px`。Layui 已经按空内容完成定位,所以最终验证码窗口会偏离预期位置。 + +## 执行清单 + +1. [x] 写失败测试:要求验证码弹层通过 Layui `area` 预留 `318px * 318px`,并保持默认居中。 +2. [x] 修改 `captcha-pages.js`:只给 `layer.open` 增加 TAC 原生尺寸和默认居中参数。 +3. [x] 运行焦点测试、结构测试、语法检查和空白检查。 +4. [x] 将完成结果同步回 `pc-api-page-integration-tracker-2026-07-09.md`。 + +## 验证记录 + +- 2026-07-10 11:11:05 +08:00:`node tests\captcha-pages.test.js` 已按预期失败,失败点为 `layerOptions.area` 缺失。 +- 2026-07-10 11:14:31 +08:00:修复后 `node tests\captcha-pages.test.js`、`node tests\auth-page-structure.test.js`、`node --check public\js\captcha-pages.js` 均通过;`git diff --check` 无空白错误,仅有既有 LF/CRLF 换行提示。 diff --git a/docs/superpowers/plans/2026-07-10-tac-layer-real-mount-fix.md b/docs/superpowers/plans/2026-07-10-tac-layer-real-mount-fix.md new file mode 100644 index 0000000..7d45eec --- /dev/null +++ b/docs/superpowers/plans/2026-07-10-tac-layer-real-mount-fix.md @@ -0,0 +1,72 @@ +# TAC Layui 实际挂载点修复实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**目标:** 修复 Layui 遮罩已显示、验证码接口已返回 200,但 TAC 验证窗口仍不显示的问题。 + +**架构:** `CaptchaPages.createCaptchaLayer` 使用 Layui `layer.open` 的字符串 `content` 创建弹层内部真实挂载节点;弹层创建完成后再从 `document` 查询该实际节点,并把它交给 `CaptchaConfig.bindEl`。这样 Layui 负责弹层,TAC 挂到已经进入页面的真实 DOM。 + +**技术栈:** Layui 2.11.5、TAC 原生 CSS/JS、原生 JavaScript、Node `assert` 测试。 + +## 全局约束 + +- 计划创建时间:2026-07-10 10:59:52 +08:00。 +- 不修改 `public/tac/css/tac.css`、`public/tac/js/tac.min.js` 或 Layui 第三方文件。 +- 不新增验证码自定义 CSS,不注入样式,不传 TAC 视觉配置。 +- 保留 `auth-pages.js -> CaptchaPages.ensureToken -> TAC` 调用链,只修正弹层挂载点。 + +--- + +### 任务 1:复现“遮罩有但实际挂载点不可见” + +**文件:** +- 修改:`tests/captcha-pages.test.js` + +- [x] **步骤 1:写失败测试** + +断言 `layer.open` 的 `content` 是包含唯一 `data-captcha-layer-box` 的字符串;`createCaptchaLayer` 必须在弹层打开后从 `document.querySelector` 取到实际挂载节点并返回。 + +- [x] **步骤 2:运行失败测试** + +运行:`node tests\captcha-pages.test.js` + +预期:失败,当前实现仍返回打开前创建的原生 DOM。 + +### 任务 2:修正实际挂载点 + +**文件:** +- 修改:`public/js/captcha-pages.js` + +- [x] **步骤 1:最小实现** + +增加内部自增 ID,用字符串内容创建 `
`;`layer.open` 后查询该节点并作为 `box` 返回。 + +- [x] **步骤 2:运行聚焦测试** + +运行:`node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js` + +预期:全部通过。 + +### 任务 3:记录和回归 + +**文件:** +- 修改:`docs/pc-api-page-integration-tracker-2026-07-09.md` +- 修改:`docs/superpowers/plans/2026-07-10-tac-layer-real-mount-fix.md` + +- [x] **步骤 1:补充 PC-019 记录** + +记录用户浏览器复现现象、根因、修正方式、验证命令。 + +- [x] **步骤 2:最终验证** + +运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js; git diff --check` + +预期:全部通过;`git diff --check` 仅允许 Git 行尾提示,不允许空白错误。 + +## 完成记录 + +- 完成时间:2026-07-10 11:04:18 +08:00。 +- 根因:PC-018 只让 `layer.open` 不再因 `.parents()` 报错,但仍把打开前创建的临时 DOM 返回给 TAC;真实浏览器里 Layui 遮罩已出现,`/captcha/challenge` 也返回 200,但 TAC 没有挂到可见弹层内容区。 +- 修复:`layer.open` 使用字符串 `content` 创建弹层内部真实节点,随后通过 `document.querySelector('[data-captcha-layer-box="..."]')` 获取实际挂载点并交给 `CaptchaConfig.bindEl`。 +- 约束:未修改 `public/tac`,未新增验证码 CSS,未传 TAC 视觉配置。 +- 验证:失败测试已先复现;修复后 `node tests\captcha-pages.test.js` 和 `node --check public\js\captcha-pages.js` 通过。最终回归记录见 `docs/pc-api-page-integration-tracker-2026-07-09.md` 的 PC-019。 diff --git a/docs/superpowers/plans/2026-07-10-tac-layui-content-contract-fix.md b/docs/superpowers/plans/2026-07-10-tac-layui-content-contract-fix.md new file mode 100644 index 0000000..1cb9e06 --- /dev/null +++ b/docs/superpowers/plans/2026-07-10-tac-layui-content-contract-fix.md @@ -0,0 +1,71 @@ +# TAC Layui content 契约修复实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**目标:** 修复认证页点击后只出现 `d.parents is not a function` 提示、TAC 弹层不显示的问题。 + +**架构:** `CaptchaPages.createCaptchaLayer` 继续只负责创建 Layui 默认弹层容器,不写验证码样式、不改 TAC 文件。Layui `layer.open` 的 `content` 使用 Layui/jQuery 包装对象满足弹层内部 `.parents()` 契约,TAC `bindEl` 继续使用对应的真实 DOM 节点。 + +**技术栈:** Layui 2.11.5、TAC 原生 CSS/JS、原生 JavaScript、Node `assert` 测试。 + +## 全局约束 + +- 计划创建时间:2026-07-10 10:50:06 +08:00。 +- 不修改 `public/tac/css/tac.css`、`public/tac/js/tac.min.js` 或 Layui 第三方文件。 +- 不新增验证码自定义 CSS,不注入样式,不传 TAC 视觉配置。 +- 只修正 Layui 弹层 content 契约、对应测试和记录文档。 + +--- + +### 任务 1:复现 Layui content 契约错误 + +**文件:** +- 修改:`tests/captcha-pages.test.js` + +- [x] **步骤 1:写失败测试** + +断言 `createCaptchaLayer` 传给 `layui.layer.open` 的 `content` 必须是包装对象,包装对象需要保留真实 DOM 节点,并具备 `.parents()` 方法。 + +- [x] **步骤 2:运行失败测试** + +运行:`node tests\captcha-pages.test.js` + +预期:失败,当前实现把原生 DOM 直接传给 `content`。 + +### 任务 2:修正 createCaptchaLayer + +**文件:** +- 修改:`public/js/captcha-pages.js` + +- [x] **步骤 1:最小实现** + +在 `createCaptchaLayer` 内用 `root.layui.$ || root.$ || root.jQuery` 包装真实 DOM 节点;`layer.open({ content })` 使用包装对象;返回值中的 `box` 仍为真实 DOM,供 TAC `bindEl` 使用。 + +- [x] **步骤 2:运行聚焦测试** + +运行:`node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js` + +预期:全部通过。 + +### 任务 3:记录和回归 + +**文件:** +- 修改:`docs/pc-api-page-integration-tracker-2026-07-09.md` +- 修改:`docs/superpowers/plans/2026-07-10-tac-layui-content-contract-fix.md` + +- [x] **步骤 1:补充 PC-018 完成记录** + +记录根因、修改文件、验证命令和结果。 + +- [x] **步骤 2:运行最终验证** + +运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js; git diff --check` + +预期:全部通过;`git diff --check` 仅允许 Git 行尾提示,不允许空白错误。 + +## 完成记录 + +- 完成时间:2026-07-10 10:54:03 +08:00。 +- 根因:Layui 2.11.5 的 `layer.open` 在处理 DOM 内容时会调用 `.parents()`;原实现将原生 DOM 节点直接传给 `content`,导致浏览器报 `d.parents is not a function`,弹层创建中断。 +- 修复:`createCaptchaLayer` 使用 `layui.$`、`$` 或 `jQuery` 包装临时 DOM 后传给 `layer.open({ content })`,同时继续把真实 DOM 节点传给 `CaptchaConfig.bindEl`。 +- 验证:失败测试已先复现;修复后 `node tests\captcha-pages.test.js` 和 `node --check public\js\captcha-pages.js` 通过。最终回归记录见 `docs/pc-api-page-integration-tracker-2026-07-09.md` 的 PC-018。 diff --git a/docs/superpowers/plans/2026-07-10-tac-modal-cleanup.md b/docs/superpowers/plans/2026-07-10-tac-modal-cleanup.md new file mode 100644 index 0000000..bb6ee35 --- /dev/null +++ b/docs/superpowers/plans/2026-07-10-tac-modal-cleanup.md @@ -0,0 +1,150 @@ +# TAC 验证弹窗遮罩清理 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 在 TAC 验证被取消、初始化异常或接口失败时,主动关闭认证页遮罩并清空残留的验证码 DOM。 + +**Architecture:** `public/js/captcha-pages.js` 作为验证码弹窗生命周期的唯一拥有者,新增打开和关闭挂载容器的函数。由于原 `public.css` 不是 UTF-8,无法使用补丁安全编辑,`public/css/captcha-modal.css` 作为新增公共样式文件只根据 `is-active` 类决定遮罩显示;三张认证页继续复用同一个页面级挂载点。 + +**Tech Stack:** 原生 JavaScript、Node `assert` 测试、layui 提示、TAC `public/tac/js/tac.min.js`。 + +## Global Constraints + +- 计划创建时间:2026-07-10 09:10:03 +08:00。 +- 仅修改 TAC 遮罩生命周期,不修改 `public/tac/js/tac.min.js`,不改变后端验证码类型选择。 +- 新增或修改的 JS、CSS 注释使用中文;样式只写入 CSS 文件。 +- 失败提示继续使用 `MessageUtil`/layui,禁止回退到浏览器原生 `alert`。 +- 503/CORS 是服务端或跨域环境可用性问题;本任务只保证前端失败后不遗留遮罩。 + +--- + +### Task 1: 用例锁定与生命周期工具 + +**Files:** +- Modify: `tests/captcha-pages.test.js` +- Modify: `public/js/captcha-pages.js` + +**Interfaces:** +- Produces: `openCaptchaBox(box)`,清空旧内容、激活容器、设置 `aria-hidden="false"`。 +- Produces: `closeCaptchaBox(box)`,清空内容、取消激活、设置 `aria-hidden="true"`。 + +- [x] **Step 1: 写失败测试** + +```js +CaptchaPages.openCaptchaBox(box); +assert.strictEqual(box.classList.contains('is-active'), true); +assert.strictEqual(box.attributes['aria-hidden'], 'false'); + +CaptchaPages.closeCaptchaBox(box); +assert.strictEqual(box.classList.contains('is-active'), false); +assert.strictEqual(box.attributes['aria-hidden'], 'true'); +assert.strictEqual(box.innerHTML, ''); +``` + +- [x] **Step 2: 运行失败测试** + +Run: `node tests\\captcha-pages.test.js` +Expected: FAIL,提示 `openCaptchaBox is not a function`。 + +- [x] **Step 3: 实现最小生命周期函数** + +```js +function openCaptchaBox(box) { + if (!box) return; + box.innerHTML = ''; + box.classList.add('is-active'); + box.setAttribute('aria-hidden', 'false'); +} + +function closeCaptchaBox(box) { + if (!box) return; + box.innerHTML = ''; + box.classList.remove('is-active'); + box.setAttribute('aria-hidden', 'true'); +} +``` + +- [x] **Step 4: 将关闭函数接入 TAC 成功、取消、初始化异常和 Promise 捕获路径** + +```js +if (captchaInstance && captchaInstance.destroyWindow) captchaInstance.destroyWindow(); +closeCaptchaBox(box); +``` + +- [x] **Step 5: 运行单元测试与语法检查** + +Run: `node tests\\captcha-pages.test.js; node --check public\\js\\captcha-pages.js` +Expected: 两条命令均通过。 + +### Task 2: CSS 显示状态收口 + +**Files:** +- Create: `public/css/captcha-modal.css` +- Modify: `login.html` +- Modify: `register.html` +- Modify: `forgot-password.html` +- Test: `tests/auth-page-structure.test.js` + +**Interfaces:** +- Consumes: `.auth-captcha-modal.is-active`。 +- Produces: 默认隐藏、激活后显示的页面级遮罩。 + +- [x] **Step 1: 写失败结构测试** + +```js +assert.ok(publicCss.includes('.auth-captcha-modal.is-active')); +assert.ok(publicCss.includes('display: none;')); +``` + +- [x] **Step 2: 运行失败测试** + +Run: `node tests\\auth-page-structure.test.js` +Expected: FAIL,缺少 `.auth-captcha-modal.is-active` 规则。 + +- [x] **Step 3: 实现 CSS 状态规则** + +```css +.auth-captcha-modal { + display: none; +} + +.auth-captcha-modal.is-active { + display: grid; +} +``` + +- [x] **Step 4: 运行结构测试** + +Run: `node tests\\auth-page-structure.test.js` +Expected: PASS。 + +### Task 3: 全量验证与记录 + +**Files:** +- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md` +- Modify: `docs/superpowers/plans/2026-07-10-tac-modal-cleanup.md` + +- [x] **Step 1: 运行验证码相关和全量 Node 测试** + +Run: `Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }` +Expected: 所有测试通过。 + +- [x] **Step 2: 记录完成时间、修改文件和验证结果** + +```markdown +- [x] 完成时间:YYYY-MM-DD HH:mm:ss +08:00 +- [x] 验证:相关测试、语法检查、全量 Node 测试均通过。 +``` + +## 自检 + +- 需求覆盖:取消后遮罩清理、失败后遮罩清理、成功后遮罩清理、CSS 显示状态和回归测试均有对应任务。 +- 占位扫描:无 `TODO`、`TBD` 或未定义接口。 +- 接口一致性:JS 导出名、测试调用名和 CSS 类名统一为 `openCaptchaBox`、`closeCaptchaBox`、`is-active`。 + +## 完成记录 + +- 完成时间:2026-07-10 09:15:55 +08:00。 +- 修改文件:`public/js/captcha-pages.js`、`public/css/captcha-modal.css`、`login.html`、`register.html`、`forgot-password.html`、`tests/captcha-pages.test.js`、`tests/auth-page-structure.test.js`。 +- 验证:`node tests\\captcha-pages.test.js`、`node tests\\auth-page-structure.test.js`、`node --check public\\js\\captcha-pages.js` 和全量 `tests/*.test.js` 均通过。 +- 已知边界:测试域名的 503、CORS 或 TLS 可用性问题仍需后端或网关处理;该前端修复保证取消或异常后不残留页面遮罩。 diff --git a/docs/superpowers/plans/2026-07-10-tac-native-style-cleanup.md b/docs/superpowers/plans/2026-07-10-tac-native-style-cleanup.md new file mode 100644 index 0000000..a7cc2d9 --- /dev/null +++ b/docs/superpowers/plans/2026-07-10-tac-native-style-cleanup.md @@ -0,0 +1,85 @@ +# TAC 原生样式调用清理实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**目标:** 登录、注册、找回密码页面只调用 `public/tac` 自带的验证码组件和样式,不再追加任何验证码视觉样式或视觉配置。 + +**架构:** 页面保留一个无 class 的 `[data-captcha-box]` 挂载点;`captcha-pages.js` 只负责验证码接口参数和成功、取消、异常后的 DOM 清理;TAC 的外观完全由 `public/tac/css/tac.css` 与 `tac.min.js` 决定。 + +**技术栈:** 原生 JavaScript、TAC 本地组件、Node `assert` 测试。 + +## 全局约束 + +- 计划创建时间:2026-07-10 10:19:46 +08:00。 +- 不修改 `public/tac/css/tac.css` 或 `public/tac/js/tac.min.js`。 +- 不为验证码新增、覆盖或注入 CSS;不向 `new TAC` 传入 `bgUrl`、`logoUrl` 等视觉配置。 +- 仅修改登录、注册、找回密码三页及其验证码适配、测试和记录。 + +--- + +### 任务 1:用测试锁定原生 TAC 调用边界 + +**文件:** +- 修改:`tests/auth-page-structure.test.js` +- 修改:`tests/captcha-pages.test.js` + +- [x] **步骤 1:将页面结构测试改为禁止自定义验证码 CSS 与容器 class。** + +断言三页加载 `public/tac/css/tac.css`,不加载 `public/css/captcha-modal.css`,且 `[data-captcha-box]` 不含 `auth-captcha-modal` class。 + +- [x] **步骤 2:将组件测试改为要求原生构造调用。** + +断言 `captcha-pages.js` 中不存在 `bgUrl`、`logoUrl` 与 `.auth-captcha-modal` 视觉控制。 + +- [x] **步骤 3:运行测试并确认当前实现失败。** + +运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js` + +预期:因当前自定义验证码 CSS、容器 class 和 TAC 视觉配置而失败。 + +### 任务 2:删除自定义验证码视觉层 + +**文件:** +- 修改:`login.html` +- 修改:`register.html` +- 修改:`forgot-password.html` +- 修改:`public/css/public.css` +- 删除:`public/css/captcha-modal.css` +- 修改:`public/js/captcha-pages.js` + +- [x] **步骤 1:删除三页对 `captcha-modal.css` 的引用,并将挂载点改为无 class 的 `data-captcha-box`。** + +- [x] **步骤 2:删除 `public.css` 中 `.auth-captcha-modal` 及其 TAC 尺寸限制规则。** + +- [x] **步骤 3:删除 `captcha-modal.css`。** + +- [x] **步骤 4:将 `new TAC(config, { ... })` 改为 `new TAC(config)`,并将挂载点查询、打开和关闭逻辑改为无视觉状态的 DOM 清理。** + +- [x] **步骤 5:运行两项聚焦测试并确认通过。** + +运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js` + +预期:全部通过。 + +### 任务 3:全量验证并记录 + +**文件:** +- 修改:`docs/pc-api-page-integration-tracker-2026-07-09.md` +- 修改:`docs/superpowers/plans/2026-07-10-tac-native-style-cleanup.md` + +- [x] **步骤 1:运行全量 Node 测试、验证码样式静态扫描和差异检查。** + +运行:`Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }; rg -n "captcha-modal.css|auth-captcha-modal|bgUrl|logoUrl" login.html register.html forgot-password.html public\css public\js; git diff --check` + +预期:测试与差异检查通过;扫描没有自定义验证码视觉标识。 + +- [x] **步骤 2:记录完成时间、修改范围、验证结果和外部验证码实测边界。** + +## 完成记录 + +- 完成时间:2026-07-10 10:32:20 +08:00。 +- 删除 `public/css/captcha-modal.css`,并从 `public/css/public.css` 删除 35 行 `.auth-captcha-box`、`.auth-captcha-modal` 和 TAC 尺寸限制规则。 +- 三个认证页仅加载 `public/tac/css/tac.css`,无 class 的 `[data-captcha-box]` 只作为 TAC 挂载点。 +- `captcha-pages.js` 现在只执行 `new TAC(config)`;不再传入 `bgUrl`、`logoUrl` 或切换任何视觉 class。成功、取消、初始化异常后仍清空 TAC 第三方 DOM。 +- 验证:聚焦结构/验证码测试、全量 Node 测试、`node --check public/js/captcha-pages.js`、自定义样式标识扫描和 `git diff --check` 均通过。 +- 外部边界:尚未用有效验证码轨迹完成真实服务端验证;`/captcha/verify` 的尺寸校验问题仍需以后端挑战响应原始尺寸为准单独处理。 diff --git a/download.html b/download.html index 88fb31e..e69b274 100644 --- a/download.html +++ b/download.html @@ -97,7 +97,11 @@ - + + + + + diff --git a/family-detail.html b/family-detail.html index 5a6c439..04d1990 100644 --- a/family-detail.html +++ b/family-detail.html @@ -131,7 +131,11 @@ - + + + + + diff --git a/forgot-password.html b/forgot-password.html index ef58688..12310bc 100644 --- a/forgot-password.html +++ b/forgot-password.html @@ -59,15 +59,14 @@ - -
- + + - + diff --git a/help.html b/help.html index 9874410..55f95c1 100644 --- a/help.html +++ b/help.html @@ -119,7 +119,11 @@ - + + + + + diff --git a/join-genealogy.html b/join-genealogy.html index 7ccd1b7..5b1e212 100644 --- a/join-genealogy.html +++ b/join-genealogy.html @@ -31,7 +31,11 @@ - + + + + + diff --git a/login.html b/login.html index 96ffcaa..6a41b1e 100644 --- a/login.html +++ b/login.html @@ -89,15 +89,14 @@ - -
- + + - + diff --git a/notice-detail.html b/notice-detail.html index 79be977..c812882 100644 --- a/notice-detail.html +++ b/notice-detail.html @@ -107,7 +107,11 @@ - + + + + + diff --git a/plaza.html b/plaza.html index 590cfdf..37fcf51 100644 --- a/plaza.html +++ b/plaza.html @@ -138,7 +138,11 @@ - + + + + + diff --git a/profile-admin-permissions.html b/profile-admin-permissions.html index 7a4d54e..1bc63a7 100644 --- a/profile-admin-permissions.html +++ b/profile-admin-permissions.html @@ -78,7 +78,11 @@ - + + + + + diff --git a/profile-album.html b/profile-album.html index 7ec4813..fbccbb0 100644 --- a/profile-album.html +++ b/profile-album.html @@ -133,8 +133,9 @@ - - + + + diff --git a/profile-article-edit.html b/profile-article-edit.html index 7dab71d..383635a 100644 --- a/profile-article-edit.html +++ b/profile-article-edit.html @@ -120,8 +120,9 @@ - - + + + diff --git a/profile-article.html b/profile-article.html index 9e473eb..f494f22 100644 --- a/profile-article.html +++ b/profile-article.html @@ -87,7 +87,11 @@ - + + + + + diff --git a/profile-content.html b/profile-content.html index 7d82355..458edba 100644 --- a/profile-content.html +++ b/profile-content.html @@ -119,7 +119,11 @@ - + + + + + diff --git a/profile-create-family.html b/profile-create-family.html index d31d71a..8ac4bab 100644 --- a/profile-create-family.html +++ b/profile-create-family.html @@ -127,8 +127,9 @@ - - + + + diff --git a/profile-data.html b/profile-data.html index 79f09a0..67c0685 100644 --- a/profile-data.html +++ b/profile-data.html @@ -202,8 +202,9 @@ - - + + + diff --git a/profile-families.html b/profile-families.html index 2bd6aec..c5d635c 100644 --- a/profile-families.html +++ b/profile-families.html @@ -102,7 +102,11 @@ - + + + + + diff --git a/profile-family-admin.html b/profile-family-admin.html index 165f3e2..c0b9888 100644 --- a/profile-family-admin.html +++ b/profile-family-admin.html @@ -112,7 +112,11 @@ - + + + + + diff --git a/profile-family-home.html b/profile-family-home.html index 0fef349..da75cd5 100644 --- a/profile-family-home.html +++ b/profile-family-home.html @@ -107,7 +107,11 @@ - + + + + + diff --git a/profile-feed-edit.html b/profile-feed-edit.html index 54d583d..1c9e56d 100644 --- a/profile-feed-edit.html +++ b/profile-feed-edit.html @@ -104,8 +104,9 @@ - - + + + diff --git a/profile-feed.html b/profile-feed.html index 8d890d3..fcfa5ee 100644 --- a/profile-feed.html +++ b/profile-feed.html @@ -101,7 +101,11 @@ - + + + + + diff --git a/profile-feedback.html b/profile-feedback.html index fcb25dd..d51da27 100644 --- a/profile-feedback.html +++ b/profile-feedback.html @@ -94,7 +94,11 @@ - + + + + + diff --git a/profile-generation.html b/profile-generation.html index fe165b1..f2d75d2 100644 --- a/profile-generation.html +++ b/profile-generation.html @@ -146,7 +146,11 @@ - + + + + + diff --git a/profile-gift-edit.html b/profile-gift-edit.html index ede430c..92c65cb 100644 --- a/profile-gift-edit.html +++ b/profile-gift-edit.html @@ -120,8 +120,9 @@ - - + + + diff --git a/profile-gift.html b/profile-gift.html index f1c8cd9..50315e1 100644 --- a/profile-gift.html +++ b/profile-gift.html @@ -91,7 +91,11 @@ - + + + + + diff --git a/profile-growth-edit.html b/profile-growth-edit.html index ddf5e69..9458ebf 100644 --- a/profile-growth-edit.html +++ b/profile-growth-edit.html @@ -121,8 +121,9 @@ - - + + + diff --git a/profile-growth.html b/profile-growth.html index 0f06d20..0cc8848 100644 --- a/profile-growth.html +++ b/profile-growth.html @@ -79,7 +79,11 @@ - + + + + + diff --git a/profile-invite.html b/profile-invite.html index b92c42b..c9299cd 100644 --- a/profile-invite.html +++ b/profile-invite.html @@ -67,7 +67,11 @@ - + + + + + diff --git a/profile-join-family.html b/profile-join-family.html index b456eab..5a4904d 100644 --- a/profile-join-family.html +++ b/profile-join-family.html @@ -70,7 +70,11 @@ - + + + + + diff --git a/profile-join-review.html b/profile-join-review.html index ce4c67e..6861c26 100644 --- a/profile-join-review.html +++ b/profile-join-review.html @@ -99,7 +99,11 @@ - + + + + + diff --git a/profile-memo-edit.html b/profile-memo-edit.html index 8a1ee91..336914b 100644 --- a/profile-memo-edit.html +++ b/profile-memo-edit.html @@ -109,8 +109,9 @@ - - + + + diff --git a/profile-memo.html b/profile-memo.html index 6ef8474..a781079 100644 --- a/profile-memo.html +++ b/profile-memo.html @@ -84,7 +84,11 @@ - + + + + + diff --git a/profile-merit-edit.html b/profile-merit-edit.html index cbcdac3..72c7e48 100644 --- a/profile-merit-edit.html +++ b/profile-merit-edit.html @@ -116,7 +116,11 @@ - + + + + + diff --git a/profile-merit.html b/profile-merit.html index ecb662d..553a16a 100644 --- a/profile-merit.html +++ b/profile-merit.html @@ -85,7 +85,11 @@ - + + + + + diff --git a/profile-messages.html b/profile-messages.html index f61d794..876c9fe 100644 --- a/profile-messages.html +++ b/profile-messages.html @@ -110,7 +110,11 @@ - + + + + + diff --git a/profile-security.html b/profile-security.html index 5b92280..e131862 100644 --- a/profile-security.html +++ b/profile-security.html @@ -150,8 +150,9 @@ - - + + + diff --git a/profile-services.html b/profile-services.html index 076a7a3..af2773c 100644 --- a/profile-services.html +++ b/profile-services.html @@ -159,7 +159,11 @@ - + + + + + diff --git a/profile-share.html b/profile-share.html index d60c259..8027277 100644 --- a/profile-share.html +++ b/profile-share.html @@ -82,7 +82,11 @@ - + + + + + diff --git a/profile-tree.html b/profile-tree.html index ae58883..11877d6 100644 --- a/profile-tree.html +++ b/profile-tree.html @@ -162,7 +162,11 @@ - + + + + + diff --git a/profile.html b/profile.html index 7403ee5..e396fe0 100644 --- a/profile.html +++ b/profile.html @@ -365,8 +365,9 @@ - - + + + diff --git a/promo-album.html b/promo-album.html index 7ef98d8..4af1d2d 100644 --- a/promo-album.html +++ b/promo-album.html @@ -93,7 +93,11 @@ - + + + + + diff --git a/public/css/public.css b/public/css/public.css index daa11fe..0e58463 100644 --- a/public/css/public.css +++ b/public/css/public.css @@ -591,41 +591,6 @@ textarea { transform: translateY(-1px); } -.auth-captcha-box { - /* 认证页滑动验证挂载点,TAC 内部 DOM 由第三方库生成 */ - display: flex; - justify-content: center; -} - -.auth-captcha-box:empty { - display: none; -} - -.auth-captcha-box #tianai-captcha-parent { - max-width: 100%; -} - -.auth-captcha-modal { - /* 认证页滑动验证弹窗挂载点,TAC 内部 DOM 由第三方库生成 */ - position: fixed; - inset: 0; - z-index: 1000; - display: grid; - place-items: center; - padding: 24px; - background: rgba(38, 48, 44, .32); - backdrop-filter: blur(4px); -} - -.auth-captcha-modal:empty { - display: none; -} - -.auth-captcha-modal #tianai-captcha-parent { - max-width: min(318px, calc(100vw - 32px)); - max-height: calc(100vh - 32px); -} - /* 验证码输入和按钮的通用双列布局,窄屏在页面 CSS 中改为单列 */ .code-row { display: grid; diff --git a/public/js/captcha-pages.js b/public/js/captcha-pages.js index ca78d4d..085fd61 100644 --- a/public/js/captcha-pages.js +++ b/public/js/captcha-pages.js @@ -10,6 +10,8 @@ 'use strict'; var DEFAULT_SCENE_CODE = 'WEB_H5_LOGIN'; + var CAPTCHA_LAYER_AREA = ['318px', '318px']; + var captchaLayerSeed = 0; function getApi() { return root.GenealogyApi && root.GenealogyApi.defaultClient; @@ -111,11 +113,44 @@ return query('input[name="validToken"]', form); } - function getCaptchaBox(form) { - // 优先使用页面级弹窗挂载点,避免 TAC 验证码挤占表单布局。 - return query('.auth-captcha-modal[data-captcha-box]') - || query('[data-captcha-box]', form) - || query('[data-captcha-box]'); + function createCaptchaLayer() { + // Layui 只负责提供默认弹层容器,验证码外观完全由 TAC 自带文件决定。 + if (!root.layui || !root.layui.layer || !root.layui.layer.open || !root.document) { + throw new Error('缺少 layui.layer 验证码弹层依赖'); + } + + var layerId = 'captcha-layer-' + (++captchaLayerSeed); + var selector = '[data-captcha-layer-box="' + layerId + '"]'; + var layerIndex = root.layui.layer.open({ + type: 1, + title: false, + closeBtn: 0, + // 尺寸来自 TAC 原生 #tianai-captcha-parent,提前交给 Layui 计算居中位置。 + area: CAPTCHA_LAYER_AREA, + offset: 'auto', + content: '
' + }); + var box = query(selector); + + if (!box) { + root.layui.layer.close(layerIndex); + throw new Error('缺少 Layui 验证码实际挂载点'); + } + + return { + box: box, + layerIndex: layerIndex + }; + } + + function closeCaptchaLayer(captchaLayer) { + // 组件结束后清空第三方 DOM 并关闭本次创建的 Layui 层。 + if (!captchaLayer) return; + + if (captchaLayer.box) captchaLayer.box.innerHTML = ''; + if (root.layui && root.layui.layer && root.layui.layer.close) { + root.layui.layer.close(captchaLayer.layerIndex); + } } function writeValidToken(form, token) { @@ -132,7 +167,8 @@ function createTac(form, options, api, resolve, reject) { var settings = options || {}; - var box = getCaptchaBox(form); + var captchaLayer; + var box; var context = buildChallengeBody(settings, api); var lastChallenge = {}; var captcha; @@ -143,8 +179,11 @@ return null; } - if (!box) { - reject(new Error('缺少滑动验证容器')); + try { + captchaLayer = createCaptchaLayer(); + box = captchaLayer.box; + } catch (error) { + reject(error); return null; } @@ -159,12 +198,15 @@ var token = extractValidToken(response); if (!token) { + if (captchaInstance && captchaInstance.destroyWindow) captchaInstance.destroyWindow(); + closeCaptchaLayer(captchaLayer); reject(new Error('滑动验证未返回有效票据')); return; } writeValidToken(form, token); if (captchaInstance && captchaInstance.destroyWindow) captchaInstance.destroyWindow(); + closeCaptchaLayer(captchaLayer); resolve(token); }, validFail: function (response, captchaControl, captchaInstance) { @@ -173,6 +215,7 @@ }, btnCloseFun: function (event, captchaInstance) { if (captchaInstance) captchaInstance.destroyWindow(); + closeCaptchaLayer(captchaLayer); reject(new Error('已取消滑动验证')); } }); @@ -213,11 +256,15 @@ } }); - captcha = new root.TAC(config, { - bgUrl: 'public/tac/images/dun.jpeg', - logoUrl: null - }); - captcha.init(); + try { + captcha = new root.TAC(config); + captcha.init(); + } catch (error) { + closeCaptchaLayer(captchaLayer); + reject(error); + return null; + } + return captcha; } @@ -252,7 +299,8 @@ buildChallengeBody: buildChallengeBody, adaptChallengeResponse: adaptChallengeResponse, buildVerifyBody: buildVerifyBody, - getCaptchaBox: getCaptchaBox, + createCaptchaLayer: createCaptchaLayer, + closeCaptchaLayer: closeCaptchaLayer, extractValidToken: extractValidToken, normalizeVerifyResponse: normalizeVerifyResponse, shouldRequireCaptcha: shouldRequireCaptcha, diff --git a/register.html b/register.html index e3a4385..14f9a6b 100644 --- a/register.html +++ b/register.html @@ -45,22 +45,21 @@ placeholder="设置密码" /> - + - -
- + + - + diff --git a/submit-ticket.html b/submit-ticket.html index a6f08a6..f2b4c61 100644 --- a/submit-ticket.html +++ b/submit-ticket.html @@ -105,7 +105,11 @@ - + + + + + diff --git a/tests/api-client.test.js b/tests/api-client.test.js index b7a2ca7..e22ef0b 100644 --- a/tests/api-client.test.js +++ b/tests/api-client.test.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const GenealogyApi = require('../public/js/api-client.js'); +const GenealogyApi = require('../utils/ApiClient.js'); function createStore(initial) { const values = Object.assign({}, initial); @@ -24,7 +24,8 @@ async function run() { 'token-key': 'abc-token' }); - assert.strictEqual(GenealogyApi.DEFAULT_BASE_URL, 'http://test-genealogy-api.ddxcjp.cn'); + // 接口基础地址只能由根目录 config.js 负责,公共客户端不能重复声明。 + assert.strictEqual(GenealogyApi.DEFAULT_BASE_URL, undefined); const client = GenealogyApi.createClient({ baseUrl: 'https://test-genealogy-api.ddxcjp.cn', @@ -33,31 +34,32 @@ async function run() { tokenKey: 'token-key', tokenHeaderName: 'Authorization', tokenStore: store, - fetchImpl: async (url, options) => { - calls.push({ url, options }); + axiosInstance: { + request: async (config) => { + calls.push(config); - return { - ok: true, - status: 200, - json: async () => ({ + return { + status: 200, + data: { code: 200, msg: '操作成功', - data: { token: 'server-token', url } - }) - }; + data: { token: 'server-token', url: config.url } + } + }; + } } }); await client.login({ phone: '13800000000', password: 'md5' }); - assert.strictEqual(calls[0].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/login'); - assert.strictEqual(calls[0].options.headers.clientid, 'client-x'); - assert.strictEqual(calls[0].options.headers.Authorization, undefined); - assert.strictEqual(JSON.parse(calls[0].options.body).tenantId, 'tenant-x'); + assert.strictEqual(calls[0].url, '/genealogy/pc/auth/login'); + assert.strictEqual(calls[0].headers.clientid, 'client-x'); + assert.strictEqual(calls[0].headers.Authorization, undefined); + assert.strictEqual(calls[0].data.tenantId, 'tenant-x'); assert.strictEqual(store.values['token-key'], 'server-token'); await client.getProfile(); - assert.strictEqual(calls[1].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile'); - assert.strictEqual(calls[1].options.headers.Authorization, 'Bearer server-token'); + assert.strictEqual(calls[1].url, '/genealogy/pc/auth/profile'); + assert.strictEqual(calls[1].headers.Authorization, 'Bearer server-token'); await client.updateProfile({ nickName: '张三', @@ -65,12 +67,12 @@ async function run() { cityCode: '510100', districtCode: '510104' }); - assert.strictEqual(calls[2].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile'); - assert.strictEqual(calls[2].options.method, 'PUT'); + assert.strictEqual(calls[2].url, '/genealogy/pc/auth/profile'); + assert.strictEqual(calls[2].method, 'put'); await client.uploadFile('file-content'); - assert.strictEqual(calls[3].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/files/upload'); - assert.strictEqual(calls[3].options.headers['Content-Type'], undefined); + assert.strictEqual(calls[3].url, '/genealogy/pc/files/upload'); + assert.strictEqual(calls[3].headers['Content-Type'], undefined); await client.initResumableUpload({ fileName: 'cover.jpg', @@ -79,10 +81,11 @@ async function run() { chunkSize: 4194304, totalChunks: 1 }); - assert.strictEqual(calls[4].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/files/resumable/init'); + assert.strictEqual(calls[4].url, '/genealogy/pc/files/resumable/init'); await client.getRegionChildren('51'); - assert.strictEqual(calls[5].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/region/children?parentCode=51'); + assert.strictEqual(calls[5].url, '/genealogy/region/children'); + assert.deepStrictEqual(calls[5].params, { parentCode: '51' }); await client.captchaRequire({ sceneCode: 'WEB_H5_LOGIN', @@ -90,31 +93,37 @@ async function run() { }); assert.strictEqual( calls[6].url, - 'https://test-genealogy-api.ddxcjp.cn/captcha/require?tenantId=tenant-x&clientId=client-x&sceneCode=WEB_H5_LOGIN&subject=13800000000' + '/captcha/require' ); - assert.strictEqual(calls[6].options.headers.Authorization, undefined); + assert.deepStrictEqual(calls[6].params, { + tenantId: 'tenant-x', + clientId: 'client-x', + sceneCode: 'WEB_H5_LOGIN', + subject: '13800000000' + }); + assert.strictEqual(calls[6].headers.Authorization, undefined); await client.loginBySms({ phone: '13800000000', smsCode: '6666', validToken: 'sms-ticket' }); - assert.strictEqual(calls[7].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/login/sms'); - assert.strictEqual(calls[7].options.headers.Authorization, undefined); - assert.strictEqual(JSON.parse(calls[7].options.body).grantType, 'sms'); - assert.strictEqual(JSON.parse(calls[7].options.body).tenantId, 'tenant-x'); - assert.strictEqual(JSON.parse(calls[7].options.body).clientId, 'client-x'); + assert.strictEqual(calls[7].url, '/genealogy/pc/auth/login/sms'); + assert.strictEqual(calls[7].headers.Authorization, undefined); + assert.strictEqual(calls[7].data.grantType, 'sms'); + assert.strictEqual(calls[7].data.tenantId, 'tenant-x'); + assert.strictEqual(calls[7].data.clientId, 'client-x'); await client.sendSmsCode({ sceneCode: 'WEB_H5_LOGIN', phone: '13800000000', validToken: 'captcha-ticket' }); - assert.strictEqual(calls[8].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/sms/code'); - assert.strictEqual(calls[8].options.headers.Authorization, undefined); - assert.strictEqual(JSON.parse(calls[8].options.body).grantType, 'sms'); - assert.strictEqual(JSON.parse(calls[8].options.body).sceneCode, 'WEB_H5_LOGIN'); - assert.strictEqual(JSON.parse(calls[8].options.body).validToken, 'captcha-ticket'); + assert.strictEqual(calls[8].url, '/genealogy/pc/auth/sms/code'); + assert.strictEqual(calls[8].headers.Authorization, undefined); + assert.strictEqual(calls[8].data.grantType, 'sms'); + assert.strictEqual(calls[8].data.sceneCode, 'WEB_H5_LOGIN'); + assert.strictEqual(calls[8].data.validToken, 'captcha-ticket'); assert.throws( () => client.listGenealogies(), diff --git a/tests/api-script-order.test.js b/tests/api-script-order.test.js new file mode 100644 index 0000000..33484a6 --- /dev/null +++ b/tests/api-script-order.test.js @@ -0,0 +1,45 @@ +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const rootDirectory = path.join(__dirname, '..'); + +function scriptIndex(html, source) { + return html.indexOf(`src="${source}"`); +} + +function run() { + const pages = fs.readdirSync(rootDirectory).filter((fileName) => { + if (!fileName.endsWith('.html')) return false; + + const html = fs.readFileSync(path.join(rootDirectory, fileName), 'utf8'); + return html.includes('public/js/api-client.js') || html.includes('utils/ApiClient.js'); + }); + + assert.strictEqual(pages.length, 45, '接口客户端加载页面数量应保持为 45 页'); + + pages.forEach((fileName) => { + const html = fs.readFileSync(path.join(rootDirectory, fileName), 'utf8'); + const configIndex = scriptIndex(html, 'config.js'); + const storageIndex = scriptIndex(html, 'utils/StorageUtil.js'); + const axiosIndex = scriptIndex(html, 'utils/axios.js'); + const requestIndex = scriptIndex(html, 'utils/AxiosRequestUtil.js'); + const apiIndex = scriptIndex(html, 'utils/ApiClient.js'); + + assert.notStrictEqual(configIndex, -1, `${fileName} 应加载环境配置`); + assert.notStrictEqual(storageIndex, -1, `${fileName} 应加载 token 存储工具`); + assert.notStrictEqual(axiosIndex, -1, `${fileName} 应加载本地 Axios`); + assert.notStrictEqual(requestIndex, -1, `${fileName} 应加载 AxiosRequestUtil`); + assert.notStrictEqual(apiIndex, -1, `${fileName} 应加载 ApiClient`); + assert.ok(configIndex < storageIndex, `${fileName} 应先加载环境配置再加载存储工具`); + assert.ok(storageIndex < axiosIndex, `${fileName} 应先加载存储工具再加载 Axios`); + assert.ok(axiosIndex < requestIndex, `${fileName} 应先加载 Axios 再加载请求封装`); + assert.ok(requestIndex < apiIndex, `${fileName} 应先加载请求封装再加载接口客户端`); + assert.strictEqual(html.includes('utils/RequestUtil.js'), false, `${fileName} 不应继续加载旧 RequestUtil`); + assert.strictEqual(html.includes('public/js/api-client.js'), false, `${fileName} 不应继续加载旧 ApiClient`); + }); + + console.log('api-script-order tests passed'); +} + +run(); diff --git a/tests/auth-page-structure.test.js b/tests/auth-page-structure.test.js index 16aeb10..726ba15 100644 --- a/tests/auth-page-structure.test.js +++ b/tests/auth-page-structure.test.js @@ -16,15 +16,18 @@ function run() { pages.forEach((fileName) => { const html = readPage(fileName); const formCaptchaBox = //i.test(html); - const pageModalBox = /class="[^"]*\bauth-captcha-modal\b[^"]*"[^>]*data-captcha-box/.test(html) - || /data-captcha-box[^>]*class="[^"]*\bauth-captcha-modal\b[^"]*"/.test(html); + const pageCaptchaBox = /data-captcha-box/.test(html); const hasLayuiCss = html.includes('href="public/layui/css/layui.css"'); + const hasTacCss = html.includes('href="public/tac/css/tac.css"'); + const hasCaptchaModalCss = html.includes('href="public/css/captcha-modal.css"'); const layuiScriptIndex = html.indexOf('src="public/layui/layui.js"'); const authScriptIndex = html.indexOf('src="public/js/auth-pages.js"'); assert.strictEqual(formCaptchaBox, false, `${fileName} 不应把 TAC 挂载点放在表单内部`); - assert.strictEqual(pageModalBox, true, `${fileName} 应提供页面级 TAC 弹窗挂载点`); + assert.strictEqual(pageCaptchaBox, false, `${fileName} 不应保留会将 TAC 渲染到页面底部的静态挂载点`); assert.strictEqual(hasLayuiCss, true, `${fileName} 应加载 layui.css 作为 layer 提示样式依赖`); + assert.strictEqual(hasTacCss, true, `${fileName} 应加载 TAC 自带样式`); + assert.strictEqual(hasCaptchaModalCss, false, `${fileName} 不应加载自定义验证码样式`); assert.notStrictEqual(layuiScriptIndex, -1, `${fileName} 应加载 layui.js 使用 layer.msg`); assert.ok(layuiScriptIndex < authScriptIndex, `${fileName} 应在 auth-pages.js 之前加载 layui.js`); }); diff --git a/tests/captcha-pages.test.js b/tests/captcha-pages.test.js index 67a64ed..93c8386 100644 --- a/tests/captcha-pages.test.js +++ b/tests/captcha-pages.test.js @@ -1,4 +1,6 @@ const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); const CaptchaPages = require('../public/js/captcha-pages.js'); const fakeApi = { @@ -7,6 +9,14 @@ const fakeApi = { }; function run() { + const captchaSource = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'captcha-pages.js'), 'utf8'); + + assert.strictEqual(captchaSource.includes('auth-captcha-modal'), false, '验证码适配层不应控制自定义弹窗样式'); + assert.strictEqual(captchaSource.includes('bgUrl:'), false, '验证码适配层不应覆盖 TAC 背景样式'); + assert.strictEqual(captchaSource.includes('logoUrl:'), false, '验证码适配层不应覆盖 TAC Logo 样式'); + assert.strictEqual(captchaSource.includes('root.layui.layer.open'), true, '验证码适配层应使用 Layui 默认弹层承载 TAC'); + assert.strictEqual(captchaSource.includes('root.layui.layer.close'), true, '验证码结束时应关闭对应 Layui 弹层'); + assert.deepStrictEqual(CaptchaPages.buildChallengeBody({ sceneCode: 'WEB_H5_LOGIN', subject: '13800000000' @@ -81,23 +91,49 @@ function run() { required: true }), true); - const modalBox = { nodeName: 'modal' }; - const formBox = { nodeName: 'form-box' }; const originalDocument = global.document; + const originalLayui = global.layui; + const mountedBox = { + innerHTML: '
' + }; + let layerOptions; + let closedLayerIndex; global.document = { querySelector(selector) { - return selector === '.auth-captcha-modal[data-captcha-box]' ? modalBox : null; + assert.strictEqual(selector, '[data-captcha-layer-box="captcha-layer-1"]'); + return mountedBox; + } + }; + global.layui = { + layer: { + open(options) { + layerOptions = options; + return 7; + }, + close(index) { + closedLayerIndex = index; + } } }; - assert.strictEqual(CaptchaPages.getCaptchaBox({ - querySelector(selector) { - return selector === '[data-captcha-box]' ? formBox : null; - } - }), modalBox); + const captchaLayer = CaptchaPages.createCaptchaLayer(); + assert.strictEqual(captchaLayer.box, mountedBox); + assert.strictEqual(captchaLayer.layerIndex, 7); + assert.strictEqual(layerOptions.type, 1); + assert.strictEqual(layerOptions.title, false); + assert.strictEqual(layerOptions.closeBtn, 0); + assert.deepStrictEqual(layerOptions.area, ['318px', '318px']); + assert.strictEqual(layerOptions.offset, 'auto'); + assert.strictEqual(typeof layerOptions.content, 'string'); + assert.strictEqual(layerOptions.content.includes('data-captcha-layer-box="captcha-layer-1"'), true); + + CaptchaPages.closeCaptchaLayer(captchaLayer); + assert.strictEqual(mountedBox.innerHTML, ''); + assert.strictEqual(closedLayerIndex, 7); global.document = originalDocument; + global.layui = originalLayui; console.log('captcha-pages tests passed'); } diff --git a/tests/config.test.js b/tests/config.test.js index 32719a3..2743312 100644 --- a/tests/config.test.js +++ b/tests/config.test.js @@ -1,26 +1,30 @@ const assert = require('assert'); const configFactory = require('../config.js'); -const store = {}; -const config = configFactory({ - localStorage: { - getItem: (key) => Object.prototype.hasOwnProperty.call(store, key) ? store[key] : null, - setItem: (key, value) => { - store[key] = String(value); - } +const developmentConfig = configFactory({ + location: { + hostname: '127.0.0.1', + port: '5501' } }); -assert.strictEqual(config.getApiBaseUrl(), 'http://test-genealogy-api.ddxcjp.cn'); -assert.strictEqual(config.getClientId(), 'ced7e5f0498645c6ec642dcf450b036f'); -assert.strictEqual(config.getTenantId(), '000000'); -assert.strictEqual(config.getTokenKey(), 'genealogy_auth_token'); -assert.strictEqual(config.getConfig().tokenHeaderName, 'Authorization'); +assert.deepStrictEqual(developmentConfig.getConfig(), { + environment: 'development', + apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn' +}); +assert.strictEqual(developmentConfig.getClientId, undefined); +assert.strictEqual(developmentConfig.getTenantId, undefined); +assert.strictEqual(developmentConfig.getTokenKey, undefined); +assert.strictEqual(developmentConfig.getTokenHeaderName, undefined); -store.genealogy_api_base_url = 'https://custom.example.test'; -assert.strictEqual(config.getApiBaseUrl(), 'https://custom.example.test'); +const productionConfig = configFactory({ + location: { + hostname: 'genealogy.example.test', + port: '' + } +}); -store.genealogy_api_base_url = 'https://test-genealogy-api.ddxcjp.cn'; -assert.strictEqual(config.getApiBaseUrl(), 'http://test-genealogy-api.ddxcjp.cn'); +assert.strictEqual(productionConfig.getEnvironment(), 'production'); +assert.strictEqual(productionConfig.getApiBaseUrl(), 'http://test-genealogy-api.ddxcjp.cn'); console.log('config tests passed'); diff --git a/tests/utils.test.js b/tests/utils.test.js index ee24aaf..7a2f692 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -1,7 +1,7 @@ const assert = require('assert'); const StorageUtil = require('../utils/StorageUtil.js'); const FormUtil = require('../utils/FormUtil.js'); -const RequestUtil = require('../utils/RequestUtil.js'); +const AxiosRequestUtil = require('../utils/AxiosRequestUtil.js'); assert.strictEqual(FormUtil.trimOrUndefined(' 张三 '), '张三'); assert.strictEqual(FormUtil.trimOrUndefined(' '), undefined); @@ -27,19 +27,20 @@ StorageUtil.remove(storage, 'token'); assert.strictEqual(StorageUtil.read(storage, 'token'), null); const calls = []; -const requester = RequestUtil.createRequester({ +const requester = AxiosRequestUtil.createRequester({ baseUrl: 'https://test-genealogy-api.ddxcjp.cn', clientId: 'pc-client', tokenHeaderName: 'Authorization', getToken: () => 'token-x', - fetchImpl: async (url, options) => { - calls.push({ url, options }); + axiosInstance: { + request: async (config) => { + calls.push(config); - return { - ok: true, - status: 200, - json: async () => ({ code: 200, data: { ok: true } }) - }; + return { + status: 200, + data: { code: 200, data: { ok: true } } + }; + } } }); @@ -47,8 +48,10 @@ requester('GET', '/genealogy/pc/auth/profile', { query: { keyword: '汤 氏' } }).then((data) => { assert.deepStrictEqual(data, { ok: true }); - assert.strictEqual(calls[0].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile?keyword=%E6%B1%A4+%E6%B0%8F'); - assert.strictEqual(calls[0].options.headers.clientid, 'pc-client'); - assert.strictEqual(calls[0].options.headers.Authorization, 'Bearer token-x'); + assert.strictEqual(calls[0].method, 'get'); + assert.strictEqual(calls[0].url, '/genealogy/pc/auth/profile'); + assert.deepStrictEqual(calls[0].params, { keyword: '汤 氏' }); + assert.strictEqual(calls[0].headers.clientid, 'pc-client'); + assert.strictEqual(calls[0].headers.Authorization, 'Bearer token-x'); console.log('utils tests passed'); }); diff --git a/public/js/api-client.js b/utils/ApiClient.js similarity index 66% rename from public/js/api-client.js rename to utils/ApiClient.js index 97c70f6..b18c597 100644 --- a/public/js/api-client.js +++ b/utils/ApiClient.js @@ -1,5 +1,5 @@ (function (root, factory) { - // 同一份接口客户端同时支持浏览器页面和 Node 测试环境。 + // PC 接口客户端同时支持浏览器页面和 Node 单元测试加载。 if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; @@ -9,7 +9,6 @@ })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; - var DEFAULT_BASE_URL = 'http://test-genealogy-api.ddxcjp.cn'; var DEFAULT_CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f'; var DEFAULT_TENANT_ID = '000000'; var DEFAULT_TOKEN_KEY = 'genealogy_auth_token'; @@ -26,20 +25,18 @@ } function getConfigApi() { - var configFactory; - if (root.GenealogyConfig) return root.GenealogyConfig; - configFactory = loadNodeModule('../../config.js'); + var configFactory = loadNodeModule('../config.js'); return configFactory ? configFactory(root) : null; } function getStorageUtil() { - return root.StorageUtil || loadNodeModule('../../utils/StorageUtil.js'); + return root.StorageUtil || loadNodeModule('./StorageUtil.js'); } - function getRequestUtil() { - return root.RequestUtil || loadNodeModule('../../utils/RequestUtil.js'); + function getAxiosRequestUtil() { + return root.AxiosRequestUtil || loadNodeModule('./AxiosRequestUtil.js'); } function getStorage(store) { @@ -53,7 +50,7 @@ } function getTokenFromResponse(data) { - // 登录响应可能使用不同 token 字段,这里统一收敛为本地登录票据。 + // 登录响应兼容后端已声明的多种 token 字段。 if (!data) return ''; return data.token || data.accessToken || data.tokenValue || ''; } @@ -79,12 +76,11 @@ function createFormData(fileOrFormData) { var FormDataCtor = root.FormData; - var formData; if (FormDataCtor && fileOrFormData instanceof FormDataCtor) return fileOrFormData; if (!FormDataCtor) throw new ApiError('当前环境不支持文件上传'); - formData = new FormDataCtor(); + var formData = new FormDataCtor(); formData.append('file', fileOrFormData); return formData; } @@ -92,12 +88,11 @@ function createChunkFormData(body) { var FormDataCtor = root.FormData; var source = body || {}; - var formData; if (FormDataCtor && body instanceof FormDataCtor) return body; if (!FormDataCtor) throw new ApiError('当前环境不支持文件上传'); - formData = new FormDataCtor(); + var formData = new FormDataCtor(); formData.append('uploadId', source.uploadId); formData.append('chunkIndex', source.chunkIndex); formData.append('chunkMd5', source.chunkMd5); @@ -105,22 +100,37 @@ return formData; } + function buildApiUrl(baseUrl, path, query) { + var url = String(baseUrl || '').replace(/\/+$/g, '') + '/' + String(path || '').replace(/^\/+/, ''); + var params = new URLSearchParams(); + + Object.keys(query || {}).forEach(function (key) { + var value = query[key]; + if (value !== undefined && value !== null && value !== '') params.append(key, value); + }); + + return params.toString() ? url + '?' + params.toString() : url; + } + function createClient(options) { var settings = options || {}; var configApi = getConfigApi(); var config = configApi && configApi.getConfig ? configApi.getConfig() : {}; var storageUtil = getStorageUtil(); - var requestUtil = getRequestUtil(); + var axiosRequestUtil = getAxiosRequestUtil(); var store = getStorage(settings.tokenStore || settings.storage); var clientId = settings.clientId || config.clientId || DEFAULT_CLIENT_ID; var tenantId = settings.tenantId || config.tenantId || DEFAULT_TENANT_ID; var tokenKey = settings.tokenKey || config.tokenKey || DEFAULT_TOKEN_KEY; var tokenHeaderName = settings.tokenHeaderName || config.tokenHeaderName || DEFAULT_TOKEN_HEADER_NAME; - var baseUrl = settings.baseUrl || config.apiBaseUrl || DEFAULT_BASE_URL; - var requester; + var baseUrl = settings.baseUrl || config.apiBaseUrl; - if (!requestUtil || !requestUtil.createRequester) { - throw new ApiError('缺少 RequestUtil.createRequester 公共请求工具'); + if (!axiosRequestUtil || !axiosRequestUtil.createRequester) { + throw new ApiError('缺少 AxiosRequestUtil.createRequester 公共请求工具'); + } + + if (!baseUrl) { + throw new ApiError('缺少接口基础地址,请检查根目录 config.js'); } function getToken() { @@ -135,12 +145,12 @@ if (storageUtil && storageUtil.remove) storageUtil.remove(store, tokenKey); } - requester = requestUtil.createRequester({ + var requester = axiosRequestUtil.createRequester({ baseUrl: baseUrl, clientId: clientId, tokenHeaderName: tokenHeaderName, getToken: getToken, - fetchImpl: settings.fetchImpl || root.fetch + axiosInstance: settings.axiosInstance || root.axios }); function request(method, path, requestOptions) { @@ -157,11 +167,8 @@ async function login(body) { var data = await request('POST', '/genealogy/pc/auth/login', { auth: false, - body: Object.assign({ - grantType: 'password' - }, withTenant(body)) + body: Object.assign({ grantType: 'password' }, withTenant(body)) }); - setToken(getTokenFromResponse(data)); return data; } @@ -169,12 +176,8 @@ async function register(body) { var data = await request('POST', '/genealogy/pc/auth/register', { auth: false, - body: Object.assign({ - grantType: 'password', - registerSource: 'PC' - }, withTenant(body)) + body: Object.assign({ grantType: 'password', registerSource: 'PC' }, withTenant(body)) }); - setToken(getTokenFromResponse(data)); return data; } @@ -182,11 +185,8 @@ async function loginBySms(body) { var data = await request('POST', '/genealogy/pc/auth/login/sms', { auth: false, - body: Object.assign({ - grantType: 'sms' - }, withTenant(body)) + body: Object.assign({ grantType: 'sms' }, withTenant(body)) }); - setToken(getTokenFromResponse(data)); return data; } @@ -194,9 +194,7 @@ function sendSmsCode(body) { return request('POST', '/genealogy/pc/auth/sms/code', { auth: false, - body: Object.assign({ - grantType: 'sms' - }, withTenant(body)) + body: Object.assign({ grantType: 'sms' }, withTenant(body)) }); } @@ -214,14 +212,6 @@ }); } - function buildApiUrl(path, query) { - if (requestUtil.joinUrl && requestUtil.appendQuery) { - return requestUtil.appendQuery(requestUtil.joinUrl(baseUrl, path), query); - } - - return baseUrl + path; - } - var client = { clientId: clientId, tenantId: tenantId, @@ -230,18 +220,20 @@ setToken: setToken, clearToken: clearToken, request: request, - buildApiUrl: buildApiUrl, + buildApiUrl: function (path, query) { + return buildApiUrl(baseUrl, path, query); + }, get: function (path, query) { return request('GET', path, { query: query }); }, - post: function (path, body, reqOptions) { - return request('POST', path, Object.assign({}, reqOptions || {}, { body: body })); + post: function (path, body, requestOptions) { + return request('POST', path, Object.assign({}, requestOptions || {}, { body: body })); }, - put: function (path, body, reqOptions) { - return request('PUT', path, Object.assign({}, reqOptions || {}, { body: body })); + put: function (path, body, requestOptions) { + return request('PUT', path, Object.assign({}, requestOptions || {}, { body: body })); }, - delete: function (path, reqOptions) { - return request('DELETE', path, reqOptions || {}); + delete: function (path, requestOptions) { + return request('DELETE', path, requestOptions || {}); }, login: login, register: register, @@ -263,9 +255,7 @@ resetPassword: function (body) { return request('PUT', '/genealogy/pc/auth/password/reset', { auth: false, - body: Object.assign({ - tenantId: tenantId - }, body || {}) + body: Object.assign({ tenantId: tenantId }, body || {}) }); }, changePhone: function (body) { @@ -273,28 +263,22 @@ }, deactivateAccount: async function (body) { var data = await request('POST', '/genealogy/pc/auth/account/deactivate', { body: body }); - clearToken(); return data; }, logout: async function () { var data = await request('DELETE', '/genealogy/pc/auth/logout'); - clearToken(); return data; }, uploadFile: function (fileOrFormData) { - return request('POST', '/genealogy/pc/files/upload', { - body: createFormData(fileOrFormData) - }); + return request('POST', '/genealogy/pc/files/upload', { body: createFormData(fileOrFormData) }); }, initResumableUpload: function (body) { return request('POST', '/genealogy/pc/files/resumable/init', { body: body }); }, uploadChunk: function (body) { - return request('POST', '/genealogy/pc/files/resumable/chunk', { - body: createChunkFormData(body) - }); + return request('POST', '/genealogy/pc/files/resumable/chunk', { body: createChunkFormData(body) }); }, uploadResumableChunk: function (body) { return this.uploadChunk(body); @@ -309,11 +293,7 @@ return request('DELETE', '/genealogy/pc/files/reference', { query: query }); }, getRegionChildren: function (parentCode) { - return request('GET', '/genealogy/region/children', { - query: { - parentCode: parentCode - } - }); + return request('GET', '/genealogy/region/children', { query: { parentCode: parentCode } }); }, regionChildren: function (parentCode) { return this.getRegionChildren(parentCode); @@ -349,87 +329,20 @@ } }; - // PC YAML 未提供这些业务 paths,保留方法名只用于阻止旧移动端路径继续发请求。 [ - 'listGenealogies', - 'createGenealogy', - 'publicGenealogies', - 'myGenealogies', - 'genealogyDetail', - 'genealogyOverview', - 'applyJoinGenealogy', - 'myJoinApplies', - 'pendingJoinApplies', - 'auditJoinApply', - 'cancelJoinApply', - 'genealogyMembers', - 'genealogyMemberOptions', - 'updateGenealogyMember', - 'removeGenealogyMember', - 'leaveGenealogy', - 'transferGenealogyOwner', - 'articleCategories', - 'articles', - 'articleDetail', - 'createArticle', - 'updateArticle', - 'feeds', - 'feedsPage', - 'feedDetail', - 'createFeed', - 'updateFeed', - 'likeFeed', - 'unlikeFeed', - 'feedComments', - 'feedCommentsPage', - 'createFeedComment', - 'deleteFeedComment', - 'albums', - 'createAlbum', - 'updateAlbum', - 'albumPhotos', - 'createAlbumPhoto', - 'ceremonies', - 'ceremonyDetail', - 'createCeremony', - 'updateCeremony', - 'ceremonyGifts', - 'createCeremonyGift', - 'meritRecords', - 'createMeritRecord', - 'growthRecords', - 'growthRecordDetail', - 'createGrowthRecord', - 'updateGrowthRecord', - 'memos', - 'memoDetail', - 'createMemo', - 'updateMemo', - 'vipPackages', - 'vipOrders', - 'createVipOrder', - 'generationPoems', - 'createGenerationPoem', - 'updateGenerationPoem', - 'previewGenerationPoems', - 'saveGenerationPoemsBatch', - 'lineageTree', - 'lineagePersons', - 'lineagePersonsPage', - 'lineagePersonOptions', - 'lineagePersonDetail', - 'createLineagePerson', - 'updateLineagePerson', - 'deleteLineagePerson', - 'addLineageRelation', - 'notifications', - 'markNotificationRead', - 'markAllNotificationsRead', - 'helpArticles', - 'helpArticleDetail', - 'promotions', - 'feedbackList', - 'submitFeedback' + 'listGenealogies', 'createGenealogy', 'publicGenealogies', 'myGenealogies', 'genealogyDetail', 'genealogyOverview', + 'applyJoinGenealogy', 'myJoinApplies', 'pendingJoinApplies', 'auditJoinApply', 'cancelJoinApply', 'genealogyMembers', + 'genealogyMemberOptions', 'updateGenealogyMember', 'removeGenealogyMember', 'leaveGenealogy', 'transferGenealogyOwner', + 'articleCategories', 'articles', 'articleDetail', 'createArticle', 'updateArticle', 'feeds', 'feedsPage', 'feedDetail', + 'createFeed', 'updateFeed', 'likeFeed', 'unlikeFeed', 'feedComments', 'feedCommentsPage', 'createFeedComment', + 'deleteFeedComment', 'albums', 'createAlbum', 'updateAlbum', 'albumPhotos', 'createAlbumPhoto', 'ceremonies', + 'ceremonyDetail', 'createCeremony', 'updateCeremony', 'ceremonyGifts', 'createCeremonyGift', 'meritRecords', + 'createMeritRecord', 'growthRecords', 'growthRecordDetail', 'createGrowthRecord', 'updateGrowthRecord', 'memos', + 'memoDetail', 'createMemo', 'updateMemo', 'vipPackages', 'vipOrders', 'createVipOrder', 'generationPoems', + 'createGenerationPoem', 'updateGenerationPoem', 'previewGenerationPoems', 'saveGenerationPoemsBatch', 'lineageTree', + 'lineagePersons', 'lineagePersonsPage', 'lineagePersonOptions', 'lineagePersonDetail', 'createLineagePerson', + 'updateLineagePerson', 'deleteLineagePerson', 'addLineageRelation', 'notifications', 'markNotificationRead', + 'markAllNotificationsRead', 'helpArticles', 'helpArticleDetail', 'promotions', 'feedbackList', 'submitFeedback' ].forEach(function (name) { client[name] = createUnavailableMethod(name); }); @@ -437,13 +350,21 @@ return client; } + function createDefaultClient() { + // Node 测试环境可无全局 Axios,浏览器页面在加载顺序正确时会创建默认客户端。 + try { + return createClient(); + } catch (error) { + return null; + } + } + return { - DEFAULT_BASE_URL: DEFAULT_BASE_URL, DEFAULT_CLIENT_ID: DEFAULT_CLIENT_ID, DEFAULT_TENANT_ID: DEFAULT_TENANT_ID, TOKEN_KEY: DEFAULT_TOKEN_KEY, ApiError: ApiError, createClient: createClient, - defaultClient: createClient() + defaultClient: createDefaultClient() }; }); diff --git a/utils/RequestUtil.js b/utils/AxiosRequestUtil.js similarity index 54% rename from utils/RequestUtil.js rename to utils/AxiosRequestUtil.js index 8faa813..da03fb3 100644 --- a/utils/RequestUtil.js +++ b/utils/AxiosRequestUtil.js @@ -1,62 +1,32 @@ (function (root, factory) { - // 请求工具封装 URL、Header 和响应解包,业务路径由 api-client 统一提供。 + // Axios 请求工具同时支持浏览器页面和 Node 单元测试加载。 if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } - root.RequestUtil = factory(root); + root.AxiosRequestUtil = factory(root); })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; - function trimSlashes(value) { - return String(value || '').replace(/^\/+|\/+$/g, ''); - } - - function joinUrl(baseUrl, path) { - var base = String(baseUrl || '').replace(/\/+$/g, ''); - var cleanPath = '/' + trimSlashes(path); - return base + cleanPath; - } - - function appendQuery(url, query) { - if (!query) return url; - - var params = new URLSearchParams(); - Object.keys(query).forEach(function (key) { - var value = query[key]; - if (value === undefined || value === null || value === '') return; - params.append(key, value); - }); - - var queryText = params.toString(); - return queryText ? url + '?' + queryText : url; - } - function isFormData(body) { return typeof FormData !== 'undefined' && body instanceof FormData; } function createHttpError(message, detail) { var error = new Error(message); + Object.keys(detail || {}).forEach(function (key) { error[key] = detail[key]; }); - return error; - } - async function parseJson(response) { - try { - return await response.json(); - } catch (error) { - return null; - } + return error; } function unwrapResponse(payload) { if (!payload || typeof payload !== 'object') return payload; - if (payload.code !== undefined && payload.code !== 200) { + if (payload.code !== undefined && Number(payload.code) !== 200) { throw createHttpError(payload.msg || '接口请求失败', { code: payload.code, response: payload @@ -74,47 +44,66 @@ return payload; } + function getAxiosInstance(options) { + var settings = options || {}; + var axios = settings.axiosInstance || root.axios; + + if (!axios || typeof axios.request !== 'function' && typeof axios.create !== 'function') { + throw new Error('缺少 Axios 公共依赖'); + } + + if (typeof axios.create === 'function') { + return axios.create({ + baseURL: settings.baseUrl + }); + } + + return axios; + } + function createRequester(options) { var settings = options || {}; - var fetchImpl = settings.fetchImpl || root.fetch; + var axiosInstance = getAxiosInstance(settings); return async function request(method, path, requestOptions) { var req = requestOptions || {}; var headers = Object.assign({}, req.headers || {}); var token = settings.getToken ? settings.getToken() : ''; var tokenHeaderName = settings.tokenHeaderName || 'Authorization'; - var url = appendQuery(joinUrl(settings.baseUrl, path), req.query); var body = req.body; + var response; headers.clientid = settings.clientId; if (token && req.auth !== false) headers[tokenHeaderName] = 'Bearer ' + token; if (body !== undefined && body !== null && !isFormData(body)) { headers['Content-Type'] = headers['Content-Type'] || 'application/json'; - body = JSON.stringify(body); } - var response = await fetchImpl(url, { - method: method, - headers: headers, - body: body - }); - var payload = await parseJson(response); + try { + response = await axiosInstance.request({ + method: String(method || 'GET').toLowerCase(), + url: path, + params: req.query, + data: body, + headers: headers + }); + } catch (error) { + var responseData = error.response && error.response.data; - if (!response.ok) { - throw createHttpError((payload && payload.msg) || '接口请求失败', { - status: response.status, - response: payload + throw createHttpError((responseData && responseData.msg) || error.message || '接口请求失败', { + status: error.response && error.response.status, + response: responseData, + cause: error }); } - return unwrapResponse(payload); + return unwrapResponse(response.data); }; } return { createRequester: createRequester, - joinUrl: joinUrl, - appendQuery: appendQuery + unwrapResponse: unwrapResponse }; }); diff --git a/utils/axios.js b/utils/axios.js new file mode 100644 index 0000000..1fe9c64 --- /dev/null +++ b/utils/axios.js @@ -0,0 +1,4288 @@ +// Axios v1.7.9 Copyright (c) 2024 Matt Zabriskie and contributors +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.axios = factory()); +})(this, (function () { 'use strict'; + + function _AsyncGenerator(e) { + var r, t; + function resume(r, t) { + try { + var n = e[r](t), + o = n.value, + u = o instanceof _OverloadYield; + Promise.resolve(u ? o.v : o).then(function (t) { + if (u) { + var i = "return" === r ? "return" : "next"; + if (!o.k || t.done) return resume(i, t); + t = e[i](t).value; + } + settle(n.done ? "return" : "normal", t); + }, function (e) { + resume("throw", e); + }); + } catch (e) { + settle("throw", e); + } + } + function settle(e, n) { + switch (e) { + case "return": + r.resolve({ + value: n, + done: !0 + }); + break; + case "throw": + r.reject(n); + break; + default: + r.resolve({ + value: n, + done: !1 + }); + } + (r = r.next) ? resume(r.key, r.arg) : t = null; + } + this._invoke = function (e, n) { + return new Promise(function (o, u) { + var i = { + key: e, + arg: n, + resolve: o, + reject: u, + next: null + }; + t ? t = t.next = i : (r = t = i, resume(e, n)); + }); + }, "function" != typeof e.return && (this.return = void 0); + } + _AsyncGenerator.prototype["function" == typeof Symbol && Symbol.asyncIterator || "@@asyncIterator"] = function () { + return this; + }, _AsyncGenerator.prototype.next = function (e) { + return this._invoke("next", e); + }, _AsyncGenerator.prototype.throw = function (e) { + return this._invoke("throw", e); + }, _AsyncGenerator.prototype.return = function (e) { + return this._invoke("return", e); + }; + function _OverloadYield(t, e) { + this.v = t, this.k = e; + } + function _asyncGeneratorDelegate(t) { + var e = {}, + n = !1; + function pump(e, r) { + return n = !0, r = new Promise(function (n) { + n(t[e](r)); + }), { + done: !1, + value: new _OverloadYield(r, 1) + }; + } + return e["undefined" != typeof Symbol && Symbol.iterator || "@@iterator"] = function () { + return this; + }, e.next = function (t) { + return n ? (n = !1, t) : pump("next", t); + }, "function" == typeof t.throw && (e.throw = function (t) { + if (n) throw n = !1, t; + return pump("throw", t); + }), "function" == typeof t.return && (e.return = function (t) { + return n ? (n = !1, t) : pump("return", t); + }), e; + } + function _asyncIterator(r) { + var n, + t, + o, + e = 2; + for ("undefined" != typeof Symbol && (t = Symbol.asyncIterator, o = Symbol.iterator); e--;) { + if (t && null != (n = r[t])) return n.call(r); + if (o && null != (n = r[o])) return new AsyncFromSyncIterator(n.call(r)); + t = "@@asyncIterator", o = "@@iterator"; + } + throw new TypeError("Object is not async iterable"); + } + function AsyncFromSyncIterator(r) { + function AsyncFromSyncIteratorContinuation(r) { + if (Object(r) !== r) return Promise.reject(new TypeError(r + " is not an object.")); + var n = r.done; + return Promise.resolve(r.value).then(function (r) { + return { + value: r, + done: n + }; + }); + } + return AsyncFromSyncIterator = function (r) { + this.s = r, this.n = r.next; + }, AsyncFromSyncIterator.prototype = { + s: null, + n: null, + next: function () { + return AsyncFromSyncIteratorContinuation(this.n.apply(this.s, arguments)); + }, + return: function (r) { + var n = this.s.return; + return void 0 === n ? Promise.resolve({ + value: r, + done: !0 + }) : AsyncFromSyncIteratorContinuation(n.apply(this.s, arguments)); + }, + throw: function (r) { + var n = this.s.return; + return void 0 === n ? Promise.reject(r) : AsyncFromSyncIteratorContinuation(n.apply(this.s, arguments)); + } + }, new AsyncFromSyncIterator(r); + } + function _awaitAsyncGenerator(e) { + return new _OverloadYield(e, 0); + } + function _iterableToArrayLimit(r, l) { + var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; + if (null != t) { + var e, + n, + i, + u, + a = [], + f = !0, + o = !1; + try { + if (i = (t = t.call(r)).next, 0 === l) { + if (Object(t) !== t) return; + f = !1; + } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); + } catch (r) { + o = !0, n = r; + } finally { + try { + if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; + } finally { + if (o) throw n; + } + } + return a; + } + } + function ownKeys(e, r) { + var t = Object.keys(e); + if (Object.getOwnPropertySymbols) { + var o = Object.getOwnPropertySymbols(e); + r && (o = o.filter(function (r) { + return Object.getOwnPropertyDescriptor(e, r).enumerable; + })), t.push.apply(t, o); + } + return t; + } + function _objectSpread2(e) { + for (var r = 1; r < arguments.length; r++) { + var t = null != arguments[r] ? arguments[r] : {}; + r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { + _defineProperty(e, r, t[r]); + }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { + Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); + }); + } + return e; + } + function _regeneratorRuntime() { + _regeneratorRuntime = function () { + return e; + }; + var t, + e = {}, + r = Object.prototype, + n = r.hasOwnProperty, + o = Object.defineProperty || function (t, e, r) { + t[e] = r.value; + }, + i = "function" == typeof Symbol ? Symbol : {}, + a = i.iterator || "@@iterator", + c = i.asyncIterator || "@@asyncIterator", + u = i.toStringTag || "@@toStringTag"; + function define(t, e, r) { + return Object.defineProperty(t, e, { + value: r, + enumerable: !0, + configurable: !0, + writable: !0 + }), t[e]; + } + try { + define({}, ""); + } catch (t) { + define = function (t, e, r) { + return t[e] = r; + }; + } + function wrap(t, e, r, n) { + var i = e && e.prototype instanceof Generator ? e : Generator, + a = Object.create(i.prototype), + c = new Context(n || []); + return o(a, "_invoke", { + value: makeInvokeMethod(t, r, c) + }), a; + } + function tryCatch(t, e, r) { + try { + return { + type: "normal", + arg: t.call(e, r) + }; + } catch (t) { + return { + type: "throw", + arg: t + }; + } + } + e.wrap = wrap; + var h = "suspendedStart", + l = "suspendedYield", + f = "executing", + s = "completed", + y = {}; + function Generator() {} + function GeneratorFunction() {} + function GeneratorFunctionPrototype() {} + var p = {}; + define(p, a, function () { + return this; + }); + var d = Object.getPrototypeOf, + v = d && d(d(values([]))); + v && v !== r && n.call(v, a) && (p = v); + var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); + function defineIteratorMethods(t) { + ["next", "throw", "return"].forEach(function (e) { + define(t, e, function (t) { + return this._invoke(e, t); + }); + }); + } + function AsyncIterator(t, e) { + function invoke(r, o, i, a) { + var c = tryCatch(t[r], t, o); + if ("throw" !== c.type) { + var u = c.arg, + h = u.value; + return h && "object" == typeof h && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { + invoke("next", t, i, a); + }, function (t) { + invoke("throw", t, i, a); + }) : e.resolve(h).then(function (t) { + u.value = t, i(u); + }, function (t) { + return invoke("throw", t, i, a); + }); + } + a(c.arg); + } + var r; + o(this, "_invoke", { + value: function (t, n) { + function callInvokeWithMethodAndArg() { + return new e(function (e, r) { + invoke(t, n, e, r); + }); + } + return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); + } + }); + } + function makeInvokeMethod(e, r, n) { + var o = h; + return function (i, a) { + if (o === f) throw new Error("Generator is already running"); + if (o === s) { + if ("throw" === i) throw a; + return { + value: t, + done: !0 + }; + } + for (n.method = i, n.arg = a;;) { + var c = n.delegate; + if (c) { + var u = maybeInvokeDelegate(c, n); + if (u) { + if (u === y) continue; + return u; + } + } + if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { + if (o === h) throw o = s, n.arg; + n.dispatchException(n.arg); + } else "return" === n.method && n.abrupt("return", n.arg); + o = f; + var p = tryCatch(e, r, n); + if ("normal" === p.type) { + if (o = n.done ? s : l, p.arg === y) continue; + return { + value: p.arg, + done: n.done + }; + } + "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); + } + }; + } + function maybeInvokeDelegate(e, r) { + var n = r.method, + o = e.iterator[n]; + if (o === t) return r.delegate = null, "throw" === n && e.iterator.return && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; + var i = tryCatch(o, e.iterator, r.arg); + if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; + var a = i.arg; + return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); + } + function pushTryEntry(t) { + var e = { + tryLoc: t[0] + }; + 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); + } + function resetTryEntry(t) { + var e = t.completion || {}; + e.type = "normal", delete e.arg, t.completion = e; + } + function Context(t) { + this.tryEntries = [{ + tryLoc: "root" + }], t.forEach(pushTryEntry, this), this.reset(!0); + } + function values(e) { + if (e || "" === e) { + var r = e[a]; + if (r) return r.call(e); + if ("function" == typeof e.next) return e; + if (!isNaN(e.length)) { + var o = -1, + i = function next() { + for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; + return next.value = t, next.done = !0, next; + }; + return i.next = i; + } + } + throw new TypeError(typeof e + " is not iterable"); + } + return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { + value: GeneratorFunctionPrototype, + configurable: !0 + }), o(GeneratorFunctionPrototype, "constructor", { + value: GeneratorFunction, + configurable: !0 + }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { + var e = "function" == typeof t && t.constructor; + return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); + }, e.mark = function (t) { + return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; + }, e.awrap = function (t) { + return { + __await: t + }; + }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { + return this; + }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { + void 0 === i && (i = Promise); + var a = new AsyncIterator(wrap(t, r, n, o), i); + return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { + return t.done ? t.value : a.next(); + }); + }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { + return this; + }), define(g, "toString", function () { + return "[object Generator]"; + }), e.keys = function (t) { + var e = Object(t), + r = []; + for (var n in e) r.push(n); + return r.reverse(), function next() { + for (; r.length;) { + var t = r.pop(); + if (t in e) return next.value = t, next.done = !1, next; + } + return next.done = !0, next; + }; + }, e.values = values, Context.prototype = { + constructor: Context, + reset: function (e) { + if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); + }, + stop: function () { + this.done = !0; + var t = this.tryEntries[0].completion; + if ("throw" === t.type) throw t.arg; + return this.rval; + }, + dispatchException: function (e) { + if (this.done) throw e; + var r = this; + function handle(n, o) { + return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; + } + for (var o = this.tryEntries.length - 1; o >= 0; --o) { + var i = this.tryEntries[o], + a = i.completion; + if ("root" === i.tryLoc) return handle("end"); + if (i.tryLoc <= this.prev) { + var c = n.call(i, "catchLoc"), + u = n.call(i, "finallyLoc"); + if (c && u) { + if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); + if (this.prev < i.finallyLoc) return handle(i.finallyLoc); + } else if (c) { + if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); + } else { + if (!u) throw new Error("try statement without catch or finally"); + if (this.prev < i.finallyLoc) return handle(i.finallyLoc); + } + } + } + }, + abrupt: function (t, e) { + for (var r = this.tryEntries.length - 1; r >= 0; --r) { + var o = this.tryEntries[r]; + if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { + var i = o; + break; + } + } + i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); + var a = i ? i.completion : {}; + return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); + }, + complete: function (t, e) { + if ("throw" === t.type) throw t.arg; + return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; + }, + finish: function (t) { + for (var e = this.tryEntries.length - 1; e >= 0; --e) { + var r = this.tryEntries[e]; + if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; + } + }, + catch: function (t) { + for (var e = this.tryEntries.length - 1; e >= 0; --e) { + var r = this.tryEntries[e]; + if (r.tryLoc === t) { + var n = r.completion; + if ("throw" === n.type) { + var o = n.arg; + resetTryEntry(r); + } + return o; + } + } + throw new Error("illegal catch attempt"); + }, + delegateYield: function (e, r, n) { + return this.delegate = { + iterator: values(e), + resultName: r, + nextLoc: n + }, "next" === this.method && (this.arg = t), y; + } + }, e; + } + function _toPrimitive(t, r) { + if ("object" != typeof t || !t) return t; + var e = t[Symbol.toPrimitive]; + if (void 0 !== e) { + var i = e.call(t, r || "default"); + if ("object" != typeof i) return i; + throw new TypeError("@@toPrimitive must return a primitive value."); + } + return ("string" === r ? String : Number)(t); + } + function _toPropertyKey(t) { + var i = _toPrimitive(t, "string"); + return "symbol" == typeof i ? i : String(i); + } + function _typeof(o) { + "@babel/helpers - typeof"; + + return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { + return typeof o; + } : function (o) { + return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; + }, _typeof(o); + } + function _wrapAsyncGenerator(fn) { + return function () { + return new _AsyncGenerator(fn.apply(this, arguments)); + }; + } + function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } + if (info.done) { + resolve(value); + } else { + Promise.resolve(value).then(_next, _throw); + } + } + function _asyncToGenerator(fn) { + return function () { + var self = this, + args = arguments; + return new Promise(function (resolve, reject) { + var gen = fn.apply(self, args); + function _next(value) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); + } + function _throw(err) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); + } + _next(undefined); + }); + }; + } + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); + } + } + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + Object.defineProperty(Constructor, "prototype", { + writable: false + }); + return Constructor; + } + function _defineProperty(obj, key, value) { + key = _toPropertyKey(key); + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; + } + function _slicedToArray(arr, i) { + return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); + } + function _toArray(arr) { + return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest(); + } + function _toConsumableArray(arr) { + return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); + } + function _arrayWithoutHoles(arr) { + if (Array.isArray(arr)) return _arrayLikeToArray(arr); + } + function _arrayWithHoles(arr) { + if (Array.isArray(arr)) return arr; + } + function _iterableToArray(iter) { + if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); + } + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); + } + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; + return arr2; + } + function _nonIterableSpread() { + throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + function _createForOfIteratorHelper(o, allowArrayLike) { + var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; + if (!it) { + if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { + if (it) o = it; + var i = 0; + var F = function () {}; + return { + s: F, + n: function () { + if (i >= o.length) return { + done: true + }; + return { + done: false, + value: o[i++] + }; + }, + e: function (e) { + throw e; + }, + f: F + }; + } + throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + var normalCompletion = true, + didErr = false, + err; + return { + s: function () { + it = it.call(o); + }, + n: function () { + var step = it.next(); + normalCompletion = step.done; + return step; + }, + e: function (e) { + didErr = true; + err = e; + }, + f: function () { + try { + if (!normalCompletion && it.return != null) it.return(); + } finally { + if (didErr) throw err; + } + } + }; + } + + function bind(fn, thisArg) { + return function wrap() { + return fn.apply(thisArg, arguments); + }; + } + + // utils is a library of generic helper functions non-specific to axios + + var toString = Object.prototype.toString; + var getPrototypeOf = Object.getPrototypeOf; + var kindOf = function (cache) { + return function (thing) { + var str = toString.call(thing); + return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase()); + }; + }(Object.create(null)); + var kindOfTest = function kindOfTest(type) { + type = type.toLowerCase(); + return function (thing) { + return kindOf(thing) === type; + }; + }; + var typeOfTest = function typeOfTest(type) { + return function (thing) { + return _typeof(thing) === type; + }; + }; + + /** + * Determine if a value is an Array + * + * @param {Object} val The value to test + * + * @returns {boolean} True if value is an Array, otherwise false + */ + var isArray = Array.isArray; + + /** + * Determine if a value is undefined + * + * @param {*} val The value to test + * + * @returns {boolean} True if the value is undefined, otherwise false + */ + var isUndefined = typeOfTest('undefined'); + + /** + * Determine if a value is a Buffer + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a Buffer, otherwise false + */ + function isBuffer(val) { + return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor) && isFunction(val.constructor.isBuffer) && val.constructor.isBuffer(val); + } + + /** + * Determine if a value is an ArrayBuffer + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is an ArrayBuffer, otherwise false + */ + var isArrayBuffer = kindOfTest('ArrayBuffer'); + + /** + * Determine if a value is a view on an ArrayBuffer + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false + */ + function isArrayBufferView(val) { + var result; + if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView) { + result = ArrayBuffer.isView(val); + } else { + result = val && val.buffer && isArrayBuffer(val.buffer); + } + return result; + } + + /** + * Determine if a value is a String + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a String, otherwise false + */ + var isString = typeOfTest('string'); + + /** + * Determine if a value is a Function + * + * @param {*} val The value to test + * @returns {boolean} True if value is a Function, otherwise false + */ + var isFunction = typeOfTest('function'); + + /** + * Determine if a value is a Number + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a Number, otherwise false + */ + var isNumber = typeOfTest('number'); + + /** + * Determine if a value is an Object + * + * @param {*} thing The value to test + * + * @returns {boolean} True if value is an Object, otherwise false + */ + var isObject = function isObject(thing) { + return thing !== null && _typeof(thing) === 'object'; + }; + + /** + * Determine if a value is a Boolean + * + * @param {*} thing The value to test + * @returns {boolean} True if value is a Boolean, otherwise false + */ + var isBoolean = function isBoolean(thing) { + return thing === true || thing === false; + }; + + /** + * Determine if a value is a plain Object + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a plain Object, otherwise false + */ + var isPlainObject = function isPlainObject(val) { + if (kindOf(val) !== 'object') { + return false; + } + var prototype = getPrototypeOf(val); + return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in val) && !(Symbol.iterator in val); + }; + + /** + * Determine if a value is a Date + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a Date, otherwise false + */ + var isDate = kindOfTest('Date'); + + /** + * Determine if a value is a File + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a File, otherwise false + */ + var isFile = kindOfTest('File'); + + /** + * Determine if a value is a Blob + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a Blob, otherwise false + */ + var isBlob = kindOfTest('Blob'); + + /** + * Determine if a value is a FileList + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a File, otherwise false + */ + var isFileList = kindOfTest('FileList'); + + /** + * Determine if a value is a Stream + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a Stream, otherwise false + */ + var isStream = function isStream(val) { + return isObject(val) && isFunction(val.pipe); + }; + + /** + * Determine if a value is a FormData + * + * @param {*} thing The value to test + * + * @returns {boolean} True if value is an FormData, otherwise false + */ + var isFormData = function isFormData(thing) { + var kind; + return thing && (typeof FormData === 'function' && thing instanceof FormData || isFunction(thing.append) && ((kind = kindOf(thing)) === 'formdata' || + // detect form-data instance + kind === 'object' && isFunction(thing.toString) && thing.toString() === '[object FormData]')); + }; + + /** + * Determine if a value is a URLSearchParams object + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a URLSearchParams object, otherwise false + */ + var isURLSearchParams = kindOfTest('URLSearchParams'); + var _map = ['ReadableStream', 'Request', 'Response', 'Headers'].map(kindOfTest), + _map2 = _slicedToArray(_map, 4), + isReadableStream = _map2[0], + isRequest = _map2[1], + isResponse = _map2[2], + isHeaders = _map2[3]; + + /** + * Trim excess whitespace off the beginning and end of a string + * + * @param {String} str The String to trim + * + * @returns {String} The String freed of excess whitespace + */ + var trim = function trim(str) { + return str.trim ? str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + }; + + /** + * Iterate over an Array or an Object invoking a function for each item. + * + * If `obj` is an Array callback will be called passing + * the value, index, and complete array for each item. + * + * If 'obj' is an Object callback will be called passing + * the value, key, and complete object for each property. + * + * @param {Object|Array} obj The object to iterate + * @param {Function} fn The callback to invoke for each item + * + * @param {Boolean} [allOwnKeys = false] + * @returns {any} + */ + function forEach(obj, fn) { + var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, + _ref$allOwnKeys = _ref.allOwnKeys, + allOwnKeys = _ref$allOwnKeys === void 0 ? false : _ref$allOwnKeys; + // Don't bother if no value provided + if (obj === null || typeof obj === 'undefined') { + return; + } + var i; + var l; + + // Force an array if not already something iterable + if (_typeof(obj) !== 'object') { + /*eslint no-param-reassign:0*/ + obj = [obj]; + } + if (isArray(obj)) { + // Iterate over array values + for (i = 0, l = obj.length; i < l; i++) { + fn.call(null, obj[i], i, obj); + } + } else { + // Iterate over object keys + var keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj); + var len = keys.length; + var key; + for (i = 0; i < len; i++) { + key = keys[i]; + fn.call(null, obj[key], key, obj); + } + } + } + function findKey(obj, key) { + key = key.toLowerCase(); + var keys = Object.keys(obj); + var i = keys.length; + var _key; + while (i-- > 0) { + _key = keys[i]; + if (key === _key.toLowerCase()) { + return _key; + } + } + return null; + } + var _global = function () { + /*eslint no-undef:0*/ + if (typeof globalThis !== "undefined") return globalThis; + return typeof self !== "undefined" ? self : typeof window !== 'undefined' ? window : global; + }(); + var isContextDefined = function isContextDefined(context) { + return !isUndefined(context) && context !== _global; + }; + + /** + * Accepts varargs expecting each argument to be an object, then + * immutably merges the properties of each object and returns result. + * + * When multiple objects contain the same key the later object in + * the arguments list will take precedence. + * + * Example: + * + * ```js + * var result = merge({foo: 123}, {foo: 456}); + * console.log(result.foo); // outputs 456 + * ``` + * + * @param {Object} obj1 Object to merge + * + * @returns {Object} Result of all merge properties + */ + function merge( /* obj1, obj2, obj3, ... */ + ) { + var _ref2 = isContextDefined(this) && this || {}, + caseless = _ref2.caseless; + var result = {}; + var assignValue = function assignValue(val, key) { + var targetKey = caseless && findKey(result, key) || key; + if (isPlainObject(result[targetKey]) && isPlainObject(val)) { + result[targetKey] = merge(result[targetKey], val); + } else if (isPlainObject(val)) { + result[targetKey] = merge({}, val); + } else if (isArray(val)) { + result[targetKey] = val.slice(); + } else { + result[targetKey] = val; + } + }; + for (var i = 0, l = arguments.length; i < l; i++) { + arguments[i] && forEach(arguments[i], assignValue); + } + return result; + } + + /** + * Extends object a by mutably adding to it the properties of object b. + * + * @param {Object} a The object to be extended + * @param {Object} b The object to copy properties from + * @param {Object} thisArg The object to bind function to + * + * @param {Boolean} [allOwnKeys] + * @returns {Object} The resulting value of object a + */ + var extend = function extend(a, b, thisArg) { + var _ref3 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}, + allOwnKeys = _ref3.allOwnKeys; + forEach(b, function (val, key) { + if (thisArg && isFunction(val)) { + a[key] = bind(val, thisArg); + } else { + a[key] = val; + } + }, { + allOwnKeys: allOwnKeys + }); + return a; + }; + + /** + * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) + * + * @param {string} content with BOM + * + * @returns {string} content value without BOM + */ + var stripBOM = function stripBOM(content) { + if (content.charCodeAt(0) === 0xFEFF) { + content = content.slice(1); + } + return content; + }; + + /** + * Inherit the prototype methods from one constructor into another + * @param {function} constructor + * @param {function} superConstructor + * @param {object} [props] + * @param {object} [descriptors] + * + * @returns {void} + */ + var inherits = function inherits(constructor, superConstructor, props, descriptors) { + constructor.prototype = Object.create(superConstructor.prototype, descriptors); + constructor.prototype.constructor = constructor; + Object.defineProperty(constructor, 'super', { + value: superConstructor.prototype + }); + props && Object.assign(constructor.prototype, props); + }; + + /** + * Resolve object with deep prototype chain to a flat object + * @param {Object} sourceObj source object + * @param {Object} [destObj] + * @param {Function|Boolean} [filter] + * @param {Function} [propFilter] + * + * @returns {Object} + */ + var toFlatObject = function toFlatObject(sourceObj, destObj, filter, propFilter) { + var props; + var i; + var prop; + var merged = {}; + destObj = destObj || {}; + // eslint-disable-next-line no-eq-null,eqeqeq + if (sourceObj == null) return destObj; + do { + props = Object.getOwnPropertyNames(sourceObj); + i = props.length; + while (i-- > 0) { + prop = props[i]; + if ((!propFilter || propFilter(prop, sourceObj, destObj)) && !merged[prop]) { + destObj[prop] = sourceObj[prop]; + merged[prop] = true; + } + } + sourceObj = filter !== false && getPrototypeOf(sourceObj); + } while (sourceObj && (!filter || filter(sourceObj, destObj)) && sourceObj !== Object.prototype); + return destObj; + }; + + /** + * Determines whether a string ends with the characters of a specified string + * + * @param {String} str + * @param {String} searchString + * @param {Number} [position= 0] + * + * @returns {boolean} + */ + var endsWith = function endsWith(str, searchString, position) { + str = String(str); + if (position === undefined || position > str.length) { + position = str.length; + } + position -= searchString.length; + var lastIndex = str.indexOf(searchString, position); + return lastIndex !== -1 && lastIndex === position; + }; + + /** + * Returns new array from array like object or null if failed + * + * @param {*} [thing] + * + * @returns {?Array} + */ + var toArray = function toArray(thing) { + if (!thing) return null; + if (isArray(thing)) return thing; + var i = thing.length; + if (!isNumber(i)) return null; + var arr = new Array(i); + while (i-- > 0) { + arr[i] = thing[i]; + } + return arr; + }; + + /** + * Checking if the Uint8Array exists and if it does, it returns a function that checks if the + * thing passed in is an instance of Uint8Array + * + * @param {TypedArray} + * + * @returns {Array} + */ + // eslint-disable-next-line func-names + var isTypedArray = function (TypedArray) { + // eslint-disable-next-line func-names + return function (thing) { + return TypedArray && thing instanceof TypedArray; + }; + }(typeof Uint8Array !== 'undefined' && getPrototypeOf(Uint8Array)); + + /** + * For each entry in the object, call the function with the key and value. + * + * @param {Object} obj - The object to iterate over. + * @param {Function} fn - The function to call for each entry. + * + * @returns {void} + */ + var forEachEntry = function forEachEntry(obj, fn) { + var generator = obj && obj[Symbol.iterator]; + var iterator = generator.call(obj); + var result; + while ((result = iterator.next()) && !result.done) { + var pair = result.value; + fn.call(obj, pair[0], pair[1]); + } + }; + + /** + * It takes a regular expression and a string, and returns an array of all the matches + * + * @param {string} regExp - The regular expression to match against. + * @param {string} str - The string to search. + * + * @returns {Array} + */ + var matchAll = function matchAll(regExp, str) { + var matches; + var arr = []; + while ((matches = regExp.exec(str)) !== null) { + arr.push(matches); + } + return arr; + }; + + /* Checking if the kindOfTest function returns true when passed an HTMLFormElement. */ + var isHTMLForm = kindOfTest('HTMLFormElement'); + var toCamelCase = function toCamelCase(str) { + return str.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g, function replacer(m, p1, p2) { + return p1.toUpperCase() + p2; + }); + }; + + /* Creating a function that will check if an object has a property. */ + var hasOwnProperty = function (_ref4) { + var hasOwnProperty = _ref4.hasOwnProperty; + return function (obj, prop) { + return hasOwnProperty.call(obj, prop); + }; + }(Object.prototype); + + /** + * Determine if a value is a RegExp object + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is a RegExp object, otherwise false + */ + var isRegExp = kindOfTest('RegExp'); + var reduceDescriptors = function reduceDescriptors(obj, reducer) { + var descriptors = Object.getOwnPropertyDescriptors(obj); + var reducedDescriptors = {}; + forEach(descriptors, function (descriptor, name) { + var ret; + if ((ret = reducer(descriptor, name, obj)) !== false) { + reducedDescriptors[name] = ret || descriptor; + } + }); + Object.defineProperties(obj, reducedDescriptors); + }; + + /** + * Makes all methods read-only + * @param {Object} obj + */ + + var freezeMethods = function freezeMethods(obj) { + reduceDescriptors(obj, function (descriptor, name) { + // skip restricted props in strict mode + if (isFunction(obj) && ['arguments', 'caller', 'callee'].indexOf(name) !== -1) { + return false; + } + var value = obj[name]; + if (!isFunction(value)) return; + descriptor.enumerable = false; + if ('writable' in descriptor) { + descriptor.writable = false; + return; + } + if (!descriptor.set) { + descriptor.set = function () { + throw Error('Can not rewrite read-only method \'' + name + '\''); + }; + } + }); + }; + var toObjectSet = function toObjectSet(arrayOrString, delimiter) { + var obj = {}; + var define = function define(arr) { + arr.forEach(function (value) { + obj[value] = true; + }); + }; + isArray(arrayOrString) ? define(arrayOrString) : define(String(arrayOrString).split(delimiter)); + return obj; + }; + var noop = function noop() {}; + var toFiniteNumber = function toFiniteNumber(value, defaultValue) { + return value != null && Number.isFinite(value = +value) ? value : defaultValue; + }; + var ALPHA = 'abcdefghijklmnopqrstuvwxyz'; + var DIGIT = '0123456789'; + var ALPHABET = { + DIGIT: DIGIT, + ALPHA: ALPHA, + ALPHA_DIGIT: ALPHA + ALPHA.toUpperCase() + DIGIT + }; + var generateString = function generateString() { + var size = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 16; + var alphabet = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ALPHABET.ALPHA_DIGIT; + var str = ''; + var length = alphabet.length; + while (size--) { + str += alphabet[Math.random() * length | 0]; + } + return str; + }; + + /** + * If the thing is a FormData object, return true, otherwise return false. + * + * @param {unknown} thing - The thing to check. + * + * @returns {boolean} + */ + function isSpecCompliantForm(thing) { + return !!(thing && isFunction(thing.append) && thing[Symbol.toStringTag] === 'FormData' && thing[Symbol.iterator]); + } + var toJSONObject = function toJSONObject(obj) { + var stack = new Array(10); + var visit = function visit(source, i) { + if (isObject(source)) { + if (stack.indexOf(source) >= 0) { + return; + } + if (!('toJSON' in source)) { + stack[i] = source; + var target = isArray(source) ? [] : {}; + forEach(source, function (value, key) { + var reducedValue = visit(value, i + 1); + !isUndefined(reducedValue) && (target[key] = reducedValue); + }); + stack[i] = undefined; + return target; + } + } + return source; + }; + return visit(obj, 0); + }; + var isAsyncFn = kindOfTest('AsyncFunction'); + var isThenable = function isThenable(thing) { + return thing && (isObject(thing) || isFunction(thing)) && isFunction(thing.then) && isFunction(thing["catch"]); + }; + + // original code + // https://github.com/DigitalBrainJS/AxiosPromise/blob/16deab13710ec09779922131f3fa5954320f83ab/lib/utils.js#L11-L34 + + var _setImmediate = function (setImmediateSupported, postMessageSupported) { + if (setImmediateSupported) { + return setImmediate; + } + return postMessageSupported ? function (token, callbacks) { + _global.addEventListener("message", function (_ref5) { + var source = _ref5.source, + data = _ref5.data; + if (source === _global && data === token) { + callbacks.length && callbacks.shift()(); + } + }, false); + return function (cb) { + callbacks.push(cb); + _global.postMessage(token, "*"); + }; + }("axios@".concat(Math.random()), []) : function (cb) { + return setTimeout(cb); + }; + }(typeof setImmediate === 'function', isFunction(_global.postMessage)); + var asap = typeof queueMicrotask !== 'undefined' ? queueMicrotask.bind(_global) : typeof process !== 'undefined' && process.nextTick || _setImmediate; + + // ********************* + + var utils$1 = { + isArray: isArray, + isArrayBuffer: isArrayBuffer, + isBuffer: isBuffer, + isFormData: isFormData, + isArrayBufferView: isArrayBufferView, + isString: isString, + isNumber: isNumber, + isBoolean: isBoolean, + isObject: isObject, + isPlainObject: isPlainObject, + isReadableStream: isReadableStream, + isRequest: isRequest, + isResponse: isResponse, + isHeaders: isHeaders, + isUndefined: isUndefined, + isDate: isDate, + isFile: isFile, + isBlob: isBlob, + isRegExp: isRegExp, + isFunction: isFunction, + isStream: isStream, + isURLSearchParams: isURLSearchParams, + isTypedArray: isTypedArray, + isFileList: isFileList, + forEach: forEach, + merge: merge, + extend: extend, + trim: trim, + stripBOM: stripBOM, + inherits: inherits, + toFlatObject: toFlatObject, + kindOf: kindOf, + kindOfTest: kindOfTest, + endsWith: endsWith, + toArray: toArray, + forEachEntry: forEachEntry, + matchAll: matchAll, + isHTMLForm: isHTMLForm, + hasOwnProperty: hasOwnProperty, + hasOwnProp: hasOwnProperty, + // an alias to avoid ESLint no-prototype-builtins detection + reduceDescriptors: reduceDescriptors, + freezeMethods: freezeMethods, + toObjectSet: toObjectSet, + toCamelCase: toCamelCase, + noop: noop, + toFiniteNumber: toFiniteNumber, + findKey: findKey, + global: _global, + isContextDefined: isContextDefined, + ALPHABET: ALPHABET, + generateString: generateString, + isSpecCompliantForm: isSpecCompliantForm, + toJSONObject: toJSONObject, + isAsyncFn: isAsyncFn, + isThenable: isThenable, + setImmediate: _setImmediate, + asap: asap + }; + + /** + * Create an Error with the specified message, config, error code, request and response. + * + * @param {string} message The error message. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [config] The config. + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * + * @returns {Error} The created error. + */ + function AxiosError(message, code, config, request, response) { + Error.call(this); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } else { + this.stack = new Error().stack; + } + this.message = message; + this.name = 'AxiosError'; + code && (this.code = code); + config && (this.config = config); + request && (this.request = request); + if (response) { + this.response = response; + this.status = response.status ? response.status : null; + } + } + utils$1.inherits(AxiosError, Error, { + toJSON: function toJSON() { + return { + // Standard + message: this.message, + name: this.name, + // Microsoft + description: this.description, + number: this.number, + // Mozilla + fileName: this.fileName, + lineNumber: this.lineNumber, + columnNumber: this.columnNumber, + stack: this.stack, + // Axios + config: utils$1.toJSONObject(this.config), + code: this.code, + status: this.status + }; + } + }); + var prototype$1 = AxiosError.prototype; + var descriptors = {}; + ['ERR_BAD_OPTION_VALUE', 'ERR_BAD_OPTION', 'ECONNABORTED', 'ETIMEDOUT', 'ERR_NETWORK', 'ERR_FR_TOO_MANY_REDIRECTS', 'ERR_DEPRECATED', 'ERR_BAD_RESPONSE', 'ERR_BAD_REQUEST', 'ERR_CANCELED', 'ERR_NOT_SUPPORT', 'ERR_INVALID_URL' + // eslint-disable-next-line func-names + ].forEach(function (code) { + descriptors[code] = { + value: code + }; + }); + Object.defineProperties(AxiosError, descriptors); + Object.defineProperty(prototype$1, 'isAxiosError', { + value: true + }); + + // eslint-disable-next-line func-names + AxiosError.from = function (error, code, config, request, response, customProps) { + var axiosError = Object.create(prototype$1); + utils$1.toFlatObject(error, axiosError, function filter(obj) { + return obj !== Error.prototype; + }, function (prop) { + return prop !== 'isAxiosError'; + }); + AxiosError.call(axiosError, error.message, code, config, request, response); + axiosError.cause = error; + axiosError.name = error.name; + customProps && Object.assign(axiosError, customProps); + return axiosError; + }; + + // eslint-disable-next-line strict + var httpAdapter = null; + + /** + * Determines if the given thing is a array or js object. + * + * @param {string} thing - The object or array to be visited. + * + * @returns {boolean} + */ + function isVisitable(thing) { + return utils$1.isPlainObject(thing) || utils$1.isArray(thing); + } + + /** + * It removes the brackets from the end of a string + * + * @param {string} key - The key of the parameter. + * + * @returns {string} the key without the brackets. + */ + function removeBrackets(key) { + return utils$1.endsWith(key, '[]') ? key.slice(0, -2) : key; + } + + /** + * It takes a path, a key, and a boolean, and returns a string + * + * @param {string} path - The path to the current key. + * @param {string} key - The key of the current object being iterated over. + * @param {string} dots - If true, the key will be rendered with dots instead of brackets. + * + * @returns {string} The path to the current key. + */ + function renderKey(path, key, dots) { + if (!path) return key; + return path.concat(key).map(function each(token, i) { + // eslint-disable-next-line no-param-reassign + token = removeBrackets(token); + return !dots && i ? '[' + token + ']' : token; + }).join(dots ? '.' : ''); + } + + /** + * If the array is an array and none of its elements are visitable, then it's a flat array. + * + * @param {Array} arr - The array to check + * + * @returns {boolean} + */ + function isFlatArray(arr) { + return utils$1.isArray(arr) && !arr.some(isVisitable); + } + var predicates = utils$1.toFlatObject(utils$1, {}, null, function filter(prop) { + return /^is[A-Z]/.test(prop); + }); + + /** + * Convert a data object to FormData + * + * @param {Object} obj + * @param {?Object} [formData] + * @param {?Object} [options] + * @param {Function} [options.visitor] + * @param {Boolean} [options.metaTokens = true] + * @param {Boolean} [options.dots = false] + * @param {?Boolean} [options.indexes = false] + * + * @returns {Object} + **/ + + /** + * It converts an object into a FormData object + * + * @param {Object} obj - The object to convert to form data. + * @param {string} formData - The FormData object to append to. + * @param {Object} options + * + * @returns + */ + function toFormData(obj, formData, options) { + if (!utils$1.isObject(obj)) { + throw new TypeError('target must be an object'); + } + + // eslint-disable-next-line no-param-reassign + formData = formData || new (FormData)(); + + // eslint-disable-next-line no-param-reassign + options = utils$1.toFlatObject(options, { + metaTokens: true, + dots: false, + indexes: false + }, false, function defined(option, source) { + // eslint-disable-next-line no-eq-null,eqeqeq + return !utils$1.isUndefined(source[option]); + }); + var metaTokens = options.metaTokens; + // eslint-disable-next-line no-use-before-define + var visitor = options.visitor || defaultVisitor; + var dots = options.dots; + var indexes = options.indexes; + var _Blob = options.Blob || typeof Blob !== 'undefined' && Blob; + var useBlob = _Blob && utils$1.isSpecCompliantForm(formData); + if (!utils$1.isFunction(visitor)) { + throw new TypeError('visitor must be a function'); + } + function convertValue(value) { + if (value === null) return ''; + if (utils$1.isDate(value)) { + return value.toISOString(); + } + if (!useBlob && utils$1.isBlob(value)) { + throw new AxiosError('Blob is not supported. Use a Buffer instead.'); + } + if (utils$1.isArrayBuffer(value) || utils$1.isTypedArray(value)) { + return useBlob && typeof Blob === 'function' ? new Blob([value]) : Buffer.from(value); + } + return value; + } + + /** + * Default visitor. + * + * @param {*} value + * @param {String|Number} key + * @param {Array} path + * @this {FormData} + * + * @returns {boolean} return true to visit the each prop of the value recursively + */ + function defaultVisitor(value, key, path) { + var arr = value; + if (value && !path && _typeof(value) === 'object') { + if (utils$1.endsWith(key, '{}')) { + // eslint-disable-next-line no-param-reassign + key = metaTokens ? key : key.slice(0, -2); + // eslint-disable-next-line no-param-reassign + value = JSON.stringify(value); + } else if (utils$1.isArray(value) && isFlatArray(value) || (utils$1.isFileList(value) || utils$1.endsWith(key, '[]')) && (arr = utils$1.toArray(value))) { + // eslint-disable-next-line no-param-reassign + key = removeBrackets(key); + arr.forEach(function each(el, index) { + !(utils$1.isUndefined(el) || el === null) && formData.append( + // eslint-disable-next-line no-nested-ternary + indexes === true ? renderKey([key], index, dots) : indexes === null ? key : key + '[]', convertValue(el)); + }); + return false; + } + } + if (isVisitable(value)) { + return true; + } + formData.append(renderKey(path, key, dots), convertValue(value)); + return false; + } + var stack = []; + var exposedHelpers = Object.assign(predicates, { + defaultVisitor: defaultVisitor, + convertValue: convertValue, + isVisitable: isVisitable + }); + function build(value, path) { + if (utils$1.isUndefined(value)) return; + if (stack.indexOf(value) !== -1) { + throw Error('Circular reference detected in ' + path.join('.')); + } + stack.push(value); + utils$1.forEach(value, function each(el, key) { + var result = !(utils$1.isUndefined(el) || el === null) && visitor.call(formData, el, utils$1.isString(key) ? key.trim() : key, path, exposedHelpers); + if (result === true) { + build(el, path ? path.concat(key) : [key]); + } + }); + stack.pop(); + } + if (!utils$1.isObject(obj)) { + throw new TypeError('data must be an object'); + } + build(obj); + return formData; + } + + /** + * It encodes a string by replacing all characters that are not in the unreserved set with + * their percent-encoded equivalents + * + * @param {string} str - The string to encode. + * + * @returns {string} The encoded string. + */ + function encode$1(str) { + var charMap = { + '!': '%21', + "'": '%27', + '(': '%28', + ')': '%29', + '~': '%7E', + '%20': '+', + '%00': '\x00' + }; + return encodeURIComponent(str).replace(/[!'()~]|%20|%00/g, function replacer(match) { + return charMap[match]; + }); + } + + /** + * It takes a params object and converts it to a FormData object + * + * @param {Object} params - The parameters to be converted to a FormData object. + * @param {Object} options - The options object passed to the Axios constructor. + * + * @returns {void} + */ + function AxiosURLSearchParams(params, options) { + this._pairs = []; + params && toFormData(params, this, options); + } + var prototype = AxiosURLSearchParams.prototype; + prototype.append = function append(name, value) { + this._pairs.push([name, value]); + }; + prototype.toString = function toString(encoder) { + var _encode = encoder ? function (value) { + return encoder.call(this, value, encode$1); + } : encode$1; + return this._pairs.map(function each(pair) { + return _encode(pair[0]) + '=' + _encode(pair[1]); + }, '').join('&'); + }; + + /** + * It replaces all instances of the characters `:`, `$`, `,`, `+`, `[`, and `]` with their + * URI encoded counterparts + * + * @param {string} val The value to be encoded. + * + * @returns {string} The encoded value. + */ + function encode(val) { + return encodeURIComponent(val).replace(/%3A/gi, ':').replace(/%24/g, '$').replace(/%2C/gi, ',').replace(/%20/g, '+').replace(/%5B/gi, '[').replace(/%5D/gi, ']'); + } + + /** + * Build a URL by appending params to the end + * + * @param {string} url The base of the url (e.g., http://www.google.com) + * @param {object} [params] The params to be appended + * @param {?(object|Function)} options + * + * @returns {string} The formatted url + */ + function buildURL(url, params, options) { + /*eslint no-param-reassign:0*/ + if (!params) { + return url; + } + var _encode = options && options.encode || encode; + if (utils$1.isFunction(options)) { + options = { + serialize: options + }; + } + var serializeFn = options && options.serialize; + var serializedParams; + if (serializeFn) { + serializedParams = serializeFn(params, options); + } else { + serializedParams = utils$1.isURLSearchParams(params) ? params.toString() : new AxiosURLSearchParams(params, options).toString(_encode); + } + if (serializedParams) { + var hashmarkIndex = url.indexOf("#"); + if (hashmarkIndex !== -1) { + url = url.slice(0, hashmarkIndex); + } + url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; + } + return url; + } + + var InterceptorManager = /*#__PURE__*/function () { + function InterceptorManager() { + _classCallCheck(this, InterceptorManager); + this.handlers = []; + } + + /** + * Add a new interceptor to the stack + * + * @param {Function} fulfilled The function to handle `then` for a `Promise` + * @param {Function} rejected The function to handle `reject` for a `Promise` + * + * @return {Number} An ID used to remove interceptor later + */ + _createClass(InterceptorManager, [{ + key: "use", + value: function use(fulfilled, rejected, options) { + this.handlers.push({ + fulfilled: fulfilled, + rejected: rejected, + synchronous: options ? options.synchronous : false, + runWhen: options ? options.runWhen : null + }); + return this.handlers.length - 1; + } + + /** + * Remove an interceptor from the stack + * + * @param {Number} id The ID that was returned by `use` + * + * @returns {Boolean} `true` if the interceptor was removed, `false` otherwise + */ + }, { + key: "eject", + value: function eject(id) { + if (this.handlers[id]) { + this.handlers[id] = null; + } + } + + /** + * Clear all interceptors from the stack + * + * @returns {void} + */ + }, { + key: "clear", + value: function clear() { + if (this.handlers) { + this.handlers = []; + } + } + + /** + * Iterate over all the registered interceptors + * + * This method is particularly useful for skipping over any + * interceptors that may have become `null` calling `eject`. + * + * @param {Function} fn The function to call for each interceptor + * + * @returns {void} + */ + }, { + key: "forEach", + value: function forEach(fn) { + utils$1.forEach(this.handlers, function forEachHandler(h) { + if (h !== null) { + fn(h); + } + }); + } + }]); + return InterceptorManager; + }(); + var InterceptorManager$1 = InterceptorManager; + + var transitionalDefaults = { + silentJSONParsing: true, + forcedJSONParsing: true, + clarifyTimeoutError: false + }; + + var URLSearchParams$1 = typeof URLSearchParams !== 'undefined' ? URLSearchParams : AxiosURLSearchParams; + + var FormData$1 = typeof FormData !== 'undefined' ? FormData : null; + + var Blob$1 = typeof Blob !== 'undefined' ? Blob : null; + + var platform$1 = { + isBrowser: true, + classes: { + URLSearchParams: URLSearchParams$1, + FormData: FormData$1, + Blob: Blob$1 + }, + protocols: ['http', 'https', 'file', 'blob', 'url', 'data'] + }; + + var hasBrowserEnv = typeof window !== 'undefined' && typeof document !== 'undefined'; + var _navigator = (typeof navigator === "undefined" ? "undefined" : _typeof(navigator)) === 'object' && navigator || undefined; + + /** + * Determine if we're running in a standard browser environment + * + * This allows axios to run in a web worker, and react-native. + * Both environments support XMLHttpRequest, but not fully standard globals. + * + * web workers: + * typeof window -> undefined + * typeof document -> undefined + * + * react-native: + * navigator.product -> 'ReactNative' + * nativescript + * navigator.product -> 'NativeScript' or 'NS' + * + * @returns {boolean} + */ + var hasStandardBrowserEnv = hasBrowserEnv && (!_navigator || ['ReactNative', 'NativeScript', 'NS'].indexOf(_navigator.product) < 0); + + /** + * Determine if we're running in a standard browser webWorker environment + * + * Although the `isStandardBrowserEnv` method indicates that + * `allows axios to run in a web worker`, the WebWorker will still be + * filtered out due to its judgment standard + * `typeof window !== 'undefined' && typeof document !== 'undefined'`. + * This leads to a problem when axios post `FormData` in webWorker + */ + var hasStandardBrowserWebWorkerEnv = function () { + return typeof WorkerGlobalScope !== 'undefined' && + // eslint-disable-next-line no-undef + self instanceof WorkerGlobalScope && typeof self.importScripts === 'function'; + }(); + var origin = hasBrowserEnv && window.location.href || 'http://localhost'; + + var utils = /*#__PURE__*/Object.freeze({ + __proto__: null, + hasBrowserEnv: hasBrowserEnv, + hasStandardBrowserWebWorkerEnv: hasStandardBrowserWebWorkerEnv, + hasStandardBrowserEnv: hasStandardBrowserEnv, + navigator: _navigator, + origin: origin + }); + + var platform = _objectSpread2(_objectSpread2({}, utils), platform$1); + + function toURLEncodedForm(data, options) { + return toFormData(data, new platform.classes.URLSearchParams(), Object.assign({ + visitor: function visitor(value, key, path, helpers) { + if (platform.isNode && utils$1.isBuffer(value)) { + this.append(key, value.toString('base64')); + return false; + } + return helpers.defaultVisitor.apply(this, arguments); + } + }, options)); + } + + /** + * It takes a string like `foo[x][y][z]` and returns an array like `['foo', 'x', 'y', 'z'] + * + * @param {string} name - The name of the property to get. + * + * @returns An array of strings. + */ + function parsePropPath(name) { + // foo[x][y][z] + // foo.x.y.z + // foo-x-y-z + // foo x y z + return utils$1.matchAll(/\w+|\[(\w*)]/g, name).map(function (match) { + return match[0] === '[]' ? '' : match[1] || match[0]; + }); + } + + /** + * Convert an array to an object. + * + * @param {Array} arr - The array to convert to an object. + * + * @returns An object with the same keys and values as the array. + */ + function arrayToObject(arr) { + var obj = {}; + var keys = Object.keys(arr); + var i; + var len = keys.length; + var key; + for (i = 0; i < len; i++) { + key = keys[i]; + obj[key] = arr[key]; + } + return obj; + } + + /** + * It takes a FormData object and returns a JavaScript object + * + * @param {string} formData The FormData object to convert to JSON. + * + * @returns {Object | null} The converted object. + */ + function formDataToJSON(formData) { + function buildPath(path, value, target, index) { + var name = path[index++]; + if (name === '__proto__') return true; + var isNumericKey = Number.isFinite(+name); + var isLast = index >= path.length; + name = !name && utils$1.isArray(target) ? target.length : name; + if (isLast) { + if (utils$1.hasOwnProp(target, name)) { + target[name] = [target[name], value]; + } else { + target[name] = value; + } + return !isNumericKey; + } + if (!target[name] || !utils$1.isObject(target[name])) { + target[name] = []; + } + var result = buildPath(path, value, target[name], index); + if (result && utils$1.isArray(target[name])) { + target[name] = arrayToObject(target[name]); + } + return !isNumericKey; + } + if (utils$1.isFormData(formData) && utils$1.isFunction(formData.entries)) { + var obj = {}; + utils$1.forEachEntry(formData, function (name, value) { + buildPath(parsePropPath(name), value, obj, 0); + }); + return obj; + } + return null; + } + + /** + * It takes a string, tries to parse it, and if it fails, it returns the stringified version + * of the input + * + * @param {any} rawValue - The value to be stringified. + * @param {Function} parser - A function that parses a string into a JavaScript object. + * @param {Function} encoder - A function that takes a value and returns a string. + * + * @returns {string} A stringified version of the rawValue. + */ + function stringifySafely(rawValue, parser, encoder) { + if (utils$1.isString(rawValue)) { + try { + (parser || JSON.parse)(rawValue); + return utils$1.trim(rawValue); + } catch (e) { + if (e.name !== 'SyntaxError') { + throw e; + } + } + } + return (encoder || JSON.stringify)(rawValue); + } + var defaults = { + transitional: transitionalDefaults, + adapter: ['xhr', 'http', 'fetch'], + transformRequest: [function transformRequest(data, headers) { + var contentType = headers.getContentType() || ''; + var hasJSONContentType = contentType.indexOf('application/json') > -1; + var isObjectPayload = utils$1.isObject(data); + if (isObjectPayload && utils$1.isHTMLForm(data)) { + data = new FormData(data); + } + var isFormData = utils$1.isFormData(data); + if (isFormData) { + return hasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data; + } + if (utils$1.isArrayBuffer(data) || utils$1.isBuffer(data) || utils$1.isStream(data) || utils$1.isFile(data) || utils$1.isBlob(data) || utils$1.isReadableStream(data)) { + return data; + } + if (utils$1.isArrayBufferView(data)) { + return data.buffer; + } + if (utils$1.isURLSearchParams(data)) { + headers.setContentType('application/x-www-form-urlencoded;charset=utf-8', false); + return data.toString(); + } + var isFileList; + if (isObjectPayload) { + if (contentType.indexOf('application/x-www-form-urlencoded') > -1) { + return toURLEncodedForm(data, this.formSerializer).toString(); + } + if ((isFileList = utils$1.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) { + var _FormData = this.env && this.env.FormData; + return toFormData(isFileList ? { + 'files[]': data + } : data, _FormData && new _FormData(), this.formSerializer); + } + } + if (isObjectPayload || hasJSONContentType) { + headers.setContentType('application/json', false); + return stringifySafely(data); + } + return data; + }], + transformResponse: [function transformResponse(data) { + var transitional = this.transitional || defaults.transitional; + var forcedJSONParsing = transitional && transitional.forcedJSONParsing; + var JSONRequested = this.responseType === 'json'; + if (utils$1.isResponse(data) || utils$1.isReadableStream(data)) { + return data; + } + if (data && utils$1.isString(data) && (forcedJSONParsing && !this.responseType || JSONRequested)) { + var silentJSONParsing = transitional && transitional.silentJSONParsing; + var strictJSONParsing = !silentJSONParsing && JSONRequested; + try { + return JSON.parse(data); + } catch (e) { + if (strictJSONParsing) { + if (e.name === 'SyntaxError') { + throw AxiosError.from(e, AxiosError.ERR_BAD_RESPONSE, this, null, this.response); + } + throw e; + } + } + } + return data; + }], + /** + * A timeout in milliseconds to abort a request. If set to 0 (default) a + * timeout is not created. + */ + timeout: 0, + xsrfCookieName: 'XSRF-TOKEN', + xsrfHeaderName: 'X-XSRF-TOKEN', + maxContentLength: -1, + maxBodyLength: -1, + env: { + FormData: platform.classes.FormData, + Blob: platform.classes.Blob + }, + validateStatus: function validateStatus(status) { + return status >= 200 && status < 300; + }, + headers: { + common: { + 'Accept': 'application/json, text/plain, */*', + 'Content-Type': undefined + } + } + }; + utils$1.forEach(['delete', 'get', 'head', 'post', 'put', 'patch'], function (method) { + defaults.headers[method] = {}; + }); + var defaults$1 = defaults; + + // RawAxiosHeaders whose duplicates are ignored by node + // c.f. https://nodejs.org/api/http.html#http_message_headers + var ignoreDuplicateOf = utils$1.toObjectSet(['age', 'authorization', 'content-length', 'content-type', 'etag', 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', 'last-modified', 'location', 'max-forwards', 'proxy-authorization', 'referer', 'retry-after', 'user-agent']); + + /** + * Parse headers into an object + * + * ``` + * Date: Wed, 27 Aug 2014 08:58:49 GMT + * Content-Type: application/json + * Connection: keep-alive + * Transfer-Encoding: chunked + * ``` + * + * @param {String} rawHeaders Headers needing to be parsed + * + * @returns {Object} Headers parsed into an object + */ + var parseHeaders = (function (rawHeaders) { + var parsed = {}; + var key; + var val; + var i; + rawHeaders && rawHeaders.split('\n').forEach(function parser(line) { + i = line.indexOf(':'); + key = line.substring(0, i).trim().toLowerCase(); + val = line.substring(i + 1).trim(); + if (!key || parsed[key] && ignoreDuplicateOf[key]) { + return; + } + if (key === 'set-cookie') { + if (parsed[key]) { + parsed[key].push(val); + } else { + parsed[key] = [val]; + } + } else { + parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; + } + }); + return parsed; + }); + + var $internals = Symbol('internals'); + function normalizeHeader(header) { + return header && String(header).trim().toLowerCase(); + } + function normalizeValue(value) { + if (value === false || value == null) { + return value; + } + return utils$1.isArray(value) ? value.map(normalizeValue) : String(value); + } + function parseTokens(str) { + var tokens = Object.create(null); + var tokensRE = /([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g; + var match; + while (match = tokensRE.exec(str)) { + tokens[match[1]] = match[2]; + } + return tokens; + } + var isValidHeaderName = function isValidHeaderName(str) { + return /^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(str.trim()); + }; + function matchHeaderValue(context, value, header, filter, isHeaderNameFilter) { + if (utils$1.isFunction(filter)) { + return filter.call(this, value, header); + } + if (isHeaderNameFilter) { + value = header; + } + if (!utils$1.isString(value)) return; + if (utils$1.isString(filter)) { + return value.indexOf(filter) !== -1; + } + if (utils$1.isRegExp(filter)) { + return filter.test(value); + } + } + function formatHeader(header) { + return header.trim().toLowerCase().replace(/([a-z\d])(\w*)/g, function (w, _char, str) { + return _char.toUpperCase() + str; + }); + } + function buildAccessors(obj, header) { + var accessorName = utils$1.toCamelCase(' ' + header); + ['get', 'set', 'has'].forEach(function (methodName) { + Object.defineProperty(obj, methodName + accessorName, { + value: function value(arg1, arg2, arg3) { + return this[methodName].call(this, header, arg1, arg2, arg3); + }, + configurable: true + }); + }); + } + var AxiosHeaders = /*#__PURE__*/function (_Symbol$iterator, _Symbol$toStringTag) { + function AxiosHeaders(headers) { + _classCallCheck(this, AxiosHeaders); + headers && this.set(headers); + } + _createClass(AxiosHeaders, [{ + key: "set", + value: function set(header, valueOrRewrite, rewrite) { + var self = this; + function setHeader(_value, _header, _rewrite) { + var lHeader = normalizeHeader(_header); + if (!lHeader) { + throw new Error('header name must be a non-empty string'); + } + var key = utils$1.findKey(self, lHeader); + if (!key || self[key] === undefined || _rewrite === true || _rewrite === undefined && self[key] !== false) { + self[key || _header] = normalizeValue(_value); + } + } + var setHeaders = function setHeaders(headers, _rewrite) { + return utils$1.forEach(headers, function (_value, _header) { + return setHeader(_value, _header, _rewrite); + }); + }; + if (utils$1.isPlainObject(header) || header instanceof this.constructor) { + setHeaders(header, valueOrRewrite); + } else if (utils$1.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) { + setHeaders(parseHeaders(header), valueOrRewrite); + } else if (utils$1.isHeaders(header)) { + var _iterator = _createForOfIteratorHelper(header.entries()), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var _step$value = _slicedToArray(_step.value, 2), + key = _step$value[0], + value = _step$value[1]; + setHeader(value, key, rewrite); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + } else { + header != null && setHeader(valueOrRewrite, header, rewrite); + } + return this; + } + }, { + key: "get", + value: function get(header, parser) { + header = normalizeHeader(header); + if (header) { + var key = utils$1.findKey(this, header); + if (key) { + var value = this[key]; + if (!parser) { + return value; + } + if (parser === true) { + return parseTokens(value); + } + if (utils$1.isFunction(parser)) { + return parser.call(this, value, key); + } + if (utils$1.isRegExp(parser)) { + return parser.exec(value); + } + throw new TypeError('parser must be boolean|regexp|function'); + } + } + } + }, { + key: "has", + value: function has(header, matcher) { + header = normalizeHeader(header); + if (header) { + var key = utils$1.findKey(this, header); + return !!(key && this[key] !== undefined && (!matcher || matchHeaderValue(this, this[key], key, matcher))); + } + return false; + } + }, { + key: "delete", + value: function _delete(header, matcher) { + var self = this; + var deleted = false; + function deleteHeader(_header) { + _header = normalizeHeader(_header); + if (_header) { + var key = utils$1.findKey(self, _header); + if (key && (!matcher || matchHeaderValue(self, self[key], key, matcher))) { + delete self[key]; + deleted = true; + } + } + } + if (utils$1.isArray(header)) { + header.forEach(deleteHeader); + } else { + deleteHeader(header); + } + return deleted; + } + }, { + key: "clear", + value: function clear(matcher) { + var keys = Object.keys(this); + var i = keys.length; + var deleted = false; + while (i--) { + var key = keys[i]; + if (!matcher || matchHeaderValue(this, this[key], key, matcher, true)) { + delete this[key]; + deleted = true; + } + } + return deleted; + } + }, { + key: "normalize", + value: function normalize(format) { + var self = this; + var headers = {}; + utils$1.forEach(this, function (value, header) { + var key = utils$1.findKey(headers, header); + if (key) { + self[key] = normalizeValue(value); + delete self[header]; + return; + } + var normalized = format ? formatHeader(header) : String(header).trim(); + if (normalized !== header) { + delete self[header]; + } + self[normalized] = normalizeValue(value); + headers[normalized] = true; + }); + return this; + } + }, { + key: "concat", + value: function concat() { + var _this$constructor; + for (var _len = arguments.length, targets = new Array(_len), _key = 0; _key < _len; _key++) { + targets[_key] = arguments[_key]; + } + return (_this$constructor = this.constructor).concat.apply(_this$constructor, [this].concat(targets)); + } + }, { + key: "toJSON", + value: function toJSON(asStrings) { + var obj = Object.create(null); + utils$1.forEach(this, function (value, header) { + value != null && value !== false && (obj[header] = asStrings && utils$1.isArray(value) ? value.join(', ') : value); + }); + return obj; + } + }, { + key: _Symbol$iterator, + value: function value() { + return Object.entries(this.toJSON())[Symbol.iterator](); + } + }, { + key: "toString", + value: function toString() { + return Object.entries(this.toJSON()).map(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + header = _ref2[0], + value = _ref2[1]; + return header + ': ' + value; + }).join('\n'); + } + }, { + key: _Symbol$toStringTag, + get: function get() { + return 'AxiosHeaders'; + } + }], [{ + key: "from", + value: function from(thing) { + return thing instanceof this ? thing : new this(thing); + } + }, { + key: "concat", + value: function concat(first) { + var computed = new this(first); + for (var _len2 = arguments.length, targets = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + targets[_key2 - 1] = arguments[_key2]; + } + targets.forEach(function (target) { + return computed.set(target); + }); + return computed; + } + }, { + key: "accessor", + value: function accessor(header) { + var internals = this[$internals] = this[$internals] = { + accessors: {} + }; + var accessors = internals.accessors; + var prototype = this.prototype; + function defineAccessor(_header) { + var lHeader = normalizeHeader(_header); + if (!accessors[lHeader]) { + buildAccessors(prototype, _header); + accessors[lHeader] = true; + } + } + utils$1.isArray(header) ? header.forEach(defineAccessor) : defineAccessor(header); + return this; + } + }]); + return AxiosHeaders; + }(Symbol.iterator, Symbol.toStringTag); + AxiosHeaders.accessor(['Content-Type', 'Content-Length', 'Accept', 'Accept-Encoding', 'User-Agent', 'Authorization']); + + // reserved names hotfix + utils$1.reduceDescriptors(AxiosHeaders.prototype, function (_ref3, key) { + var value = _ref3.value; + var mapped = key[0].toUpperCase() + key.slice(1); // map `set` => `Set` + return { + get: function get() { + return value; + }, + set: function set(headerValue) { + this[mapped] = headerValue; + } + }; + }); + utils$1.freezeMethods(AxiosHeaders); + var AxiosHeaders$1 = AxiosHeaders; + + /** + * Transform the data for a request or a response + * + * @param {Array|Function} fns A single function or Array of functions + * @param {?Object} response The response object + * + * @returns {*} The resulting transformed data + */ + function transformData(fns, response) { + var config = this || defaults$1; + var context = response || config; + var headers = AxiosHeaders$1.from(context.headers); + var data = context.data; + utils$1.forEach(fns, function transform(fn) { + data = fn.call(config, data, headers.normalize(), response ? response.status : undefined); + }); + headers.normalize(); + return data; + } + + function isCancel(value) { + return !!(value && value.__CANCEL__); + } + + /** + * A `CanceledError` is an object that is thrown when an operation is canceled. + * + * @param {string=} message The message. + * @param {Object=} config The config. + * @param {Object=} request The request. + * + * @returns {CanceledError} The created error. + */ + function CanceledError(message, config, request) { + // eslint-disable-next-line no-eq-null,eqeqeq + AxiosError.call(this, message == null ? 'canceled' : message, AxiosError.ERR_CANCELED, config, request); + this.name = 'CanceledError'; + } + utils$1.inherits(CanceledError, AxiosError, { + __CANCEL__: true + }); + + /** + * Resolve or reject a Promise based on response status. + * + * @param {Function} resolve A function that resolves the promise. + * @param {Function} reject A function that rejects the promise. + * @param {object} response The response. + * + * @returns {object} The response. + */ + function settle(resolve, reject, response) { + var validateStatus = response.config.validateStatus; + if (!response.status || !validateStatus || validateStatus(response.status)) { + resolve(response); + } else { + reject(new AxiosError('Request failed with status code ' + response.status, [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4], response.config, response.request, response)); + } + } + + function parseProtocol(url) { + var match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url); + return match && match[1] || ''; + } + + /** + * Calculate data maxRate + * @param {Number} [samplesCount= 10] + * @param {Number} [min= 1000] + * @returns {Function} + */ + function speedometer(samplesCount, min) { + samplesCount = samplesCount || 10; + var bytes = new Array(samplesCount); + var timestamps = new Array(samplesCount); + var head = 0; + var tail = 0; + var firstSampleTS; + min = min !== undefined ? min : 1000; + return function push(chunkLength) { + var now = Date.now(); + var startedAt = timestamps[tail]; + if (!firstSampleTS) { + firstSampleTS = now; + } + bytes[head] = chunkLength; + timestamps[head] = now; + var i = tail; + var bytesCount = 0; + while (i !== head) { + bytesCount += bytes[i++]; + i = i % samplesCount; + } + head = (head + 1) % samplesCount; + if (head === tail) { + tail = (tail + 1) % samplesCount; + } + if (now - firstSampleTS < min) { + return; + } + var passed = startedAt && now - startedAt; + return passed ? Math.round(bytesCount * 1000 / passed) : undefined; + }; + } + + /** + * Throttle decorator + * @param {Function} fn + * @param {Number} freq + * @return {Function} + */ + function throttle(fn, freq) { + var timestamp = 0; + var threshold = 1000 / freq; + var lastArgs; + var timer; + var invoke = function invoke(args) { + var now = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Date.now(); + timestamp = now; + lastArgs = null; + if (timer) { + clearTimeout(timer); + timer = null; + } + fn.apply(null, args); + }; + var throttled = function throttled() { + var now = Date.now(); + var passed = now - timestamp; + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + if (passed >= threshold) { + invoke(args, now); + } else { + lastArgs = args; + if (!timer) { + timer = setTimeout(function () { + timer = null; + invoke(lastArgs); + }, threshold - passed); + } + } + }; + var flush = function flush() { + return lastArgs && invoke(lastArgs); + }; + return [throttled, flush]; + } + + var progressEventReducer = function progressEventReducer(listener, isDownloadStream) { + var freq = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 3; + var bytesNotified = 0; + var _speedometer = speedometer(50, 250); + return throttle(function (e) { + var loaded = e.loaded; + var total = e.lengthComputable ? e.total : undefined; + var progressBytes = loaded - bytesNotified; + var rate = _speedometer(progressBytes); + var inRange = loaded <= total; + bytesNotified = loaded; + var data = _defineProperty({ + loaded: loaded, + total: total, + progress: total ? loaded / total : undefined, + bytes: progressBytes, + rate: rate ? rate : undefined, + estimated: rate && total && inRange ? (total - loaded) / rate : undefined, + event: e, + lengthComputable: total != null + }, isDownloadStream ? 'download' : 'upload', true); + listener(data); + }, freq); + }; + var progressEventDecorator = function progressEventDecorator(total, throttled) { + var lengthComputable = total != null; + return [function (loaded) { + return throttled[0]({ + lengthComputable: lengthComputable, + total: total, + loaded: loaded + }); + }, throttled[1]]; + }; + var asyncDecorator = function asyncDecorator(fn) { + return function () { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + return utils$1.asap(function () { + return fn.apply(void 0, args); + }); + }; + }; + + var isURLSameOrigin = platform.hasStandardBrowserEnv ? function (origin, isMSIE) { + return function (url) { + url = new URL(url, platform.origin); + return origin.protocol === url.protocol && origin.host === url.host && (isMSIE || origin.port === url.port); + }; + }(new URL(platform.origin), platform.navigator && /(msie|trident)/i.test(platform.navigator.userAgent)) : function () { + return true; + }; + + var cookies = platform.hasStandardBrowserEnv ? + // Standard browser envs support document.cookie + { + write: function write(name, value, expires, path, domain, secure) { + var cookie = [name + '=' + encodeURIComponent(value)]; + utils$1.isNumber(expires) && cookie.push('expires=' + new Date(expires).toGMTString()); + utils$1.isString(path) && cookie.push('path=' + path); + utils$1.isString(domain) && cookie.push('domain=' + domain); + secure === true && cookie.push('secure'); + document.cookie = cookie.join('; '); + }, + read: function read(name) { + var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); + return match ? decodeURIComponent(match[3]) : null; + }, + remove: function remove(name) { + this.write(name, '', Date.now() - 86400000); + } + } : + // Non-standard browser env (web workers, react-native) lack needed support. + { + write: function write() {}, + read: function read() { + return null; + }, + remove: function remove() {} + }; + + /** + * Determines whether the specified URL is absolute + * + * @param {string} url The URL to test + * + * @returns {boolean} True if the specified URL is absolute, otherwise false + */ + function isAbsoluteURL(url) { + // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). + // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed + // by any combination of letters, digits, plus, period, or hyphen. + return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url); + } + + /** + * Creates a new URL by combining the specified URLs + * + * @param {string} baseURL The base URL + * @param {string} relativeURL The relative URL + * + * @returns {string} The combined URL + */ + function combineURLs(baseURL, relativeURL) { + return relativeURL ? baseURL.replace(/\/?\/$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL; + } + + /** + * Creates a new URL by combining the baseURL with the requestedURL, + * only when the requestedURL is not already an absolute URL. + * If the requestURL is absolute, this function returns the requestedURL untouched. + * + * @param {string} baseURL The base URL + * @param {string} requestedURL Absolute or relative URL to combine + * + * @returns {string} The combined full path + */ + function buildFullPath(baseURL, requestedURL) { + if (baseURL && !isAbsoluteURL(requestedURL)) { + return combineURLs(baseURL, requestedURL); + } + return requestedURL; + } + + var headersToObject = function headersToObject(thing) { + return thing instanceof AxiosHeaders$1 ? _objectSpread2({}, thing) : thing; + }; + + /** + * Config-specific merge-function which creates a new config-object + * by merging two configuration objects together. + * + * @param {Object} config1 + * @param {Object} config2 + * + * @returns {Object} New object resulting from merging config2 to config1 + */ + function mergeConfig(config1, config2) { + // eslint-disable-next-line no-param-reassign + config2 = config2 || {}; + var config = {}; + function getMergedValue(target, source, prop, caseless) { + if (utils$1.isPlainObject(target) && utils$1.isPlainObject(source)) { + return utils$1.merge.call({ + caseless: caseless + }, target, source); + } else if (utils$1.isPlainObject(source)) { + return utils$1.merge({}, source); + } else if (utils$1.isArray(source)) { + return source.slice(); + } + return source; + } + + // eslint-disable-next-line consistent-return + function mergeDeepProperties(a, b, prop, caseless) { + if (!utils$1.isUndefined(b)) { + return getMergedValue(a, b, prop, caseless); + } else if (!utils$1.isUndefined(a)) { + return getMergedValue(undefined, a, prop, caseless); + } + } + + // eslint-disable-next-line consistent-return + function valueFromConfig2(a, b) { + if (!utils$1.isUndefined(b)) { + return getMergedValue(undefined, b); + } + } + + // eslint-disable-next-line consistent-return + function defaultToConfig2(a, b) { + if (!utils$1.isUndefined(b)) { + return getMergedValue(undefined, b); + } else if (!utils$1.isUndefined(a)) { + return getMergedValue(undefined, a); + } + } + + // eslint-disable-next-line consistent-return + function mergeDirectKeys(a, b, prop) { + if (prop in config2) { + return getMergedValue(a, b); + } else if (prop in config1) { + return getMergedValue(undefined, a); + } + } + var mergeMap = { + url: valueFromConfig2, + method: valueFromConfig2, + data: valueFromConfig2, + baseURL: defaultToConfig2, + transformRequest: defaultToConfig2, + transformResponse: defaultToConfig2, + paramsSerializer: defaultToConfig2, + timeout: defaultToConfig2, + timeoutMessage: defaultToConfig2, + withCredentials: defaultToConfig2, + withXSRFToken: defaultToConfig2, + adapter: defaultToConfig2, + responseType: defaultToConfig2, + xsrfCookieName: defaultToConfig2, + xsrfHeaderName: defaultToConfig2, + onUploadProgress: defaultToConfig2, + onDownloadProgress: defaultToConfig2, + decompress: defaultToConfig2, + maxContentLength: defaultToConfig2, + maxBodyLength: defaultToConfig2, + beforeRedirect: defaultToConfig2, + transport: defaultToConfig2, + httpAgent: defaultToConfig2, + httpsAgent: defaultToConfig2, + cancelToken: defaultToConfig2, + socketPath: defaultToConfig2, + responseEncoding: defaultToConfig2, + validateStatus: mergeDirectKeys, + headers: function headers(a, b, prop) { + return mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true); + } + }; + utils$1.forEach(Object.keys(Object.assign({}, config1, config2)), function computeConfigValue(prop) { + var merge = mergeMap[prop] || mergeDeepProperties; + var configValue = merge(config1[prop], config2[prop], prop); + utils$1.isUndefined(configValue) && merge !== mergeDirectKeys || (config[prop] = configValue); + }); + return config; + } + + var resolveConfig = (function (config) { + var newConfig = mergeConfig({}, config); + var data = newConfig.data, + withXSRFToken = newConfig.withXSRFToken, + xsrfHeaderName = newConfig.xsrfHeaderName, + xsrfCookieName = newConfig.xsrfCookieName, + headers = newConfig.headers, + auth = newConfig.auth; + newConfig.headers = headers = AxiosHeaders$1.from(headers); + newConfig.url = buildURL(buildFullPath(newConfig.baseURL, newConfig.url), config.params, config.paramsSerializer); + + // HTTP basic authentication + if (auth) { + headers.set('Authorization', 'Basic ' + btoa((auth.username || '') + ':' + (auth.password ? unescape(encodeURIComponent(auth.password)) : ''))); + } + var contentType; + if (utils$1.isFormData(data)) { + if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) { + headers.setContentType(undefined); // Let the browser set it + } else if ((contentType = headers.getContentType()) !== false) { + // fix semicolon duplication issue for ReactNative FormData implementation + var _ref = contentType ? contentType.split(';').map(function (token) { + return token.trim(); + }).filter(Boolean) : [], + _ref2 = _toArray(_ref), + type = _ref2[0], + tokens = _ref2.slice(1); + headers.setContentType([type || 'multipart/form-data'].concat(_toConsumableArray(tokens)).join('; ')); + } + } + + // Add xsrf header + // This is only done if running in a standard browser environment. + // Specifically not if we're in a web worker, or react-native. + + if (platform.hasStandardBrowserEnv) { + withXSRFToken && utils$1.isFunction(withXSRFToken) && (withXSRFToken = withXSRFToken(newConfig)); + if (withXSRFToken || withXSRFToken !== false && isURLSameOrigin(newConfig.url)) { + // Add xsrf header + var xsrfValue = xsrfHeaderName && xsrfCookieName && cookies.read(xsrfCookieName); + if (xsrfValue) { + headers.set(xsrfHeaderName, xsrfValue); + } + } + } + return newConfig; + }); + + var isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined'; + var xhrAdapter = isXHRAdapterSupported && function (config) { + return new Promise(function dispatchXhrRequest(resolve, reject) { + var _config = resolveConfig(config); + var requestData = _config.data; + var requestHeaders = AxiosHeaders$1.from(_config.headers).normalize(); + var responseType = _config.responseType, + onUploadProgress = _config.onUploadProgress, + onDownloadProgress = _config.onDownloadProgress; + var onCanceled; + var uploadThrottled, downloadThrottled; + var flushUpload, flushDownload; + function done() { + flushUpload && flushUpload(); // flush events + flushDownload && flushDownload(); // flush events + + _config.cancelToken && _config.cancelToken.unsubscribe(onCanceled); + _config.signal && _config.signal.removeEventListener('abort', onCanceled); + } + var request = new XMLHttpRequest(); + request.open(_config.method.toUpperCase(), _config.url, true); + + // Set the request timeout in MS + request.timeout = _config.timeout; + function onloadend() { + if (!request) { + return; + } + // Prepare the response + var responseHeaders = AxiosHeaders$1.from('getAllResponseHeaders' in request && request.getAllResponseHeaders()); + var responseData = !responseType || responseType === 'text' || responseType === 'json' ? request.responseText : request.response; + var response = { + data: responseData, + status: request.status, + statusText: request.statusText, + headers: responseHeaders, + config: config, + request: request + }; + settle(function _resolve(value) { + resolve(value); + done(); + }, function _reject(err) { + reject(err); + done(); + }, response); + + // Clean up request + request = null; + } + if ('onloadend' in request) { + // Use onloadend if available + request.onloadend = onloadend; + } else { + // Listen for ready state to emulate onloadend + request.onreadystatechange = function handleLoad() { + if (!request || request.readyState !== 4) { + return; + } + + // The request errored out and we didn't get a response, this will be + // handled by onerror instead + // With one exception: request that using file: protocol, most browsers + // will return status as 0 even though it's a successful request + if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { + return; + } + // readystate handler is calling before onerror or ontimeout handlers, + // so we should call onloadend on the next 'tick' + setTimeout(onloadend); + }; + } + + // Handle browser request cancellation (as opposed to a manual cancellation) + request.onabort = function handleAbort() { + if (!request) { + return; + } + reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request)); + + // Clean up request + request = null; + }; + + // Handle low level network errors + request.onerror = function handleError() { + // Real errors are hidden from us by the browser + // onerror should only fire if it's a network error + reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request)); + + // Clean up request + request = null; + }; + + // Handle timeout + request.ontimeout = function handleTimeout() { + var timeoutErrorMessage = _config.timeout ? 'timeout of ' + _config.timeout + 'ms exceeded' : 'timeout exceeded'; + var transitional = _config.transitional || transitionalDefaults; + if (_config.timeoutErrorMessage) { + timeoutErrorMessage = _config.timeoutErrorMessage; + } + reject(new AxiosError(timeoutErrorMessage, transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, request)); + + // Clean up request + request = null; + }; + + // Remove Content-Type if data is undefined + requestData === undefined && requestHeaders.setContentType(null); + + // Add headers to the request + if ('setRequestHeader' in request) { + utils$1.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) { + request.setRequestHeader(key, val); + }); + } + + // Add withCredentials to request if needed + if (!utils$1.isUndefined(_config.withCredentials)) { + request.withCredentials = !!_config.withCredentials; + } + + // Add responseType to request if needed + if (responseType && responseType !== 'json') { + request.responseType = _config.responseType; + } + + // Handle progress if needed + if (onDownloadProgress) { + var _progressEventReducer = progressEventReducer(onDownloadProgress, true); + var _progressEventReducer2 = _slicedToArray(_progressEventReducer, 2); + downloadThrottled = _progressEventReducer2[0]; + flushDownload = _progressEventReducer2[1]; + request.addEventListener('progress', downloadThrottled); + } + + // Not all browsers support upload events + if (onUploadProgress && request.upload) { + var _progressEventReducer3 = progressEventReducer(onUploadProgress); + var _progressEventReducer4 = _slicedToArray(_progressEventReducer3, 2); + uploadThrottled = _progressEventReducer4[0]; + flushUpload = _progressEventReducer4[1]; + request.upload.addEventListener('progress', uploadThrottled); + request.upload.addEventListener('loadend', flushUpload); + } + if (_config.cancelToken || _config.signal) { + // Handle cancellation + // eslint-disable-next-line func-names + onCanceled = function onCanceled(cancel) { + if (!request) { + return; + } + reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel); + request.abort(); + request = null; + }; + _config.cancelToken && _config.cancelToken.subscribe(onCanceled); + if (_config.signal) { + _config.signal.aborted ? onCanceled() : _config.signal.addEventListener('abort', onCanceled); + } + } + var protocol = parseProtocol(_config.url); + if (protocol && platform.protocols.indexOf(protocol) === -1) { + reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config)); + return; + } + + // Send the request + request.send(requestData || null); + }); + }; + + var composeSignals = function composeSignals(signals, timeout) { + var _signals = signals = signals ? signals.filter(Boolean) : [], + length = _signals.length; + if (timeout || length) { + var controller = new AbortController(); + var aborted; + var onabort = function onabort(reason) { + if (!aborted) { + aborted = true; + unsubscribe(); + var err = reason instanceof Error ? reason : this.reason; + controller.abort(err instanceof AxiosError ? err : new CanceledError(err instanceof Error ? err.message : err)); + } + }; + var timer = timeout && setTimeout(function () { + timer = null; + onabort(new AxiosError("timeout ".concat(timeout, " of ms exceeded"), AxiosError.ETIMEDOUT)); + }, timeout); + var unsubscribe = function unsubscribe() { + if (signals) { + timer && clearTimeout(timer); + timer = null; + signals.forEach(function (signal) { + signal.unsubscribe ? signal.unsubscribe(onabort) : signal.removeEventListener('abort', onabort); + }); + signals = null; + } + }; + signals.forEach(function (signal) { + return signal.addEventListener('abort', onabort); + }); + var signal = controller.signal; + signal.unsubscribe = function () { + return utils$1.asap(unsubscribe); + }; + return signal; + } + }; + var composeSignals$1 = composeSignals; + + var streamChunk = /*#__PURE__*/_regeneratorRuntime().mark(function streamChunk(chunk, chunkSize) { + var len, pos, end; + return _regeneratorRuntime().wrap(function streamChunk$(_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + len = chunk.byteLength; + if (!(!chunkSize || len < chunkSize)) { + _context.next = 5; + break; + } + _context.next = 4; + return chunk; + case 4: + return _context.abrupt("return"); + case 5: + pos = 0; + case 6: + if (!(pos < len)) { + _context.next = 13; + break; + } + end = pos + chunkSize; + _context.next = 10; + return chunk.slice(pos, end); + case 10: + pos = end; + _context.next = 6; + break; + case 13: + case "end": + return _context.stop(); + } + }, streamChunk); + }); + var readBytes = /*#__PURE__*/function () { + var _ref = _wrapAsyncGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(iterable, chunkSize) { + var _iteratorAbruptCompletion, _didIteratorError, _iteratorError, _iterator, _step, chunk; + return _regeneratorRuntime().wrap(function _callee$(_context2) { + while (1) switch (_context2.prev = _context2.next) { + case 0: + _iteratorAbruptCompletion = false; + _didIteratorError = false; + _context2.prev = 2; + _iterator = _asyncIterator(readStream(iterable)); + case 4: + _context2.next = 6; + return _awaitAsyncGenerator(_iterator.next()); + case 6: + if (!(_iteratorAbruptCompletion = !(_step = _context2.sent).done)) { + _context2.next = 12; + break; + } + chunk = _step.value; + return _context2.delegateYield(_asyncGeneratorDelegate(_asyncIterator(streamChunk(chunk, chunkSize))), "t0", 9); + case 9: + _iteratorAbruptCompletion = false; + _context2.next = 4; + break; + case 12: + _context2.next = 18; + break; + case 14: + _context2.prev = 14; + _context2.t1 = _context2["catch"](2); + _didIteratorError = true; + _iteratorError = _context2.t1; + case 18: + _context2.prev = 18; + _context2.prev = 19; + if (!(_iteratorAbruptCompletion && _iterator["return"] != null)) { + _context2.next = 23; + break; + } + _context2.next = 23; + return _awaitAsyncGenerator(_iterator["return"]()); + case 23: + _context2.prev = 23; + if (!_didIteratorError) { + _context2.next = 26; + break; + } + throw _iteratorError; + case 26: + return _context2.finish(23); + case 27: + return _context2.finish(18); + case 28: + case "end": + return _context2.stop(); + } + }, _callee, null, [[2, 14, 18, 28], [19,, 23, 27]]); + })); + return function readBytes(_x, _x2) { + return _ref.apply(this, arguments); + }; + }(); + var readStream = /*#__PURE__*/function () { + var _ref2 = _wrapAsyncGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(stream) { + var reader, _yield$_awaitAsyncGen, done, value; + return _regeneratorRuntime().wrap(function _callee2$(_context3) { + while (1) switch (_context3.prev = _context3.next) { + case 0: + if (!stream[Symbol.asyncIterator]) { + _context3.next = 3; + break; + } + return _context3.delegateYield(_asyncGeneratorDelegate(_asyncIterator(stream)), "t0", 2); + case 2: + return _context3.abrupt("return"); + case 3: + reader = stream.getReader(); + _context3.prev = 4; + case 5: + _context3.next = 7; + return _awaitAsyncGenerator(reader.read()); + case 7: + _yield$_awaitAsyncGen = _context3.sent; + done = _yield$_awaitAsyncGen.done; + value = _yield$_awaitAsyncGen.value; + if (!done) { + _context3.next = 12; + break; + } + return _context3.abrupt("break", 16); + case 12: + _context3.next = 14; + return value; + case 14: + _context3.next = 5; + break; + case 16: + _context3.prev = 16; + _context3.next = 19; + return _awaitAsyncGenerator(reader.cancel()); + case 19: + return _context3.finish(16); + case 20: + case "end": + return _context3.stop(); + } + }, _callee2, null, [[4,, 16, 20]]); + })); + return function readStream(_x3) { + return _ref2.apply(this, arguments); + }; + }(); + var trackStream = function trackStream(stream, chunkSize, onProgress, onFinish) { + var iterator = readBytes(stream, chunkSize); + var bytes = 0; + var done; + var _onFinish = function _onFinish(e) { + if (!done) { + done = true; + onFinish && onFinish(e); + } + }; + return new ReadableStream({ + pull: function pull(controller) { + return _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3() { + var _yield$iterator$next, _done, value, len, loadedBytes; + return _regeneratorRuntime().wrap(function _callee3$(_context4) { + while (1) switch (_context4.prev = _context4.next) { + case 0: + _context4.prev = 0; + _context4.next = 3; + return iterator.next(); + case 3: + _yield$iterator$next = _context4.sent; + _done = _yield$iterator$next.done; + value = _yield$iterator$next.value; + if (!_done) { + _context4.next = 10; + break; + } + _onFinish(); + controller.close(); + return _context4.abrupt("return"); + case 10: + len = value.byteLength; + if (onProgress) { + loadedBytes = bytes += len; + onProgress(loadedBytes); + } + controller.enqueue(new Uint8Array(value)); + _context4.next = 19; + break; + case 15: + _context4.prev = 15; + _context4.t0 = _context4["catch"](0); + _onFinish(_context4.t0); + throw _context4.t0; + case 19: + case "end": + return _context4.stop(); + } + }, _callee3, null, [[0, 15]]); + }))(); + }, + cancel: function cancel(reason) { + _onFinish(reason); + return iterator["return"](); + } + }, { + highWaterMark: 2 + }); + }; + + var isFetchSupported = typeof fetch === 'function' && typeof Request === 'function' && typeof Response === 'function'; + var isReadableStreamSupported = isFetchSupported && typeof ReadableStream === 'function'; + + // used only inside the fetch adapter + var encodeText = isFetchSupported && (typeof TextEncoder === 'function' ? function (encoder) { + return function (str) { + return encoder.encode(str); + }; + }(new TextEncoder()) : ( /*#__PURE__*/function () { + var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(str) { + return _regeneratorRuntime().wrap(function _callee$(_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + _context.t0 = Uint8Array; + _context.next = 3; + return new Response(str).arrayBuffer(); + case 3: + _context.t1 = _context.sent; + return _context.abrupt("return", new _context.t0(_context.t1)); + case 5: + case "end": + return _context.stop(); + } + }, _callee); + })); + return function (_x) { + return _ref.apply(this, arguments); + }; + }())); + var test = function test(fn) { + try { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + return !!fn.apply(void 0, args); + } catch (e) { + return false; + } + }; + var supportsRequestStream = isReadableStreamSupported && test(function () { + var duplexAccessed = false; + var hasContentType = new Request(platform.origin, { + body: new ReadableStream(), + method: 'POST', + get duplex() { + duplexAccessed = true; + return 'half'; + } + }).headers.has('Content-Type'); + return duplexAccessed && !hasContentType; + }); + var DEFAULT_CHUNK_SIZE = 64 * 1024; + var supportsResponseStream = isReadableStreamSupported && test(function () { + return utils$1.isReadableStream(new Response('').body); + }); + var resolvers = { + stream: supportsResponseStream && function (res) { + return res.body; + } + }; + isFetchSupported && function (res) { + ['text', 'arrayBuffer', 'blob', 'formData', 'stream'].forEach(function (type) { + !resolvers[type] && (resolvers[type] = utils$1.isFunction(res[type]) ? function (res) { + return res[type](); + } : function (_, config) { + throw new AxiosError("Response type '".concat(type, "' is not supported"), AxiosError.ERR_NOT_SUPPORT, config); + }); + }); + }(new Response()); + var getBodyLength = /*#__PURE__*/function () { + var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(body) { + var _request; + return _regeneratorRuntime().wrap(function _callee2$(_context2) { + while (1) switch (_context2.prev = _context2.next) { + case 0: + if (!(body == null)) { + _context2.next = 2; + break; + } + return _context2.abrupt("return", 0); + case 2: + if (!utils$1.isBlob(body)) { + _context2.next = 4; + break; + } + return _context2.abrupt("return", body.size); + case 4: + if (!utils$1.isSpecCompliantForm(body)) { + _context2.next = 9; + break; + } + _request = new Request(platform.origin, { + method: 'POST', + body: body + }); + _context2.next = 8; + return _request.arrayBuffer(); + case 8: + return _context2.abrupt("return", _context2.sent.byteLength); + case 9: + if (!(utils$1.isArrayBufferView(body) || utils$1.isArrayBuffer(body))) { + _context2.next = 11; + break; + } + return _context2.abrupt("return", body.byteLength); + case 11: + if (utils$1.isURLSearchParams(body)) { + body = body + ''; + } + if (!utils$1.isString(body)) { + _context2.next = 16; + break; + } + _context2.next = 15; + return encodeText(body); + case 15: + return _context2.abrupt("return", _context2.sent.byteLength); + case 16: + case "end": + return _context2.stop(); + } + }, _callee2); + })); + return function getBodyLength(_x2) { + return _ref2.apply(this, arguments); + }; + }(); + var resolveBodyLength = /*#__PURE__*/function () { + var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(headers, body) { + var length; + return _regeneratorRuntime().wrap(function _callee3$(_context3) { + while (1) switch (_context3.prev = _context3.next) { + case 0: + length = utils$1.toFiniteNumber(headers.getContentLength()); + return _context3.abrupt("return", length == null ? getBodyLength(body) : length); + case 2: + case "end": + return _context3.stop(); + } + }, _callee3); + })); + return function resolveBodyLength(_x3, _x4) { + return _ref3.apply(this, arguments); + }; + }(); + var fetchAdapter = isFetchSupported && ( /*#__PURE__*/function () { + var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(config) { + var _resolveConfig, url, method, data, signal, cancelToken, timeout, onDownloadProgress, onUploadProgress, responseType, headers, _resolveConfig$withCr, withCredentials, fetchOptions, composedSignal, request, unsubscribe, requestContentLength, _request, contentTypeHeader, _progressEventDecorat, _progressEventDecorat2, onProgress, flush, isCredentialsSupported, response, isStreamResponse, options, responseContentLength, _ref5, _ref6, _onProgress, _flush, responseData; + return _regeneratorRuntime().wrap(function _callee4$(_context4) { + while (1) switch (_context4.prev = _context4.next) { + case 0: + _resolveConfig = resolveConfig(config), url = _resolveConfig.url, method = _resolveConfig.method, data = _resolveConfig.data, signal = _resolveConfig.signal, cancelToken = _resolveConfig.cancelToken, timeout = _resolveConfig.timeout, onDownloadProgress = _resolveConfig.onDownloadProgress, onUploadProgress = _resolveConfig.onUploadProgress, responseType = _resolveConfig.responseType, headers = _resolveConfig.headers, _resolveConfig$withCr = _resolveConfig.withCredentials, withCredentials = _resolveConfig$withCr === void 0 ? 'same-origin' : _resolveConfig$withCr, fetchOptions = _resolveConfig.fetchOptions; + responseType = responseType ? (responseType + '').toLowerCase() : 'text'; + composedSignal = composeSignals$1([signal, cancelToken && cancelToken.toAbortSignal()], timeout); + unsubscribe = composedSignal && composedSignal.unsubscribe && function () { + composedSignal.unsubscribe(); + }; + _context4.prev = 4; + _context4.t0 = onUploadProgress && supportsRequestStream && method !== 'get' && method !== 'head'; + if (!_context4.t0) { + _context4.next = 11; + break; + } + _context4.next = 9; + return resolveBodyLength(headers, data); + case 9: + _context4.t1 = requestContentLength = _context4.sent; + _context4.t0 = _context4.t1 !== 0; + case 11: + if (!_context4.t0) { + _context4.next = 15; + break; + } + _request = new Request(url, { + method: 'POST', + body: data, + duplex: "half" + }); + if (utils$1.isFormData(data) && (contentTypeHeader = _request.headers.get('content-type'))) { + headers.setContentType(contentTypeHeader); + } + if (_request.body) { + _progressEventDecorat = progressEventDecorator(requestContentLength, progressEventReducer(asyncDecorator(onUploadProgress))), _progressEventDecorat2 = _slicedToArray(_progressEventDecorat, 2), onProgress = _progressEventDecorat2[0], flush = _progressEventDecorat2[1]; + data = trackStream(_request.body, DEFAULT_CHUNK_SIZE, onProgress, flush); + } + case 15: + if (!utils$1.isString(withCredentials)) { + withCredentials = withCredentials ? 'include' : 'omit'; + } + + // Cloudflare Workers throws when credentials are defined + // see https://github.com/cloudflare/workerd/issues/902 + isCredentialsSupported = "credentials" in Request.prototype; + request = new Request(url, _objectSpread2(_objectSpread2({}, fetchOptions), {}, { + signal: composedSignal, + method: method.toUpperCase(), + headers: headers.normalize().toJSON(), + body: data, + duplex: "half", + credentials: isCredentialsSupported ? withCredentials : undefined + })); + _context4.next = 20; + return fetch(request); + case 20: + response = _context4.sent; + isStreamResponse = supportsResponseStream && (responseType === 'stream' || responseType === 'response'); + if (supportsResponseStream && (onDownloadProgress || isStreamResponse && unsubscribe)) { + options = {}; + ['status', 'statusText', 'headers'].forEach(function (prop) { + options[prop] = response[prop]; + }); + responseContentLength = utils$1.toFiniteNumber(response.headers.get('content-length')); + _ref5 = onDownloadProgress && progressEventDecorator(responseContentLength, progressEventReducer(asyncDecorator(onDownloadProgress), true)) || [], _ref6 = _slicedToArray(_ref5, 2), _onProgress = _ref6[0], _flush = _ref6[1]; + response = new Response(trackStream(response.body, DEFAULT_CHUNK_SIZE, _onProgress, function () { + _flush && _flush(); + unsubscribe && unsubscribe(); + }), options); + } + responseType = responseType || 'text'; + _context4.next = 26; + return resolvers[utils$1.findKey(resolvers, responseType) || 'text'](response, config); + case 26: + responseData = _context4.sent; + !isStreamResponse && unsubscribe && unsubscribe(); + _context4.next = 30; + return new Promise(function (resolve, reject) { + settle(resolve, reject, { + data: responseData, + headers: AxiosHeaders$1.from(response.headers), + status: response.status, + statusText: response.statusText, + config: config, + request: request + }); + }); + case 30: + return _context4.abrupt("return", _context4.sent); + case 33: + _context4.prev = 33; + _context4.t2 = _context4["catch"](4); + unsubscribe && unsubscribe(); + if (!(_context4.t2 && _context4.t2.name === 'TypeError' && /fetch/i.test(_context4.t2.message))) { + _context4.next = 38; + break; + } + throw Object.assign(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request), { + cause: _context4.t2.cause || _context4.t2 + }); + case 38: + throw AxiosError.from(_context4.t2, _context4.t2 && _context4.t2.code, config, request); + case 39: + case "end": + return _context4.stop(); + } + }, _callee4, null, [[4, 33]]); + })); + return function (_x5) { + return _ref4.apply(this, arguments); + }; + }()); + + var knownAdapters = { + http: httpAdapter, + xhr: xhrAdapter, + fetch: fetchAdapter + }; + utils$1.forEach(knownAdapters, function (fn, value) { + if (fn) { + try { + Object.defineProperty(fn, 'name', { + value: value + }); + } catch (e) { + // eslint-disable-next-line no-empty + } + Object.defineProperty(fn, 'adapterName', { + value: value + }); + } + }); + var renderReason = function renderReason(reason) { + return "- ".concat(reason); + }; + var isResolvedHandle = function isResolvedHandle(adapter) { + return utils$1.isFunction(adapter) || adapter === null || adapter === false; + }; + var adapters = { + getAdapter: function getAdapter(adapters) { + adapters = utils$1.isArray(adapters) ? adapters : [adapters]; + var _adapters = adapters, + length = _adapters.length; + var nameOrAdapter; + var adapter; + var rejectedReasons = {}; + for (var i = 0; i < length; i++) { + nameOrAdapter = adapters[i]; + var id = void 0; + adapter = nameOrAdapter; + if (!isResolvedHandle(nameOrAdapter)) { + adapter = knownAdapters[(id = String(nameOrAdapter)).toLowerCase()]; + if (adapter === undefined) { + throw new AxiosError("Unknown adapter '".concat(id, "'")); + } + } + if (adapter) { + break; + } + rejectedReasons[id || '#' + i] = adapter; + } + if (!adapter) { + var reasons = Object.entries(rejectedReasons).map(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + id = _ref2[0], + state = _ref2[1]; + return "adapter ".concat(id, " ") + (state === false ? 'is not supported by the environment' : 'is not available in the build'); + }); + var s = length ? reasons.length > 1 ? 'since :\n' + reasons.map(renderReason).join('\n') : ' ' + renderReason(reasons[0]) : 'as no adapter specified'; + throw new AxiosError("There is no suitable adapter to dispatch the request " + s, 'ERR_NOT_SUPPORT'); + } + return adapter; + }, + adapters: knownAdapters + }; + + /** + * Throws a `CanceledError` if cancellation has been requested. + * + * @param {Object} config The config that is to be used for the request + * + * @returns {void} + */ + function throwIfCancellationRequested(config) { + if (config.cancelToken) { + config.cancelToken.throwIfRequested(); + } + if (config.signal && config.signal.aborted) { + throw new CanceledError(null, config); + } + } + + /** + * Dispatch a request to the server using the configured adapter. + * + * @param {object} config The config that is to be used for the request + * + * @returns {Promise} The Promise to be fulfilled + */ + function dispatchRequest(config) { + throwIfCancellationRequested(config); + config.headers = AxiosHeaders$1.from(config.headers); + + // Transform request data + config.data = transformData.call(config, config.transformRequest); + if (['post', 'put', 'patch'].indexOf(config.method) !== -1) { + config.headers.setContentType('application/x-www-form-urlencoded', false); + } + var adapter = adapters.getAdapter(config.adapter || defaults$1.adapter); + return adapter(config).then(function onAdapterResolution(response) { + throwIfCancellationRequested(config); + + // Transform response data + response.data = transformData.call(config, config.transformResponse, response); + response.headers = AxiosHeaders$1.from(response.headers); + return response; + }, function onAdapterRejection(reason) { + if (!isCancel(reason)) { + throwIfCancellationRequested(config); + + // Transform response data + if (reason && reason.response) { + reason.response.data = transformData.call(config, config.transformResponse, reason.response); + reason.response.headers = AxiosHeaders$1.from(reason.response.headers); + } + } + return Promise.reject(reason); + }); + } + + var VERSION = "1.7.9"; + + var validators$1 = {}; + + // eslint-disable-next-line func-names + ['object', 'boolean', 'number', 'function', 'string', 'symbol'].forEach(function (type, i) { + validators$1[type] = function validator(thing) { + return _typeof(thing) === type || 'a' + (i < 1 ? 'n ' : ' ') + type; + }; + }); + var deprecatedWarnings = {}; + + /** + * Transitional option validator + * + * @param {function|boolean?} validator - set to false if the transitional option has been removed + * @param {string?} version - deprecated version / removed since version + * @param {string?} message - some message with additional info + * + * @returns {function} + */ + validators$1.transitional = function transitional(validator, version, message) { + function formatMessage(opt, desc) { + return '[Axios v' + VERSION + '] Transitional option \'' + opt + '\'' + desc + (message ? '. ' + message : ''); + } + + // eslint-disable-next-line func-names + return function (value, opt, opts) { + if (validator === false) { + throw new AxiosError(formatMessage(opt, ' has been removed' + (version ? ' in ' + version : '')), AxiosError.ERR_DEPRECATED); + } + if (version && !deprecatedWarnings[opt]) { + deprecatedWarnings[opt] = true; + // eslint-disable-next-line no-console + console.warn(formatMessage(opt, ' has been deprecated since v' + version + ' and will be removed in the near future')); + } + return validator ? validator(value, opt, opts) : true; + }; + }; + validators$1.spelling = function spelling(correctSpelling) { + return function (value, opt) { + // eslint-disable-next-line no-console + console.warn("".concat(opt, " is likely a misspelling of ").concat(correctSpelling)); + return true; + }; + }; + + /** + * Assert object's properties type + * + * @param {object} options + * @param {object} schema + * @param {boolean?} allowUnknown + * + * @returns {object} + */ + + function assertOptions(options, schema, allowUnknown) { + if (_typeof(options) !== 'object') { + throw new AxiosError('options must be an object', AxiosError.ERR_BAD_OPTION_VALUE); + } + var keys = Object.keys(options); + var i = keys.length; + while (i-- > 0) { + var opt = keys[i]; + var validator = schema[opt]; + if (validator) { + var value = options[opt]; + var result = value === undefined || validator(value, opt, options); + if (result !== true) { + throw new AxiosError('option ' + opt + ' must be ' + result, AxiosError.ERR_BAD_OPTION_VALUE); + } + continue; + } + if (allowUnknown !== true) { + throw new AxiosError('Unknown option ' + opt, AxiosError.ERR_BAD_OPTION); + } + } + } + var validator = { + assertOptions: assertOptions, + validators: validators$1 + }; + + var validators = validator.validators; + + /** + * Create a new instance of Axios + * + * @param {Object} instanceConfig The default config for the instance + * + * @return {Axios} A new instance of Axios + */ + var Axios = /*#__PURE__*/function () { + function Axios(instanceConfig) { + _classCallCheck(this, Axios); + this.defaults = instanceConfig; + this.interceptors = { + request: new InterceptorManager$1(), + response: new InterceptorManager$1() + }; + } + + /** + * Dispatch a request + * + * @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults) + * @param {?Object} config + * + * @returns {Promise} The Promise to be fulfilled + */ + _createClass(Axios, [{ + key: "request", + value: (function () { + var _request2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(configOrUrl, config) { + var dummy, stack; + return _regeneratorRuntime().wrap(function _callee$(_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + _context.prev = 0; + _context.next = 3; + return this._request(configOrUrl, config); + case 3: + return _context.abrupt("return", _context.sent); + case 6: + _context.prev = 6; + _context.t0 = _context["catch"](0); + if (_context.t0 instanceof Error) { + dummy = {}; + Error.captureStackTrace ? Error.captureStackTrace(dummy) : dummy = new Error(); + + // slice off the Error: ... line + stack = dummy.stack ? dummy.stack.replace(/^.+\n/, '') : ''; + try { + if (!_context.t0.stack) { + _context.t0.stack = stack; + // match without the 2 top stack lines + } else if (stack && !String(_context.t0.stack).endsWith(stack.replace(/^.+\n.+\n/, ''))) { + _context.t0.stack += '\n' + stack; + } + } catch (e) { + // ignore the case where "stack" is an un-writable property + } + } + throw _context.t0; + case 10: + case "end": + return _context.stop(); + } + }, _callee, this, [[0, 6]]); + })); + function request(_x, _x2) { + return _request2.apply(this, arguments); + } + return request; + }()) + }, { + key: "_request", + value: function _request(configOrUrl, config) { + /*eslint no-param-reassign:0*/ + // Allow for axios('example/url'[, config]) a la fetch API + if (typeof configOrUrl === 'string') { + config = config || {}; + config.url = configOrUrl; + } else { + config = configOrUrl || {}; + } + config = mergeConfig(this.defaults, config); + var _config = config, + transitional = _config.transitional, + paramsSerializer = _config.paramsSerializer, + headers = _config.headers; + if (transitional !== undefined) { + validator.assertOptions(transitional, { + silentJSONParsing: validators.transitional(validators["boolean"]), + forcedJSONParsing: validators.transitional(validators["boolean"]), + clarifyTimeoutError: validators.transitional(validators["boolean"]) + }, false); + } + if (paramsSerializer != null) { + if (utils$1.isFunction(paramsSerializer)) { + config.paramsSerializer = { + serialize: paramsSerializer + }; + } else { + validator.assertOptions(paramsSerializer, { + encode: validators["function"], + serialize: validators["function"] + }, true); + } + } + validator.assertOptions(config, { + baseUrl: validators.spelling('baseURL'), + withXsrfToken: validators.spelling('withXSRFToken') + }, true); + + // Set config.method + config.method = (config.method || this.defaults.method || 'get').toLowerCase(); + + // Flatten headers + var contextHeaders = headers && utils$1.merge(headers.common, headers[config.method]); + headers && utils$1.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], function (method) { + delete headers[method]; + }); + config.headers = AxiosHeaders$1.concat(contextHeaders, headers); + + // filter out skipped interceptors + var requestInterceptorChain = []; + var synchronousRequestInterceptors = true; + this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { + if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) { + return; + } + synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous; + requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); + }); + var responseInterceptorChain = []; + this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { + responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); + }); + var promise; + var i = 0; + var len; + if (!synchronousRequestInterceptors) { + var chain = [dispatchRequest.bind(this), undefined]; + chain.unshift.apply(chain, requestInterceptorChain); + chain.push.apply(chain, responseInterceptorChain); + len = chain.length; + promise = Promise.resolve(config); + while (i < len) { + promise = promise.then(chain[i++], chain[i++]); + } + return promise; + } + len = requestInterceptorChain.length; + var newConfig = config; + i = 0; + while (i < len) { + var onFulfilled = requestInterceptorChain[i++]; + var onRejected = requestInterceptorChain[i++]; + try { + newConfig = onFulfilled(newConfig); + } catch (error) { + onRejected.call(this, error); + break; + } + } + try { + promise = dispatchRequest.call(this, newConfig); + } catch (error) { + return Promise.reject(error); + } + i = 0; + len = responseInterceptorChain.length; + while (i < len) { + promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]); + } + return promise; + } + }, { + key: "getUri", + value: function getUri(config) { + config = mergeConfig(this.defaults, config); + var fullPath = buildFullPath(config.baseURL, config.url); + return buildURL(fullPath, config.params, config.paramsSerializer); + } + }]); + return Axios; + }(); // Provide aliases for supported request methods + utils$1.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { + /*eslint func-names:0*/ + Axios.prototype[method] = function (url, config) { + return this.request(mergeConfig(config || {}, { + method: method, + url: url, + data: (config || {}).data + })); + }; + }); + utils$1.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { + /*eslint func-names:0*/ + + function generateHTTPMethod(isForm) { + return function httpMethod(url, data, config) { + return this.request(mergeConfig(config || {}, { + method: method, + headers: isForm ? { + 'Content-Type': 'multipart/form-data' + } : {}, + url: url, + data: data + })); + }; + } + Axios.prototype[method] = generateHTTPMethod(); + Axios.prototype[method + 'Form'] = generateHTTPMethod(true); + }); + var Axios$1 = Axios; + + /** + * A `CancelToken` is an object that can be used to request cancellation of an operation. + * + * @param {Function} executor The executor function. + * + * @returns {CancelToken} + */ + var CancelToken = /*#__PURE__*/function () { + function CancelToken(executor) { + _classCallCheck(this, CancelToken); + if (typeof executor !== 'function') { + throw new TypeError('executor must be a function.'); + } + var resolvePromise; + this.promise = new Promise(function promiseExecutor(resolve) { + resolvePromise = resolve; + }); + var token = this; + + // eslint-disable-next-line func-names + this.promise.then(function (cancel) { + if (!token._listeners) return; + var i = token._listeners.length; + while (i-- > 0) { + token._listeners[i](cancel); + } + token._listeners = null; + }); + + // eslint-disable-next-line func-names + this.promise.then = function (onfulfilled) { + var _resolve; + // eslint-disable-next-line func-names + var promise = new Promise(function (resolve) { + token.subscribe(resolve); + _resolve = resolve; + }).then(onfulfilled); + promise.cancel = function reject() { + token.unsubscribe(_resolve); + }; + return promise; + }; + executor(function cancel(message, config, request) { + if (token.reason) { + // Cancellation has already been requested + return; + } + token.reason = new CanceledError(message, config, request); + resolvePromise(token.reason); + }); + } + + /** + * Throws a `CanceledError` if cancellation has been requested. + */ + _createClass(CancelToken, [{ + key: "throwIfRequested", + value: function throwIfRequested() { + if (this.reason) { + throw this.reason; + } + } + + /** + * Subscribe to the cancel signal + */ + }, { + key: "subscribe", + value: function subscribe(listener) { + if (this.reason) { + listener(this.reason); + return; + } + if (this._listeners) { + this._listeners.push(listener); + } else { + this._listeners = [listener]; + } + } + + /** + * Unsubscribe from the cancel signal + */ + }, { + key: "unsubscribe", + value: function unsubscribe(listener) { + if (!this._listeners) { + return; + } + var index = this._listeners.indexOf(listener); + if (index !== -1) { + this._listeners.splice(index, 1); + } + } + }, { + key: "toAbortSignal", + value: function toAbortSignal() { + var _this = this; + var controller = new AbortController(); + var abort = function abort(err) { + controller.abort(err); + }; + this.subscribe(abort); + controller.signal.unsubscribe = function () { + return _this.unsubscribe(abort); + }; + return controller.signal; + } + + /** + * Returns an object that contains a new `CancelToken` and a function that, when called, + * cancels the `CancelToken`. + */ + }], [{ + key: "source", + value: function source() { + var cancel; + var token = new CancelToken(function executor(c) { + cancel = c; + }); + return { + token: token, + cancel: cancel + }; + } + }]); + return CancelToken; + }(); + var CancelToken$1 = CancelToken; + + /** + * Syntactic sugar for invoking a function and expanding an array for arguments. + * + * Common use case would be to use `Function.prototype.apply`. + * + * ```js + * function f(x, y, z) {} + * var args = [1, 2, 3]; + * f.apply(null, args); + * ``` + * + * With `spread` this example can be re-written. + * + * ```js + * spread(function(x, y, z) {})([1, 2, 3]); + * ``` + * + * @param {Function} callback + * + * @returns {Function} + */ + function spread(callback) { + return function wrap(arr) { + return callback.apply(null, arr); + }; + } + + /** + * Determines whether the payload is an error thrown by Axios + * + * @param {*} payload The value to test + * + * @returns {boolean} True if the payload is an error thrown by Axios, otherwise false + */ + function isAxiosError(payload) { + return utils$1.isObject(payload) && payload.isAxiosError === true; + } + + var HttpStatusCode = { + Continue: 100, + SwitchingProtocols: 101, + Processing: 102, + EarlyHints: 103, + Ok: 200, + Created: 201, + Accepted: 202, + NonAuthoritativeInformation: 203, + NoContent: 204, + ResetContent: 205, + PartialContent: 206, + MultiStatus: 207, + AlreadyReported: 208, + ImUsed: 226, + MultipleChoices: 300, + MovedPermanently: 301, + Found: 302, + SeeOther: 303, + NotModified: 304, + UseProxy: 305, + Unused: 306, + TemporaryRedirect: 307, + PermanentRedirect: 308, + BadRequest: 400, + Unauthorized: 401, + PaymentRequired: 402, + Forbidden: 403, + NotFound: 404, + MethodNotAllowed: 405, + NotAcceptable: 406, + ProxyAuthenticationRequired: 407, + RequestTimeout: 408, + Conflict: 409, + Gone: 410, + LengthRequired: 411, + PreconditionFailed: 412, + PayloadTooLarge: 413, + UriTooLong: 414, + UnsupportedMediaType: 415, + RangeNotSatisfiable: 416, + ExpectationFailed: 417, + ImATeapot: 418, + MisdirectedRequest: 421, + UnprocessableEntity: 422, + Locked: 423, + FailedDependency: 424, + TooEarly: 425, + UpgradeRequired: 426, + PreconditionRequired: 428, + TooManyRequests: 429, + RequestHeaderFieldsTooLarge: 431, + UnavailableForLegalReasons: 451, + InternalServerError: 500, + NotImplemented: 501, + BadGateway: 502, + ServiceUnavailable: 503, + GatewayTimeout: 504, + HttpVersionNotSupported: 505, + VariantAlsoNegotiates: 506, + InsufficientStorage: 507, + LoopDetected: 508, + NotExtended: 510, + NetworkAuthenticationRequired: 511 + }; + Object.entries(HttpStatusCode).forEach(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + key = _ref2[0], + value = _ref2[1]; + HttpStatusCode[value] = key; + }); + var HttpStatusCode$1 = HttpStatusCode; + + /** + * Create an instance of Axios + * + * @param {Object} defaultConfig The default config for the instance + * + * @returns {Axios} A new instance of Axios + */ + function createInstance(defaultConfig) { + var context = new Axios$1(defaultConfig); + var instance = bind(Axios$1.prototype.request, context); + + // Copy axios.prototype to instance + utils$1.extend(instance, Axios$1.prototype, context, { + allOwnKeys: true + }); + + // Copy context to instance + utils$1.extend(instance, context, null, { + allOwnKeys: true + }); + + // Factory for creating new instances + instance.create = function create(instanceConfig) { + return createInstance(mergeConfig(defaultConfig, instanceConfig)); + }; + return instance; + } + + // Create the default instance to be exported + var axios = createInstance(defaults$1); + + // Expose Axios class to allow class inheritance + axios.Axios = Axios$1; + + // Expose Cancel & CancelToken + axios.CanceledError = CanceledError; + axios.CancelToken = CancelToken$1; + axios.isCancel = isCancel; + axios.VERSION = VERSION; + axios.toFormData = toFormData; + + // Expose AxiosError class + axios.AxiosError = AxiosError; + + // alias for CanceledError for backward compatibility + axios.Cancel = axios.CanceledError; + + // Expose all/spread + axios.all = function all(promises) { + return Promise.all(promises); + }; + axios.spread = spread; + + // Expose isAxiosError + axios.isAxiosError = isAxiosError; + + // Expose mergeConfig + axios.mergeConfig = mergeConfig; + axios.AxiosHeaders = AxiosHeaders$1; + axios.formToJSON = function (thing) { + return formDataToJSON(utils$1.isHTMLForm(thing) ? new FormData(thing) : thing); + }; + axios.getAdapter = adapters.getAdapter; + axios.HttpStatusCode = HttpStatusCode$1; + axios["default"] = axios; + + return axios; + +})); +//# sourceMappingURL=axios.js.map