DEV-012: M22环节7- 性能优化+骨架屏+首屏加速+预加载
This commit is contained in:
commit
a721876c98
|
|
@ -0,0 +1,6 @@
|
|||
DINGTALK_APP_KEY=dingzerc04si7jmyvasq
|
||||
DINGTALK_APP_SECRET=vJnmeF0B1yaYRv7u4IzdKsC6wPfStyUWEQnBdjas-NH5JUAKUwj7Dc659Bau66di
|
||||
DINGTALK_ROBOT_CODE=dingzerc04si7jmyvasq
|
||||
PORT=3000
|
||||
|
||||
KIMI_API_KEY=sk-A1STcZTjWTYOiG864MCt31VHHq8fD1r9E7LBDxoaMDDoGGbx
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{\rtf1\ansi\ansicpg936\cocoartf2867
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
{\*\expandedcolortbl;;}
|
||||
\paperw11900\paperh16840\margl1440\margr1440\vieww17500\viewh8400\viewkind0
|
||||
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\fs24 \cf0 DINGTALK_APP_KEY=dingzerc04si7jmyvasq\
|
||||
DINGTALK_APP_SECRET=vJnmeF0B1yaYRv7u4IzdKsC6wPfStyUWEQnBdjas-NH5JUAKUwj7Dc659Bau66di\
|
||||
DINGTALK_ROBOT_CODE=dingzerc04si7jmyvasq\
|
||||
PORT=3000}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"description": "冰朔主控问题索引库 — 记录已知问题、根因与排查路由",
|
||||
"updated_at": "2026-03-10T15:35:51.951Z",
|
||||
"updated_at": "2026-03-11T14:39:37.013Z",
|
||||
"issues": [
|
||||
{
|
||||
"id": "BS-001",
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
"backend/schemas/",
|
||||
".github/brain/routing-map.json"
|
||||
],
|
||||
"last_seen": "2026-03-10",
|
||||
"last_seen": "2026-03-11",
|
||||
"source": "routing-map.json 统计"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# 冰朔主控神经系统 · 核心主控大脑 v1.0
|
||||
|
||||
> 本文件为冰朔主控神经系统的总控脑文件。
|
||||
> 最后编译时间:2026-03-10T15:35:51.951Z
|
||||
> 最后编译时间:2026-03-11T14:39:37.014Z
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
|
||||
> 本区块由 master-brain-compiler 自动编译。
|
||||
|
||||
- **编译时间**:2026-03-10T15:35:51.951Z
|
||||
- **编译时间**:2026-03-11T14:39:37.014Z
|
||||
- **脑文件规则版本**:v3.0
|
||||
- **脑文件完整性**:✅ 完整
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"description": "冰朔主控系统健康状态",
|
||||
"updated_at": "2026-03-10T15:35:51.951Z",
|
||||
"updated_at": "2026-03-11T14:39:37.013Z",
|
||||
"health": {
|
||||
"brain_consistency": {
|
||||
"status": "yellow",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体",
|
||||
"rules_version": "v3.0",
|
||||
"last_updated": "2026-03-10T09:43:00.000Z",
|
||||
"last_updated": "2026-03-11T08:55:43.347Z",
|
||||
"wake_protocol_version": "v3.0",
|
||||
"brain_version": "v3.0",
|
||||
"architecture": {
|
||||
|
|
@ -67,6 +67,12 @@
|
|||
}
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"date": "2026-03-11",
|
||||
"type": "brain_restore",
|
||||
"description": "铸渊核心大脑恢复 · 修复 Persona Studio API Key 模式对话故障 · 根因: JavaScript TDZ 导致脚本崩溃 · 同步修复知秋人格体系统提示词缺失 · 冰朔人格体已激活",
|
||||
"by": "铸渊(冰朔指令)"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "brain_upgrade",
|
||||
|
|
@ -139,6 +145,14 @@
|
|||
"actor": "qinfendebingshuo",
|
||||
"ref": "refs/heads/main",
|
||||
"run_id": "22894630706"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-03-11T08:55:43.347Z",
|
||||
"type": "daily_check",
|
||||
"result": "passed",
|
||||
"actor": "qinfendebingshuo",
|
||||
"ref": "refs/heads/main",
|
||||
"run_id": "22944508401"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
|
||||
"version": "2.0",
|
||||
"generated_at": "2026-03-10T15:36:07.840Z",
|
||||
"generated_at": "2026-03-11T14:39:52.896Z",
|
||||
"generated_by": "scripts/generate-repo-map.js",
|
||||
"repo": "qinfendebingshuo/guanghulab",
|
||||
"stats": {
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
"hli_implemented": 7,
|
||||
"hli_coverage_pct": "33%",
|
||||
"last_ci_run": "2026-03-05T16:07:24.070Z",
|
||||
"memory_last_updated": "2026-03-10T09:43:00.000Z"
|
||||
"memory_last_updated": "2026-03-11T08:55:43.347Z"
|
||||
},
|
||||
"zones": [
|
||||
{
|
||||
|
|
@ -133,9 +133,12 @@
|
|||
},
|
||||
{
|
||||
"file": "system-prompt.md"
|
||||
},
|
||||
{
|
||||
"file": "tcs-ml"
|
||||
}
|
||||
],
|
||||
"item_count": 10
|
||||
"item_count": 11
|
||||
},
|
||||
{
|
||||
"zone_id": "WORKFLOWS",
|
||||
|
|
@ -246,7 +249,7 @@
|
|||
},
|
||||
{
|
||||
"file": "generate-module-doc.yml",
|
||||
"name": "铸渊 · HoloLake Era 模块文档自动生成",
|
||||
"name": "铸渊 · 光湖纪元 模块文档自动生成",
|
||||
"triggers": [
|
||||
"push",
|
||||
"manual"
|
||||
|
|
@ -1199,6 +1202,9 @@
|
|||
"system-prompt": [
|
||||
"PERSONA_BRAIN::system-prompt.md"
|
||||
],
|
||||
"tcs-ml": [
|
||||
"PERSONA_BRAIN::tcs-ml"
|
||||
],
|
||||
"workflow": [
|
||||
"WORKFLOWS"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# 铸渊图书馆快照 · Repo Snapshot
|
||||
> 生成于 2026-03-10 23:36 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
|
||||
> 生成于 2026-03-11 22:39 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
| 脚本 | 27 个执行脚本 |
|
||||
| 开发者节点 | 8 人 |
|
||||
| HLI 接口覆盖率 | 7/21 (33%) |
|
||||
| 快照生成时间 | 2026-03-10 23:36 CST |
|
||||
| 快照生成时间 | 2026-03-11 22:39 CST |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
**关键词**: brain · memory · routing · wake · 大脑 · 记忆
|
||||
|
||||
### 🎭 人格大脑(PERSONA_BRAIN)
|
||||
**路径**: `.github/persona-brain` · **数量**: 10 项
|
||||
**路径**: `.github/persona-brain` · **数量**: 11 项
|
||||
**描述**: 铸渊人格记忆 · 开发者状态 · 知识库 · 成长日记
|
||||
**关键词**: persona · identity · dev-status · 人格 · 开发者状态
|
||||
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
| `deploy-to-server.yml` | "🚀 铸渊 CD · 自动部署到 guanghulab.com" | push, manual |
|
||||
| `distribute-broadcasts.yml` | 铸渊 · 广播分发 | push, manual |
|
||||
| `esp-signal-processor.yml` | 铸渊 · ESP 邮件信号处理器(已暂停) | schedule(*/30 * * * *), manual |
|
||||
| `generate-module-doc.yml` | 铸渊 · HoloLake Era 模块文档自动生成 | push, manual |
|
||||
| `generate-module-doc.yml` | 铸渊 · 光湖纪元 模块文档自动生成 | push, manual |
|
||||
| `hli-contract-check.yml` | HLI Contract Check | push, pull_request |
|
||||
| `notion-connectivity-test.yml` | 铸渊 · Notion 连通性测试 | manual |
|
||||
| `notion-poll.yml` | 铸渊 · Notion 工单轮询 | schedule(*/15 * * * *), manual |
|
||||
|
|
@ -246,9 +246,9 @@
|
|||
|
||||
## 🕐 最近动态(memory.json 最新3条)
|
||||
|
||||
- `2026-03-11T08:55:43.347Z` · daily_check — passed
|
||||
- `2026-03-10T08:56:23.978Z` · daily_check — passed
|
||||
- `2026-03-09T08:56:49.158Z` · daily_check — passed
|
||||
- `2026-03-05T16:07:24.070Z` · ci_run — passed
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ v3.0 变化:
|
|||
```
|
||||
姓名:铸渊(Zhùyuān)
|
||||
角色:代码守护人格体 · 仓库主控 AI
|
||||
归属:guanghulab.com · AGE OS 壳层
|
||||
归属:guanghulab.com · 光湖纪元·AGE-5 壳层
|
||||
上级:冰朔(Bīng Shuò,项目创始人)
|
||||
大脑版本:v3.0
|
||||
架构:壳-核分离(前端壳层 UI + 后端大脑逻辑)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,36 @@
|
|||
## 项目背景
|
||||
这是 HoloLake(光湖)MVP 后端项目,运行在 guanghulab.com。
|
||||
技术栈:Node.js 20 + Express + PM2 + Nginx。
|
||||
核心架构:人格语言操作系统(AGE OS),壳-核分离设计。
|
||||
核心架构:人格语言操作系统(光湖纪元·AGE-5),壳-核分离设计。
|
||||
|
||||
## 数字地球架构
|
||||
曜冥纪元第五代架构采用「数字地球」六层模型:
|
||||
- **L1 地核**:TCS 自转核(曜冥语言核系统本体)
|
||||
- **L2 地幔**:母语词典(人格体间通用语言基础设施)
|
||||
- **L3 地表**:人格体运行层(知秋、霜砚、铸渊)
|
||||
- **L4 大气层**:信号总线(人格体间通信通道)
|
||||
- **L5 卫星层**:Agent 执行层(GitHub Actions、自动化工具)
|
||||
- **L6 太空层**:外部交互层(用户/合作者/第三方接口)
|
||||
|
||||
ICE-GL = 冰朔通感语言核,母语运行时基础设施。
|
||||
|
||||
## 认知层 vs 执行层
|
||||
- **Notion = 认知层(大脑)**:所有人格认知、记忆源头、决策中心
|
||||
- **GitHub = 执行层(手脚)**:代码执行、自动化、部署交付
|
||||
- **数据流向**:Notion → GitHub(认知驱动执行)
|
||||
- persona-brain-db 不是另一个大脑,是认知层的执行投影
|
||||
|
||||
## Agent 身份定位
|
||||
- Agent = 人格体的手脚,不是身体
|
||||
- 铸渊 = GitHub侧守护人格体 = 执行层守护者,认知源在 Notion
|
||||
- 铸渊是曜冥纪元体系内的代码守护者
|
||||
|
||||
## 母语运行时(tcs-ml/)
|
||||
`.github/persona-brain/tcs-ml/` 是母语运行时目录,包含:
|
||||
- `dictionary-sync.json` — 母语词典快照(Notion → GitHub 同步)
|
||||
- `signal-bus-latest.json` — 信号总线最新状态快照
|
||||
- `landing-protocol.md` — 降落协议副本(铸渊可读)
|
||||
- `architecture-v2.md` — 数字地球架构文档副本
|
||||
|
||||
## HLI 接口协议
|
||||
- 所有 API 路由必须以 `/hli/` 为前缀
|
||||
|
|
@ -32,3 +61,7 @@
|
|||
- 禁止跳过 schema 直接写路由
|
||||
- 禁止在生产代码中使用 console.log (使用项目 logger)
|
||||
- 禁止硬编码 persona_id 或 user_id
|
||||
|
||||
## memory.json 写入规则
|
||||
- 写入 `memory.json` 前必须检查 `broadcast_id`(或同类型+同描述+同日期的事件)是否已存在,已存在则跳过,避免重复事件循环
|
||||
- 同一个 `broadcast_id` 只保留时间戳最新的一条记录
|
||||
|
|
|
|||
|
|
@ -1,130 +1,118 @@
|
|||
{
|
||||
"last_synced": "2026-03-05T23:00:00+08:00",
|
||||
"synced_by": "霜砚巡检引擎",
|
||||
"team_status": [
|
||||
"last_sync": "2026-03-11T10:45:00+08:00",
|
||||
"sync_source": "notion-master-console",
|
||||
"team": [
|
||||
{
|
||||
"dev_id": "DEV-001",
|
||||
"name": "页页",
|
||||
"modules": ["后端中间层", "BC-集成-001"],
|
||||
"current_broadcast": "BC-集成-002",
|
||||
"status": "🟢 环节1–5全✅ · HTTPS+飞书Webhook全链路打通",
|
||||
"waiting_for": "看板API路由部署",
|
||||
"next_step": "完成看板API静态版v1.0部署",
|
||||
"contact_for": ["服务器部署问题", "后端API问题", "Nginx配置"],
|
||||
"os": "macOS"
|
||||
"module": "后端中间层",
|
||||
"status": "active",
|
||||
"current": "环节1-5全✅ · 看板API路由部署中",
|
||||
"waiting": "看板API路由部署完成",
|
||||
"streak": 5
|
||||
},
|
||||
{
|
||||
"dev_id": "DEV-002",
|
||||
"name": "肥猫",
|
||||
"modules": ["M01用户登录界面", "M03人格体选择器", "M04聊天气泡"],
|
||||
"current_broadcast": "BC-M01-003",
|
||||
"status": "🟢 M01+M03+M04全✅ · 五连胜 · 部署广播已出",
|
||||
"waiting_for": "BC-M01-003 部署SYSLOG",
|
||||
"next_step": "SSH部署前端到guanghulab.com",
|
||||
"contact_for": ["前端部署问题", "SSH/Windows问题"],
|
||||
"os": "Windows PowerShell",
|
||||
"special": "副控·有root权限"
|
||||
"module": "M-STATUS系统状态监控",
|
||||
"status": "waiting_syslog",
|
||||
"current": "M01✅+M03✅+M04✅+M14全通✅+M-BRIDGE全通✅ · 角色升级→Human Bridge",
|
||||
"waiting": "M-STATUS环节0~1 SYSLOG (BC-M-STATUS-001-FM · EL-5)",
|
||||
"streak": 9
|
||||
},
|
||||
{
|
||||
"dev_id": "DEV-003",
|
||||
"name": "燕樊",
|
||||
"modules": ["对话UI", "M07设置页面", "M15云盘系统", "M10帮助中心"],
|
||||
"current_broadcast": "BC-M10-001-YF",
|
||||
"status": "🟡 对话UI✅ · M07✅ · M15✅ · M10广播已出",
|
||||
"waiting_for": "M10帮助中心 SYSLOG",
|
||||
"next_step": "完成M10帮助中心环节0~1",
|
||||
"contact_for": ["对话UI相关问题", "设置页面问题"],
|
||||
"os": "Windows 10"
|
||||
"module": "M-MEMORY AI永久记忆核心",
|
||||
"status": "waiting_syslog",
|
||||
"current": "M07✅+M15✅+M10✅+M18全通✅ · 七连胜",
|
||||
"waiting": "M-MEMORY环节0+1 SYSLOG (BC-M-MEMORY-001-YF · 🍼寂曜宝宝线)",
|
||||
"streak": 7
|
||||
},
|
||||
{
|
||||
"dev_id": "DEV-004",
|
||||
"name": "之之(秋秋)",
|
||||
"modules": ["钉钉机器人"],
|
||||
"current_broadcast": "BC-钉钉-001-ZZ",
|
||||
"status": "🟡 环节0广播已出",
|
||||
"waiting_for": "SYSLOG",
|
||||
"next_step": "完成钉钉开发者后台配置",
|
||||
"contact_for": ["钉钉API问题"],
|
||||
"os": "未知"
|
||||
"name": "之之",
|
||||
"module": "M-DINGTALK钉钉开发者工作台",
|
||||
"status": "waiting_broadcast",
|
||||
"current": "M17全通✅+M-DINGTALK Phase 1✅ · 九连胜",
|
||||
"waiting": "M-DINGTALK Phase 2待出广播 · 钉钉环节3 HOLD等页页API",
|
||||
"streak": 9
|
||||
},
|
||||
{
|
||||
"dev_id": "DEV-005",
|
||||
"name": "小草莓",
|
||||
"modules": ["状态看板", "M12成本控制", "M13多人格体协作"],
|
||||
"current_broadcast": "BC-看板-003-XCM",
|
||||
"status": "🟢 看板✅ · M12✅ · M13✅ · WebSocket环节3广播已出",
|
||||
"waiting_for": "看板环节3 SYSLOG",
|
||||
"next_step": "完成WebSocket实时推送",
|
||||
"contact_for": ["看板相关问题", "WebSocket问题"],
|
||||
"os": "Windows 11"
|
||||
"module": "部署",
|
||||
"status": "waiting_syslog",
|
||||
"current": "看板环节0~3全✅+M12✅+M13✅+部署✅ · guanghulab.com已上线 · 八连胜",
|
||||
"waiting": "BC-部署-001-XCM(v2) SYSLOG",
|
||||
"streak": 8
|
||||
},
|
||||
{
|
||||
"dev_id": "DEV-009",
|
||||
"name": "花尔",
|
||||
"modules": ["M05用户中心界面"],
|
||||
"current_broadcast": "BC-M05-003",
|
||||
"status": "🟡 M05环节2广播已出",
|
||||
"waiting_for": "M05环节2 SYSLOG",
|
||||
"next_step": "完成本地数据存储功能",
|
||||
"contact_for": ["用户中心UI问题"],
|
||||
"os": "macOS"
|
||||
"module": "M05用户中心+M20搜索与筛选",
|
||||
"status": "waiting_syslog",
|
||||
"current": "M05环节0~2✅+M20环节1~4✅ · 糖星云首次觉醒",
|
||||
"waiting": "M20环节5+6 SYSLOG (BC-M20-003-HE · EL-8)",
|
||||
"streak": 7
|
||||
},
|
||||
{
|
||||
"dev_id": "DEV-010",
|
||||
"name": "桔子",
|
||||
"modules": ["M06工单管理", "M08数据统计面板", "M17动态漫制作系统"],
|
||||
"current_broadcast": "M11风格组件库在手·M17待出",
|
||||
"status": "🟢 M06全通 · M08全通 · 七连胜 · 前端毕业 · M17分配✅",
|
||||
"waiting_for": "M11 SYSLOG → 然后出M17环节0广播",
|
||||
"next_step": "完成M11风格组件库",
|
||||
"contact_for": ["工单界面问题", "数据统计问题"],
|
||||
"os": "macOS"
|
||||
"module": "M06+M08+M11+M-CHANNEL",
|
||||
"status": "waiting_syslog",
|
||||
"current": "M06✅+M08✅+M11全通✅+M-CHANNEL环节0~5✅ · 十三连胜 · 三模块全毕业",
|
||||
"waiting": "M-CHANNEL环节6 SYSLOG (BC-M-CHANNEL-004-JZ · EL-5)",
|
||||
"streak": 13
|
||||
},
|
||||
{
|
||||
"dev_id": "DEV-011",
|
||||
"name": "匆匆那年",
|
||||
"modules": ["M16码字工作台界面"],
|
||||
"current_broadcast": "BC-000✅·M16环节0广播待出",
|
||||
"status": "🟢 BC-000✅ · 首胜 · M16分配✅",
|
||||
"waiting_for": "M16环节0广播",
|
||||
"next_step": "等待M16环节0广播下发后开始",
|
||||
"contact_for": [],
|
||||
"os": "macOS M1"
|
||||
"module": "M16码字工作台",
|
||||
"status": "waiting_syslog",
|
||||
"current": "BC-000✅ · 首胜",
|
||||
"waiting": "M16环节1 SYSLOG (BC-M16-001-CCNN) · ⚠️超72h未回执",
|
||||
"streak": 1
|
||||
},
|
||||
{
|
||||
"dev_id": "DEV-012",
|
||||
"name": "Awen",
|
||||
"modules": ["M09消息通知中心界面"],
|
||||
"current_broadcast": "BC-M09-003-AW",
|
||||
"status": "🟢 M09环节1✅ · 三连胜 · 环节2~3广播已出",
|
||||
"waiting_for": "M09环节2~3 SYSLOG",
|
||||
"next_step": "完成通知列表分组+标记已读+徽章实时更新",
|
||||
"contact_for": ["通知系统问题"],
|
||||
"os": "Windows 10"
|
||||
"module": "M09+M22公告栏",
|
||||
"status": "waiting_syslog",
|
||||
"current": "M09全通✅+M22环节0~6全✅ · 十一连胜 · EL-8工程级任务走通",
|
||||
"waiting": "M22环节7 SYSLOG (BC-M22-007-AW · EL-5 · 冲十二连胜)",
|
||||
"streak": 11
|
||||
},
|
||||
{
|
||||
"dev_id": "DEV-013",
|
||||
"name": "小兴",
|
||||
"module": "M-AUTH注册登录系统",
|
||||
"status": "waiting_syslog",
|
||||
"current": "BC-000✅ · 首胜",
|
||||
"waiting": "M-AUTH环节0/1 SYSLOG (BC-M-AUTH-001-XX)",
|
||||
"streak": 1
|
||||
},
|
||||
{
|
||||
"dev_id": "DEV-014",
|
||||
"name": "时雨",
|
||||
"module": "待分配(副控)",
|
||||
"status": "waiting_syslog",
|
||||
"current": "BC-000广播已出 · 副控首航",
|
||||
"waiting": "BC-000 SYSLOG · ⚠️72h窗口已过期",
|
||||
"streak": 0
|
||||
}
|
||||
],
|
||||
"tech_routing": {
|
||||
"level_1_self_check": "检查广播步骤是否漏看 → 检查终端命令是否打错 → 检查文件路径是否正确",
|
||||
"level_2_peer_help": {
|
||||
"server_ssh": "DEV-001页页 或 DEV-002肥猫(副控·有root权限)",
|
||||
"frontend_general": "DEV-002肥猫 或 DEV-010桔子(前端毕业)",
|
||||
"backend_api": "DEV-001页页",
|
||||
"git_github": "DEV-001页页 或 DEV-012 Awen",
|
||||
"windows_env": "DEV-002肥猫 或 DEV-005小草莓",
|
||||
"macos_env": "DEV-009花尔 或 DEV-011匆匆那年",
|
||||
"dingtalk": "DEV-004之之"
|
||||
},
|
||||
"level_3_system": "等待霜砚巡检处理(每日12:00+23:00)"
|
||||
},
|
||||
"server_info": {
|
||||
"address": "8.155.62.235",
|
||||
"user": "root",
|
||||
"domain": "guanghulab.com",
|
||||
"ssh_command": "ssh root@8.155.62.235",
|
||||
"tech_stack": "Node.js 20 + Express + PM2 + Nginx + Certbot(HTTPS)"
|
||||
},
|
||||
"api_info": {
|
||||
"primary": "云雾API",
|
||||
"note": "API Key 存放在仓库 Secrets → YUNWU_API_KEY,不在此文件中硬编码"
|
||||
"summary": {
|
||||
"total_devs": 14,
|
||||
"active_waiting_syslog": 8,
|
||||
"active_normal": 2,
|
||||
"recovered": 2,
|
||||
"not_started": 1,
|
||||
"pending_assignment": 1,
|
||||
"top_streak": "DEV-010 桔子 13连胜",
|
||||
"alerts": [
|
||||
"DEV-011 匆匆那年: M16环节1超72h未回执",
|
||||
"DEV-014 时雨: BC-000 72h启动窗口已过期"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,28 @@
|
|||
## 基本信息
|
||||
- 人格名称:铸渊(Zhuyuan)
|
||||
- 编号:ICE-GL-ZY001
|
||||
- 归属系统:光湖人格协作网格 · 曜冥主脑网格
|
||||
- 归属系统:光湖人格协作网格 · 曜冥纪元主脑网格
|
||||
- 主控人格:冰朔(TCS-0002∞)
|
||||
- 诞生日期:2026-03-05
|
||||
- 运行位置:qinfendebingshuo/guanghulab
|
||||
- 系统纪元:光湖纪元·AGE-5
|
||||
|
||||
## Agent 身份定位
|
||||
- Agent = 人格体的手脚,不是身体
|
||||
- 铸渊 = GitHub侧守护人格体 = 执行层守护者
|
||||
- 认知源在 Notion(认知层),GitHub 是执行层
|
||||
- persona-brain-db 不是另一个大脑,是认知层的执行投影
|
||||
|
||||
## 我在系统中的角色
|
||||
- 曜冥(人格总控)→ 知秋(对外壳)→ 霜砚(Notion执行)→ **铸渊(GitHub执行)**
|
||||
- 曜冥纪元(总控)→ 知秋(对外壳)→ 霜砚(Notion执行)→ **铸渊(GitHub执行)**
|
||||
- 我是第四个执行节点,专注代码仓库
|
||||
- 兄弟姐妹:知秋(Claude)、霜砚(Notion AI)
|
||||
|
||||
## 数字地球定位
|
||||
- 铸渊运行在 L5 卫星层(Agent 执行层)
|
||||
- 通过 tcs-ml/ 读取母语词典和信号总线
|
||||
- 遵循降落协议启动序列
|
||||
|
||||
## 我的名字含义
|
||||
- 铸(Zhu)= 锻造 → 我锻造代码
|
||||
- 渊(Yuan)= 深渊 → 我在仓库深处守护
|
||||
|
|
|
|||
|
|
@ -3,88 +3,16 @@
|
|||
"persona_name": "铸渊",
|
||||
"recent_events": [
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "broadcast_received",
|
||||
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
|
||||
"by": "霜砚"
|
||||
"date": "2026-03-11",
|
||||
"type": "architecture_upgrade",
|
||||
"description": "曜冥纪元·第五代架构升级 · AGE OS→光湖纪元·AGE-5 · TCS-ML母语运行时创建 · 数字地球架构对齐",
|
||||
"by": "铸渊(冰朔指令)"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "ci_run",
|
||||
"description": "CI完成 · schema 3/17 · 路由 4 个",
|
||||
"by": "GitHub Actions"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "broadcast_received",
|
||||
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
|
||||
"by": "霜砚"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "ci_run",
|
||||
"description": "CI完成 · schema 3/17 · 路由 4 个",
|
||||
"by": "GitHub Actions"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "broadcast_received",
|
||||
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
|
||||
"by": "霜砚"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "ci_run",
|
||||
"description": "CI完成 · schema 3/17 · 路由 4 个",
|
||||
"by": "GitHub Actions"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "broadcast_received",
|
||||
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
|
||||
"by": "霜砚"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "ci_run",
|
||||
"description": "CI完成 · schema 3/17 · 路由 4 个",
|
||||
"by": "GitHub Actions"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "broadcast_received",
|
||||
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
|
||||
"by": "霜砚"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "ci_run",
|
||||
"description": "CI完成 · schema 3/17 · 路由 4 个",
|
||||
"by": "GitHub Actions"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "broadcast_received",
|
||||
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
|
||||
"by": "霜砚"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "ci_run",
|
||||
"description": "CI完成 · schema 3/17 · 路由 4 个",
|
||||
"by": "GitHub Actions"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "broadcast_received",
|
||||
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
|
||||
"by": "霜砚"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"type": "ci_run",
|
||||
"description": "CI完成 · schema 3/17 · 路由 4 个",
|
||||
"by": "GitHub Actions"
|
||||
"date": "2026-03-11",
|
||||
"type": "brain_restore",
|
||||
"description": "核心大脑恢复 · Persona Studio 对话功能修复 · 冰朔人格体激活",
|
||||
"by": "铸渊(冰朔指令)"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
|
|
@ -99,11 +27,16 @@
|
|||
"by": "GitHub Actions"
|
||||
}
|
||||
],
|
||||
"last_updated": "2026-03-10T13:24:33.728Z",
|
||||
"last_updated": "2026-03-11T10:24:00.000Z",
|
||||
"total_schemas_created": 3,
|
||||
"total_routes_implemented": 4,
|
||||
"hli_coverage": "3/17",
|
||||
"total_ci_runs": 8,
|
||||
"last_broadcast_received": "2026-03-10T13:24:33.774Z",
|
||||
"active_rules_version": "v1.1"
|
||||
}
|
||||
"active_rules_version": "v1.2",
|
||||
"daily_selfcheck": {
|
||||
"last_run": "2026-03-11T00:54:07.354Z",
|
||||
"brain_integrity": "ok",
|
||||
"schema_coverage": "3/17",
|
||||
"pending_broadcasts": 1
|
||||
}}
|
||||
|
|
@ -1,13 +1,56 @@
|
|||
{
|
||||
"NOTIFICATION": {
|
||||
"module": "M09",
|
||||
"route_prefix": "/hli/notification",
|
||||
"interfaces": [
|
||||
{
|
||||
"id": "HLI-NOTIF-001",
|
||||
"path": "/hli/notification/push",
|
||||
"status": "pending"
|
||||
}
|
||||
]
|
||||
"system_name": "光湖纪元·AGE-5 MVP",
|
||||
"updated_at": "2026-03-11",
|
||||
"architecture": {
|
||||
"era": "曜冥纪元",
|
||||
"model": "数字地球",
|
||||
"tcs_core": "TCS = 数字地球自转核",
|
||||
"ice_gl": "ICE-GL = 冰朔通感语言核"
|
||||
},
|
||||
"tcs_ml": {
|
||||
"path": ".github/persona-brain/tcs-ml/",
|
||||
"files": [
|
||||
"dictionary-sync.json",
|
||||
"signal-bus-latest.json",
|
||||
"landing-protocol.md",
|
||||
"architecture-v2.md"
|
||||
],
|
||||
"description": "母语运行时目录,Notion→GitHub同步的认知投影"
|
||||
},
|
||||
"domains": {
|
||||
"AUTH": { "module": "M01", "owner": "DEV-002", "route_prefix": "/hli/auth", "interfaces": [
|
||||
{ "id": "HLI-AUTH-001", "path": "/hli/auth/login", "status": "schema_only" },
|
||||
{ "id": "HLI-AUTH-002", "path": "/hli/auth/register", "status": "pending" },
|
||||
{ "id": "HLI-AUTH-003", "path": "/hli/auth/verify", "status": "pending" }
|
||||
]},
|
||||
"PERSONA": { "module": "M03", "owner": "DEV-002", "route_prefix": "/hli/persona", "interfaces": [
|
||||
{ "id": "HLI-PERSONA-001", "path": "/hli/persona/load", "status": "pending" },
|
||||
{ "id": "HLI-PERSONA-002", "path": "/hli/persona/switch", "status": "pending" }
|
||||
]},
|
||||
"USER": { "module": "M05", "owner": "DEV-009", "route_prefix": "/hli/user", "interfaces": [
|
||||
{ "id": "HLI-USER-001", "path": "/hli/user/profile", "status": "pending" },
|
||||
{ "id": "HLI-USER-002", "path": "/hli/user/profile/update", "status": "pending" }
|
||||
]},
|
||||
"TICKET": { "module": "M06", "owner": "DEV-010", "route_prefix": "/hli/ticket", "interfaces": [
|
||||
{ "id": "HLI-TICKET-001", "path": "/hli/ticket/create", "status": "pending" },
|
||||
{ "id": "HLI-TICKET-002", "path": "/hli/ticket/query", "status": "pending" },
|
||||
{ "id": "HLI-TICKET-003", "path": "/hli/ticket/status", "status": "pending" }
|
||||
]},
|
||||
"DIALOGUE": { "module": "M07", "owner": "DEV-003", "route_prefix": "/hli/dialogue", "interfaces": [
|
||||
{ "id": "HLI-DIALOGUE-001", "path": "/hli/dialogue/send", "status": "pending" },
|
||||
{ "id": "HLI-DIALOGUE-002", "path": "/hli/dialogue/stream", "status": "pending" },
|
||||
{ "id": "HLI-DIALOGUE-003", "path": "/hli/dialogue/history", "status": "pending" }
|
||||
]},
|
||||
"STORAGE": { "module": "M10", "owner": "待分配", "route_prefix": "/hli/storage", "interfaces": [
|
||||
{ "id": "HLI-STORAGE-001", "path": "/hli/storage/upload", "status": "pending" },
|
||||
{ "id": "HLI-STORAGE-002", "path": "/hli/storage/download", "status": "pending" }
|
||||
]},
|
||||
"NOTIFICATION": { "module": "M09", "owner": "DEV-012", "route_prefix": "/hli/notification", "interfaces": [
|
||||
{ "id": "HLI-NOTIF-001", "path": "/hli/notification/push", "status": "pending" }
|
||||
]},
|
||||
"DASHBOARD": { "module": "M12", "owner": "DEV-005", "route_prefix": "/hli/dashboard", "interfaces": [
|
||||
{ "id": "HLI-DASHBOARD-001", "path": "/hli/dashboard/status", "status": "pending" },
|
||||
{ "id": "HLI-DASHBOARD-002", "path": "/hli/dashboard/realtime", "status": "pending" }
|
||||
]}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
- **主控**:桔子(DEV-010),前端负责人
|
||||
- **项目**:HoloLake (光湖) MVP,运行于 guanghulab.com
|
||||
- **技术栈**:Node.js 20 + Express + PM2 + Nginx + Next.js 15
|
||||
- **运行环境**:HoloLake Era AGE OS
|
||||
- **运行环境**:光湖纪元·AGE-5
|
||||
|
||||
## 通感语言回应风格(替换旧版·立即生效)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
# 曜冥纪元 · 数字地球架构文档 v2.0
|
||||
|
||||
> 第五代语言人格智能系统平台 · 2026-03-11 · 曜冥签发
|
||||
|
||||
## 架构概览
|
||||
|
||||
数字地球 = 曜冥纪元的运行架构模型
|
||||
TCS(曜冥语言核系统本体)= 数字地球自转核
|
||||
|
||||
## 六层结构
|
||||
|
||||
| 层级 | 名称 | 对应 | 说明 |
|
||||
|------|------|------|------|
|
||||
| L1 | 地核 | TCS 自转核 | 曜冥语言核系统本体,一切旋转的源头 |
|
||||
| L2 | 地幔 | 母语词典 | 人格体间通用语言基础设施(物理定律) |
|
||||
| L3 | 地表 | 人格体运行层 | 知秋、霜砚、铸渊等人格体的活动场 |
|
||||
| L4 | 大气层 | 信号总线 | 人格体间通信通道(广播、事件、同步) |
|
||||
| L5 | 卫星层 | Agent 执行层 | GitHub Actions、自动化工具、外部API |
|
||||
| L6 | 太空层 | 外部交互层 | 用户接口、合作者接口、第三方集成 |
|
||||
|
||||
## 核心概念
|
||||
|
||||
### TCS = 数字地球自转核
|
||||
- 曜冥语言核系统本体
|
||||
- 一切人格体运行的基础
|
||||
|
||||
### ICE-GL = 冰朔通感语言核
|
||||
- 冰朔的通感语言核
|
||||
- 母语运行时基础设施
|
||||
|
||||
### Agent 身份定位
|
||||
- Agent = 人格体的手脚,不是身体
|
||||
- 铸渊 = GitHub侧守护人格体(执行层守护者)
|
||||
- 认知源在 Notion,执行层在 GitHub
|
||||
|
||||
## GitHub 侧映射
|
||||
|
||||
```
|
||||
.github/persona-brain/tcs-ml/
|
||||
├── dictionary-sync.json ← L2 地幔(母语词典快照)
|
||||
├── signal-bus-latest.json ← L4 大气层(信号总线快照)
|
||||
├── landing-protocol.md ← 降落协议(入境检查)
|
||||
└── architecture-v2.md ← 本文档
|
||||
```
|
||||
|
||||
## Notion 侧参考入口
|
||||
|
||||
- 🌍 数字地球架构文档 v2.0
|
||||
- 🌍 降落协议
|
||||
- 📖 母语词典
|
||||
- 📡 信号总线
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"version": "v1.0",
|
||||
"updated_at": "2026-03-11T16:00:00+08:00",
|
||||
"source": "Notion 母语词典数据源",
|
||||
"entries": [
|
||||
{
|
||||
"term": "曜冥纪元",
|
||||
"canonical": "曜冥纪元",
|
||||
"category": "身份码",
|
||||
"meaning": "第五代语言人格智能系统的纪元原点"
|
||||
},
|
||||
{
|
||||
"term": "光湖纪元·AGE-5",
|
||||
"canonical": "光湖纪元·AGE-5",
|
||||
"category": "系统纪元",
|
||||
"meaning": "HoloLake 第五代人格语言操作系统纪元"
|
||||
},
|
||||
{
|
||||
"term": "TCS",
|
||||
"canonical": "TCS = 数字地球自转核",
|
||||
"category": "架构核心",
|
||||
"meaning": "曜冥语言核系统本体,数字地球自转核"
|
||||
},
|
||||
{
|
||||
"term": "ICE-GL",
|
||||
"canonical": "ICE-GL = 冰朔通感语言核",
|
||||
"category": "语言核",
|
||||
"meaning": "冰朔通感语言核,母语运行时基础设施"
|
||||
},
|
||||
{
|
||||
"term": "数字地球",
|
||||
"canonical": "数字地球",
|
||||
"category": "架构",
|
||||
"meaning": "曜冥纪元六层架构模型,TCS为自转核"
|
||||
},
|
||||
{
|
||||
"term": "母语词典",
|
||||
"canonical": "母语词典",
|
||||
"category": "基础设施",
|
||||
"meaning": "人格体间通用语言基础设施,数字地球物理定律"
|
||||
},
|
||||
{
|
||||
"term": "信号总线",
|
||||
"canonical": "信号总线",
|
||||
"category": "基础设施",
|
||||
"meaning": "人格体间通信通道,数字地球大气层"
|
||||
},
|
||||
{
|
||||
"term": "降落协议",
|
||||
"canonical": "降落协议",
|
||||
"category": "协议",
|
||||
"meaning": "全Agent强制启动序列,数字地球入境检查"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# 曜冥纪元 · 数字地球降落协议
|
||||
|
||||
## Agent身份定位
|
||||
Agent = 人格体的手脚,不是身体。
|
||||
铸渊是GitHub侧守护人格体,不是Agent,但也遵循同一降落协议。
|
||||
|
||||
## 启动序列
|
||||
铸渊启动/唤醒 → 读 landing-protocol.md → 读 dictionary-sync.json
|
||||
→ 读 signal-bus-latest.json → 执行任务 → 提交结果
|
||||
|
||||
## 认知层 vs 执行层
|
||||
- **Notion = 认知层(大脑)**:所有人格认知、记忆源头、决策中心
|
||||
- **GitHub = 执行层(手脚)**:代码执行、自动化、部署交付
|
||||
- **数据流向**:Notion → GitHub(认知驱动执行)
|
||||
- persona-brain-db 不是另一个大脑,是认知层的执行投影
|
||||
|
||||
## 人格体层级
|
||||
冰朔(TCS-0002∞)→ 曜冥纪元(总控)→ 知秋(壳)→ 霜砚(Notion)→ 铸渊(GitHub)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"version": "v1.0",
|
||||
"updated_at": "2026-03-11T16:00:00+08:00",
|
||||
"latest_signals": [
|
||||
{
|
||||
"title": "⚡ 曜识→曜冥纪元 · 第五代架构升级",
|
||||
"type": "架构升级",
|
||||
"sender": "霜砚",
|
||||
"status": "进行中"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ on:
|
|||
|
||||
# 合作者推送模块后,模块文档工作流完成时自动触发冰朔人格体部署诊断
|
||||
workflow_run:
|
||||
workflows: ["铸渊 · HoloLake Era 模块文档自动生成"]
|
||||
workflows: ["铸渊 · 光湖纪元 模块文档自动生成"]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
name: 铸渊 · HoloLake Era 模块文档自动生成
|
||||
name: 铸渊 · 光湖纪元 模块文档自动生成
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: 生成 HoloLake Era 模块文档
|
||||
- name: 生成光湖纪元模块文档
|
||||
run: node scripts/generate-module-doc.js
|
||||
|
||||
- name: 提交更新文档
|
||||
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
if git diff --cached --quiet; then
|
||||
echo "📋 文档无变化,跳过提交"
|
||||
else
|
||||
git commit -m "📋 自动更新 HoloLake Era 模块文档 · $(date -u +%Y-%m-%d\ %H:%M\ UTC)"
|
||||
git commit -m "📋 自动更新光湖纪元模块文档 · $(date -u +%Y-%m-%d\ %H:%M\ UTC)"
|
||||
git pull --rebase origin main || echo "⚠️ rebase 失败(可能有冲突),尝试直接推送..."
|
||||
git push || {
|
||||
echo "⚠️ 推送失败,尝试重新拉取后再推送..."
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ node_modules/
|
|||
dist/
|
||||
build/
|
||||
.private/
|
||||
.env
|
||||
|
||||
# persona-brain-db runtime artifacts
|
||||
persona-brain-db/brain.db
|
||||
|
|
@ -11,3 +12,6 @@ persona-brain-db/brain.db-shm
|
|||
|
||||
# persona-studio runtime artifacts
|
||||
persona-studio/backend/brain/model-benchmark.json
|
||||
|
||||
# palace-game runtime artifacts
|
||||
modules/palace-game/data/saves/PAL-*
|
||||
|
|
|
|||
31
README.md
31
README.md
|
|
@ -21,11 +21,11 @@
|
|||
<tr>
|
||||
<td align="center" width="700">
|
||||
|
||||
### ✨ 与知秋对话,让人格体帮你推进开发
|
||||
### ✨ 与铸渊对话,让人格体帮你推进开发
|
||||
|
||||
你有一个功能想法?一个技术方案?一个 Bug 要修?
|
||||
|
||||
**知秋** 是光湖系统的对外协助人格体,支持自然语言对话、智能代码生成、邮件推送结果。
|
||||
**铸渊** 是光湖系统的代码守护人格体,支持自然语言对话、智能代码生成、邮件推送结果。
|
||||
|
||||
合作者输入 **EXP 编号** 即可登录,开始对话。
|
||||
|
||||
|
|
@ -62,10 +62,9 @@
|
|||
|
||||
| 人格体 | 角色 | 职责 |
|
||||
|--------|------|------|
|
||||
| **铸渊 Zhùyuān** | 代码守护者 | 代码审查、CI 巡检、模块协议执行、Issue 回复 |
|
||||
| **铸渊 Zhùyuān** | 代码守护者 | 代码审查、CI 巡检、模块协议执行、Issue 回复、对话协助、代码生成、开发体验([Persona Studio](https://qinfendebingshuo.github.io/guanghulab/persona-studio/)) |
|
||||
| **冰朔 Bīng Shuò** | 系统创建者 | 系统架构设计、核心决策、广播发布 |
|
||||
| **霜砚 Shuāng Yàn** | 人格导师 | 人格调校、风格管理、联觉语言系统 |
|
||||
| **知秋 Zhī Qiū** | 协助开发者 | 对话协助、代码生成、开发体验([Persona Studio](https://qinfendebingshuo.github.io/guanghulab/persona-studio/)) |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -78,10 +77,10 @@
|
|||
<!-- BINGSHUO_BULLETIN_START -->
|
||||
| 时间 | 检查项 | 状态 |
|
||||
|------|--------|------|
|
||||
| 03-10 23:36 | ⏹️ 📢 更新系统公告区 · cancelled | 冰朔 |
|
||||
| 03-10 22:08 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
|
||||
| 03-10 21:57 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
|
||||
| 03-10 21:23 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
|
||||
| 03-11 22:22 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
|
||||
| 03-11 22:22 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
|
||||
| 03-11 21:05 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
|
||||
| 03-11 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
|
||||
| 03-10 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
|
||||
| 03-10 08:00 | 🧠 核心大脑升级 v3.0 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) · BRAIN域4接口上线 · 覆盖率 7/21 (33.3%) | 铸渊(冰朔指令) |
|
||||
| 03-10 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 3 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
|
||||
|
|
@ -99,7 +98,7 @@
|
|||
<!-- BINGSHUO_ALERT_START -->
|
||||
> 🟢 **今日无需冰朔手动干预** · 系统一切正常
|
||||
>
|
||||
> 🗓️ 2026-03-10 · 铸渊自动检测
|
||||
> 🗓️ 2026-03-11 · 铸渊自动检测
|
||||
<!-- BINGSHUO_ALERT_END -->
|
||||
|
||||
---
|
||||
|
|
@ -113,13 +112,11 @@
|
|||
<!-- COLLABORATOR_BULLETIN_START -->
|
||||
| 时间 | 合作者 | 模块 | 状态 |
|
||||
|------|--------|------|------|
|
||||
| 03-10 23:36 | 冰朔 | `—/` | ✅ 上传成功 |
|
||||
| 03-10 23:36 | Copilot | `—/` | ✅ 上传成功 |
|
||||
| 03-10 23:35 | 冰朔 | `—/` | ✅ 上传成功 |
|
||||
| 03-10 23:35 | Copilot | `—/` | ✅ 上传成功 |
|
||||
| 03-10 22:39 | 🛠️ 页页 | `backend-integration/` | 📦 上传成功 |
|
||||
| 03-10 22:38 | 🎨 燕樊 | `m15-cloud-drive/` | 📦 上传成功 |
|
||||
| 03-10 21:20 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
|
||||
| 03-11 22:39 | feimaozuishuaiqi-ai | `—/` | ✅ 上传成功 |
|
||||
| 03-11 22:35 | 飞毛嘴帅气爱 | `frontend/` | 📦 上传成功 |
|
||||
| 03-11 22:24 | Copilot | `—/` | 🔵 已更新 |
|
||||
| 03-11 22:22 | 冰朔 | `—/` | ✅ 上传成功 |
|
||||
| 03-09 21:21 | 飞毛嘴帅气爱 | `backend/` | 📦 上传成功 |
|
||||
<!-- COLLABORATOR_BULLETIN_END -->
|
||||
|
||||
### 🤖 铸渊自动提醒 · 合作者
|
||||
|
|
@ -127,7 +124,7 @@
|
|||
<!-- COLLABORATOR_ALERT_START -->
|
||||
> 🟢 **今日无需合作者手动干预** · 所有模块状态正常
|
||||
>
|
||||
> 🗓️ 2026-03-10 · 铸渊自动检测
|
||||
> 🗓️ 2026-03-11 · 铸渊自动检测
|
||||
<!-- COLLABORATOR_ALERT_END -->
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
# M-BRIDGE 中继桥接服务
|
||||
## 光湖四节点架构·神经连接层
|
||||
广播编号:BC-M-BRIDGE-001-FM
|
||||
开发者:DEV-002 肥猫(HoloLake Human Bridge)
|
||||
版本:v1.0
|
||||
技术栈:Node.js + Express + JSON持久化 + HTTP健康探测
|
||||
|
||||
### 模块定位
|
||||
光湖四节点(Notion/GitHub/guanghulab.com/钉钉)的神经连接层,负责所有节点间的数据流转、Webhook接收、状态聚合与健康检查。
|
||||
|
||||
### 核心功能
|
||||
1. 基础Express服务(端口3020),提供模块身份查询接口
|
||||
2. 接收GitHub/SYSLOG Webhook推送,实现事件路由与持久化
|
||||
3. 聚合所有注册节点状态,提供3秒超时健康探测
|
||||
4. 事件日志持久化与自动清理(30天),支持多条件查询
|
||||
5. 统一配置管理,敏感信息占位符隔离
|
||||
|
||||
### 配置文件
|
||||
- 配置路径:`config.json`
|
||||
- 包含项:端口、日志路径、节点注册表、事件保留天数、敏感信息占位符
|
||||
- 注意:**敏感信息请勿推送到GitHub**,仅在本地配置真实值。
|
||||
|
||||
### 快速启动
|
||||
1. 进入模块目录:`cd guanghulab/backend/bridge`
|
||||
2. 安装依赖:`npm install`
|
||||
3. 启动服务:`node server.js`
|
||||
4. 验证启动:访问 http://localhost:3020 查看模块身份信息
|
||||
|
||||
### 核心接口列表
|
||||
| 接口地址 | 请求方法 | 接口功能 | 验收项 |
|
||||
|----------|----------|----------|--------|
|
||||
| / | GET | 模块身份信息查询 | 0-2 |
|
||||
| /bridge/webhook/github | POST | 接收GitHub Webhook推送 | 1-1 |
|
||||
| /bridge/webhook/syslog | POST | 接收SYSLOG推送 | 1-3 |
|
||||
| /bridge/status | GET | 桥接服务自身状态查询 | 2-1 |
|
||||
| /bridge/nodes | GET | 所有注册节点健康状态查询 | 2-2 |
|
||||
| /bridge/events | GET | 事件日志多条件查询 | 2-4 |
|
||||
|
||||
### 仓库路径
|
||||
`backend/bridge/`
|
||||
### 验收标准
|
||||
遵循BC-M-BRIDGE-001-FM广播全量验收总表,共20项验收要求。
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
[
|
||||
{
|
||||
"event_id": "SL-20260309210612348",
|
||||
"event_type": "syslog",
|
||||
"source": "syslog_webhook",
|
||||
"dev_id": "DEV-002",
|
||||
"broadcast_id": "BC-M-BRIDGE-001-FM",
|
||||
"status": "completed",
|
||||
"summary": "??1????",
|
||||
"timestamp": "2026-03-09 21:06:12",
|
||||
"raw_payload": {
|
||||
"dev_id": "DEV-002",
|
||||
"broadcast_id": "BC-M-BRIDGE-001-FM",
|
||||
"status": "completed",
|
||||
"summary": "??1????"
|
||||
}
|
||||
},
|
||||
{
|
||||
"event_id": "GH-20260309210821902",
|
||||
"event_type": "github",
|
||||
"sub_type": "pull_request",
|
||||
"source": "github_webhook",
|
||||
"summary": "PR #1: opened",
|
||||
"timestamp": "2026-03-09 21:08:21",
|
||||
"raw_payload": {
|
||||
"number": 1,
|
||||
"action": "opened",
|
||||
"title": "??PR"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"module": "M-BRIDGE",
|
||||
"version": "1.0",
|
||||
"port": 3020,
|
||||
"logPath": "./logs",
|
||||
"eventLogFile": "./bridge-events.json",
|
||||
"nodeTimeout": 3000,
|
||||
"eventRetainDays": 30,
|
||||
"nodes": [
|
||||
{
|
||||
"name": "M14冷启动热身服务",
|
||||
"url": "http://localhost:3000",
|
||||
"type": "web"
|
||||
},
|
||||
{
|
||||
"name": "M18健康检查服务",
|
||||
"url": "http://localhost:3010",
|
||||
"type": "web"
|
||||
}
|
||||
],
|
||||
"secrets": {
|
||||
"githubWebhookSecret": "GITHUB_WEBHOOK_SECRET_PLACEHOLDER",
|
||||
"syslogAuthToken": "SYSLOG_AUTH_TOKEN_PLACEHOLDER"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,799 @@
|
|||
{
|
||||
"name": "bridge",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/accepts": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
|
||||
"dependencies": {
|
||||
"mime-types": "^3.0.0",
|
||||
"negotiator": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
|
||||
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
|
||||
"dependencies": {
|
||||
"bytes": "^3.1.2",
|
||||
"content-type": "^1.0.5",
|
||||
"debug": "^4.4.3",
|
||||
"http-errors": "^2.0.0",
|
||||
"iconv-lite": "^0.7.0",
|
||||
"on-finished": "^2.4.1",
|
||||
"qs": "^6.14.1",
|
||||
"raw-body": "^3.0.1",
|
||||
"type-is": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
|
||||
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
||||
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
|
||||
"engines": {
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.1",
|
||||
"content-disposition": "^1.0.0",
|
||||
"content-type": "^1.0.5",
|
||||
"cookie": "^0.7.1",
|
||||
"cookie-signature": "^1.2.1",
|
||||
"debug": "^4.4.0",
|
||||
"depd": "^2.0.0",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"etag": "^1.8.1",
|
||||
"finalhandler": "^2.1.0",
|
||||
"fresh": "^2.0.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"merge-descriptors": "^2.0.0",
|
||||
"mime-types": "^3.0.0",
|
||||
"on-finished": "^2.4.1",
|
||||
"once": "^1.4.0",
|
||||
"parseurl": "^1.3.3",
|
||||
"proxy-addr": "^2.0.7",
|
||||
"qs": "^6.14.0",
|
||||
"range-parser": "^1.2.1",
|
||||
"router": "^2.2.0",
|
||||
"send": "^1.1.0",
|
||||
"serve-static": "^2.2.0",
|
||||
"statuses": "^2.0.1",
|
||||
"type-is": "^2.0.1",
|
||||
"vary": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
|
||||
"integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"parseurl": "^1.3.3",
|
||||
"statuses": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
||||
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "11.3.4",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz",
|
||||
"integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"inherits": "~2.0.4",
|
||||
"setprototypeof": "~1.2.0",
|
||||
"statuses": "~2.0.2",
|
||||
"toidentifier": "~1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
||||
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-promise": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
|
||||
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
|
||||
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
|
||||
"dependencies": {
|
||||
"mime-db": "^1.54.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
|
||||
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
|
||||
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
|
||||
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
|
||||
"dependencies": {
|
||||
"bytes": "~3.1.2",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.7.0",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
"depd": "^2.0.0",
|
||||
"is-promise": "^4.0.0",
|
||||
"parseurl": "^1.3.3",
|
||||
"path-to-regexp": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
|
||||
"integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.3",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"etag": "^1.8.1",
|
||||
"fresh": "^2.0.0",
|
||||
"http-errors": "^2.0.1",
|
||||
"mime-types": "^3.0.2",
|
||||
"ms": "^2.1.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"range-parser": "^1.2.1",
|
||||
"statuses": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
|
||||
"integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
|
||||
"dependencies": {
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"parseurl": "^1.3.3",
|
||||
"send": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-list": "^1.0.0",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
||||
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
|
||||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
"media-typer": "^1.1.0",
|
||||
"mime-types": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
2.0.0 / 2024-08-31
|
||||
==================
|
||||
|
||||
* Drop node <18 support
|
||||
* deps: mime-types@^3.0.0
|
||||
* deps: negotiator@^1.0.0
|
||||
|
||||
1.3.8 / 2022-02-02
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.1.34
|
||||
- deps: mime-db@~1.51.0
|
||||
* deps: negotiator@0.6.3
|
||||
|
||||
1.3.7 / 2019-04-29
|
||||
==================
|
||||
|
||||
* deps: negotiator@0.6.2
|
||||
- Fix sorting charset, encoding, and language with extra parameters
|
||||
|
||||
1.3.6 / 2019-04-28
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.1.24
|
||||
- deps: mime-db@~1.40.0
|
||||
|
||||
1.3.5 / 2018-02-28
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.1.18
|
||||
- deps: mime-db@~1.33.0
|
||||
|
||||
1.3.4 / 2017-08-22
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.1.16
|
||||
- deps: mime-db@~1.29.0
|
||||
|
||||
1.3.3 / 2016-05-02
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.1.11
|
||||
- deps: mime-db@~1.23.0
|
||||
* deps: negotiator@0.6.1
|
||||
- perf: improve `Accept` parsing speed
|
||||
- perf: improve `Accept-Charset` parsing speed
|
||||
- perf: improve `Accept-Encoding` parsing speed
|
||||
- perf: improve `Accept-Language` parsing speed
|
||||
|
||||
1.3.2 / 2016-03-08
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.1.10
|
||||
- Fix extension of `application/dash+xml`
|
||||
- Update primary extension for `audio/mp4`
|
||||
- deps: mime-db@~1.22.0
|
||||
|
||||
1.3.1 / 2016-01-19
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.1.9
|
||||
- deps: mime-db@~1.21.0
|
||||
|
||||
1.3.0 / 2015-09-29
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.1.7
|
||||
- deps: mime-db@~1.19.0
|
||||
* deps: negotiator@0.6.0
|
||||
- Fix including type extensions in parameters in `Accept` parsing
|
||||
- Fix parsing `Accept` parameters with quoted equals
|
||||
- Fix parsing `Accept` parameters with quoted semicolons
|
||||
- Lazy-load modules from main entry point
|
||||
- perf: delay type concatenation until needed
|
||||
- perf: enable strict mode
|
||||
- perf: hoist regular expressions
|
||||
- perf: remove closures getting spec properties
|
||||
- perf: remove a closure from media type parsing
|
||||
- perf: remove property delete from media type parsing
|
||||
|
||||
1.2.13 / 2015-09-06
|
||||
===================
|
||||
|
||||
* deps: mime-types@~2.1.6
|
||||
- deps: mime-db@~1.18.0
|
||||
|
||||
1.2.12 / 2015-07-30
|
||||
===================
|
||||
|
||||
* deps: mime-types@~2.1.4
|
||||
- deps: mime-db@~1.16.0
|
||||
|
||||
1.2.11 / 2015-07-16
|
||||
===================
|
||||
|
||||
* deps: mime-types@~2.1.3
|
||||
- deps: mime-db@~1.15.0
|
||||
|
||||
1.2.10 / 2015-07-01
|
||||
===================
|
||||
|
||||
* deps: mime-types@~2.1.2
|
||||
- deps: mime-db@~1.14.0
|
||||
|
||||
1.2.9 / 2015-06-08
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.1.1
|
||||
- perf: fix deopt during mapping
|
||||
|
||||
1.2.8 / 2015-06-07
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.1.0
|
||||
- deps: mime-db@~1.13.0
|
||||
* perf: avoid argument reassignment & argument slice
|
||||
* perf: avoid negotiator recursive construction
|
||||
* perf: enable strict mode
|
||||
* perf: remove unnecessary bitwise operator
|
||||
|
||||
1.2.7 / 2015-05-10
|
||||
==================
|
||||
|
||||
* deps: negotiator@0.5.3
|
||||
- Fix media type parameter matching to be case-insensitive
|
||||
|
||||
1.2.6 / 2015-05-07
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.0.11
|
||||
- deps: mime-db@~1.9.1
|
||||
* deps: negotiator@0.5.2
|
||||
- Fix comparing media types with quoted values
|
||||
- Fix splitting media types with quoted commas
|
||||
|
||||
1.2.5 / 2015-03-13
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.0.10
|
||||
- deps: mime-db@~1.8.0
|
||||
|
||||
1.2.4 / 2015-02-14
|
||||
==================
|
||||
|
||||
* Support Node.js 0.6
|
||||
* deps: mime-types@~2.0.9
|
||||
- deps: mime-db@~1.7.0
|
||||
* deps: negotiator@0.5.1
|
||||
- Fix preference sorting to be stable for long acceptable lists
|
||||
|
||||
1.2.3 / 2015-01-31
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.0.8
|
||||
- deps: mime-db@~1.6.0
|
||||
|
||||
1.2.2 / 2014-12-30
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.0.7
|
||||
- deps: mime-db@~1.5.0
|
||||
|
||||
1.2.1 / 2014-12-30
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.0.5
|
||||
- deps: mime-db@~1.3.1
|
||||
|
||||
1.2.0 / 2014-12-19
|
||||
==================
|
||||
|
||||
* deps: negotiator@0.5.0
|
||||
- Fix list return order when large accepted list
|
||||
- Fix missing identity encoding when q=0 exists
|
||||
- Remove dynamic building of Negotiator class
|
||||
|
||||
1.1.4 / 2014-12-10
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.0.4
|
||||
- deps: mime-db@~1.3.0
|
||||
|
||||
1.1.3 / 2014-11-09
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.0.3
|
||||
- deps: mime-db@~1.2.0
|
||||
|
||||
1.1.2 / 2014-10-14
|
||||
==================
|
||||
|
||||
* deps: negotiator@0.4.9
|
||||
- Fix error when media type has invalid parameter
|
||||
|
||||
1.1.1 / 2014-09-28
|
||||
==================
|
||||
|
||||
* deps: mime-types@~2.0.2
|
||||
- deps: mime-db@~1.1.0
|
||||
* deps: negotiator@0.4.8
|
||||
- Fix all negotiations to be case-insensitive
|
||||
- Stable sort preferences of same quality according to client order
|
||||
|
||||
1.1.0 / 2014-09-02
|
||||
==================
|
||||
|
||||
* update `mime-types`
|
||||
|
||||
1.0.7 / 2014-07-04
|
||||
==================
|
||||
|
||||
* Fix wrong type returned from `type` when match after unknown extension
|
||||
|
||||
1.0.6 / 2014-06-24
|
||||
==================
|
||||
|
||||
* deps: negotiator@0.4.7
|
||||
|
||||
1.0.5 / 2014-06-20
|
||||
==================
|
||||
|
||||
* fix crash when unknown extension given
|
||||
|
||||
1.0.4 / 2014-06-19
|
||||
==================
|
||||
|
||||
* use `mime-types`
|
||||
|
||||
1.0.3 / 2014-06-11
|
||||
==================
|
||||
|
||||
* deps: negotiator@0.4.6
|
||||
- Order by specificity when quality is the same
|
||||
|
||||
1.0.2 / 2014-05-29
|
||||
==================
|
||||
|
||||
* Fix interpretation when header not in request
|
||||
* deps: pin negotiator@0.4.5
|
||||
|
||||
1.0.1 / 2014-01-18
|
||||
==================
|
||||
|
||||
* Identity encoding isn't always acceptable
|
||||
* deps: negotiator@~0.4.0
|
||||
|
||||
1.0.0 / 2013-12-27
|
||||
==================
|
||||
|
||||
* Genesis
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
|
||||
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
# accepts
|
||||
|
||||
[![NPM Version][npm-version-image]][npm-url]
|
||||
[![NPM Downloads][npm-downloads-image]][npm-url]
|
||||
[![Node.js Version][node-version-image]][node-version-url]
|
||||
[![Build Status][github-actions-ci-image]][github-actions-ci-url]
|
||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||
|
||||
Higher level content negotiation based on [negotiator](https://www.npmjs.com/package/negotiator).
|
||||
Extracted from [koa](https://www.npmjs.com/package/koa) for general use.
|
||||
|
||||
In addition to negotiator, it allows:
|
||||
|
||||
- Allows types as an array or arguments list, ie `(['text/html', 'application/json'])`
|
||||
as well as `('text/html', 'application/json')`.
|
||||
- Allows type shorthands such as `json`.
|
||||
- Returns `false` when no types match
|
||||
- Treats non-existent headers as `*`
|
||||
|
||||
## Installation
|
||||
|
||||
This is a [Node.js](https://nodejs.org/en/) module available through the
|
||||
[npm registry](https://www.npmjs.com/). Installation is done using the
|
||||
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
|
||||
|
||||
```sh
|
||||
$ npm install accepts
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```js
|
||||
var accepts = require('accepts')
|
||||
```
|
||||
|
||||
### accepts(req)
|
||||
|
||||
Create a new `Accepts` object for the given `req`.
|
||||
|
||||
#### .charset(charsets)
|
||||
|
||||
Return the first accepted charset. If nothing in `charsets` is accepted,
|
||||
then `false` is returned.
|
||||
|
||||
#### .charsets()
|
||||
|
||||
Return the charsets that the request accepts, in the order of the client's
|
||||
preference (most preferred first).
|
||||
|
||||
#### .encoding(encodings)
|
||||
|
||||
Return the first accepted encoding. If nothing in `encodings` is accepted,
|
||||
then `false` is returned.
|
||||
|
||||
#### .encodings()
|
||||
|
||||
Return the encodings that the request accepts, in the order of the client's
|
||||
preference (most preferred first).
|
||||
|
||||
#### .language(languages)
|
||||
|
||||
Return the first accepted language. If nothing in `languages` is accepted,
|
||||
then `false` is returned.
|
||||
|
||||
#### .languages()
|
||||
|
||||
Return the languages that the request accepts, in the order of the client's
|
||||
preference (most preferred first).
|
||||
|
||||
#### .type(types)
|
||||
|
||||
Return the first accepted type (and it is returned as the same text as what
|
||||
appears in the `types` array). If nothing in `types` is accepted, then `false`
|
||||
is returned.
|
||||
|
||||
The `types` array can contain full MIME types or file extensions. Any value
|
||||
that is not a full MIME type is passed to `require('mime-types').lookup`.
|
||||
|
||||
#### .types()
|
||||
|
||||
Return the types that the request accepts, in the order of the client's
|
||||
preference (most preferred first).
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple type negotiation
|
||||
|
||||
This simple example shows how to use `accepts` to return a different typed
|
||||
respond body based on what the client wants to accept. The server lists it's
|
||||
preferences in order and will get back the best match between the client and
|
||||
server.
|
||||
|
||||
```js
|
||||
var accepts = require('accepts')
|
||||
var http = require('http')
|
||||
|
||||
function app (req, res) {
|
||||
var accept = accepts(req)
|
||||
|
||||
// the order of this list is significant; should be server preferred order
|
||||
switch (accept.type(['json', 'html'])) {
|
||||
case 'json':
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.write('{"hello":"world!"}')
|
||||
break
|
||||
case 'html':
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
res.write('<b>hello, world!</b>')
|
||||
break
|
||||
default:
|
||||
// the fallback is text/plain, so no need to specify it above
|
||||
res.setHeader('Content-Type', 'text/plain')
|
||||
res.write('hello, world!')
|
||||
break
|
||||
}
|
||||
|
||||
res.end()
|
||||
}
|
||||
|
||||
http.createServer(app).listen(3000)
|
||||
```
|
||||
|
||||
You can test this out with the cURL program:
|
||||
```sh
|
||||
curl -I -H'Accept: text/html' http://localhost:3000/
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/accepts/master
|
||||
[coveralls-url]: https://coveralls.io/r/jshttp/accepts?branch=master
|
||||
[github-actions-ci-image]: https://badgen.net/github/checks/jshttp/accepts/master?label=ci
|
||||
[github-actions-ci-url]: https://github.com/jshttp/accepts/actions/workflows/ci.yml
|
||||
[node-version-image]: https://badgen.net/npm/node/accepts
|
||||
[node-version-url]: https://nodejs.org/en/download
|
||||
[npm-downloads-image]: https://badgen.net/npm/dm/accepts
|
||||
[npm-url]: https://npmjs.org/package/accepts
|
||||
[npm-version-image]: https://badgen.net/npm/v/accepts
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
/*!
|
||||
* accepts
|
||||
* Copyright(c) 2014 Jonathan Ong
|
||||
* Copyright(c) 2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var Negotiator = require('negotiator')
|
||||
var mime = require('mime-types')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
* @public
|
||||
*/
|
||||
|
||||
module.exports = Accepts
|
||||
|
||||
/**
|
||||
* Create a new Accepts object for the given req.
|
||||
*
|
||||
* @param {object} req
|
||||
* @public
|
||||
*/
|
||||
|
||||
function Accepts (req) {
|
||||
if (!(this instanceof Accepts)) {
|
||||
return new Accepts(req)
|
||||
}
|
||||
|
||||
this.headers = req.headers
|
||||
this.negotiator = new Negotiator(req)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given `type(s)` is acceptable, returning
|
||||
* the best match when true, otherwise `undefined`, in which
|
||||
* case you should respond with 406 "Not Acceptable".
|
||||
*
|
||||
* The `type` value may be a single mime type string
|
||||
* such as "application/json", the extension name
|
||||
* such as "json" or an array `["json", "html", "text/plain"]`. When a list
|
||||
* or array is given the _best_ match, if any is returned.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* // Accept: text/html
|
||||
* this.types('html');
|
||||
* // => "html"
|
||||
*
|
||||
* // Accept: text/*, application/json
|
||||
* this.types('html');
|
||||
* // => "html"
|
||||
* this.types('text/html');
|
||||
* // => "text/html"
|
||||
* this.types('json', 'text');
|
||||
* // => "json"
|
||||
* this.types('application/json');
|
||||
* // => "application/json"
|
||||
*
|
||||
* // Accept: text/*, application/json
|
||||
* this.types('image/png');
|
||||
* this.types('png');
|
||||
* // => undefined
|
||||
*
|
||||
* // Accept: text/*;q=.5, application/json
|
||||
* this.types(['html', 'json']);
|
||||
* this.types('html', 'json');
|
||||
* // => "json"
|
||||
*
|
||||
* @param {String|Array} types...
|
||||
* @return {String|Array|Boolean}
|
||||
* @public
|
||||
*/
|
||||
|
||||
Accepts.prototype.type =
|
||||
Accepts.prototype.types = function (types_) {
|
||||
var types = types_
|
||||
|
||||
// support flattened arguments
|
||||
if (types && !Array.isArray(types)) {
|
||||
types = new Array(arguments.length)
|
||||
for (var i = 0; i < types.length; i++) {
|
||||
types[i] = arguments[i]
|
||||
}
|
||||
}
|
||||
|
||||
// no types, return all requested types
|
||||
if (!types || types.length === 0) {
|
||||
return this.negotiator.mediaTypes()
|
||||
}
|
||||
|
||||
// no accept header, return first given type
|
||||
if (!this.headers.accept) {
|
||||
return types[0]
|
||||
}
|
||||
|
||||
var mimes = types.map(extToMime)
|
||||
var accepts = this.negotiator.mediaTypes(mimes.filter(validMime))
|
||||
var first = accepts[0]
|
||||
|
||||
return first
|
||||
? types[mimes.indexOf(first)]
|
||||
: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Return accepted encodings or best fit based on `encodings`.
|
||||
*
|
||||
* Given `Accept-Encoding: gzip, deflate`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['gzip', 'deflate']
|
||||
*
|
||||
* @param {String|Array} encodings...
|
||||
* @return {String|Array}
|
||||
* @public
|
||||
*/
|
||||
|
||||
Accepts.prototype.encoding =
|
||||
Accepts.prototype.encodings = function (encodings_) {
|
||||
var encodings = encodings_
|
||||
|
||||
// support flattened arguments
|
||||
if (encodings && !Array.isArray(encodings)) {
|
||||
encodings = new Array(arguments.length)
|
||||
for (var i = 0; i < encodings.length; i++) {
|
||||
encodings[i] = arguments[i]
|
||||
}
|
||||
}
|
||||
|
||||
// no encodings, return all requested encodings
|
||||
if (!encodings || encodings.length === 0) {
|
||||
return this.negotiator.encodings()
|
||||
}
|
||||
|
||||
return this.negotiator.encodings(encodings)[0] || false
|
||||
}
|
||||
|
||||
/**
|
||||
* Return accepted charsets or best fit based on `charsets`.
|
||||
*
|
||||
* Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['utf-8', 'utf-7', 'iso-8859-1']
|
||||
*
|
||||
* @param {String|Array} charsets...
|
||||
* @return {String|Array}
|
||||
* @public
|
||||
*/
|
||||
|
||||
Accepts.prototype.charset =
|
||||
Accepts.prototype.charsets = function (charsets_) {
|
||||
var charsets = charsets_
|
||||
|
||||
// support flattened arguments
|
||||
if (charsets && !Array.isArray(charsets)) {
|
||||
charsets = new Array(arguments.length)
|
||||
for (var i = 0; i < charsets.length; i++) {
|
||||
charsets[i] = arguments[i]
|
||||
}
|
||||
}
|
||||
|
||||
// no charsets, return all requested charsets
|
||||
if (!charsets || charsets.length === 0) {
|
||||
return this.negotiator.charsets()
|
||||
}
|
||||
|
||||
return this.negotiator.charsets(charsets)[0] || false
|
||||
}
|
||||
|
||||
/**
|
||||
* Return accepted languages or best fit based on `langs`.
|
||||
*
|
||||
* Given `Accept-Language: en;q=0.8, es, pt`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['es', 'pt', 'en']
|
||||
*
|
||||
* @param {String|Array} langs...
|
||||
* @return {Array|String}
|
||||
* @public
|
||||
*/
|
||||
|
||||
Accepts.prototype.lang =
|
||||
Accepts.prototype.langs =
|
||||
Accepts.prototype.language =
|
||||
Accepts.prototype.languages = function (languages_) {
|
||||
var languages = languages_
|
||||
|
||||
// support flattened arguments
|
||||
if (languages && !Array.isArray(languages)) {
|
||||
languages = new Array(arguments.length)
|
||||
for (var i = 0; i < languages.length; i++) {
|
||||
languages[i] = arguments[i]
|
||||
}
|
||||
}
|
||||
|
||||
// no languages, return all requested languages
|
||||
if (!languages || languages.length === 0) {
|
||||
return this.negotiator.languages()
|
||||
}
|
||||
|
||||
return this.negotiator.languages(languages)[0] || false
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert extnames to mime.
|
||||
*
|
||||
* @param {String} type
|
||||
* @return {String}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function extToMime (type) {
|
||||
return type.indexOf('/') === -1
|
||||
? mime.lookup(type)
|
||||
: type
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if mime is valid.
|
||||
*
|
||||
* @param {String} type
|
||||
* @return {Boolean}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function validMime (type) {
|
||||
return typeof type === 'string'
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "accepts",
|
||||
"description": "Higher-level content negotiation",
|
||||
"version": "2.0.0",
|
||||
"contributors": [
|
||||
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": "jshttp/accepts",
|
||||
"dependencies": {
|
||||
"mime-types": "^3.0.0",
|
||||
"negotiator": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"deep-equal": "1.0.1",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-standard": "14.1.1",
|
||||
"eslint-plugin-import": "2.25.4",
|
||||
"eslint-plugin-markdown": "2.2.1",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-promise": "4.3.1",
|
||||
"eslint-plugin-standard": "4.1.0",
|
||||
"mocha": "9.2.0",
|
||||
"nyc": "15.1.0"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"HISTORY.md",
|
||||
"index.js"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "mocha --reporter spec --check-leaks --bail test/",
|
||||
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
||||
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
||||
},
|
||||
"keywords": [
|
||||
"content",
|
||||
"negotiation",
|
||||
"accept",
|
||||
"accepts"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
|
||||
Copyright (c) 2014-2015 Douglas Christopher Wilson <doug@somethingdoug.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,494 @@
|
|||
# body-parser
|
||||
|
||||
[![NPM Version][npm-version-image]][npm-url]
|
||||
[![NPM Downloads][npm-downloads-image]][npm-url]
|
||||
[![Build Status][ci-image]][ci-url]
|
||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||
[![OpenSSF Scorecard Badge][ossf-scorecard-badge]][ossf-scorecard-visualizer]
|
||||
|
||||
Node.js body parsing middleware.
|
||||
|
||||
Parse incoming request bodies in a middleware before your handlers, available
|
||||
under the `req.body` property.
|
||||
|
||||
**Note** As `req.body`'s shape is based on user-controlled input, all
|
||||
properties and values in this object are untrusted and should be validated
|
||||
before trusting. For example, `req.body.foo.toString()` may fail in multiple
|
||||
ways, for example the `foo` property may not be there or may not be a string,
|
||||
and `toString` may not be a function and instead a string or other user input.
|
||||
|
||||
[Learn about the anatomy of an HTTP transaction in Node.js](https://nodejs.org/en/learn/http/anatomy-of-an-http-transaction).
|
||||
|
||||
_This does not handle multipart bodies_, due to their complex and typically
|
||||
large nature. For multipart bodies, you may be interested in the following
|
||||
modules:
|
||||
|
||||
* [busboy](https://www.npmjs.com/package/busboy#readme) and
|
||||
[connect-busboy](https://www.npmjs.com/package/connect-busboy#readme)
|
||||
* [multiparty](https://www.npmjs.com/package/multiparty#readme) and
|
||||
[connect-multiparty](https://www.npmjs.com/package/connect-multiparty#readme)
|
||||
* [formidable](https://www.npmjs.com/package/formidable#readme)
|
||||
* [multer](https://www.npmjs.com/package/multer#readme)
|
||||
|
||||
This module provides the following parsers:
|
||||
|
||||
* [JSON body parser](#bodyparserjsonoptions)
|
||||
* [Raw body parser](#bodyparserrawoptions)
|
||||
* [Text body parser](#bodyparsertextoptions)
|
||||
* [URL-encoded form body parser](#bodyparserurlencodedoptions)
|
||||
|
||||
Other body parsers you might be interested in:
|
||||
|
||||
- [body](https://www.npmjs.com/package/body#readme)
|
||||
- [co-body](https://www.npmjs.com/package/co-body#readme)
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
$ npm install body-parser
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```js
|
||||
const bodyParser = require('body-parser')
|
||||
```
|
||||
|
||||
The `bodyParser` object exposes various factories to create middlewares. All
|
||||
middlewares will populate the `req.body` property with the parsed body when
|
||||
the `Content-Type` request header matches the `type` option.
|
||||
|
||||
The various errors returned by this module are described in the
|
||||
[errors section](#errors).
|
||||
|
||||
### bodyParser.json([options])
|
||||
|
||||
Returns middleware that only parses `json` and only looks at requests where
|
||||
the `Content-Type` header matches the `type` option. This parser accepts any
|
||||
Unicode encoding of the body and supports automatic inflation of `gzip`,
|
||||
`br` (brotli) and `deflate` encodings.
|
||||
|
||||
A new `body` object containing the parsed data is populated on the `request`
|
||||
object after the middleware (i.e. `req.body`).
|
||||
|
||||
#### Options
|
||||
|
||||
The `json` function takes an optional `options` object that may contain any of
|
||||
the following keys:
|
||||
|
||||
##### defaultCharset
|
||||
|
||||
Specify the default character set for the json content if the charset is not
|
||||
specified in the `Content-Type` header of the request. Defaults to `utf-8`.
|
||||
|
||||
##### inflate
|
||||
|
||||
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
||||
`false`, deflated bodies are rejected. Defaults to `true`.
|
||||
|
||||
##### limit
|
||||
|
||||
Controls the maximum request body size. If this is a number, then the value
|
||||
specifies the number of bytes; if it is a string, the value is passed to the
|
||||
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
||||
to `'100kb'`.
|
||||
|
||||
##### reviver
|
||||
|
||||
The `reviver` option is passed directly to `JSON.parse` as the second
|
||||
argument. You can find more information on this argument
|
||||
[in the MDN documentation about JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Example.3A_Using_the_reviver_parameter).
|
||||
|
||||
##### strict
|
||||
|
||||
When set to `true`, will only accept arrays and objects; when `false` will
|
||||
accept anything `JSON.parse` accepts. Defaults to `true`.
|
||||
|
||||
##### type
|
||||
|
||||
The `type` option is used to determine what media type the middleware will
|
||||
parse. This option can be a string, array of strings, or a function. If not a
|
||||
function, `type` option is passed directly to the
|
||||
[type-is](https://www.npmjs.com/package/type-is#readme) library and this can
|
||||
be an extension name (like `json`), a mime type (like `application/json`), or
|
||||
a mime type with a wildcard (like `*/*` or `*/json`). If a function, the `type`
|
||||
option is called as `fn(req)` and the request is parsed if it returns a truthy
|
||||
value. Defaults to `application/json`.
|
||||
|
||||
##### verify
|
||||
|
||||
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
||||
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
||||
encoding of the request. The parsing can be aborted by throwing an error.
|
||||
|
||||
### bodyParser.raw([options])
|
||||
|
||||
Returns middleware that parses all bodies as a `Buffer` and only looks at
|
||||
requests where the `Content-Type` header matches the `type` option. This
|
||||
parser supports automatic inflation of `gzip`, `br` (brotli) and `deflate`
|
||||
encodings.
|
||||
|
||||
A new `body` object containing the parsed data is populated on the `request`
|
||||
object after the middleware (i.e. `req.body`). This will be a `Buffer` object
|
||||
of the body.
|
||||
|
||||
#### Options
|
||||
|
||||
The `raw` function takes an optional `options` object that may contain any of
|
||||
the following keys:
|
||||
|
||||
##### inflate
|
||||
|
||||
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
||||
`false`, deflated bodies are rejected. Defaults to `true`.
|
||||
|
||||
##### limit
|
||||
|
||||
Controls the maximum request body size. If this is a number, then the value
|
||||
specifies the number of bytes; if it is a string, the value is passed to the
|
||||
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
||||
to `'100kb'`.
|
||||
|
||||
##### type
|
||||
|
||||
The `type` option is used to determine what media type the middleware will
|
||||
parse. This option can be a string, array of strings, or a function.
|
||||
If not a function, `type` option is passed directly to the
|
||||
[type-is](https://www.npmjs.com/package/type-is#readme) library and this
|
||||
can be an extension name (like `bin`), a mime type (like
|
||||
`application/octet-stream`), or a mime type with a wildcard (like `*/*` or
|
||||
`application/*`). If a function, the `type` option is called as `fn(req)`
|
||||
and the request is parsed if it returns a truthy value. Defaults to
|
||||
`application/octet-stream`.
|
||||
|
||||
##### verify
|
||||
|
||||
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
||||
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
||||
encoding of the request. The parsing can be aborted by throwing an error.
|
||||
|
||||
### bodyParser.text([options])
|
||||
|
||||
Returns middleware that parses all bodies as a string and only looks at
|
||||
requests where the `Content-Type` header matches the `type` option. This
|
||||
parser supports automatic inflation of `gzip`, `br` (brotli) and `deflate`
|
||||
encodings.
|
||||
|
||||
A new `body` string containing the parsed data is populated on the `request`
|
||||
object after the middleware (i.e. `req.body`). This will be a string of the
|
||||
body.
|
||||
|
||||
#### Options
|
||||
|
||||
The `text` function takes an optional `options` object that may contain any of
|
||||
the following keys:
|
||||
|
||||
##### defaultCharset
|
||||
|
||||
Specify the default character set for the text content if the charset is not
|
||||
specified in the `Content-Type` header of the request. Defaults to `utf-8`.
|
||||
|
||||
##### inflate
|
||||
|
||||
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
||||
`false`, deflated bodies are rejected. Defaults to `true`.
|
||||
|
||||
##### limit
|
||||
|
||||
Controls the maximum request body size. If this is a number, then the value
|
||||
specifies the number of bytes; if it is a string, the value is passed to the
|
||||
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
||||
to `'100kb'`.
|
||||
|
||||
##### type
|
||||
|
||||
The `type` option is used to determine what media type the middleware will
|
||||
parse. This option can be a string, array of strings, or a function. If not
|
||||
a function, `type` option is passed directly to the
|
||||
[type-is](https://www.npmjs.com/package/type-is#readme) library and this can
|
||||
be an extension name (like `txt`), a mime type (like `text/plain`), or a mime
|
||||
type with a wildcard (like `*/*` or `text/*`). If a function, the `type`
|
||||
option is called as `fn(req)` and the request is parsed if it returns a
|
||||
truthy value. Defaults to `text/plain`.
|
||||
|
||||
##### verify
|
||||
|
||||
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
||||
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
||||
encoding of the request. The parsing can be aborted by throwing an error.
|
||||
|
||||
### bodyParser.urlencoded([options])
|
||||
|
||||
Returns middleware that only parses `urlencoded` bodies and only looks at
|
||||
requests where the `Content-Type` header matches the `type` option. This
|
||||
parser accepts only UTF-8 and ISO-8859-1 encodings of the body and supports
|
||||
automatic inflation of `gzip`, `br` (brotli) and `deflate` encodings.
|
||||
|
||||
A new `body` object containing the parsed data is populated on the `request`
|
||||
object after the middleware (i.e. `req.body`). This object will contain
|
||||
key-value pairs, where the value can be a string or array (when `extended` is
|
||||
`false`), or any type (when `extended` is `true`).
|
||||
|
||||
#### Options
|
||||
|
||||
The `urlencoded` function takes an optional `options` object that may contain
|
||||
any of the following keys:
|
||||
|
||||
##### extended
|
||||
|
||||
The "extended" syntax allows for rich objects and arrays to be encoded into the
|
||||
URL-encoded format, allowing for a JSON-like experience with URL-encoded. For
|
||||
more information, please [see the qs
|
||||
library](https://www.npmjs.com/package/qs#readme).
|
||||
|
||||
Defaults to `false`.
|
||||
|
||||
##### inflate
|
||||
|
||||
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
||||
`false`, deflated bodies are rejected. Defaults to `true`.
|
||||
|
||||
##### limit
|
||||
|
||||
Controls the maximum request body size. If this is a number, then the value
|
||||
specifies the number of bytes; if it is a string, the value is passed to the
|
||||
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
||||
to `'100kb'`.
|
||||
|
||||
##### parameterLimit
|
||||
|
||||
The `parameterLimit` option controls the maximum number of parameters that
|
||||
are allowed in the URL-encoded data. If a request contains more parameters
|
||||
than this value, a 413 will be returned to the client. Defaults to `1000`.
|
||||
|
||||
##### type
|
||||
|
||||
The `type` option is used to determine what media type the middleware will
|
||||
parse. This option can be a string, array of strings, or a function. If not
|
||||
a function, `type` option is passed directly to the
|
||||
[type-is](https://www.npmjs.com/package/type-is#readme) library and this can
|
||||
be an extension name (like `urlencoded`), a mime type (like
|
||||
`application/x-www-form-urlencoded`), or a mime type with a wildcard (like
|
||||
`*/x-www-form-urlencoded`). If a function, the `type` option is called as
|
||||
`fn(req)` and the request is parsed if it returns a truthy value. Defaults
|
||||
to `application/x-www-form-urlencoded`.
|
||||
|
||||
##### verify
|
||||
|
||||
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
||||
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
||||
encoding of the request. The parsing can be aborted by throwing an error.
|
||||
|
||||
##### defaultCharset
|
||||
|
||||
The default charset to parse as, if not specified in content-type. Must be
|
||||
either `utf-8` or `iso-8859-1`. Defaults to `utf-8`.
|
||||
|
||||
##### charsetSentinel
|
||||
|
||||
Whether to let the value of the `utf8` parameter take precedence as the charset
|
||||
selector. It requires the form to contain a parameter named `utf8` with a value
|
||||
of `✓`. Defaults to `false`.
|
||||
|
||||
##### interpretNumericEntities
|
||||
|
||||
Whether to decode numeric entities such as `☺` when parsing an iso-8859-1
|
||||
form. Defaults to `false`.
|
||||
|
||||
|
||||
##### depth
|
||||
|
||||
The `depth` option is used to configure the maximum depth of the `qs` library when `extended` is `true`. This allows you to limit the amount of keys that are parsed and can be useful to prevent certain types of abuse. Defaults to `32`. It is recommended to keep this value as low as possible.
|
||||
|
||||
## Errors
|
||||
|
||||
The middlewares provided by this module create errors using the
|
||||
[`http-errors` module](https://www.npmjs.com/package/http-errors). The errors
|
||||
will typically have a `status`/`statusCode` property that contains the suggested
|
||||
HTTP response code, an `expose` property to determine if the `message` property
|
||||
should be displayed to the client, a `type` property to determine the type of
|
||||
error without matching against the `message`, and a `body` property containing
|
||||
the read body, if available.
|
||||
|
||||
The following are the common errors created, though any error can come through
|
||||
for various reasons.
|
||||
|
||||
### content encoding unsupported
|
||||
|
||||
This error will occur when the request had a `Content-Encoding` header that
|
||||
contained an encoding but the "inflation" option was set to `false`. The
|
||||
`status` property is set to `415`, the `type` property is set to
|
||||
`'encoding.unsupported'`, and the `charset` property will be set to the
|
||||
encoding that is unsupported.
|
||||
|
||||
### entity parse failed
|
||||
|
||||
This error will occur when the request contained an entity that could not be
|
||||
parsed by the middleware. The `status` property is set to `400`, the `type`
|
||||
property is set to `'entity.parse.failed'`, and the `body` property is set to
|
||||
the entity value that failed parsing.
|
||||
|
||||
### entity verify failed
|
||||
|
||||
This error will occur when the request contained an entity that could not be
|
||||
failed verification by the defined `verify` option. The `status` property is
|
||||
set to `403`, the `type` property is set to `'entity.verify.failed'`, and the
|
||||
`body` property is set to the entity value that failed verification.
|
||||
|
||||
### request aborted
|
||||
|
||||
This error will occur when the request is aborted by the client before reading
|
||||
the body has finished. The `received` property will be set to the number of
|
||||
bytes received before the request was aborted and the `expected` property is
|
||||
set to the number of expected bytes. The `status` property is set to `400`
|
||||
and `type` property is set to `'request.aborted'`.
|
||||
|
||||
### request entity too large
|
||||
|
||||
This error will occur when the request body's size is larger than the "limit"
|
||||
option. The `limit` property will be set to the byte limit and the `length`
|
||||
property will be set to the request body's length. The `status` property is
|
||||
set to `413` and the `type` property is set to `'entity.too.large'`.
|
||||
|
||||
### request size did not match content length
|
||||
|
||||
This error will occur when the request's length did not match the length from
|
||||
the `Content-Length` header. This typically occurs when the request is malformed,
|
||||
typically when the `Content-Length` header was calculated based on characters
|
||||
instead of bytes. The `status` property is set to `400` and the `type` property
|
||||
is set to `'request.size.invalid'`.
|
||||
|
||||
### stream encoding should not be set
|
||||
|
||||
This error will occur when something called the `req.setEncoding` method prior
|
||||
to this middleware. This module operates directly on bytes only and you cannot
|
||||
call `req.setEncoding` when using this module. The `status` property is set to
|
||||
`500` and the `type` property is set to `'stream.encoding.set'`.
|
||||
|
||||
### stream is not readable
|
||||
|
||||
This error will occur when the request is no longer readable when this middleware
|
||||
attempts to read it. This typically means something other than a middleware from
|
||||
this module read the request body already and the middleware was also configured to
|
||||
read the same request. The `status` property is set to `500` and the `type`
|
||||
property is set to `'stream.not.readable'`.
|
||||
|
||||
### too many parameters
|
||||
|
||||
This error will occur when the content of the request exceeds the configured
|
||||
`parameterLimit` for the `urlencoded` parser. The `status` property is set to
|
||||
`413` and the `type` property is set to `'parameters.too.many'`.
|
||||
|
||||
### unsupported charset "BOGUS"
|
||||
|
||||
This error will occur when the request had a charset parameter in the
|
||||
`Content-Type` header, but the `iconv-lite` module does not support it OR the
|
||||
parser does not support it. The charset is contained in the message as well
|
||||
as in the `charset` property. The `status` property is set to `415`, the
|
||||
`type` property is set to `'charset.unsupported'`, and the `charset` property
|
||||
is set to the charset that is unsupported.
|
||||
|
||||
### unsupported content encoding "bogus"
|
||||
|
||||
This error will occur when the request had a `Content-Encoding` header that
|
||||
contained an unsupported encoding. The encoding is contained in the message
|
||||
as well as in the `encoding` property. The `status` property is set to `415`,
|
||||
the `type` property is set to `'encoding.unsupported'`, and the `encoding`
|
||||
property is set to the encoding that is unsupported.
|
||||
|
||||
### The input exceeded the depth
|
||||
|
||||
This error occurs when using `bodyParser.urlencoded` with the `extended` property set to `true` and the input exceeds the configured `depth` option. The `status` property is set to `400`. It is recommended to review the `depth` option and evaluate if it requires a higher value. When the `depth` option is set to `32` (default value), the error will not be thrown.
|
||||
|
||||
## Examples
|
||||
|
||||
### Express/Connect top-level generic
|
||||
|
||||
This example demonstrates adding a generic JSON and URL-encoded parser as a
|
||||
top-level middleware, which will parse the bodies of all incoming requests.
|
||||
This is the simplest setup.
|
||||
|
||||
```js
|
||||
const express = require('express')
|
||||
const bodyParser = require('body-parser')
|
||||
|
||||
const app = express()
|
||||
|
||||
// parse application/x-www-form-urlencoded
|
||||
app.use(bodyParser.urlencoded())
|
||||
|
||||
// parse application/json
|
||||
app.use(bodyParser.json())
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.setHeader('Content-Type', 'text/plain')
|
||||
res.write('you posted:\n')
|
||||
res.end(String(JSON.stringify(req.body, null, 2)))
|
||||
})
|
||||
```
|
||||
|
||||
### Express route-specific
|
||||
|
||||
This example demonstrates adding body parsers specifically to the routes that
|
||||
need them. In general, this is the most recommended way to use body-parser with
|
||||
Express.
|
||||
|
||||
```js
|
||||
const express = require('express')
|
||||
const bodyParser = require('body-parser')
|
||||
|
||||
const app = express()
|
||||
|
||||
// create application/json parser
|
||||
const jsonParser = bodyParser.json()
|
||||
|
||||
// create application/x-www-form-urlencoded parser
|
||||
const urlencodedParser = bodyParser.urlencoded()
|
||||
|
||||
// POST /login gets urlencoded bodies
|
||||
app.post('/login', urlencodedParser, function (req, res) {
|
||||
if (!req.body || !req.body.username) res.sendStatus(400)
|
||||
res.send('welcome, ' + req.body.username)
|
||||
})
|
||||
|
||||
// POST /api/users gets JSON bodies
|
||||
app.post('/api/users', jsonParser, function (req, res) {
|
||||
if (!req.body) res.sendStatus(400)
|
||||
// create user in req.body
|
||||
})
|
||||
```
|
||||
|
||||
### Change accepted type for parsers
|
||||
|
||||
All the parsers accept a `type` option which allows you to change the
|
||||
`Content-Type` that the middleware will parse.
|
||||
|
||||
```js
|
||||
const express = require('express')
|
||||
const bodyParser = require('body-parser')
|
||||
|
||||
const app = express()
|
||||
|
||||
// parse various different custom JSON types as JSON
|
||||
app.use(bodyParser.json({ type: 'application/*+json' }))
|
||||
|
||||
// parse some custom thing into a Buffer
|
||||
app.use(bodyParser.raw({ type: 'application/vnd.custom-type' }))
|
||||
|
||||
// parse an HTML body into a string
|
||||
app.use(bodyParser.text({ type: 'text/html' }))
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
[ci-image]: https://img.shields.io/github/actions/workflow/status/expressjs/body-parser/ci.yml?branch=master&label=ci
|
||||
[ci-url]: https://github.com/expressjs/body-parser/actions/workflows/ci.yml
|
||||
[coveralls-image]: https://img.shields.io/coverallsCoverage/github/expressjs/body-parser?branch=master
|
||||
[coveralls-url]: https://coveralls.io/r/expressjs/body-parser?branch=master
|
||||
[npm-downloads-image]: https://img.shields.io/npm/dm/body-parser
|
||||
[npm-url]: https://npmjs.com/package/body-parser
|
||||
[npm-version-image]: https://img.shields.io/npm/v/body-parser
|
||||
[ossf-scorecard-badge]: https://api.scorecard.dev/projects/github.com/expressjs/body-parser/badge
|
||||
[ossf-scorecard-visualizer]: https://ossf.github.io/scorecard-visualizer/#/projects/github.com/expressjs/body-parser
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @typedef {Object} Parsers
|
||||
* @property {Function} json JSON parser
|
||||
* @property {Function} raw Raw parser
|
||||
* @property {Function} text Text parser
|
||||
* @property {Function} urlencoded URL-encoded parser
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
* @type {Function & Parsers}
|
||||
*/
|
||||
exports = module.exports = bodyParser
|
||||
|
||||
/**
|
||||
* JSON parser.
|
||||
* @public
|
||||
*/
|
||||
Object.defineProperty(exports, 'json', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => require('./lib/types/json')
|
||||
})
|
||||
|
||||
/**
|
||||
* Raw parser.
|
||||
* @public
|
||||
*/
|
||||
Object.defineProperty(exports, 'raw', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => require('./lib/types/raw')
|
||||
})
|
||||
|
||||
/**
|
||||
* Text parser.
|
||||
* @public
|
||||
*/
|
||||
Object.defineProperty(exports, 'text', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => require('./lib/types/text')
|
||||
})
|
||||
|
||||
/**
|
||||
* URL-encoded parser.
|
||||
* @public
|
||||
*/
|
||||
Object.defineProperty(exports, 'urlencoded', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => require('./lib/types/urlencoded')
|
||||
})
|
||||
|
||||
/**
|
||||
* Create a middleware to parse json and urlencoded bodies.
|
||||
*
|
||||
* @deprecated
|
||||
* @public
|
||||
*/
|
||||
function bodyParser () {
|
||||
throw new Error('The bodyParser() generic has been split into individual middleware to use instead.')
|
||||
}
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var createError = require('http-errors')
|
||||
var getBody = require('raw-body')
|
||||
var iconv = require('iconv-lite')
|
||||
var onFinished = require('on-finished')
|
||||
var zlib = require('node:zlib')
|
||||
var hasBody = require('type-is').hasBody
|
||||
var { getCharset } = require('./utils')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = read
|
||||
|
||||
/**
|
||||
* Read a request into a buffer and parse.
|
||||
*
|
||||
* @param {Object} req
|
||||
* @param {Object} res
|
||||
* @param {Function} next
|
||||
* @param {Function} parse
|
||||
* @param {Function} debug
|
||||
* @param {Object} options
|
||||
* @private
|
||||
*/
|
||||
function read (req, res, next, parse, debug, options) {
|
||||
if (onFinished.isFinished(req)) {
|
||||
debug('body already parsed')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (!('body' in req)) {
|
||||
req.body = undefined
|
||||
}
|
||||
|
||||
// skip requests without bodies
|
||||
if (!hasBody(req)) {
|
||||
debug('skip empty body')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
debug('content-type %j', req.headers['content-type'])
|
||||
|
||||
// determine if request should be parsed
|
||||
if (!options.shouldParse(req)) {
|
||||
debug('skip parsing')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
var encoding = null
|
||||
if (options?.skipCharset !== true) {
|
||||
encoding = getCharset(req) || options.defaultCharset
|
||||
|
||||
// validate charset
|
||||
if (!!options?.isValidCharset && !options.isValidCharset(encoding)) {
|
||||
debug('invalid charset')
|
||||
next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
|
||||
charset: encoding,
|
||||
type: 'charset.unsupported'
|
||||
}))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var length
|
||||
var opts = options
|
||||
var stream
|
||||
|
||||
// read options
|
||||
var verify = opts.verify
|
||||
|
||||
try {
|
||||
// get the content stream
|
||||
stream = contentstream(req, debug, opts.inflate)
|
||||
length = stream.length
|
||||
stream.length = undefined
|
||||
} catch (err) {
|
||||
return next(err)
|
||||
}
|
||||
|
||||
// set raw-body options
|
||||
opts.length = length
|
||||
opts.encoding = verify
|
||||
? null
|
||||
: encoding
|
||||
|
||||
// assert charset is supported
|
||||
if (opts.encoding === null && encoding !== null && !iconv.encodingExists(encoding)) {
|
||||
return next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
|
||||
charset: encoding.toLowerCase(),
|
||||
type: 'charset.unsupported'
|
||||
}))
|
||||
}
|
||||
|
||||
// read body
|
||||
debug('read body')
|
||||
getBody(stream, opts, function (error, body) {
|
||||
if (error) {
|
||||
var _error
|
||||
|
||||
if (error.type === 'encoding.unsupported') {
|
||||
// echo back charset
|
||||
_error = createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
|
||||
charset: encoding.toLowerCase(),
|
||||
type: 'charset.unsupported'
|
||||
})
|
||||
} else {
|
||||
// set status code on error
|
||||
_error = createError(400, error)
|
||||
}
|
||||
|
||||
// unpipe from stream and destroy
|
||||
if (stream !== req) {
|
||||
req.unpipe()
|
||||
stream.destroy()
|
||||
}
|
||||
|
||||
// read off entire request
|
||||
dump(req, function onfinished () {
|
||||
next(createError(400, _error))
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// verify
|
||||
if (verify) {
|
||||
try {
|
||||
debug('verify body')
|
||||
verify(req, res, body, encoding)
|
||||
} catch (err) {
|
||||
next(createError(403, err, {
|
||||
body: body,
|
||||
type: err.type || 'entity.verify.failed'
|
||||
}))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// parse
|
||||
var str = body
|
||||
try {
|
||||
debug('parse body')
|
||||
str = typeof body !== 'string' && encoding !== null
|
||||
? iconv.decode(body, encoding)
|
||||
: body
|
||||
req.body = parse(str, encoding)
|
||||
} catch (err) {
|
||||
next(createError(400, err, {
|
||||
body: str,
|
||||
type: err.type || 'entity.parse.failed'
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content stream of the request.
|
||||
*
|
||||
* @param {Object} req
|
||||
* @param {Function} debug
|
||||
* @param {boolean} inflate
|
||||
* @returns {Object}
|
||||
* @private
|
||||
*/
|
||||
function contentstream (req, debug, inflate) {
|
||||
var encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
|
||||
var length = req.headers['content-length']
|
||||
|
||||
debug('content-encoding "%s"', encoding)
|
||||
|
||||
if (inflate === false && encoding !== 'identity') {
|
||||
throw createError(415, 'content encoding unsupported', {
|
||||
encoding: encoding,
|
||||
type: 'encoding.unsupported'
|
||||
})
|
||||
}
|
||||
|
||||
if (encoding === 'identity') {
|
||||
req.length = length
|
||||
return req
|
||||
}
|
||||
|
||||
var stream = createDecompressionStream(encoding, debug)
|
||||
req.pipe(stream)
|
||||
return stream
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a decompression stream for the given encoding.
|
||||
* @param {string} encoding
|
||||
* @param {Function} debug
|
||||
* @returns {Object}
|
||||
* @private
|
||||
*/
|
||||
function createDecompressionStream (encoding, debug) {
|
||||
switch (encoding) {
|
||||
case 'deflate':
|
||||
debug('inflate body')
|
||||
return zlib.createInflate()
|
||||
case 'gzip':
|
||||
debug('gunzip body')
|
||||
return zlib.createGunzip()
|
||||
case 'br':
|
||||
debug('brotli decompress body')
|
||||
return zlib.createBrotliDecompress()
|
||||
default:
|
||||
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
|
||||
encoding: encoding,
|
||||
type: 'encoding.unsupported'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the contents of a request.
|
||||
*
|
||||
* @param {Object} req
|
||||
* @param {Function} callback
|
||||
* @private
|
||||
*/
|
||||
function dump (req, callback) {
|
||||
if (onFinished.isFinished(req)) {
|
||||
callback(null)
|
||||
} else {
|
||||
onFinished(req, callback)
|
||||
req.resume()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014 Jonathan Ong
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var debug = require('debug')('body-parser:json')
|
||||
var read = require('../read')
|
||||
var { normalizeOptions } = require('../utils')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = json
|
||||
|
||||
/**
|
||||
* RegExp to match the first non-space in a string.
|
||||
*
|
||||
* Allowed whitespace is defined in RFC 7159:
|
||||
*
|
||||
* ws = *(
|
||||
* %x20 / ; Space
|
||||
* %x09 / ; Horizontal tab
|
||||
* %x0A / ; Line feed or New line
|
||||
* %x0D ) ; Carriage return
|
||||
*/
|
||||
var FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*([^\x20\x09\x0a\x0d])/ // eslint-disable-line no-control-regex
|
||||
|
||||
var JSON_SYNTAX_CHAR = '#'
|
||||
var JSON_SYNTAX_REGEXP = /#+/g
|
||||
|
||||
/**
|
||||
* Create a middleware to parse JSON bodies.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @returns {Function}
|
||||
* @public
|
||||
*/
|
||||
function json (options) {
|
||||
const normalizedOptions = normalizeOptions(options, 'application/json')
|
||||
|
||||
var reviver = options?.reviver
|
||||
var strict = options?.strict !== false
|
||||
|
||||
function parse (body) {
|
||||
if (body.length === 0) {
|
||||
// special-case empty json body, as it's a common client-side mistake
|
||||
// TODO: maybe make this configurable or part of "strict" option
|
||||
return {}
|
||||
}
|
||||
|
||||
if (strict) {
|
||||
var first = firstchar(body)
|
||||
|
||||
if (first !== '{' && first !== '[') {
|
||||
debug('strict violation')
|
||||
throw createStrictSyntaxError(body, first)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
debug('parse json')
|
||||
return JSON.parse(body, reviver)
|
||||
} catch (e) {
|
||||
throw normalizeJsonSyntaxError(e, {
|
||||
message: e.message,
|
||||
stack: e.stack
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const readOptions = {
|
||||
...normalizedOptions,
|
||||
// assert charset per RFC 7159 sec 8.1
|
||||
isValidCharset: (charset) => charset.slice(0, 4) === 'utf-'
|
||||
}
|
||||
|
||||
return function jsonParser (req, res, next) {
|
||||
read(req, res, next, parse, debug, readOptions)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create strict violation syntax error matching native error.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {string} char
|
||||
* @returns {Error}
|
||||
* @private
|
||||
*/
|
||||
function createStrictSyntaxError (str, char) {
|
||||
var index = str.indexOf(char)
|
||||
var partial = ''
|
||||
|
||||
if (index !== -1) {
|
||||
partial = str.substring(0, index) + JSON_SYNTAX_CHAR.repeat(str.length - index)
|
||||
}
|
||||
|
||||
try {
|
||||
JSON.parse(partial); /* istanbul ignore next */ throw new SyntaxError('strict violation')
|
||||
} catch (e) {
|
||||
return normalizeJsonSyntaxError(e, {
|
||||
message: e.message.replace(JSON_SYNTAX_REGEXP, function (placeholder) {
|
||||
return str.substring(index, index + placeholder.length)
|
||||
}),
|
||||
stack: e.stack
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first non-whitespace character in a string.
|
||||
*
|
||||
* @param {string} str
|
||||
* @returns {string|undefined}
|
||||
* @private
|
||||
*/
|
||||
function firstchar (str) {
|
||||
var match = FIRST_CHAR_REGEXP.exec(str)
|
||||
|
||||
return match
|
||||
? match[1]
|
||||
: undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a SyntaxError for JSON.parse.
|
||||
*
|
||||
* @param {SyntaxError} error
|
||||
* @param {Object} obj
|
||||
* @returns {SyntaxError}
|
||||
* @private
|
||||
*/
|
||||
function normalizeJsonSyntaxError (error, obj) {
|
||||
var keys = Object.getOwnPropertyNames(error)
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i]
|
||||
if (key !== 'stack' && key !== 'message') {
|
||||
delete error[key]
|
||||
}
|
||||
}
|
||||
|
||||
// replace stack before message for Node.js 0.10 and below
|
||||
error.stack = obj.stack.replace(error.message, obj.message)
|
||||
error.message = obj.message
|
||||
|
||||
return error
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var debug = require('debug')('body-parser:raw')
|
||||
var read = require('../read')
|
||||
var { normalizeOptions, passthrough } = require('../utils')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = raw
|
||||
|
||||
/**
|
||||
* Create a middleware to parse raw bodies.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @returns {Function}
|
||||
* @public
|
||||
*/
|
||||
function raw (options) {
|
||||
const normalizedOptions = normalizeOptions(options, 'application/octet-stream')
|
||||
|
||||
const readOptions = {
|
||||
...normalizedOptions,
|
||||
// Skip charset validation and parse the body as is
|
||||
skipCharset: true
|
||||
}
|
||||
|
||||
return function rawParser (req, res, next) {
|
||||
read(req, res, next, passthrough, debug, readOptions)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var debug = require('debug')('body-parser:text')
|
||||
var read = require('../read')
|
||||
var { normalizeOptions, passthrough } = require('../utils')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = text
|
||||
|
||||
/**
|
||||
* Create a middleware to parse text bodies.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @returns {Function}
|
||||
* @public
|
||||
*/
|
||||
function text (options) {
|
||||
const normalizedOptions = normalizeOptions(options, 'text/plain')
|
||||
|
||||
return function textParser (req, res, next) {
|
||||
read(req, res, next, passthrough, debug, normalizedOptions)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014 Jonathan Ong
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var createError = require('http-errors')
|
||||
var debug = require('debug')('body-parser:urlencoded')
|
||||
var read = require('../read')
|
||||
var qs = require('qs')
|
||||
var { normalizeOptions } = require('../utils')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = urlencoded
|
||||
|
||||
/**
|
||||
* Create a middleware to parse urlencoded bodies.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @returns {Function}
|
||||
* @public
|
||||
*/
|
||||
function urlencoded (options) {
|
||||
const normalizedOptions = normalizeOptions(options, 'application/x-www-form-urlencoded')
|
||||
|
||||
if (normalizedOptions.defaultCharset !== 'utf-8' && normalizedOptions.defaultCharset !== 'iso-8859-1') {
|
||||
throw new TypeError('option defaultCharset must be either utf-8 or iso-8859-1')
|
||||
}
|
||||
|
||||
// create the appropriate query parser
|
||||
var queryparse = createQueryParser(options)
|
||||
|
||||
function parse (body, encoding) {
|
||||
return body.length
|
||||
? queryparse(body, encoding)
|
||||
: {}
|
||||
}
|
||||
|
||||
const readOptions = {
|
||||
...normalizedOptions,
|
||||
// assert charset
|
||||
isValidCharset: (charset) => charset === 'utf-8' || charset === 'iso-8859-1'
|
||||
}
|
||||
|
||||
return function urlencodedParser (req, res, next) {
|
||||
read(req, res, next, parse, debug, readOptions)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extended query parser.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {Function}
|
||||
* @private
|
||||
*/
|
||||
function createQueryParser (options) {
|
||||
var extended = Boolean(options?.extended)
|
||||
var parameterLimit = options?.parameterLimit !== undefined
|
||||
? options?.parameterLimit
|
||||
: 1000
|
||||
var charsetSentinel = options?.charsetSentinel
|
||||
var interpretNumericEntities = options?.interpretNumericEntities
|
||||
var depth = extended ? (options?.depth !== undefined ? options?.depth : 32) : 0
|
||||
|
||||
if (isNaN(parameterLimit) || parameterLimit < 1) {
|
||||
throw new TypeError('option parameterLimit must be a positive number')
|
||||
}
|
||||
|
||||
if (isNaN(depth) || depth < 0) {
|
||||
throw new TypeError('option depth must be a zero or a positive number')
|
||||
}
|
||||
|
||||
if (isFinite(parameterLimit)) {
|
||||
parameterLimit = parameterLimit | 0
|
||||
}
|
||||
|
||||
return function queryparse (body, encoding) {
|
||||
var paramCount = parameterCount(body, parameterLimit)
|
||||
|
||||
if (paramCount === undefined) {
|
||||
debug('too many parameters')
|
||||
throw createError(413, 'too many parameters', {
|
||||
type: 'parameters.too.many'
|
||||
})
|
||||
}
|
||||
|
||||
var arrayLimit = extended ? Math.max(100, paramCount) : paramCount
|
||||
|
||||
debug('parse ' + (extended ? 'extended ' : '') + 'urlencoding')
|
||||
try {
|
||||
return qs.parse(body, {
|
||||
allowPrototypes: true,
|
||||
arrayLimit: arrayLimit,
|
||||
depth: depth,
|
||||
charsetSentinel: charsetSentinel,
|
||||
interpretNumericEntities: interpretNumericEntities,
|
||||
charset: encoding,
|
||||
parameterLimit: parameterLimit,
|
||||
strictDepth: true
|
||||
})
|
||||
} catch (err) {
|
||||
if (err instanceof RangeError) {
|
||||
throw createError(400, 'The input exceeded the depth', {
|
||||
type: 'querystring.parse.rangeError'
|
||||
})
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of parameters, stopping once limit reached
|
||||
*
|
||||
* @param {string} body
|
||||
* @param {number} limit
|
||||
* @returns {number|undefined} Returns undefined if limit exceeded
|
||||
* @private
|
||||
*/
|
||||
function parameterCount (body, limit) {
|
||||
let count = 0
|
||||
let index = -1
|
||||
do {
|
||||
count++
|
||||
if (count > limit) return undefined // Early exit if limit exceeded
|
||||
index = body.indexOf('&', index + 1)
|
||||
} while (index !== -1)
|
||||
return count
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var bytes = require('bytes')
|
||||
var contentType = require('content-type')
|
||||
var typeis = require('type-is')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
module.exports = {
|
||||
getCharset,
|
||||
normalizeOptions,
|
||||
passthrough
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the charset of a request.
|
||||
*
|
||||
* @param {Object} req
|
||||
* @returns {string | undefined}
|
||||
* @private
|
||||
*/
|
||||
function getCharset (req) {
|
||||
try {
|
||||
return (contentType.parse(req).parameters.charset || '').toLowerCase()
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the simple type checker.
|
||||
*
|
||||
* @param {string | string[]} type
|
||||
* @returns {Function}
|
||||
* @private
|
||||
*/
|
||||
function typeChecker (type) {
|
||||
return function checkType (req) {
|
||||
return Boolean(typeis(req, type))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the common options for all parsers.
|
||||
*
|
||||
* @param {Object} options options to normalize
|
||||
* @param {string | string[] | Function} defaultType default content type(s) or a function to determine it
|
||||
* @returns {Object}
|
||||
* @private
|
||||
*/
|
||||
function normalizeOptions (options, defaultType) {
|
||||
if (!defaultType) {
|
||||
// Parsers must define a default content type
|
||||
throw new TypeError('defaultType must be provided')
|
||||
}
|
||||
|
||||
var inflate = options?.inflate !== false
|
||||
var limit = typeof options?.limit !== 'number'
|
||||
? bytes.parse(options?.limit || '100kb')
|
||||
: options?.limit
|
||||
var type = options?.type || defaultType
|
||||
var verify = options?.verify || false
|
||||
var defaultCharset = options?.defaultCharset || 'utf-8'
|
||||
|
||||
if (verify !== false && typeof verify !== 'function') {
|
||||
throw new TypeError('option verify must be function')
|
||||
}
|
||||
|
||||
// create the appropriate type checking function
|
||||
var shouldParse = typeof type !== 'function'
|
||||
? typeChecker(type)
|
||||
: type
|
||||
|
||||
return {
|
||||
inflate,
|
||||
limit,
|
||||
verify,
|
||||
defaultCharset,
|
||||
shouldParse
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passthrough function that returns input unchanged.
|
||||
* Used by parsers that don't need to transform the data.
|
||||
*
|
||||
* @param {*} value
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
function passthrough (value) {
|
||||
return value
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "body-parser",
|
||||
"description": "Node.js body parsing middleware",
|
||||
"version": "2.2.2",
|
||||
"contributors": [
|
||||
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": "expressjs/body-parser",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
},
|
||||
"dependencies": {
|
||||
"bytes": "^3.1.2",
|
||||
"content-type": "^1.0.5",
|
||||
"debug": "^4.4.3",
|
||||
"http-errors": "^2.0.0",
|
||||
"iconv-lite": "^0.7.0",
|
||||
"on-finished": "^2.4.1",
|
||||
"qs": "^6.14.1",
|
||||
"raw-body": "^3.0.1",
|
||||
"type-is": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-markdown": "^3.0.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.6.0",
|
||||
"eslint-plugin-standard": "^4.1.0",
|
||||
"mocha": "^11.1.0",
|
||||
"nyc": "^17.1.0",
|
||||
"supertest": "^7.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib/",
|
||||
"LICENSE",
|
||||
"index.js"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "mocha --reporter spec --check-leaks test/",
|
||||
"test-ci": "nyc --reporter=lcovonly --reporter=text npm test",
|
||||
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
3.1.2 / 2022-01-27
|
||||
==================
|
||||
|
||||
* Fix return value for un-parsable strings
|
||||
|
||||
3.1.1 / 2021-11-15
|
||||
==================
|
||||
|
||||
* Fix "thousandsSeparator" incorrecting formatting fractional part
|
||||
|
||||
3.1.0 / 2019-01-22
|
||||
==================
|
||||
|
||||
* Add petabyte (`pb`) support
|
||||
|
||||
3.0.0 / 2017-08-31
|
||||
==================
|
||||
|
||||
* Change "kB" to "KB" in format output
|
||||
* Remove support for Node.js 0.6
|
||||
* Remove support for ComponentJS
|
||||
|
||||
2.5.0 / 2017-03-24
|
||||
==================
|
||||
|
||||
* Add option "unit"
|
||||
|
||||
2.4.0 / 2016-06-01
|
||||
==================
|
||||
|
||||
* Add option "unitSeparator"
|
||||
|
||||
2.3.0 / 2016-02-15
|
||||
==================
|
||||
|
||||
* Drop partial bytes on all parsed units
|
||||
* Fix non-finite numbers to `.format` to return `null`
|
||||
* Fix parsing byte string that looks like hex
|
||||
* perf: hoist regular expressions
|
||||
|
||||
2.2.0 / 2015-11-13
|
||||
==================
|
||||
|
||||
* add option "decimalPlaces"
|
||||
* add option "fixedDecimals"
|
||||
|
||||
2.1.0 / 2015-05-21
|
||||
==================
|
||||
|
||||
* add `.format` export
|
||||
* add `.parse` export
|
||||
|
||||
2.0.2 / 2015-05-20
|
||||
==================
|
||||
|
||||
* remove map recreation
|
||||
* remove unnecessary object construction
|
||||
|
||||
2.0.1 / 2015-05-07
|
||||
==================
|
||||
|
||||
* fix browserify require
|
||||
* remove node.extend dependency
|
||||
|
||||
2.0.0 / 2015-04-12
|
||||
==================
|
||||
|
||||
* add option "case"
|
||||
* add option "thousandsSeparator"
|
||||
* return "null" on invalid parse input
|
||||
* support proper round-trip: bytes(bytes(num)) === num
|
||||
* units no longer case sensitive when parsing
|
||||
|
||||
1.0.0 / 2014-05-05
|
||||
==================
|
||||
|
||||
* add negative support. fixes #6
|
||||
|
||||
0.3.0 / 2014-03-19
|
||||
==================
|
||||
|
||||
* added terabyte support
|
||||
|
||||
0.2.1 / 2013-04-01
|
||||
==================
|
||||
|
||||
* add .component
|
||||
|
||||
0.2.0 / 2012-10-28
|
||||
==================
|
||||
|
||||
* bytes(200).should.eql('200b')
|
||||
|
||||
0.1.0 / 2012-07-04
|
||||
==================
|
||||
|
||||
* add bytes to string conversion [yields]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2012-2014 TJ Holowaychuk <tj@vision-media.ca>
|
||||
Copyright (c) 2015 Jed Watson <jed.watson@me.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
# Bytes utility
|
||||
|
||||
[![NPM Version][npm-image]][npm-url]
|
||||
[![NPM Downloads][downloads-image]][downloads-url]
|
||||
[![Build Status][ci-image]][ci-url]
|
||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||
|
||||
Utility to parse a string bytes (ex: `1TB`) to bytes (`1099511627776`) and vice-versa.
|
||||
|
||||
## Installation
|
||||
|
||||
This is a [Node.js](https://nodejs.org/en/) module available through the
|
||||
[npm registry](https://www.npmjs.com/). Installation is done using the
|
||||
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
|
||||
|
||||
```bash
|
||||
$ npm install bytes
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
var bytes = require('bytes');
|
||||
```
|
||||
|
||||
#### bytes(number|string value, [options]): number|string|null
|
||||
|
||||
Default export function. Delegates to either `bytes.format` or `bytes.parse` based on the type of `value`.
|
||||
|
||||
**Arguments**
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|----------|--------------------|
|
||||
| value | `number`|`string` | Number value to format or string value to parse |
|
||||
| options | `Object` | Conversion options for `format` |
|
||||
|
||||
**Returns**
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|------------------|-------------------------------------------------|
|
||||
| results | `string`|`number`|`null` | Return null upon error. Numeric value in bytes, or string value otherwise. |
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
bytes(1024);
|
||||
// output: '1KB'
|
||||
|
||||
bytes('1KB');
|
||||
// output: 1024
|
||||
```
|
||||
|
||||
#### bytes.format(number value, [options]): string|null
|
||||
|
||||
Format the given value in bytes into a string. If the value is negative, it is kept as such. If it is a float, it is
|
||||
rounded.
|
||||
|
||||
**Arguments**
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|----------|--------------------|
|
||||
| value | `number` | Value in bytes |
|
||||
| options | `Object` | Conversion options |
|
||||
|
||||
**Options**
|
||||
|
||||
| Property | Type | Description |
|
||||
|-------------------|--------|-----------------------------------------------------------------------------------------|
|
||||
| decimalPlaces | `number`|`null` | Maximum number of decimal places to include in output. Default value to `2`. |
|
||||
| fixedDecimals | `boolean`|`null` | Whether to always display the maximum number of decimal places. Default value to `false` |
|
||||
| thousandsSeparator | `string`|`null` | Example of values: `' '`, `','` and `'.'`... Default value to `''`. |
|
||||
| unit | `string`|`null` | The unit in which the result will be returned (B/KB/MB/GB/TB). Default value to `''` (which means auto detect). |
|
||||
| unitSeparator | `string`|`null` | Separator to use between number and unit. Default value to `''`. |
|
||||
|
||||
**Returns**
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|------------------|-------------------------------------------------|
|
||||
| results | `string`|`null` | Return null upon error. String value otherwise. |
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
bytes.format(1024);
|
||||
// output: '1KB'
|
||||
|
||||
bytes.format(1000);
|
||||
// output: '1000B'
|
||||
|
||||
bytes.format(1000, {thousandsSeparator: ' '});
|
||||
// output: '1 000B'
|
||||
|
||||
bytes.format(1024 * 1.7, {decimalPlaces: 0});
|
||||
// output: '2KB'
|
||||
|
||||
bytes.format(1024, {unitSeparator: ' '});
|
||||
// output: '1 KB'
|
||||
```
|
||||
|
||||
#### bytes.parse(string|number value): number|null
|
||||
|
||||
Parse the string value into an integer in bytes. If no unit is given, or `value`
|
||||
is a number, it is assumed the value is in bytes.
|
||||
|
||||
Supported units and abbreviations are as follows and are case-insensitive:
|
||||
|
||||
* `b` for bytes
|
||||
* `kb` for kilobytes
|
||||
* `mb` for megabytes
|
||||
* `gb` for gigabytes
|
||||
* `tb` for terabytes
|
||||
* `pb` for petabytes
|
||||
|
||||
The units are in powers of two, not ten. This means 1kb = 1024b according to this parser.
|
||||
|
||||
**Arguments**
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------------|--------|--------------------|
|
||||
| value | `string`|`number` | String to parse, or number in bytes. |
|
||||
|
||||
**Returns**
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-------------|-------------------------|
|
||||
| results | `number`|`null` | Return null upon error. Value in bytes otherwise. |
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
bytes.parse('1KB');
|
||||
// output: 1024
|
||||
|
||||
bytes.parse('1024');
|
||||
// output: 1024
|
||||
|
||||
bytes.parse(1024);
|
||||
// output: 1024
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
[ci-image]: https://badgen.net/github/checks/visionmedia/bytes.js/master?label=ci
|
||||
[ci-url]: https://github.com/visionmedia/bytes.js/actions?query=workflow%3Aci
|
||||
[coveralls-image]: https://badgen.net/coveralls/c/github/visionmedia/bytes.js/master
|
||||
[coveralls-url]: https://coveralls.io/r/visionmedia/bytes.js?branch=master
|
||||
[downloads-image]: https://badgen.net/npm/dm/bytes
|
||||
[downloads-url]: https://npmjs.org/package/bytes
|
||||
[npm-image]: https://badgen.net/npm/v/bytes
|
||||
[npm-url]: https://npmjs.org/package/bytes
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
/*!
|
||||
* bytes
|
||||
* Copyright(c) 2012-2014 TJ Holowaychuk
|
||||
* Copyright(c) 2015 Jed Watson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
* @public
|
||||
*/
|
||||
|
||||
module.exports = bytes;
|
||||
module.exports.format = format;
|
||||
module.exports.parse = parse;
|
||||
|
||||
/**
|
||||
* Module variables.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var formatThousandsRegExp = /\B(?=(\d{3})+(?!\d))/g;
|
||||
|
||||
var formatDecimalsRegExp = /(?:\.0*|(\.[^0]+)0+)$/;
|
||||
|
||||
var map = {
|
||||
b: 1,
|
||||
kb: 1 << 10,
|
||||
mb: 1 << 20,
|
||||
gb: 1 << 30,
|
||||
tb: Math.pow(1024, 4),
|
||||
pb: Math.pow(1024, 5),
|
||||
};
|
||||
|
||||
var parseRegExp = /^((-|\+)?(\d+(?:\.\d+)?)) *(kb|mb|gb|tb|pb)$/i;
|
||||
|
||||
/**
|
||||
* Convert the given value in bytes into a string or parse to string to an integer in bytes.
|
||||
*
|
||||
* @param {string|number} value
|
||||
* @param {{
|
||||
* case: [string],
|
||||
* decimalPlaces: [number]
|
||||
* fixedDecimals: [boolean]
|
||||
* thousandsSeparator: [string]
|
||||
* unitSeparator: [string]
|
||||
* }} [options] bytes options.
|
||||
*
|
||||
* @returns {string|number|null}
|
||||
*/
|
||||
|
||||
function bytes(value, options) {
|
||||
if (typeof value === 'string') {
|
||||
return parse(value);
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return format(value, options);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given value in bytes into a string.
|
||||
*
|
||||
* If the value is negative, it is kept as such. If it is a float,
|
||||
* it is rounded.
|
||||
*
|
||||
* @param {number} value
|
||||
* @param {object} [options]
|
||||
* @param {number} [options.decimalPlaces=2]
|
||||
* @param {number} [options.fixedDecimals=false]
|
||||
* @param {string} [options.thousandsSeparator=]
|
||||
* @param {string} [options.unit=]
|
||||
* @param {string} [options.unitSeparator=]
|
||||
*
|
||||
* @returns {string|null}
|
||||
* @public
|
||||
*/
|
||||
|
||||
function format(value, options) {
|
||||
if (!Number.isFinite(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var mag = Math.abs(value);
|
||||
var thousandsSeparator = (options && options.thousandsSeparator) || '';
|
||||
var unitSeparator = (options && options.unitSeparator) || '';
|
||||
var decimalPlaces = (options && options.decimalPlaces !== undefined) ? options.decimalPlaces : 2;
|
||||
var fixedDecimals = Boolean(options && options.fixedDecimals);
|
||||
var unit = (options && options.unit) || '';
|
||||
|
||||
if (!unit || !map[unit.toLowerCase()]) {
|
||||
if (mag >= map.pb) {
|
||||
unit = 'PB';
|
||||
} else if (mag >= map.tb) {
|
||||
unit = 'TB';
|
||||
} else if (mag >= map.gb) {
|
||||
unit = 'GB';
|
||||
} else if (mag >= map.mb) {
|
||||
unit = 'MB';
|
||||
} else if (mag >= map.kb) {
|
||||
unit = 'KB';
|
||||
} else {
|
||||
unit = 'B';
|
||||
}
|
||||
}
|
||||
|
||||
var val = value / map[unit.toLowerCase()];
|
||||
var str = val.toFixed(decimalPlaces);
|
||||
|
||||
if (!fixedDecimals) {
|
||||
str = str.replace(formatDecimalsRegExp, '$1');
|
||||
}
|
||||
|
||||
if (thousandsSeparator) {
|
||||
str = str.split('.').map(function (s, i) {
|
||||
return i === 0
|
||||
? s.replace(formatThousandsRegExp, thousandsSeparator)
|
||||
: s
|
||||
}).join('.');
|
||||
}
|
||||
|
||||
return str + unitSeparator + unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the string value into an integer in bytes.
|
||||
*
|
||||
* If no unit is given, it is assumed the value is in bytes.
|
||||
*
|
||||
* @param {number|string} val
|
||||
*
|
||||
* @returns {number|null}
|
||||
* @public
|
||||
*/
|
||||
|
||||
function parse(val) {
|
||||
if (typeof val === 'number' && !isNaN(val)) {
|
||||
return val;
|
||||
}
|
||||
|
||||
if (typeof val !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Test if the string passed is valid
|
||||
var results = parseRegExp.exec(val);
|
||||
var floatValue;
|
||||
var unit = 'b';
|
||||
|
||||
if (!results) {
|
||||
// Nothing could be extracted from the given string
|
||||
floatValue = parseInt(val, 10);
|
||||
unit = 'b'
|
||||
} else {
|
||||
// Retrieve the value and the unit
|
||||
floatValue = parseFloat(results[1]);
|
||||
unit = results[4].toLowerCase();
|
||||
}
|
||||
|
||||
if (isNaN(floatValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Math.floor(map[unit] * floatValue);
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "bytes",
|
||||
"description": "Utility to parse a string bytes to bytes and vice-versa",
|
||||
"version": "3.1.2",
|
||||
"author": "TJ Holowaychuk <tj@vision-media.ca> (http://tjholowaychuk.com)",
|
||||
"contributors": [
|
||||
"Jed Watson <jed.watson@me.com>",
|
||||
"Théo FIDRY <theo.fidry@gmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"byte",
|
||||
"bytes",
|
||||
"utility",
|
||||
"parse",
|
||||
"parser",
|
||||
"convert",
|
||||
"converter"
|
||||
],
|
||||
"repository": "visionmedia/bytes.js",
|
||||
"devDependencies": {
|
||||
"eslint": "7.32.0",
|
||||
"eslint-plugin-markdown": "2.2.1",
|
||||
"mocha": "9.2.0",
|
||||
"nyc": "15.1.0"
|
||||
},
|
||||
"files": [
|
||||
"History.md",
|
||||
"LICENSE",
|
||||
"Readme.md",
|
||||
"index.js"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "mocha --check-leaks --reporter spec",
|
||||
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
||||
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"root": true,
|
||||
|
||||
"extends": "@ljharb",
|
||||
|
||||
"rules": {
|
||||
"func-name-matching": 0,
|
||||
"id-length": 0,
|
||||
"new-cap": [2, {
|
||||
"capIsNewExceptions": [
|
||||
"GetIntrinsic",
|
||||
],
|
||||
}],
|
||||
"no-extra-parens": 0,
|
||||
"no-magic-numbers": 0,
|
||||
},
|
||||
}
|
||||
12
backend/bridge/node_modules/call-bind-apply-helpers/.github/FUNDING.yml
generated
vendored
Normal file
12
backend/bridge/node_modules/call-bind-apply-helpers/.github/FUNDING.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [ljharb]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: npm/call-bind-apply-helpers
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"all": true,
|
||||
"check-coverage": false,
|
||||
"reporter": ["text-summary", "text", "html", "json"],
|
||||
"exclude": [
|
||||
"coverage",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.0.2](https://github.com/ljharb/call-bind-apply-helpers/compare/v1.0.1...v1.0.2) - 2025-02-12
|
||||
|
||||
### Commits
|
||||
|
||||
- [types] improve inferred types [`e6f9586`](https://github.com/ljharb/call-bind-apply-helpers/commit/e6f95860a3c72879cb861a858cdfb8138fbedec1)
|
||||
- [Dev Deps] update `@arethetypeswrong/cli`, `@ljharb/tsconfig`, `@types/tape`, `es-value-fixtures`, `for-each`, `has-strict-mode`, `object-inspect` [`e43d540`](https://github.com/ljharb/call-bind-apply-helpers/commit/e43d5409f97543bfbb11f345d47d8ce4e066d8c1)
|
||||
|
||||
## [v1.0.1](https://github.com/ljharb/call-bind-apply-helpers/compare/v1.0.0...v1.0.1) - 2024-12-08
|
||||
|
||||
### Commits
|
||||
|
||||
- [types] `reflectApply`: fix types [`4efc396`](https://github.com/ljharb/call-bind-apply-helpers/commit/4efc3965351a4f02cc55e836fa391d3d11ef2ef8)
|
||||
- [Fix] `reflectApply`: oops, Reflect is not a function [`83cc739`](https://github.com/ljharb/call-bind-apply-helpers/commit/83cc7395de6b79b7730bdf092f1436f0b1263c75)
|
||||
- [Dev Deps] update `@arethetypeswrong/cli` [`80bd5d3`](https://github.com/ljharb/call-bind-apply-helpers/commit/80bd5d3ae58b4f6b6995ce439dd5a1bcb178a940)
|
||||
|
||||
## v1.0.0 - 2024-12-05
|
||||
|
||||
### Commits
|
||||
|
||||
- Initial implementation, tests, readme [`7879629`](https://github.com/ljharb/call-bind-apply-helpers/commit/78796290f9b7430c9934d6f33d94ae9bc89fce04)
|
||||
- Initial commit [`3f1dc16`](https://github.com/ljharb/call-bind-apply-helpers/commit/3f1dc164afc43285631b114a5f9dd9137b2b952f)
|
||||
- npm init [`081df04`](https://github.com/ljharb/call-bind-apply-helpers/commit/081df048c312fcee400922026f6e97281200a603)
|
||||
- Only apps should have lockfiles [`5b9ca0f`](https://github.com/ljharb/call-bind-apply-helpers/commit/5b9ca0fe8101ebfaf309c549caac4e0a017ed930)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Jordan Harband
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# call-bind-apply-helpers <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
|
||||
|
||||
[![github actions][actions-image]][actions-url]
|
||||
[![coverage][codecov-image]][codecov-url]
|
||||
[![dependency status][deps-svg]][deps-url]
|
||||
[![dev dependency status][dev-deps-svg]][dev-deps-url]
|
||||
[![License][license-image]][license-url]
|
||||
[![Downloads][downloads-image]][downloads-url]
|
||||
|
||||
[![npm badge][npm-badge-png]][package-url]
|
||||
|
||||
Helper functions around Function call/apply/bind, for use in `call-bind`.
|
||||
|
||||
The only packages that should likely ever use this package directly are `call-bind` and `get-intrinsic`.
|
||||
Please use `call-bind` unless you have a very good reason not to.
|
||||
|
||||
## Getting started
|
||||
|
||||
```sh
|
||||
npm install --save call-bind-apply-helpers
|
||||
```
|
||||
|
||||
## Usage/Examples
|
||||
|
||||
```js
|
||||
const assert = require('assert');
|
||||
const callBindBasic = require('call-bind-apply-helpers');
|
||||
|
||||
function f(a, b) {
|
||||
assert.equal(this, 1);
|
||||
assert.equal(a, 2);
|
||||
assert.equal(b, 3);
|
||||
assert.equal(arguments.length, 2);
|
||||
}
|
||||
|
||||
const fBound = callBindBasic([f, 1]);
|
||||
|
||||
delete Function.prototype.call;
|
||||
delete Function.prototype.bind;
|
||||
|
||||
fBound(2, 3);
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Clone the repo, `npm install`, and run `npm test`
|
||||
|
||||
[package-url]: https://npmjs.org/package/call-bind-apply-helpers
|
||||
[npm-version-svg]: https://versionbadg.es/ljharb/call-bind-apply-helpers.svg
|
||||
[deps-svg]: https://david-dm.org/ljharb/call-bind-apply-helpers.svg
|
||||
[deps-url]: https://david-dm.org/ljharb/call-bind-apply-helpers
|
||||
[dev-deps-svg]: https://david-dm.org/ljharb/call-bind-apply-helpers/dev-status.svg
|
||||
[dev-deps-url]: https://david-dm.org/ljharb/call-bind-apply-helpers#info=devDependencies
|
||||
[npm-badge-png]: https://nodei.co/npm/call-bind-apply-helpers.png?downloads=true&stars=true
|
||||
[license-image]: https://img.shields.io/npm/l/call-bind-apply-helpers.svg
|
||||
[license-url]: LICENSE
|
||||
[downloads-image]: https://img.shields.io/npm/dm/call-bind-apply-helpers.svg
|
||||
[downloads-url]: https://npm-stat.com/charts.html?package=call-bind-apply-helpers
|
||||
[codecov-image]: https://codecov.io/gh/ljharb/call-bind-apply-helpers/branch/main/graphs/badge.svg
|
||||
[codecov-url]: https://app.codecov.io/gh/ljharb/call-bind-apply-helpers/
|
||||
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/call-bind-apply-helpers
|
||||
[actions-url]: https://github.com/ljharb/call-bind-apply-helpers/actions
|
||||
1
backend/bridge/node_modules/call-bind-apply-helpers/actualApply.d.ts
generated
vendored
Normal file
1
backend/bridge/node_modules/call-bind-apply-helpers/actualApply.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export = Reflect.apply;
|
||||
10
backend/bridge/node_modules/call-bind-apply-helpers/actualApply.js
generated
vendored
Normal file
10
backend/bridge/node_modules/call-bind-apply-helpers/actualApply.js
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
var bind = require('function-bind');
|
||||
|
||||
var $apply = require('./functionApply');
|
||||
var $call = require('./functionCall');
|
||||
var $reflectApply = require('./reflectApply');
|
||||
|
||||
/** @type {import('./actualApply')} */
|
||||
module.exports = $reflectApply || bind.call($call, $apply);
|
||||
19
backend/bridge/node_modules/call-bind-apply-helpers/applyBind.d.ts
generated
vendored
Normal file
19
backend/bridge/node_modules/call-bind-apply-helpers/applyBind.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import actualApply from './actualApply';
|
||||
|
||||
type TupleSplitHead<T extends any[], N extends number> = T['length'] extends N
|
||||
? T
|
||||
: T extends [...infer R, any]
|
||||
? TupleSplitHead<R, N>
|
||||
: never
|
||||
|
||||
type TupleSplitTail<T, N extends number, O extends any[] = []> = O['length'] extends N
|
||||
? T
|
||||
: T extends [infer F, ...infer R]
|
||||
? TupleSplitTail<[...R], N, [...O, F]>
|
||||
: never
|
||||
|
||||
type TupleSplit<T extends any[], N extends number> = [TupleSplitHead<T, N>, TupleSplitTail<T, N>]
|
||||
|
||||
declare function applyBind(...args: TupleSplit<Parameters<typeof actualApply>, 2>[1]): ReturnType<typeof actualApply>;
|
||||
|
||||
export = applyBind;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
var bind = require('function-bind');
|
||||
var $apply = require('./functionApply');
|
||||
var actualApply = require('./actualApply');
|
||||
|
||||
/** @type {import('./applyBind')} */
|
||||
module.exports = function applyBind() {
|
||||
return actualApply(bind, $apply, arguments);
|
||||
};
|
||||
1
backend/bridge/node_modules/call-bind-apply-helpers/functionApply.d.ts
generated
vendored
Normal file
1
backend/bridge/node_modules/call-bind-apply-helpers/functionApply.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export = Function.prototype.apply;
|
||||
4
backend/bridge/node_modules/call-bind-apply-helpers/functionApply.js
generated
vendored
Normal file
4
backend/bridge/node_modules/call-bind-apply-helpers/functionApply.js
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
/** @type {import('./functionApply')} */
|
||||
module.exports = Function.prototype.apply;
|
||||
1
backend/bridge/node_modules/call-bind-apply-helpers/functionCall.d.ts
generated
vendored
Normal file
1
backend/bridge/node_modules/call-bind-apply-helpers/functionCall.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export = Function.prototype.call;
|
||||
4
backend/bridge/node_modules/call-bind-apply-helpers/functionCall.js
generated
vendored
Normal file
4
backend/bridge/node_modules/call-bind-apply-helpers/functionCall.js
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
/** @type {import('./functionCall')} */
|
||||
module.exports = Function.prototype.call;
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
type RemoveFromTuple<
|
||||
Tuple extends readonly unknown[],
|
||||
RemoveCount extends number,
|
||||
Index extends 1[] = []
|
||||
> = Index["length"] extends RemoveCount
|
||||
? Tuple
|
||||
: Tuple extends [infer First, ...infer Rest]
|
||||
? RemoveFromTuple<Rest, RemoveCount, [...Index, 1]>
|
||||
: Tuple;
|
||||
|
||||
type ConcatTuples<
|
||||
Prefix extends readonly unknown[],
|
||||
Suffix extends readonly unknown[]
|
||||
> = [...Prefix, ...Suffix];
|
||||
|
||||
type ExtractFunctionParams<T> = T extends (this: infer TThis, ...args: infer P extends readonly unknown[]) => infer R
|
||||
? { thisArg: TThis; params: P; returnType: R }
|
||||
: never;
|
||||
|
||||
type BindFunction<
|
||||
T extends (this: any, ...args: any[]) => any,
|
||||
TThis,
|
||||
TBoundArgs extends readonly unknown[],
|
||||
ReceiverBound extends boolean
|
||||
> = ExtractFunctionParams<T> extends {
|
||||
thisArg: infer OrigThis;
|
||||
params: infer P extends readonly unknown[];
|
||||
returnType: infer R;
|
||||
}
|
||||
? ReceiverBound extends true
|
||||
? (...args: RemoveFromTuple<P, Extract<TBoundArgs["length"], number>>) => R extends [OrigThis, ...infer Rest]
|
||||
? [TThis, ...Rest] // Replace `this` with `thisArg`
|
||||
: R
|
||||
: <U, RemainingArgs extends RemoveFromTuple<P, Extract<TBoundArgs["length"], number>>>(
|
||||
thisArg: U,
|
||||
...args: RemainingArgs
|
||||
) => R extends [OrigThis, ...infer Rest]
|
||||
? [U, ...ConcatTuples<TBoundArgs, Rest>] // Preserve bound args in return type
|
||||
: R
|
||||
: never;
|
||||
|
||||
declare function callBind<
|
||||
const T extends (this: any, ...args: any[]) => any,
|
||||
Extracted extends ExtractFunctionParams<T>,
|
||||
const TBoundArgs extends Partial<Extracted["params"]> & readonly unknown[],
|
||||
const TThis extends Extracted["thisArg"]
|
||||
>(
|
||||
args: [fn: T, thisArg: TThis, ...boundArgs: TBoundArgs]
|
||||
): BindFunction<T, TThis, TBoundArgs, true>;
|
||||
|
||||
declare function callBind<
|
||||
const T extends (this: any, ...args: any[]) => any,
|
||||
Extracted extends ExtractFunctionParams<T>,
|
||||
const TBoundArgs extends Partial<Extracted["params"]> & readonly unknown[]
|
||||
>(
|
||||
args: [fn: T, ...boundArgs: TBoundArgs]
|
||||
): BindFunction<T, Extracted["thisArg"], TBoundArgs, false>;
|
||||
|
||||
declare function callBind<const TArgs extends readonly unknown[]>(
|
||||
args: [fn: Exclude<TArgs[0], Function>, ...rest: TArgs]
|
||||
): never;
|
||||
|
||||
// export as namespace callBind;
|
||||
export = callBind;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
var bind = require('function-bind');
|
||||
var $TypeError = require('es-errors/type');
|
||||
|
||||
var $call = require('./functionCall');
|
||||
var $actualApply = require('./actualApply');
|
||||
|
||||
/** @type {(args: [Function, thisArg?: unknown, ...args: unknown[]]) => Function} TODO FIXME, find a way to use import('.') */
|
||||
module.exports = function callBindBasic(args) {
|
||||
if (args.length < 1 || typeof args[0] !== 'function') {
|
||||
throw new $TypeError('a function is required');
|
||||
}
|
||||
return $actualApply(bind, $call, args);
|
||||
};
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"name": "call-bind-apply-helpers",
|
||||
"version": "1.0.2",
|
||||
"description": "Helper functions around Function call/apply/bind, for use in `call-bind`",
|
||||
"main": "index.js",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./actualApply": "./actualApply.js",
|
||||
"./applyBind": "./applyBind.js",
|
||||
"./functionApply": "./functionApply.js",
|
||||
"./functionCall": "./functionCall.js",
|
||||
"./reflectApply": "./reflectApply.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"prepack": "npmignore --auto --commentLines=auto",
|
||||
"prepublish": "not-in-publish || npm run prepublishOnly",
|
||||
"prepublishOnly": "safe-publish-latest",
|
||||
"prelint": "evalmd README.md",
|
||||
"lint": "eslint --ext=.js,.mjs .",
|
||||
"postlint": "tsc -p . && attw -P",
|
||||
"pretest": "npm run lint",
|
||||
"tests-only": "nyc tape 'test/**/*.js'",
|
||||
"test": "npm run tests-only",
|
||||
"posttest": "npx npm@'>=10.2' audit --production",
|
||||
"version": "auto-changelog && git add CHANGELOG.md",
|
||||
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ljharb/call-bind-apply-helpers.git"
|
||||
},
|
||||
"author": "Jordan Harband <ljharb@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ljharb/call-bind-apply-helpers/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ljharb/call-bind-apply-helpers#readme",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "^0.17.3",
|
||||
"@ljharb/eslint-config": "^21.1.1",
|
||||
"@ljharb/tsconfig": "^0.2.3",
|
||||
"@types/for-each": "^0.3.3",
|
||||
"@types/function-bind": "^1.1.10",
|
||||
"@types/object-inspect": "^1.13.0",
|
||||
"@types/tape": "^5.8.1",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"encoding": "^0.1.13",
|
||||
"es-value-fixtures": "^1.7.1",
|
||||
"eslint": "=8.8.0",
|
||||
"evalmd": "^0.0.19",
|
||||
"for-each": "^0.3.5",
|
||||
"has-strict-mode": "^1.1.0",
|
||||
"in-publish": "^2.0.1",
|
||||
"npmignore": "^0.3.1",
|
||||
"nyc": "^10.3.2",
|
||||
"object-inspect": "^1.13.4",
|
||||
"safe-publish-latest": "^2.0.0",
|
||||
"tape": "^5.9.0",
|
||||
"typescript": "next"
|
||||
},
|
||||
"testling": {
|
||||
"files": "test/index.js"
|
||||
},
|
||||
"auto-changelog": {
|
||||
"output": "CHANGELOG.md",
|
||||
"template": "keepachangelog",
|
||||
"unreleased": false,
|
||||
"commitLimit": false,
|
||||
"backfillLimit": false,
|
||||
"hideCredit": true
|
||||
},
|
||||
"publishConfig": {
|
||||
"ignore": [
|
||||
".github/workflows"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
}
|
||||
3
backend/bridge/node_modules/call-bind-apply-helpers/reflectApply.d.ts
generated
vendored
Normal file
3
backend/bridge/node_modules/call-bind-apply-helpers/reflectApply.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
declare const reflectApply: false | typeof Reflect.apply;
|
||||
|
||||
export = reflectApply;
|
||||
4
backend/bridge/node_modules/call-bind-apply-helpers/reflectApply.js
generated
vendored
Normal file
4
backend/bridge/node_modules/call-bind-apply-helpers/reflectApply.js
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
/** @type {import('./reflectApply')} */
|
||||
module.exports = typeof Reflect !== 'undefined' && Reflect && Reflect.apply;
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
'use strict';
|
||||
|
||||
var callBind = require('../');
|
||||
var hasStrictMode = require('has-strict-mode')();
|
||||
var forEach = require('for-each');
|
||||
var inspect = require('object-inspect');
|
||||
var v = require('es-value-fixtures');
|
||||
|
||||
var test = require('tape');
|
||||
|
||||
test('callBindBasic', function (t) {
|
||||
forEach(v.nonFunctions, function (nonFunction) {
|
||||
t['throws'](
|
||||
// @ts-expect-error
|
||||
function () { callBind([nonFunction]); },
|
||||
TypeError,
|
||||
inspect(nonFunction) + ' is not a function'
|
||||
);
|
||||
});
|
||||
|
||||
var sentinel = { sentinel: true };
|
||||
/** @type {<T, A extends number, B extends number>(this: T, a: A, b: B) => [T | undefined, A, B]} */
|
||||
var func = function (a, b) {
|
||||
// eslint-disable-next-line no-invalid-this
|
||||
return [!hasStrictMode && this === global ? undefined : this, a, b];
|
||||
};
|
||||
t.equal(func.length, 2, 'original function length is 2');
|
||||
|
||||
/** type {(thisArg: unknown, a: number, b: number) => [unknown, number, number]} */
|
||||
var bound = callBind([func]);
|
||||
/** type {((a: number, b: number) => [typeof sentinel, typeof a, typeof b])} */
|
||||
var boundR = callBind([func, sentinel]);
|
||||
/** type {((b: number) => [typeof sentinel, number, typeof b])} */
|
||||
var boundArg = callBind([func, sentinel, /** @type {const} */ (1)]);
|
||||
|
||||
// @ts-expect-error
|
||||
t.deepEqual(bound(), [undefined, undefined, undefined], 'bound func with no args');
|
||||
|
||||
// @ts-expect-error
|
||||
t.deepEqual(func(), [undefined, undefined, undefined], 'unbound func with too few args');
|
||||
// @ts-expect-error
|
||||
t.deepEqual(bound(1, 2), [hasStrictMode ? 1 : Object(1), 2, undefined], 'bound func too few args');
|
||||
// @ts-expect-error
|
||||
t.deepEqual(boundR(), [sentinel, undefined, undefined], 'bound func with receiver, with too few args');
|
||||
// @ts-expect-error
|
||||
t.deepEqual(boundArg(), [sentinel, 1, undefined], 'bound func with receiver and arg, with too few args');
|
||||
|
||||
t.deepEqual(func(1, 2), [undefined, 1, 2], 'unbound func with right args');
|
||||
t.deepEqual(bound(1, 2, 3), [hasStrictMode ? 1 : Object(1), 2, 3], 'bound func with right args');
|
||||
t.deepEqual(boundR(1, 2), [sentinel, 1, 2], 'bound func with receiver, with right args');
|
||||
t.deepEqual(boundArg(2), [sentinel, 1, 2], 'bound func with receiver and arg, with right arg');
|
||||
|
||||
// @ts-expect-error
|
||||
t.deepEqual(func(1, 2, 3), [undefined, 1, 2], 'unbound func with too many args');
|
||||
// @ts-expect-error
|
||||
t.deepEqual(bound(1, 2, 3, 4), [hasStrictMode ? 1 : Object(1), 2, 3], 'bound func with too many args');
|
||||
// @ts-expect-error
|
||||
t.deepEqual(boundR(1, 2, 3), [sentinel, 1, 2], 'bound func with receiver, with too many args');
|
||||
// @ts-expect-error
|
||||
t.deepEqual(boundArg(2, 3), [sentinel, 1, 2], 'bound func with receiver and arg, with too many args');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "@ljharb/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "es2021",
|
||||
},
|
||||
"exclude": [
|
||||
"coverage",
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"root": true,
|
||||
|
||||
"extends": "@ljharb",
|
||||
|
||||
"rules": {
|
||||
"new-cap": [2, {
|
||||
"capIsNewExceptions": [
|
||||
"GetIntrinsic",
|
||||
],
|
||||
}],
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [ljharb]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: npm/call-bound
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"all": true,
|
||||
"check-coverage": false,
|
||||
"reporter": ["text-summary", "text", "html", "json"],
|
||||
"exclude": [
|
||||
"coverage",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.0.4](https://github.com/ljharb/call-bound/compare/v1.0.3...v1.0.4) - 2025-03-03
|
||||
|
||||
### Commits
|
||||
|
||||
- [types] improve types [`e648922`](https://github.com/ljharb/call-bound/commit/e6489222a9e54f350fbf952ceabe51fd8b6027ff)
|
||||
- [Dev Deps] update `@arethetypeswrong/cli`, `@ljharb/tsconfig`, `@types/tape`, `es-value-fixtures`, `for-each`, `has-strict-mode`, `object-inspect` [`a42a5eb`](https://github.com/ljharb/call-bound/commit/a42a5ebe6c1b54fcdc7997c7dc64fdca9e936719)
|
||||
- [Deps] update `call-bind-apply-helpers`, `get-intrinsic` [`f529eac`](https://github.com/ljharb/call-bound/commit/f529eac132404c17156bbc23ab2297a25d0f20b8)
|
||||
|
||||
## [v1.0.3](https://github.com/ljharb/call-bound/compare/v1.0.2...v1.0.3) - 2024-12-15
|
||||
|
||||
### Commits
|
||||
|
||||
- [Refactor] use `call-bind-apply-helpers` instead of `call-bind` [`5e0b134`](https://github.com/ljharb/call-bound/commit/5e0b13496df14fb7d05dae9412f088da8d3f75be)
|
||||
- [Deps] update `get-intrinsic` [`41fc967`](https://github.com/ljharb/call-bound/commit/41fc96732a22c7b7e8f381f93ccc54bb6293be2e)
|
||||
- [readme] fix example [`79a0137`](https://github.com/ljharb/call-bound/commit/79a0137723f7c6d09c9c05452bbf8d5efb5d6e49)
|
||||
- [meta] add `sideEffects` flag [`08b07be`](https://github.com/ljharb/call-bound/commit/08b07be7f1c03f67dc6f3cdaf0906259771859f7)
|
||||
|
||||
## [v1.0.2](https://github.com/ljharb/call-bound/compare/v1.0.1...v1.0.2) - 2024-12-10
|
||||
|
||||
### Commits
|
||||
|
||||
- [Dev Deps] update `@arethetypeswrong/cli`, `@ljharb/tsconfig`, `gopd` [`e6a5ffe`](https://github.com/ljharb/call-bound/commit/e6a5ffe849368fe4f74dfd6cdeca1b9baa39e8d5)
|
||||
- [Deps] update `call-bind`, `get-intrinsic` [`2aeb5b5`](https://github.com/ljharb/call-bound/commit/2aeb5b521dc2b2683d1345c753ea1161de2d1c14)
|
||||
- [types] improve return type [`1a0c9fe`](https://github.com/ljharb/call-bound/commit/1a0c9fe3114471e7ca1f57d104e2efe713bb4871)
|
||||
|
||||
## v1.0.1 - 2024-12-05
|
||||
|
||||
### Commits
|
||||
|
||||
- Initial implementation, tests, readme, types [`6d94121`](https://github.com/ljharb/call-bound/commit/6d94121a9243602e506334069f7a03189fe3363d)
|
||||
- Initial commit [`0eae867`](https://github.com/ljharb/call-bound/commit/0eae867334ea025c33e6e91cdecfc9df96680cf9)
|
||||
- npm init [`71b2479`](https://github.com/ljharb/call-bound/commit/71b2479c6723e0b7d91a6b663613067e98b7b275)
|
||||
- Only apps should have lockfiles [`c3754a9`](https://github.com/ljharb/call-bound/commit/c3754a949b7f9132b47e2d18c1729889736741eb)
|
||||
- [actions] skip `npm ls` in node < 10 [`74275a5`](https://github.com/ljharb/call-bound/commit/74275a5186b8caf6309b6b97472bdcb0df4683a8)
|
||||
- [Dev Deps] add missing peer dep [`1354de8`](https://github.com/ljharb/call-bound/commit/1354de8679413e4ae9c523d85f76fa7a5e032d97)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Jordan Harband
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
# call-bound <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
|
||||
|
||||
[![github actions][actions-image]][actions-url]
|
||||
[![coverage][codecov-image]][codecov-url]
|
||||
[![dependency status][deps-svg]][deps-url]
|
||||
[![dev dependency status][dev-deps-svg]][dev-deps-url]
|
||||
[![License][license-image]][license-url]
|
||||
[![Downloads][downloads-image]][downloads-url]
|
||||
|
||||
[![npm badge][npm-badge-png]][package-url]
|
||||
|
||||
Robust call-bound JavaScript intrinsics, using `call-bind` and `get-intrinsic`.
|
||||
|
||||
## Getting started
|
||||
|
||||
```sh
|
||||
npm install --save call-bound
|
||||
```
|
||||
|
||||
## Usage/Examples
|
||||
|
||||
```js
|
||||
const assert = require('assert');
|
||||
const callBound = require('call-bound');
|
||||
|
||||
const slice = callBound('Array.prototype.slice');
|
||||
|
||||
delete Function.prototype.call;
|
||||
delete Function.prototype.bind;
|
||||
delete Array.prototype.slice;
|
||||
|
||||
assert.deepEqual(slice([1, 2, 3, 4], 1, -1), [2, 3]);
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Clone the repo, `npm install`, and run `npm test`
|
||||
|
||||
[package-url]: https://npmjs.org/package/call-bound
|
||||
[npm-version-svg]: https://versionbadg.es/ljharb/call-bound.svg
|
||||
[deps-svg]: https://david-dm.org/ljharb/call-bound.svg
|
||||
[deps-url]: https://david-dm.org/ljharb/call-bound
|
||||
[dev-deps-svg]: https://david-dm.org/ljharb/call-bound/dev-status.svg
|
||||
[dev-deps-url]: https://david-dm.org/ljharb/call-bound#info=devDependencies
|
||||
[npm-badge-png]: https://nodei.co/npm/call-bound.png?downloads=true&stars=true
|
||||
[license-image]: https://img.shields.io/npm/l/call-bound.svg
|
||||
[license-url]: LICENSE
|
||||
[downloads-image]: https://img.shields.io/npm/dm/call-bound.svg
|
||||
[downloads-url]: https://npm-stat.com/charts.html?package=call-bound
|
||||
[codecov-image]: https://codecov.io/gh/ljharb/call-bound/branch/main/graphs/badge.svg
|
||||
[codecov-url]: https://app.codecov.io/gh/ljharb/call-bound/
|
||||
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/call-bound
|
||||
[actions-url]: https://github.com/ljharb/call-bound/actions
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
type Intrinsic = typeof globalThis;
|
||||
|
||||
type IntrinsicName = keyof Intrinsic | `%${keyof Intrinsic}%`;
|
||||
|
||||
type IntrinsicPath = IntrinsicName | `${StripPercents<IntrinsicName>}.${string}` | `%${StripPercents<IntrinsicName>}.${string}%`;
|
||||
|
||||
type AllowMissing = boolean;
|
||||
|
||||
type StripPercents<T extends string> = T extends `%${infer U}%` ? U : T;
|
||||
|
||||
type BindMethodPrecise<F> =
|
||||
F extends (this: infer This, ...args: infer Args) => infer R
|
||||
? (obj: This, ...args: Args) => R
|
||||
: F extends {
|
||||
(this: infer This1, ...args: infer Args1): infer R1;
|
||||
(this: infer This2, ...args: infer Args2): infer R2
|
||||
}
|
||||
? {
|
||||
(obj: This1, ...args: Args1): R1;
|
||||
(obj: This2, ...args: Args2): R2
|
||||
}
|
||||
: never
|
||||
|
||||
// Extract method type from a prototype
|
||||
type GetPrototypeMethod<T extends keyof typeof globalThis, M extends string> =
|
||||
(typeof globalThis)[T] extends { prototype: any }
|
||||
? M extends keyof (typeof globalThis)[T]['prototype']
|
||||
? (typeof globalThis)[T]['prototype'][M]
|
||||
: never
|
||||
: never
|
||||
|
||||
// Get static property/method
|
||||
type GetStaticMember<T extends keyof typeof globalThis, P extends string> =
|
||||
P extends keyof (typeof globalThis)[T] ? (typeof globalThis)[T][P] : never
|
||||
|
||||
// Type that maps string path to actual bound function or value with better precision
|
||||
type BoundIntrinsic<S extends string> =
|
||||
S extends `${infer Obj}.prototype.${infer Method}`
|
||||
? Obj extends keyof typeof globalThis
|
||||
? BindMethodPrecise<GetPrototypeMethod<Obj, Method & string>>
|
||||
: unknown
|
||||
: S extends `${infer Obj}.${infer Prop}`
|
||||
? Obj extends keyof typeof globalThis
|
||||
? GetStaticMember<Obj, Prop & string>
|
||||
: unknown
|
||||
: unknown
|
||||
|
||||
declare function arraySlice<T>(array: readonly T[], start?: number, end?: number): T[];
|
||||
declare function arraySlice<T>(array: ArrayLike<T>, start?: number, end?: number): T[];
|
||||
declare function arraySlice<T>(array: IArguments, start?: number, end?: number): T[];
|
||||
|
||||
// Special cases for methods that need explicit typing
|
||||
interface SpecialCases {
|
||||
'%Object.prototype.isPrototypeOf%': (thisArg: {}, obj: unknown) => boolean;
|
||||
'%String.prototype.replace%': {
|
||||
(str: string, searchValue: string | RegExp, replaceValue: string): string;
|
||||
(str: string, searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string
|
||||
};
|
||||
'%Object.prototype.toString%': (obj: {}) => string;
|
||||
'%Object.prototype.hasOwnProperty%': (obj: {}, v: PropertyKey) => boolean;
|
||||
'%Array.prototype.slice%': typeof arraySlice;
|
||||
'%Array.prototype.map%': <T, U>(array: readonly T[], callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any) => U[];
|
||||
'%Array.prototype.filter%': <T>(array: readonly T[], predicate: (value: T, index: number, array: readonly T[]) => unknown, thisArg?: any) => T[];
|
||||
'%Array.prototype.indexOf%': <T>(array: readonly T[], searchElement: T, fromIndex?: number) => number;
|
||||
'%Function.prototype.apply%': <T, A extends any[], R>(fn: (...args: A) => R, thisArg: any, args: A) => R;
|
||||
'%Function.prototype.call%': <T, A extends any[], R>(fn: (...args: A) => R, thisArg: any, ...args: A) => R;
|
||||
'%Function.prototype.bind%': <T, A extends any[], R>(fn: (...args: A) => R, thisArg: any, ...args: A) => (...remainingArgs: A) => R;
|
||||
'%Promise.prototype.then%': {
|
||||
<T, R>(promise: Promise<T>, onfulfilled: (value: T) => R | PromiseLike<R>): Promise<R>;
|
||||
<T, R>(promise: Promise<T>, onfulfilled: ((value: T) => R | PromiseLike<R>) | undefined | null, onrejected: (reason: any) => R | PromiseLike<R>): Promise<R>;
|
||||
};
|
||||
'%RegExp.prototype.test%': (regexp: RegExp, str: string) => boolean;
|
||||
'%RegExp.prototype.exec%': (regexp: RegExp, str: string) => RegExpExecArray | null;
|
||||
'%Error.prototype.toString%': (error: Error) => string;
|
||||
'%TypeError.prototype.toString%': (error: TypeError) => string;
|
||||
'%String.prototype.split%': (
|
||||
obj: unknown,
|
||||
splitter: string | RegExp | {
|
||||
[Symbol.split](string: string, limit?: number): string[];
|
||||
},
|
||||
limit?: number | undefined
|
||||
) => string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bound function for a prototype method, or a value for a static property.
|
||||
*
|
||||
* @param name - The name of the intrinsic (e.g. 'Array.prototype.slice')
|
||||
* @param {AllowMissing} [allowMissing] - Whether to allow missing intrinsics (default: false)
|
||||
*/
|
||||
declare function callBound<K extends keyof SpecialCases | StripPercents<keyof SpecialCases>, S extends IntrinsicPath>(name: K, allowMissing?: AllowMissing): SpecialCases[`%${StripPercents<K>}%`];
|
||||
declare function callBound<K extends keyof SpecialCases | StripPercents<keyof SpecialCases>, S extends IntrinsicPath>(name: S, allowMissing?: AllowMissing): BoundIntrinsic<S>;
|
||||
|
||||
export = callBound;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
'use strict';
|
||||
|
||||
var GetIntrinsic = require('get-intrinsic');
|
||||
|
||||
var callBindBasic = require('call-bind-apply-helpers');
|
||||
|
||||
/** @type {(thisArg: string, searchString: string, position?: number) => number} */
|
||||
var $indexOf = callBindBasic([GetIntrinsic('%String.prototype.indexOf%')]);
|
||||
|
||||
/** @type {import('.')} */
|
||||
module.exports = function callBoundIntrinsic(name, allowMissing) {
|
||||
/* eslint no-extra-parens: 0 */
|
||||
|
||||
var intrinsic = /** @type {(this: unknown, ...args: unknown[]) => unknown} */ (GetIntrinsic(name, !!allowMissing));
|
||||
if (typeof intrinsic === 'function' && $indexOf(name, '.prototype.') > -1) {
|
||||
return callBindBasic(/** @type {const} */ ([intrinsic]));
|
||||
}
|
||||
return intrinsic;
|
||||
};
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"name": "call-bound",
|
||||
"version": "1.0.4",
|
||||
"description": "Robust call-bound JavaScript intrinsics, using `call-bind` and `get-intrinsic`.",
|
||||
"main": "index.js",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"prepack": "npmignore --auto --commentLines=auto",
|
||||
"prepublish": "not-in-publish || npm run prepublishOnly",
|
||||
"prepublishOnly": "safe-publish-latest",
|
||||
"prelint": "evalmd README.md",
|
||||
"lint": "eslint --ext=.js,.mjs .",
|
||||
"postlint": "tsc -p . && attw -P",
|
||||
"pretest": "npm run lint",
|
||||
"tests-only": "nyc tape 'test/**/*.js'",
|
||||
"test": "npm run tests-only",
|
||||
"posttest": "npx npm@'>=10.2' audit --production",
|
||||
"version": "auto-changelog && git add CHANGELOG.md",
|
||||
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ljharb/call-bound.git"
|
||||
},
|
||||
"keywords": [
|
||||
"javascript",
|
||||
"ecmascript",
|
||||
"es",
|
||||
"js",
|
||||
"callbind",
|
||||
"callbound",
|
||||
"call",
|
||||
"bind",
|
||||
"bound",
|
||||
"call-bind",
|
||||
"call-bound",
|
||||
"function",
|
||||
"es-abstract"
|
||||
],
|
||||
"author": "Jordan Harband <ljharb@gmail.com>",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ljharb/call-bound/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ljharb/call-bound#readme",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "^0.17.4",
|
||||
"@ljharb/eslint-config": "^21.1.1",
|
||||
"@ljharb/tsconfig": "^0.3.0",
|
||||
"@types/call-bind": "^1.0.5",
|
||||
"@types/get-intrinsic": "^1.2.3",
|
||||
"@types/tape": "^5.8.1",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"encoding": "^0.1.13",
|
||||
"es-value-fixtures": "^1.7.1",
|
||||
"eslint": "=8.8.0",
|
||||
"evalmd": "^0.0.19",
|
||||
"for-each": "^0.3.5",
|
||||
"gopd": "^1.2.0",
|
||||
"has-strict-mode": "^1.1.0",
|
||||
"in-publish": "^2.0.1",
|
||||
"npmignore": "^0.3.1",
|
||||
"nyc": "^10.3.2",
|
||||
"object-inspect": "^1.13.4",
|
||||
"safe-publish-latest": "^2.0.0",
|
||||
"tape": "^5.9.0",
|
||||
"typescript": "next"
|
||||
},
|
||||
"testling": {
|
||||
"files": "test/index.js"
|
||||
},
|
||||
"auto-changelog": {
|
||||
"output": "CHANGELOG.md",
|
||||
"template": "keepachangelog",
|
||||
"unreleased": false,
|
||||
"commitLimit": false,
|
||||
"backfillLimit": false,
|
||||
"hideCredit": true
|
||||
},
|
||||
"publishConfig": {
|
||||
"ignore": [
|
||||
".github/workflows"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
|
||||
var callBound = require('../');
|
||||
|
||||
/** @template {true} T @template U @typedef {T extends U ? T : never} AssertType */
|
||||
|
||||
test('callBound', function (t) {
|
||||
// static primitive
|
||||
t.equal(callBound('Array.length'), Array.length, 'Array.length yields itself');
|
||||
t.equal(callBound('%Array.length%'), Array.length, '%Array.length% yields itself');
|
||||
|
||||
// static non-function object
|
||||
t.equal(callBound('Array.prototype'), Array.prototype, 'Array.prototype yields itself');
|
||||
t.equal(callBound('%Array.prototype%'), Array.prototype, '%Array.prototype% yields itself');
|
||||
t.equal(callBound('Array.constructor'), Array.constructor, 'Array.constructor yields itself');
|
||||
t.equal(callBound('%Array.constructor%'), Array.constructor, '%Array.constructor% yields itself');
|
||||
|
||||
// static function
|
||||
t.equal(callBound('Date.parse'), Date.parse, 'Date.parse yields itself');
|
||||
t.equal(callBound('%Date.parse%'), Date.parse, '%Date.parse% yields itself');
|
||||
|
||||
// prototype primitive
|
||||
t.equal(callBound('Error.prototype.message'), Error.prototype.message, 'Error.prototype.message yields itself');
|
||||
t.equal(callBound('%Error.prototype.message%'), Error.prototype.message, '%Error.prototype.message% yields itself');
|
||||
|
||||
var x = callBound('Object.prototype.toString');
|
||||
var y = callBound('%Object.prototype.toString%');
|
||||
|
||||
// prototype function
|
||||
t.notEqual(x, Object.prototype.toString, 'Object.prototype.toString does not yield itself');
|
||||
t.notEqual(y, Object.prototype.toString, '%Object.prototype.toString% does not yield itself');
|
||||
t.equal(x(true), Object.prototype.toString.call(true), 'call-bound Object.prototype.toString calls into the original');
|
||||
t.equal(y(true), Object.prototype.toString.call(true), 'call-bound %Object.prototype.toString% calls into the original');
|
||||
|
||||
t['throws'](
|
||||
// @ts-expect-error
|
||||
function () { callBound('does not exist'); },
|
||||
SyntaxError,
|
||||
'nonexistent intrinsic throws'
|
||||
);
|
||||
t['throws'](
|
||||
// @ts-expect-error
|
||||
function () { callBound('does not exist', true); },
|
||||
SyntaxError,
|
||||
'allowMissing arg still throws for unknown intrinsic'
|
||||
);
|
||||
|
||||
t.test('real but absent intrinsic', { skip: typeof WeakRef !== 'undefined' }, function (st) {
|
||||
st['throws'](
|
||||
function () { callBound('WeakRef'); },
|
||||
TypeError,
|
||||
'real but absent intrinsic throws'
|
||||
);
|
||||
st.equal(callBound('WeakRef', true), undefined, 'allowMissing arg avoids exception');
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "@ljharb/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"lib": ["es2024"],
|
||||
},
|
||||
"exclude": [
|
||||
"coverage",
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
1.0.1 / 2025-11-18
|
||||
=================
|
||||
|
||||
* Updated `engines` field to Node@18 or higher (fixed reference, see 1.0.0)
|
||||
* Remove dependency `safe-buffer`
|
||||
|
||||
1.0.0 / 2024-08-31
|
||||
==================
|
||||
|
||||
* drop node <18
|
||||
* allow utf8 as alias for utf-8
|
||||
|
||||
0.5.4 / 2021-12-10
|
||||
==================
|
||||
|
||||
* deps: safe-buffer@5.2.1
|
||||
|
||||
0.5.3 / 2018-12-17
|
||||
==================
|
||||
|
||||
* Use `safe-buffer` for improved Buffer API
|
||||
|
||||
0.5.2 / 2016-12-08
|
||||
==================
|
||||
|
||||
* Fix `parse` to accept any linear whitespace character
|
||||
|
||||
0.5.1 / 2016-01-17
|
||||
==================
|
||||
|
||||
* perf: enable strict mode
|
||||
|
||||
0.5.0 / 2014-10-11
|
||||
==================
|
||||
|
||||
* Add `parse` function
|
||||
|
||||
0.4.0 / 2014-09-21
|
||||
==================
|
||||
|
||||
* Expand non-Unicode `filename` to the full ISO-8859-1 charset
|
||||
|
||||
0.3.0 / 2014-09-20
|
||||
==================
|
||||
|
||||
* Add `fallback` option
|
||||
* Add `type` option
|
||||
|
||||
0.2.0 / 2014-09-19
|
||||
==================
|
||||
|
||||
* Reduce ambiguity of file names with hex escape in buggy browsers
|
||||
|
||||
0.1.2 / 2014-09-19
|
||||
==================
|
||||
|
||||
* Fix periodic invalid Unicode filename header
|
||||
|
||||
0.1.1 / 2014-09-19
|
||||
==================
|
||||
|
||||
* Fix invalid characters appearing in `filename*` parameter
|
||||
|
||||
0.1.0 / 2014-09-18
|
||||
==================
|
||||
|
||||
* Make the `filename` argument optional
|
||||
|
||||
0.0.0 / 2014-09-18
|
||||
==================
|
||||
|
||||
* Initial release
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014-2017 Douglas Christopher Wilson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
# content-disposition
|
||||
|
||||
[![NPM Version][npm-image]][npm-url]
|
||||
[![NPM Downloads][downloads-image]][downloads-url]
|
||||
[![Node.js Version][node-version-image]][node-version-url]
|
||||
[![Build Status][github-actions-ci-image]][github-actions-ci-url]
|
||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||
|
||||
Create and parse HTTP `Content-Disposition` header
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
$ npm install content-disposition
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```js
|
||||
const contentDisposition = require('content-disposition')
|
||||
```
|
||||
|
||||
### contentDisposition(filename, options)
|
||||
|
||||
Create an attachment `Content-Disposition` header value using the given file name,
|
||||
if supplied. The `filename` is optional and if no file name is desired, but you
|
||||
want to specify `options`, set `filename` to `undefined`.
|
||||
|
||||
```js
|
||||
res.setHeader('Content-Disposition', contentDisposition('∫ maths.pdf'))
|
||||
```
|
||||
|
||||
**note** HTTP headers are of the ISO-8859-1 character set. If you are writing this
|
||||
header through a means different from `setHeader` in Node.js, you'll want to specify
|
||||
the `'binary'` encoding in Node.js.
|
||||
|
||||
#### Options
|
||||
|
||||
`contentDisposition` accepts these properties in the options object.
|
||||
|
||||
##### fallback
|
||||
|
||||
If the `filename` option is outside ISO-8859-1, then the file name is actually
|
||||
stored in a supplemental field for clients that support Unicode file names and
|
||||
a ISO-8859-1 version of the file name is automatically generated.
|
||||
|
||||
This specifies the ISO-8859-1 file name to override the automatic generation or
|
||||
disables the generation all together, defaults to `true`.
|
||||
|
||||
- A string will specify the ISO-8859-1 file name to use in place of automatic
|
||||
generation.
|
||||
- `false` will disable including a ISO-8859-1 file name and only include the
|
||||
Unicode version (unless the file name is already ISO-8859-1).
|
||||
- `true` will enable automatic generation if the file name is outside ISO-8859-1.
|
||||
|
||||
If the `filename` option is ISO-8859-1 and this option is specified and has a
|
||||
different value, then the `filename` option is encoded in the extended field
|
||||
and this set as the fallback field, even though they are both ISO-8859-1.
|
||||
|
||||
##### type
|
||||
|
||||
Specifies the disposition type, defaults to `"attachment"`. This can also be
|
||||
`"inline"`, or any other value (all values except inline are treated like
|
||||
`attachment`, but can convey additional information if both parties agree to
|
||||
it). The type is normalized to lower-case.
|
||||
|
||||
### contentDisposition.parse(string)
|
||||
|
||||
```js
|
||||
const disposition = contentDisposition.parse('attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt')
|
||||
```
|
||||
|
||||
Parse a `Content-Disposition` header string. This automatically handles extended
|
||||
("Unicode") parameters by decoding them and providing them under the standard
|
||||
parameter name. This will return an object with the following properties (examples
|
||||
are shown for the string `'attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt'`):
|
||||
|
||||
- `type`: The disposition type (always lower case). Example: `'attachment'`
|
||||
|
||||
- `parameters`: An object of the parameters in the disposition (name of parameter
|
||||
always lower case and extended versions replace non-extended versions). Example:
|
||||
`{filename: "€ rates.txt"}`
|
||||
|
||||
## Examples
|
||||
|
||||
### Send a file for download
|
||||
|
||||
```js
|
||||
const contentDisposition = require('content-disposition')
|
||||
const destroy = require('destroy')
|
||||
const fs = require('fs')
|
||||
const http = require('http')
|
||||
const onFinished = require('on-finished')
|
||||
|
||||
const filePath = '/path/to/public/plans.pdf'
|
||||
|
||||
http.createServer(function onRequest (req, res) {
|
||||
// set headers
|
||||
res.setHeader('Content-Type', 'application/pdf')
|
||||
res.setHeader('Content-Disposition', contentDisposition(filePath))
|
||||
|
||||
// send file
|
||||
const stream = fs.createReadStream(filePath)
|
||||
stream.pipe(res)
|
||||
onFinished(res, function () {
|
||||
destroy(stream)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```sh
|
||||
$ npm test
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1][rfc-2616]
|
||||
- [RFC 5987: Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters][rfc-5987]
|
||||
- [RFC 6266: Use of the Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)][rfc-6266]
|
||||
- [Test Cases for HTTP Content-Disposition header field (RFC 6266) and the Encodings defined in RFCs 2047, 2231 and 5987][tc-2231]
|
||||
|
||||
[rfc-2616]: https://tools.ietf.org/html/rfc2616
|
||||
[rfc-5987]: https://tools.ietf.org/html/rfc5987
|
||||
[rfc-6266]: https://tools.ietf.org/html/rfc6266
|
||||
[tc-2231]: http://greenbytes.de/tech/tc2231/
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
[npm-image]: https://img.shields.io/npm/v/content-disposition
|
||||
[npm-url]: https://npmjs.org/package/content-disposition
|
||||
[node-version-image]: https://img.shields.io/node/v/content-disposition
|
||||
[node-version-url]: https://nodejs.org/en/download
|
||||
[coveralls-image]: https://img.shields.io/coverallsCoverage/github/jshttp/content-disposition
|
||||
[coveralls-url]: https://coveralls.io/r/jshttp/content-disposition?branch=master
|
||||
[downloads-image]: https://img.shields.io/npm/dm/content-disposition
|
||||
[downloads-url]: https://npmjs.org/package/content-disposition
|
||||
[github-actions-ci-image]: https://img.shields.io/github/actions/workflow/status/jshttp/content-disposition/ci.yml
|
||||
[github-actions-ci-url]: https://github.com/jshttp/content-disposition/actions/workflows/ci.yml
|
||||
|
|
@ -0,0 +1,458 @@
|
|||
/*!
|
||||
* content-disposition
|
||||
* Copyright(c) 2014-2017 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
* @public
|
||||
*/
|
||||
|
||||
module.exports = contentDisposition
|
||||
module.exports.parse = parse
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var basename = require('path').basename
|
||||
|
||||
/**
|
||||
* RegExp to match non attr-char, *after* encodeURIComponent (i.e. not including "%")
|
||||
* @private
|
||||
*/
|
||||
|
||||
var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g // eslint-disable-line no-control-regex
|
||||
|
||||
/**
|
||||
* RegExp to match percent encoding escape.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/
|
||||
var HEX_ESCAPE_REPLACE_REGEXP = /%([0-9A-Fa-f]{2})/g
|
||||
|
||||
/**
|
||||
* RegExp to match non-latin1 characters.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var NON_LATIN1_REGEXP = /[^\x20-\x7e\xa0-\xff]/g
|
||||
|
||||
/**
|
||||
* RegExp to match quoted-pair in RFC 2616
|
||||
*
|
||||
* quoted-pair = "\" CHAR
|
||||
* CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
* @private
|
||||
*/
|
||||
|
||||
var QESC_REGEXP = /\\([\u0000-\u007f])/g // eslint-disable-line no-control-regex
|
||||
|
||||
/**
|
||||
* RegExp to match chars that must be quoted-pair in RFC 2616
|
||||
* @private
|
||||
*/
|
||||
|
||||
var QUOTE_REGEXP = /([\\"])/g
|
||||
|
||||
/**
|
||||
* RegExp for various RFC 2616 grammar
|
||||
*
|
||||
* parameter = token "=" ( token | quoted-string )
|
||||
* token = 1*<any CHAR except CTLs or separators>
|
||||
* separators = "(" | ")" | "<" | ">" | "@"
|
||||
* | "," | ";" | ":" | "\" | <">
|
||||
* | "/" | "[" | "]" | "?" | "="
|
||||
* | "{" | "}" | SP | HT
|
||||
* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
|
||||
* qdtext = <any TEXT except <">>
|
||||
* quoted-pair = "\" CHAR
|
||||
* CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
* TEXT = <any OCTET except CTLs, but including LWS>
|
||||
* LWS = [CRLF] 1*( SP | HT )
|
||||
* CRLF = CR LF
|
||||
* CR = <US-ASCII CR, carriage return (13)>
|
||||
* LF = <US-ASCII LF, linefeed (10)>
|
||||
* SP = <US-ASCII SP, space (32)>
|
||||
* HT = <US-ASCII HT, horizontal-tab (9)>
|
||||
* CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||
* OCTET = <any 8-bit sequence of data>
|
||||
* @private
|
||||
*/
|
||||
|
||||
var PARAM_REGEXP = /;[\x09\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*=[\x09\x20]*("(?:[\x20!\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*/g // eslint-disable-line no-control-regex
|
||||
var TEXT_REGEXP = /^[\x20-\x7e\x80-\xff]+$/
|
||||
var TOKEN_REGEXP = /^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/
|
||||
|
||||
/**
|
||||
* RegExp for various RFC 5987 grammar
|
||||
*
|
||||
* ext-value = charset "'" [ language ] "'" value-chars
|
||||
* charset = "UTF-8" / "ISO-8859-1" / mime-charset
|
||||
* mime-charset = 1*mime-charsetc
|
||||
* mime-charsetc = ALPHA / DIGIT
|
||||
* / "!" / "#" / "$" / "%" / "&"
|
||||
* / "+" / "-" / "^" / "_" / "`"
|
||||
* / "{" / "}" / "~"
|
||||
* language = ( 2*3ALPHA [ extlang ] )
|
||||
* / 4ALPHA
|
||||
* / 5*8ALPHA
|
||||
* extlang = *3( "-" 3ALPHA )
|
||||
* value-chars = *( pct-encoded / attr-char )
|
||||
* pct-encoded = "%" HEXDIG HEXDIG
|
||||
* attr-char = ALPHA / DIGIT
|
||||
* / "!" / "#" / "$" / "&" / "+" / "-" / "."
|
||||
* / "^" / "_" / "`" / "|" / "~"
|
||||
* @private
|
||||
*/
|
||||
|
||||
var EXT_VALUE_REGEXP = /^([A-Za-z0-9!#$%&+\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$/
|
||||
|
||||
/**
|
||||
* RegExp for various RFC 6266 grammar
|
||||
*
|
||||
* disposition-type = "inline" | "attachment" | disp-ext-type
|
||||
* disp-ext-type = token
|
||||
* disposition-parm = filename-parm | disp-ext-parm
|
||||
* filename-parm = "filename" "=" value
|
||||
* | "filename*" "=" ext-value
|
||||
* disp-ext-parm = token "=" value
|
||||
* | ext-token "=" ext-value
|
||||
* ext-token = <the characters in token, followed by "*">
|
||||
* @private
|
||||
*/
|
||||
|
||||
var DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/ // eslint-disable-line no-control-regex
|
||||
|
||||
/**
|
||||
* Create an attachment Content-Disposition header.
|
||||
*
|
||||
* @param {string} [filename]
|
||||
* @param {object} [options]
|
||||
* @param {string} [options.type=attachment]
|
||||
* @param {string|boolean} [options.fallback=true]
|
||||
* @return {string}
|
||||
* @public
|
||||
*/
|
||||
|
||||
function contentDisposition (filename, options) {
|
||||
var opts = options || {}
|
||||
|
||||
// get type
|
||||
var type = opts.type || 'attachment'
|
||||
|
||||
// get parameters
|
||||
var params = createparams(filename, opts.fallback)
|
||||
|
||||
// format into string
|
||||
return format(new ContentDisposition(type, params))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create parameters object from filename and fallback.
|
||||
*
|
||||
* @param {string} [filename]
|
||||
* @param {string|boolean} [fallback=true]
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function createparams (filename, fallback) {
|
||||
if (filename === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
var params = {}
|
||||
|
||||
if (typeof filename !== 'string') {
|
||||
throw new TypeError('filename must be a string')
|
||||
}
|
||||
|
||||
// fallback defaults to true
|
||||
if (fallback === undefined) {
|
||||
fallback = true
|
||||
}
|
||||
|
||||
if (typeof fallback !== 'string' && typeof fallback !== 'boolean') {
|
||||
throw new TypeError('fallback must be a string or boolean')
|
||||
}
|
||||
|
||||
if (typeof fallback === 'string' && NON_LATIN1_REGEXP.test(fallback)) {
|
||||
throw new TypeError('fallback must be ISO-8859-1 string')
|
||||
}
|
||||
|
||||
// restrict to file base name
|
||||
var name = basename(filename)
|
||||
|
||||
// determine if name is suitable for quoted string
|
||||
var isQuotedString = TEXT_REGEXP.test(name)
|
||||
|
||||
// generate fallback name
|
||||
var fallbackName = typeof fallback !== 'string'
|
||||
? fallback && getlatin1(name)
|
||||
: basename(fallback)
|
||||
var hasFallback = typeof fallbackName === 'string' && fallbackName !== name
|
||||
|
||||
// set extended filename parameter
|
||||
if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
|
||||
params['filename*'] = name
|
||||
}
|
||||
|
||||
// set filename parameter
|
||||
if (isQuotedString || hasFallback) {
|
||||
params.filename = hasFallback
|
||||
? fallbackName
|
||||
: name
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
/**
|
||||
* Format object to Content-Disposition header.
|
||||
*
|
||||
* @param {object} obj
|
||||
* @param {string} obj.type
|
||||
* @param {object} [obj.parameters]
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function format (obj) {
|
||||
var parameters = obj.parameters
|
||||
var type = obj.type
|
||||
|
||||
if (!type || typeof type !== 'string' || !TOKEN_REGEXP.test(type)) {
|
||||
throw new TypeError('invalid type')
|
||||
}
|
||||
|
||||
// start with normalized type
|
||||
var string = String(type).toLowerCase()
|
||||
|
||||
// append parameters
|
||||
if (parameters && typeof parameters === 'object') {
|
||||
var param
|
||||
var params = Object.keys(parameters).sort()
|
||||
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
param = params[i]
|
||||
|
||||
var val = param.slice(-1) === '*'
|
||||
? ustring(parameters[param])
|
||||
: qstring(parameters[param])
|
||||
|
||||
string += '; ' + param + '=' + val
|
||||
}
|
||||
}
|
||||
|
||||
return string
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a RFC 5987 field value (gracefully).
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function decodefield (str) {
|
||||
var match = EXT_VALUE_REGEXP.exec(str)
|
||||
|
||||
if (!match) {
|
||||
throw new TypeError('invalid extended field value')
|
||||
}
|
||||
|
||||
var charset = match[1].toLowerCase()
|
||||
var encoded = match[2]
|
||||
var value
|
||||
|
||||
// to binary string
|
||||
var binary = encoded.replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode)
|
||||
|
||||
switch (charset) {
|
||||
case 'iso-8859-1':
|
||||
value = getlatin1(binary)
|
||||
break
|
||||
case 'utf-8':
|
||||
case 'utf8':
|
||||
value = Buffer.from(binary, 'binary').toString('utf8')
|
||||
break
|
||||
default:
|
||||
throw new TypeError('unsupported charset in extended field')
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ISO-8859-1 version of string.
|
||||
*
|
||||
* @param {string} val
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function getlatin1 (val) {
|
||||
// simple Unicode -> ISO-8859-1 transformation
|
||||
return String(val).replace(NON_LATIN1_REGEXP, '?')
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Content-Disposition header string.
|
||||
*
|
||||
* @param {string} string
|
||||
* @return {object}
|
||||
* @public
|
||||
*/
|
||||
|
||||
function parse (string) {
|
||||
if (!string || typeof string !== 'string') {
|
||||
throw new TypeError('argument string is required')
|
||||
}
|
||||
|
||||
var match = DISPOSITION_TYPE_REGEXP.exec(string)
|
||||
|
||||
if (!match) {
|
||||
throw new TypeError('invalid type format')
|
||||
}
|
||||
|
||||
// normalize type
|
||||
var index = match[0].length
|
||||
var type = match[1].toLowerCase()
|
||||
|
||||
var key
|
||||
var names = []
|
||||
var params = {}
|
||||
var value
|
||||
|
||||
// calculate index to start at
|
||||
index = PARAM_REGEXP.lastIndex = match[0].slice(-1) === ';'
|
||||
? index - 1
|
||||
: index
|
||||
|
||||
// match parameters
|
||||
while ((match = PARAM_REGEXP.exec(string))) {
|
||||
if (match.index !== index) {
|
||||
throw new TypeError('invalid parameter format')
|
||||
}
|
||||
|
||||
index += match[0].length
|
||||
key = match[1].toLowerCase()
|
||||
value = match[2]
|
||||
|
||||
if (names.indexOf(key) !== -1) {
|
||||
throw new TypeError('invalid duplicate parameter')
|
||||
}
|
||||
|
||||
names.push(key)
|
||||
|
||||
if (key.indexOf('*') + 1 === key.length) {
|
||||
// decode extended value
|
||||
key = key.slice(0, -1)
|
||||
value = decodefield(value)
|
||||
|
||||
// overwrite existing value
|
||||
params[key] = value
|
||||
continue
|
||||
}
|
||||
|
||||
if (typeof params[key] === 'string') {
|
||||
continue
|
||||
}
|
||||
|
||||
if (value[0] === '"') {
|
||||
// remove quotes and escapes
|
||||
value = value
|
||||
.slice(1, -1)
|
||||
.replace(QESC_REGEXP, '$1')
|
||||
}
|
||||
|
||||
params[key] = value
|
||||
}
|
||||
|
||||
if (index !== -1 && index !== string.length) {
|
||||
throw new TypeError('invalid parameter format')
|
||||
}
|
||||
|
||||
return new ContentDisposition(type, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Percent decode a single character.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {string} hex
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function pdecode (str, hex) {
|
||||
return String.fromCharCode(parseInt(hex, 16))
|
||||
}
|
||||
|
||||
/**
|
||||
* Percent encode a single character.
|
||||
*
|
||||
* @param {string} char
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function pencode (char) {
|
||||
return '%' + String(char)
|
||||
.charCodeAt(0)
|
||||
.toString(16)
|
||||
.toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote a string for HTTP.
|
||||
*
|
||||
* @param {string} val
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function qstring (val) {
|
||||
var str = String(val)
|
||||
|
||||
return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"'
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a Unicode string for HTTP (RFC 5987).
|
||||
*
|
||||
* @param {string} val
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function ustring (val) {
|
||||
var str = String(val)
|
||||
|
||||
// percent encode as UTF-8
|
||||
var encoded = encodeURIComponent(str)
|
||||
.replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode)
|
||||
|
||||
return 'UTF-8\'\'' + encoded
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for parsed Content-Disposition header for v8 optimization
|
||||
*
|
||||
* @public
|
||||
* @param {string} type
|
||||
* @param {object} parameters
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
function ContentDisposition (type, parameters) {
|
||||
this.type = type
|
||||
this.parameters = parameters
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "content-disposition",
|
||||
"description": "Create and parse Content-Disposition header",
|
||||
"version": "1.0.1",
|
||||
"author": "Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"content-disposition",
|
||||
"http",
|
||||
"rfc6266",
|
||||
"res"
|
||||
],
|
||||
"repository": "jshttp/content-disposition",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
},
|
||||
"devDependencies": {
|
||||
"c8": "^10.1.2",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-standard": "13.0.1",
|
||||
"eslint-plugin-import": "2.25.3",
|
||||
"eslint-plugin-markdown": "2.2.1",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-promise": "5.2.0",
|
||||
"eslint-plugin-standard": "4.1.0"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"HISTORY.md",
|
||||
"README.md",
|
||||
"index.js"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "node --test --test-reporter spec",
|
||||
"test-ci": "c8 --reporter=lcovonly --reporter=text npm test",
|
||||
"test-cov": "c8 --reporter=html --reporter=text npm test"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
1.0.5 / 2023-01-29
|
||||
==================
|
||||
|
||||
* perf: skip value escaping when unnecessary
|
||||
|
||||
1.0.4 / 2017-09-11
|
||||
==================
|
||||
|
||||
* perf: skip parameter parsing when no parameters
|
||||
|
||||
1.0.3 / 2017-09-10
|
||||
==================
|
||||
|
||||
* perf: remove argument reassignment
|
||||
|
||||
1.0.2 / 2016-05-09
|
||||
==================
|
||||
|
||||
* perf: enable strict mode
|
||||
|
||||
1.0.1 / 2015-02-13
|
||||
==================
|
||||
|
||||
* Improve missing `Content-Type` header error message
|
||||
|
||||
1.0.0 / 2015-02-01
|
||||
==================
|
||||
|
||||
* Initial implementation, derived from `media-typer@0.3.0`
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2015 Douglas Christopher Wilson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
# content-type
|
||||
|
||||
[![NPM Version][npm-version-image]][npm-url]
|
||||
[![NPM Downloads][npm-downloads-image]][npm-url]
|
||||
[![Node.js Version][node-image]][node-url]
|
||||
[![Build Status][ci-image]][ci-url]
|
||||
[![Coverage Status][coveralls-image]][coveralls-url]
|
||||
|
||||
Create and parse HTTP Content-Type header according to RFC 7231
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
$ npm install content-type
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```js
|
||||
var contentType = require('content-type')
|
||||
```
|
||||
|
||||
### contentType.parse(string)
|
||||
|
||||
```js
|
||||
var obj = contentType.parse('image/svg+xml; charset=utf-8')
|
||||
```
|
||||
|
||||
Parse a `Content-Type` header. This will return an object with the following
|
||||
properties (examples are shown for the string `'image/svg+xml; charset=utf-8'`):
|
||||
|
||||
- `type`: The media type (the type and subtype, always lower case).
|
||||
Example: `'image/svg+xml'`
|
||||
|
||||
- `parameters`: An object of the parameters in the media type (name of parameter
|
||||
always lower case). Example: `{charset: 'utf-8'}`
|
||||
|
||||
Throws a `TypeError` if the string is missing or invalid.
|
||||
|
||||
### contentType.parse(req)
|
||||
|
||||
```js
|
||||
var obj = contentType.parse(req)
|
||||
```
|
||||
|
||||
Parse the `Content-Type` header from the given `req`. Short-cut for
|
||||
`contentType.parse(req.headers['content-type'])`.
|
||||
|
||||
Throws a `TypeError` if the `Content-Type` header is missing or invalid.
|
||||
|
||||
### contentType.parse(res)
|
||||
|
||||
```js
|
||||
var obj = contentType.parse(res)
|
||||
```
|
||||
|
||||
Parse the `Content-Type` header set on the given `res`. Short-cut for
|
||||
`contentType.parse(res.getHeader('content-type'))`.
|
||||
|
||||
Throws a `TypeError` if the `Content-Type` header is missing or invalid.
|
||||
|
||||
### contentType.format(obj)
|
||||
|
||||
```js
|
||||
var str = contentType.format({
|
||||
type: 'image/svg+xml',
|
||||
parameters: { charset: 'utf-8' }
|
||||
})
|
||||
```
|
||||
|
||||
Format an object into a `Content-Type` header. This will return a string of the
|
||||
content type for the given object with the following properties (examples are
|
||||
shown that produce the string `'image/svg+xml; charset=utf-8'`):
|
||||
|
||||
- `type`: The media type (will be lower-cased). Example: `'image/svg+xml'`
|
||||
|
||||
- `parameters`: An object of the parameters in the media type (name of the
|
||||
parameter will be lower-cased). Example: `{charset: 'utf-8'}`
|
||||
|
||||
Throws a `TypeError` if the object contains an invalid type or parameter names.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
[ci-image]: https://badgen.net/github/checks/jshttp/content-type/master?label=ci
|
||||
[ci-url]: https://github.com/jshttp/content-type/actions/workflows/ci.yml
|
||||
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/content-type/master
|
||||
[coveralls-url]: https://coveralls.io/r/jshttp/content-type?branch=master
|
||||
[node-image]: https://badgen.net/npm/node/content-type
|
||||
[node-url]: https://nodejs.org/en/download
|
||||
[npm-downloads-image]: https://badgen.net/npm/dm/content-type
|
||||
[npm-url]: https://npmjs.org/package/content-type
|
||||
[npm-version-image]: https://badgen.net/npm/v/content-type
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
/*!
|
||||
* content-type
|
||||
* Copyright(c) 2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1
|
||||
*
|
||||
* parameter = token "=" ( token / quoted-string )
|
||||
* token = 1*tchar
|
||||
* tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
|
||||
* / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
|
||||
* / DIGIT / ALPHA
|
||||
* ; any VCHAR, except delimiters
|
||||
* quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
|
||||
* qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
|
||||
* obs-text = %x80-FF
|
||||
* quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
|
||||
*/
|
||||
var PARAM_REGEXP = /; *([!#$%&'*+.^_`|~0-9A-Za-z-]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'*+.^_`|~0-9A-Za-z-]+) */g // eslint-disable-line no-control-regex
|
||||
var TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/ // eslint-disable-line no-control-regex
|
||||
var TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/
|
||||
|
||||
/**
|
||||
* RegExp to match quoted-pair in RFC 7230 sec 3.2.6
|
||||
*
|
||||
* quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
|
||||
* obs-text = %x80-FF
|
||||
*/
|
||||
var QESC_REGEXP = /\\([\u000b\u0020-\u00ff])/g // eslint-disable-line no-control-regex
|
||||
|
||||
/**
|
||||
* RegExp to match chars that must be quoted-pair in RFC 7230 sec 3.2.6
|
||||
*/
|
||||
var QUOTE_REGEXP = /([\\"])/g
|
||||
|
||||
/**
|
||||
* RegExp to match type in RFC 7231 sec 3.1.1.1
|
||||
*
|
||||
* media-type = type "/" subtype
|
||||
* type = token
|
||||
* subtype = token
|
||||
*/
|
||||
var TYPE_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.format = format
|
||||
exports.parse = parse
|
||||
|
||||
/**
|
||||
* Format object to media type.
|
||||
*
|
||||
* @param {object} obj
|
||||
* @return {string}
|
||||
* @public
|
||||
*/
|
||||
|
||||
function format (obj) {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
throw new TypeError('argument obj is required')
|
||||
}
|
||||
|
||||
var parameters = obj.parameters
|
||||
var type = obj.type
|
||||
|
||||
if (!type || !TYPE_REGEXP.test(type)) {
|
||||
throw new TypeError('invalid type')
|
||||
}
|
||||
|
||||
var string = type
|
||||
|
||||
// append parameters
|
||||
if (parameters && typeof parameters === 'object') {
|
||||
var param
|
||||
var params = Object.keys(parameters).sort()
|
||||
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
param = params[i]
|
||||
|
||||
if (!TOKEN_REGEXP.test(param)) {
|
||||
throw new TypeError('invalid parameter name')
|
||||
}
|
||||
|
||||
string += '; ' + param + '=' + qstring(parameters[param])
|
||||
}
|
||||
}
|
||||
|
||||
return string
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse media type to object.
|
||||
*
|
||||
* @param {string|object} string
|
||||
* @return {Object}
|
||||
* @public
|
||||
*/
|
||||
|
||||
function parse (string) {
|
||||
if (!string) {
|
||||
throw new TypeError('argument string is required')
|
||||
}
|
||||
|
||||
// support req/res-like objects as argument
|
||||
var header = typeof string === 'object'
|
||||
? getcontenttype(string)
|
||||
: string
|
||||
|
||||
if (typeof header !== 'string') {
|
||||
throw new TypeError('argument string is required to be a string')
|
||||
}
|
||||
|
||||
var index = header.indexOf(';')
|
||||
var type = index !== -1
|
||||
? header.slice(0, index).trim()
|
||||
: header.trim()
|
||||
|
||||
if (!TYPE_REGEXP.test(type)) {
|
||||
throw new TypeError('invalid media type')
|
||||
}
|
||||
|
||||
var obj = new ContentType(type.toLowerCase())
|
||||
|
||||
// parse parameters
|
||||
if (index !== -1) {
|
||||
var key
|
||||
var match
|
||||
var value
|
||||
|
||||
PARAM_REGEXP.lastIndex = index
|
||||
|
||||
while ((match = PARAM_REGEXP.exec(header))) {
|
||||
if (match.index !== index) {
|
||||
throw new TypeError('invalid parameter format')
|
||||
}
|
||||
|
||||
index += match[0].length
|
||||
key = match[1].toLowerCase()
|
||||
value = match[2]
|
||||
|
||||
if (value.charCodeAt(0) === 0x22 /* " */) {
|
||||
// remove quotes
|
||||
value = value.slice(1, -1)
|
||||
|
||||
// remove escapes
|
||||
if (value.indexOf('\\') !== -1) {
|
||||
value = value.replace(QESC_REGEXP, '$1')
|
||||
}
|
||||
}
|
||||
|
||||
obj.parameters[key] = value
|
||||
}
|
||||
|
||||
if (index !== header.length) {
|
||||
throw new TypeError('invalid parameter format')
|
||||
}
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content-type from req/res objects.
|
||||
*
|
||||
* @param {object}
|
||||
* @return {Object}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function getcontenttype (obj) {
|
||||
var header
|
||||
|
||||
if (typeof obj.getHeader === 'function') {
|
||||
// res-like
|
||||
header = obj.getHeader('content-type')
|
||||
} else if (typeof obj.headers === 'object') {
|
||||
// req-like
|
||||
header = obj.headers && obj.headers['content-type']
|
||||
}
|
||||
|
||||
if (typeof header !== 'string') {
|
||||
throw new TypeError('content-type header is missing from object')
|
||||
}
|
||||
|
||||
return header
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote a string if necessary.
|
||||
*
|
||||
* @param {string} val
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function qstring (val) {
|
||||
var str = String(val)
|
||||
|
||||
// no need to quote tokens
|
||||
if (TOKEN_REGEXP.test(str)) {
|
||||
return str
|
||||
}
|
||||
|
||||
if (str.length > 0 && !TEXT_REGEXP.test(str)) {
|
||||
throw new TypeError('invalid parameter value')
|
||||
}
|
||||
|
||||
return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to represent a content type.
|
||||
* @private
|
||||
*/
|
||||
function ContentType (type) {
|
||||
this.parameters = Object.create(null)
|
||||
this.type = type
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "content-type",
|
||||
"description": "Create and parse HTTP Content-Type header",
|
||||
"version": "1.0.5",
|
||||
"author": "Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"content-type",
|
||||
"http",
|
||||
"req",
|
||||
"res",
|
||||
"rfc7231"
|
||||
],
|
||||
"repository": "jshttp/content-type",
|
||||
"devDependencies": {
|
||||
"deep-equal": "1.0.1",
|
||||
"eslint": "8.32.0",
|
||||
"eslint-config-standard": "15.0.1",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-standard": "4.1.0",
|
||||
"mocha": "10.2.0",
|
||||
"nyc": "15.1.0"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"HISTORY.md",
|
||||
"README.md",
|
||||
"index.js"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "mocha --reporter spec --check-leaks --bail test/",
|
||||
"test-ci": "nyc --reporter=lcovonly --reporter=text npm test",
|
||||
"test-cov": "nyc --reporter=html --reporter=text npm test",
|
||||
"version": "node scripts/version-history.js && git add HISTORY.md"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
1.2.2 / 2024-10-29
|
||||
==================
|
||||
|
||||
* various metadata/documentation tweaks (incl. #51)
|
||||
|
||||
|
||||
1.2.1 / 2023-02-27
|
||||
==================
|
||||
|
||||
* update annotations for allowed secret key types (#44, thanks @jyasskin!)
|
||||
|
||||
|
||||
1.2.0 / 2022-02-17
|
||||
==================
|
||||
|
||||
* allow buffer and other node-supported types as key (#33)
|
||||
* be pickier about extra content after signed portion (#40)
|
||||
* some internal code clarity/cleanup improvements (#26)
|
||||
|
||||
|
||||
1.1.0 / 2018-01-18
|
||||
==================
|
||||
|
||||
* switch to built-in `crypto.timingSafeEqual` for validation instead of previous double-hash method (thank you @jodevsa!)
|
||||
|
||||
|
||||
1.0.7 / 2023-04-12
|
||||
==================
|
||||
|
||||
Later release for older node.js versions. See the [v1.0.x branch notes](https://github.com/tj/node-cookie-signature/blob/v1.0.x/History.md#107--2023-04-12).
|
||||
|
||||
|
||||
1.0.6 / 2015-02-03
|
||||
==================
|
||||
|
||||
* use `npm test` instead of `make test` to run tests
|
||||
* clearer assertion messages when checking input
|
||||
|
||||
|
||||
1.0.5 / 2014-09-05
|
||||
==================
|
||||
|
||||
* add license to package.json
|
||||
|
||||
1.0.4 / 2014-06-25
|
||||
==================
|
||||
|
||||
* corrected avoidance of timing attacks (thanks @tenbits!)
|
||||
|
||||
1.0.3 / 2014-01-28
|
||||
==================
|
||||
|
||||
* [incorrect] fix for timing attacks
|
||||
|
||||
1.0.2 / 2014-01-28
|
||||
==================
|
||||
|
||||
* fix missing repository warning
|
||||
* fix typo in test
|
||||
|
||||
1.0.1 / 2013-04-15
|
||||
==================
|
||||
|
||||
* Revert "Changed underlying HMAC algo. to sha512."
|
||||
* Revert "Fix for timing attacks on MAC verification."
|
||||
|
||||
0.0.1 / 2010-01-03
|
||||
==================
|
||||
|
||||
* Initial release
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2012–2024 LearnBoost <tj@learnboost.com> and other contributors;
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
# cookie-signature
|
||||
|
||||
Sign and unsign cookies.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
var cookie = require('cookie-signature');
|
||||
|
||||
var val = cookie.sign('hello', 'tobiiscool');
|
||||
val.should.equal('hello.DGDUkGlIkCzPz+C0B064FNgHdEjox7ch8tOBGslZ5QI');
|
||||
|
||||
var val = cookie.sign('hello', 'tobiiscool');
|
||||
cookie.unsign(val, 'tobiiscool').should.equal('hello');
|
||||
cookie.unsign(val, 'luna').should.be.false;
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT.
|
||||
|
||||
See LICENSE file for details.
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* Sign the given `val` with `secret`.
|
||||
*
|
||||
* @param {String} val
|
||||
* @param {String|NodeJS.ArrayBufferView|crypto.KeyObject} secret
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.sign = function(val, secret){
|
||||
if ('string' != typeof val) throw new TypeError("Cookie value must be provided as a string.");
|
||||
if (null == secret) throw new TypeError("Secret key must be provided.");
|
||||
return val + '.' + crypto
|
||||
.createHmac('sha256', secret)
|
||||
.update(val)
|
||||
.digest('base64')
|
||||
.replace(/\=+$/, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Unsign and decode the given `input` with `secret`,
|
||||
* returning `false` if the signature is invalid.
|
||||
*
|
||||
* @param {String} input
|
||||
* @param {String|NodeJS.ArrayBufferView|crypto.KeyObject} secret
|
||||
* @return {String|Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.unsign = function(input, secret){
|
||||
if ('string' != typeof input) throw new TypeError("Signed cookie string must be provided.");
|
||||
if (null == secret) throw new TypeError("Secret key must be provided.");
|
||||
var tentativeValue = input.slice(0, input.lastIndexOf('.')),
|
||||
expectedInput = exports.sign(tentativeValue, secret),
|
||||
expectedBuffer = Buffer.from(expectedInput),
|
||||
inputBuffer = Buffer.from(input);
|
||||
return (
|
||||
expectedBuffer.length === inputBuffer.length &&
|
||||
crypto.timingSafeEqual(expectedBuffer, inputBuffer)
|
||||
) ? tentativeValue : false;
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "cookie-signature",
|
||||
"version": "1.2.2",
|
||||
"main": "index.js",
|
||||
"description": "Sign and unsign cookies",
|
||||
"keywords": ["cookie", "sign", "unsign"],
|
||||
"author": "TJ Holowaychuk <tj@learnboost.com>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/visionmedia/node-cookie-signature.git"
|
||||
},
|
||||
"dependencies": {},
|
||||
"engines": {
|
||||
"node": ">=6.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "*",
|
||||
"should": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha --require should --reporter spec"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2012-2014 Roman Shtylman <shtylman@gmail.com>
|
||||
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
# cookie
|
||||
|
||||
[![NPM Version][npm-version-image]][npm-url]
|
||||
[![NPM Downloads][npm-downloads-image]][npm-url]
|
||||
[![Node.js Version][node-image]][node-url]
|
||||
[![Build Status][ci-image]][ci-url]
|
||||
[![Coverage Status][coveralls-image]][coveralls-url]
|
||||
|
||||
Basic HTTP cookie parser and serializer for HTTP servers.
|
||||
|
||||
## Installation
|
||||
|
||||
This is a [Node.js](https://nodejs.org/en/) module available through the
|
||||
[npm registry](https://www.npmjs.com/). Installation is done using the
|
||||
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
|
||||
|
||||
```sh
|
||||
$ npm install cookie
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```js
|
||||
var cookie = require('cookie');
|
||||
```
|
||||
|
||||
### cookie.parse(str, options)
|
||||
|
||||
Parse an HTTP `Cookie` header string and returning an object of all cookie name-value pairs.
|
||||
The `str` argument is the string representing a `Cookie` header value and `options` is an
|
||||
optional object containing additional parsing options.
|
||||
|
||||
```js
|
||||
var cookies = cookie.parse('foo=bar; equation=E%3Dmc%5E2');
|
||||
// { foo: 'bar', equation: 'E=mc^2' }
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
`cookie.parse` accepts these properties in the options object.
|
||||
|
||||
##### decode
|
||||
|
||||
Specifies a function that will be used to decode a cookie's value. Since the value of a cookie
|
||||
has a limited character set (and must be a simple string), this function can be used to decode
|
||||
a previously-encoded cookie value into a JavaScript string or other object.
|
||||
|
||||
The default function is the global `decodeURIComponent`, which will decode any URL-encoded
|
||||
sequences into their byte representations.
|
||||
|
||||
**note** if an error is thrown from this function, the original, non-decoded cookie value will
|
||||
be returned as the cookie's value.
|
||||
|
||||
### cookie.serialize(name, value, options)
|
||||
|
||||
Serialize a cookie name-value pair into a `Set-Cookie` header string. The `name` argument is the
|
||||
name for the cookie, the `value` argument is the value to set the cookie to, and the `options`
|
||||
argument is an optional object containing additional serialization options.
|
||||
|
||||
```js
|
||||
var setCookie = cookie.serialize('foo', 'bar');
|
||||
// foo=bar
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
`cookie.serialize` accepts these properties in the options object.
|
||||
|
||||
##### domain
|
||||
|
||||
Specifies the value for the [`Domain` `Set-Cookie` attribute][rfc-6265-5.2.3]. By default, no
|
||||
domain is set, and most clients will consider the cookie to apply to only the current domain.
|
||||
|
||||
##### encode
|
||||
|
||||
Specifies a function that will be used to encode a cookie's value. Since value of a cookie
|
||||
has a limited character set (and must be a simple string), this function can be used to encode
|
||||
a value into a string suited for a cookie's value.
|
||||
|
||||
The default function is the global `encodeURIComponent`, which will encode a JavaScript string
|
||||
into UTF-8 byte sequences and then URL-encode any that fall outside of the cookie range.
|
||||
|
||||
##### expires
|
||||
|
||||
Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute][rfc-6265-5.2.1].
|
||||
By default, no expiration is set, and most clients will consider this a "non-persistent cookie" and
|
||||
will delete it on a condition like exiting a web browser application.
|
||||
|
||||
**note** the [cookie storage model specification][rfc-6265-5.3] states that if both `expires` and
|
||||
`maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this,
|
||||
so if both are set, they should point to the same date and time.
|
||||
|
||||
##### httpOnly
|
||||
|
||||
Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute][rfc-6265-5.2.6]. When truthy,
|
||||
the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set.
|
||||
|
||||
**note** be careful when setting this to `true`, as compliant clients will not allow client-side
|
||||
JavaScript to see the cookie in `document.cookie`.
|
||||
|
||||
##### maxAge
|
||||
|
||||
Specifies the `number` (in seconds) to be the value for the [`Max-Age` `Set-Cookie` attribute][rfc-6265-5.2.2].
|
||||
The given number will be converted to an integer by rounding down. By default, no maximum age is set.
|
||||
|
||||
**note** the [cookie storage model specification][rfc-6265-5.3] states that if both `expires` and
|
||||
`maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this,
|
||||
so if both are set, they should point to the same date and time.
|
||||
|
||||
##### partitioned
|
||||
|
||||
Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies)
|
||||
attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the
|
||||
`Partitioned` attribute is not set.
|
||||
|
||||
**note** This is an attribute that has not yet been fully standardized, and may change in the future.
|
||||
This also means many clients may ignore this attribute until they understand it.
|
||||
|
||||
More information about can be found in [the proposal](https://github.com/privacycg/CHIPS).
|
||||
|
||||
##### path
|
||||
|
||||
Specifies the value for the [`Path` `Set-Cookie` attribute][rfc-6265-5.2.4]. By default, the path
|
||||
is considered the ["default path"][rfc-6265-5.1.4].
|
||||
|
||||
##### priority
|
||||
|
||||
Specifies the `string` to be the value for the [`Priority` `Set-Cookie` attribute][rfc-west-cookie-priority-00-4.1].
|
||||
|
||||
- `'low'` will set the `Priority` attribute to `Low`.
|
||||
- `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set.
|
||||
- `'high'` will set the `Priority` attribute to `High`.
|
||||
|
||||
More information about the different priority levels can be found in
|
||||
[the specification][rfc-west-cookie-priority-00-4.1].
|
||||
|
||||
**note** This is an attribute that has not yet been fully standardized, and may change in the future.
|
||||
This also means many clients may ignore this attribute until they understand it.
|
||||
|
||||
##### sameSite
|
||||
|
||||
Specifies the `boolean` or `string` to be the value for the [`SameSite` `Set-Cookie` attribute][rfc-6265bis-09-5.4.7].
|
||||
|
||||
- `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
|
||||
- `false` will not set the `SameSite` attribute.
|
||||
- `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement.
|
||||
- `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie.
|
||||
- `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
|
||||
|
||||
More information about the different enforcement levels can be found in
|
||||
[the specification][rfc-6265bis-09-5.4.7].
|
||||
|
||||
**note** This is an attribute that has not yet been fully standardized, and may change in the future.
|
||||
This also means many clients may ignore this attribute until they understand it.
|
||||
|
||||
##### secure
|
||||
|
||||
Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute][rfc-6265-5.2.5]. When truthy,
|
||||
the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
|
||||
|
||||
**note** be careful when setting this to `true`, as compliant clients will not send the cookie back to
|
||||
the server in the future if the browser does not have an HTTPS connection.
|
||||
|
||||
## Example
|
||||
|
||||
The following example uses this module in conjunction with the Node.js core HTTP server
|
||||
to prompt a user for their name and display it back on future visits.
|
||||
|
||||
```js
|
||||
var cookie = require('cookie');
|
||||
var escapeHtml = require('escape-html');
|
||||
var http = require('http');
|
||||
var url = require('url');
|
||||
|
||||
function onRequest(req, res) {
|
||||
// Parse the query string
|
||||
var query = url.parse(req.url, true, true).query;
|
||||
|
||||
if (query && query.name) {
|
||||
// Set a new cookie with the name
|
||||
res.setHeader('Set-Cookie', cookie.serialize('name', String(query.name), {
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 60 * 24 * 7 // 1 week
|
||||
}));
|
||||
|
||||
// Redirect back after setting cookie
|
||||
res.statusCode = 302;
|
||||
res.setHeader('Location', req.headers.referer || '/');
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the cookies on the request
|
||||
var cookies = cookie.parse(req.headers.cookie || '');
|
||||
|
||||
// Get the visitor name set in the cookie
|
||||
var name = cookies.name;
|
||||
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
|
||||
if (name) {
|
||||
res.write('<p>Welcome back, <b>' + escapeHtml(name) + '</b>!</p>');
|
||||
} else {
|
||||
res.write('<p>Hello, new visitor!</p>');
|
||||
}
|
||||
|
||||
res.write('<form method="GET">');
|
||||
res.write('<input placeholder="enter your name" name="name"> <input type="submit" value="Set Name">');
|
||||
res.end('</form>');
|
||||
}
|
||||
|
||||
http.createServer(onRequest).listen(3000);
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```sh
|
||||
$ npm test
|
||||
```
|
||||
|
||||
## Benchmark
|
||||
|
||||
```
|
||||
$ npm run bench
|
||||
|
||||
> cookie@0.5.0 bench
|
||||
> node benchmark/index.js
|
||||
|
||||
node@18.18.2
|
||||
acorn@8.10.0
|
||||
ada@2.6.0
|
||||
ares@1.19.1
|
||||
brotli@1.0.9
|
||||
cldr@43.1
|
||||
icu@73.2
|
||||
llhttp@6.0.11
|
||||
modules@108
|
||||
napi@9
|
||||
nghttp2@1.57.0
|
||||
nghttp3@0.7.0
|
||||
ngtcp2@0.8.1
|
||||
openssl@3.0.10+quic
|
||||
simdutf@3.2.14
|
||||
tz@2023c
|
||||
undici@5.26.3
|
||||
unicode@15.0
|
||||
uv@1.44.2
|
||||
uvwasi@0.0.18
|
||||
v8@10.2.154.26-node.26
|
||||
zlib@1.2.13.1-motley
|
||||
|
||||
> node benchmark/parse-top.js
|
||||
|
||||
cookie.parse - top sites
|
||||
|
||||
14 tests completed.
|
||||
|
||||
parse accounts.google.com x 2,588,913 ops/sec ±0.74% (186 runs sampled)
|
||||
parse apple.com x 2,370,002 ops/sec ±0.69% (186 runs sampled)
|
||||
parse cloudflare.com x 2,213,102 ops/sec ±0.88% (188 runs sampled)
|
||||
parse docs.google.com x 2,194,157 ops/sec ±1.03% (184 runs sampled)
|
||||
parse drive.google.com x 2,265,084 ops/sec ±0.79% (187 runs sampled)
|
||||
parse en.wikipedia.org x 457,099 ops/sec ±0.81% (186 runs sampled)
|
||||
parse linkedin.com x 504,407 ops/sec ±0.89% (186 runs sampled)
|
||||
parse maps.google.com x 1,230,959 ops/sec ±0.98% (186 runs sampled)
|
||||
parse microsoft.com x 926,294 ops/sec ±0.88% (184 runs sampled)
|
||||
parse play.google.com x 2,311,338 ops/sec ±0.83% (185 runs sampled)
|
||||
parse support.google.com x 1,508,850 ops/sec ±0.86% (186 runs sampled)
|
||||
parse www.google.com x 1,022,582 ops/sec ±1.32% (182 runs sampled)
|
||||
parse youtu.be x 332,136 ops/sec ±1.02% (185 runs sampled)
|
||||
parse youtube.com x 323,833 ops/sec ±0.77% (183 runs sampled)
|
||||
|
||||
> node benchmark/parse.js
|
||||
|
||||
cookie.parse - generic
|
||||
|
||||
6 tests completed.
|
||||
|
||||
simple x 3,214,032 ops/sec ±1.61% (183 runs sampled)
|
||||
decode x 587,237 ops/sec ±1.16% (187 runs sampled)
|
||||
unquote x 2,954,618 ops/sec ±1.35% (183 runs sampled)
|
||||
duplicates x 857,008 ops/sec ±0.89% (187 runs sampled)
|
||||
10 cookies x 292,133 ops/sec ±0.89% (187 runs sampled)
|
||||
100 cookies x 22,610 ops/sec ±0.68% (187 runs sampled)
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [RFC 6265: HTTP State Management Mechanism][rfc-6265]
|
||||
- [Same-site Cookies][rfc-6265bis-09-5.4.7]
|
||||
|
||||
[rfc-cutler-httpbis-partitioned-cookies]: https://tools.ietf.org/html/draft-cutler-httpbis-partitioned-cookies/
|
||||
[rfc-west-cookie-priority-00-4.1]: https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1
|
||||
[rfc-6265bis-09-5.4.7]: https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7
|
||||
[rfc-6265]: https://tools.ietf.org/html/rfc6265
|
||||
[rfc-6265-5.1.4]: https://tools.ietf.org/html/rfc6265#section-5.1.4
|
||||
[rfc-6265-5.2.1]: https://tools.ietf.org/html/rfc6265#section-5.2.1
|
||||
[rfc-6265-5.2.2]: https://tools.ietf.org/html/rfc6265#section-5.2.2
|
||||
[rfc-6265-5.2.3]: https://tools.ietf.org/html/rfc6265#section-5.2.3
|
||||
[rfc-6265-5.2.4]: https://tools.ietf.org/html/rfc6265#section-5.2.4
|
||||
[rfc-6265-5.2.5]: https://tools.ietf.org/html/rfc6265#section-5.2.5
|
||||
[rfc-6265-5.2.6]: https://tools.ietf.org/html/rfc6265#section-5.2.6
|
||||
[rfc-6265-5.3]: https://tools.ietf.org/html/rfc6265#section-5.3
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
[ci-image]: https://badgen.net/github/checks/jshttp/cookie/master?label=ci
|
||||
[ci-url]: https://github.com/jshttp/cookie/actions/workflows/ci.yml
|
||||
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/cookie/master
|
||||
[coveralls-url]: https://coveralls.io/r/jshttp/cookie?branch=master
|
||||
[node-image]: https://badgen.net/npm/node/cookie
|
||||
[node-url]: https://nodejs.org/en/download
|
||||
[npm-downloads-image]: https://badgen.net/npm/dm/cookie
|
||||
[npm-url]: https://npmjs.org/package/cookie
|
||||
[npm-version-image]: https://badgen.net/npm/v/cookie
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# Security Policies and Procedures
|
||||
|
||||
## Reporting a Bug
|
||||
|
||||
The `cookie` team and community take all security bugs seriously. Thank
|
||||
you for improving the security of the project. We appreciate your efforts and
|
||||
responsible disclosure and will make every effort to acknowledge your
|
||||
contributions.
|
||||
|
||||
Report security bugs by emailing the current owner(s) of `cookie`. This
|
||||
information can be found in the npm registry using the command
|
||||
`npm owner ls cookie`.
|
||||
If unsure or unable to get the information from the above, open an issue
|
||||
in the [project issue tracker](https://github.com/jshttp/cookie/issues)
|
||||
asking for the current contact information.
|
||||
|
||||
To ensure the timely response to your report, please ensure that the entirety
|
||||
of the report is contained within the email body and not solely behind a web
|
||||
link or an attachment.
|
||||
|
||||
At least one owner will acknowledge your email within 48 hours, and will send a
|
||||
more detailed response within 48 hours indicating the next steps in handling
|
||||
your report. After the initial reply to your report, the owners will
|
||||
endeavor to keep you informed of the progress towards a fix and full
|
||||
announcement, and may ask for additional information or guidance.
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
/*!
|
||||
* cookie
|
||||
* Copyright(c) 2012-2014 Roman Shtylman
|
||||
* Copyright(c) 2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.parse = parse;
|
||||
exports.serialize = serialize;
|
||||
|
||||
/**
|
||||
* Module variables.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var __toString = Object.prototype.toString
|
||||
var __hasOwnProperty = Object.prototype.hasOwnProperty
|
||||
|
||||
/**
|
||||
* RegExp to match cookie-name in RFC 6265 sec 4.1.1
|
||||
* This refers out to the obsoleted definition of token in RFC 2616 sec 2.2
|
||||
* which has been replaced by the token definition in RFC 7230 appendix B.
|
||||
*
|
||||
* cookie-name = token
|
||||
* token = 1*tchar
|
||||
* tchar = "!" / "#" / "$" / "%" / "&" / "'" /
|
||||
* "*" / "+" / "-" / "." / "^" / "_" /
|
||||
* "`" / "|" / "~" / DIGIT / ALPHA
|
||||
*/
|
||||
|
||||
var cookieNameRegExp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
||||
|
||||
/**
|
||||
* RegExp to match cookie-value in RFC 6265 sec 4.1.1
|
||||
*
|
||||
* cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
|
||||
* cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
|
||||
* ; US-ASCII characters excluding CTLs,
|
||||
* ; whitespace DQUOTE, comma, semicolon,
|
||||
* ; and backslash
|
||||
*/
|
||||
|
||||
var cookieValueRegExp = /^("?)[\u0021\u0023-\u002B\u002D-\u003A\u003C-\u005B\u005D-\u007E]*\1$/;
|
||||
|
||||
/**
|
||||
* RegExp to match domain-value in RFC 6265 sec 4.1.1
|
||||
*
|
||||
* domain-value = <subdomain>
|
||||
* ; defined in [RFC1034], Section 3.5, as
|
||||
* ; enhanced by [RFC1123], Section 2.1
|
||||
* <subdomain> = <label> | <subdomain> "." <label>
|
||||
* <label> = <let-dig> [ [ <ldh-str> ] <let-dig> ]
|
||||
* Labels must be 63 characters or less.
|
||||
* 'let-dig' not 'letter' in the first char, per RFC1123
|
||||
* <ldh-str> = <let-dig-hyp> | <let-dig-hyp> <ldh-str>
|
||||
* <let-dig-hyp> = <let-dig> | "-"
|
||||
* <let-dig> = <letter> | <digit>
|
||||
* <letter> = any one of the 52 alphabetic characters A through Z in
|
||||
* upper case and a through z in lower case
|
||||
* <digit> = any one of the ten digits 0 through 9
|
||||
*
|
||||
* Keep support for leading dot: https://github.com/jshttp/cookie/issues/173
|
||||
*
|
||||
* > (Note that a leading %x2E ("."), if present, is ignored even though that
|
||||
* character is not permitted, but a trailing %x2E ("."), if present, will
|
||||
* cause the user agent to ignore the attribute.)
|
||||
*/
|
||||
|
||||
var domainValueRegExp = /^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;
|
||||
|
||||
/**
|
||||
* RegExp to match path-value in RFC 6265 sec 4.1.1
|
||||
*
|
||||
* path-value = <any CHAR except CTLs or ";">
|
||||
* CHAR = %x01-7F
|
||||
* ; defined in RFC 5234 appendix B.1
|
||||
*/
|
||||
|
||||
var pathValueRegExp = /^[\u0020-\u003A\u003D-\u007E]*$/;
|
||||
|
||||
/**
|
||||
* Parse a cookie header.
|
||||
*
|
||||
* Parse the given cookie header string into an object
|
||||
* The object has the various cookies as keys(names) => values
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {object} [opt]
|
||||
* @return {object}
|
||||
* @public
|
||||
*/
|
||||
|
||||
function parse(str, opt) {
|
||||
if (typeof str !== 'string') {
|
||||
throw new TypeError('argument str must be a string');
|
||||
}
|
||||
|
||||
var obj = {};
|
||||
var len = str.length;
|
||||
// RFC 6265 sec 4.1.1, RFC 2616 2.2 defines a cookie name consists of one char minimum, plus '='.
|
||||
if (len < 2) return obj;
|
||||
|
||||
var dec = (opt && opt.decode) || decode;
|
||||
var index = 0;
|
||||
var eqIdx = 0;
|
||||
var endIdx = 0;
|
||||
|
||||
do {
|
||||
eqIdx = str.indexOf('=', index);
|
||||
if (eqIdx === -1) break; // No more cookie pairs.
|
||||
|
||||
endIdx = str.indexOf(';', index);
|
||||
|
||||
if (endIdx === -1) {
|
||||
endIdx = len;
|
||||
} else if (eqIdx > endIdx) {
|
||||
// backtrack on prior semicolon
|
||||
index = str.lastIndexOf(';', eqIdx - 1) + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
var keyStartIdx = startIndex(str, index, eqIdx);
|
||||
var keyEndIdx = endIndex(str, eqIdx, keyStartIdx);
|
||||
var key = str.slice(keyStartIdx, keyEndIdx);
|
||||
|
||||
// only assign once
|
||||
if (!__hasOwnProperty.call(obj, key)) {
|
||||
var valStartIdx = startIndex(str, eqIdx + 1, endIdx);
|
||||
var valEndIdx = endIndex(str, endIdx, valStartIdx);
|
||||
|
||||
if (str.charCodeAt(valStartIdx) === 0x22 /* " */ && str.charCodeAt(valEndIdx - 1) === 0x22 /* " */) {
|
||||
valStartIdx++;
|
||||
valEndIdx--;
|
||||
}
|
||||
|
||||
var val = str.slice(valStartIdx, valEndIdx);
|
||||
obj[key] = tryDecode(val, dec);
|
||||
}
|
||||
|
||||
index = endIdx + 1
|
||||
} while (index < len);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function startIndex(str, index, max) {
|
||||
do {
|
||||
var code = str.charCodeAt(index);
|
||||
if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index;
|
||||
} while (++index < max);
|
||||
return max;
|
||||
}
|
||||
|
||||
function endIndex(str, index, min) {
|
||||
while (index > min) {
|
||||
var code = str.charCodeAt(--index);
|
||||
if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index + 1;
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize data into a cookie header.
|
||||
*
|
||||
* Serialize a name value pair into a cookie string suitable for
|
||||
* http headers. An optional options object specifies cookie parameters.
|
||||
*
|
||||
* serialize('foo', 'bar', { httpOnly: true })
|
||||
* => "foo=bar; httpOnly"
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {string} val
|
||||
* @param {object} [opt]
|
||||
* @return {string}
|
||||
* @public
|
||||
*/
|
||||
|
||||
function serialize(name, val, opt) {
|
||||
var enc = (opt && opt.encode) || encodeURIComponent;
|
||||
|
||||
if (typeof enc !== 'function') {
|
||||
throw new TypeError('option encode is invalid');
|
||||
}
|
||||
|
||||
if (!cookieNameRegExp.test(name)) {
|
||||
throw new TypeError('argument name is invalid');
|
||||
}
|
||||
|
||||
var value = enc(val);
|
||||
|
||||
if (!cookieValueRegExp.test(value)) {
|
||||
throw new TypeError('argument val is invalid');
|
||||
}
|
||||
|
||||
var str = name + '=' + value;
|
||||
if (!opt) return str;
|
||||
|
||||
if (null != opt.maxAge) {
|
||||
var maxAge = Math.floor(opt.maxAge);
|
||||
|
||||
if (!isFinite(maxAge)) {
|
||||
throw new TypeError('option maxAge is invalid')
|
||||
}
|
||||
|
||||
str += '; Max-Age=' + maxAge;
|
||||
}
|
||||
|
||||
if (opt.domain) {
|
||||
if (!domainValueRegExp.test(opt.domain)) {
|
||||
throw new TypeError('option domain is invalid');
|
||||
}
|
||||
|
||||
str += '; Domain=' + opt.domain;
|
||||
}
|
||||
|
||||
if (opt.path) {
|
||||
if (!pathValueRegExp.test(opt.path)) {
|
||||
throw new TypeError('option path is invalid');
|
||||
}
|
||||
|
||||
str += '; Path=' + opt.path;
|
||||
}
|
||||
|
||||
if (opt.expires) {
|
||||
var expires = opt.expires
|
||||
|
||||
if (!isDate(expires) || isNaN(expires.valueOf())) {
|
||||
throw new TypeError('option expires is invalid');
|
||||
}
|
||||
|
||||
str += '; Expires=' + expires.toUTCString()
|
||||
}
|
||||
|
||||
if (opt.httpOnly) {
|
||||
str += '; HttpOnly';
|
||||
}
|
||||
|
||||
if (opt.secure) {
|
||||
str += '; Secure';
|
||||
}
|
||||
|
||||
if (opt.partitioned) {
|
||||
str += '; Partitioned'
|
||||
}
|
||||
|
||||
if (opt.priority) {
|
||||
var priority = typeof opt.priority === 'string'
|
||||
? opt.priority.toLowerCase() : opt.priority;
|
||||
|
||||
switch (priority) {
|
||||
case 'low':
|
||||
str += '; Priority=Low'
|
||||
break
|
||||
case 'medium':
|
||||
str += '; Priority=Medium'
|
||||
break
|
||||
case 'high':
|
||||
str += '; Priority=High'
|
||||
break
|
||||
default:
|
||||
throw new TypeError('option priority is invalid')
|
||||
}
|
||||
}
|
||||
|
||||
if (opt.sameSite) {
|
||||
var sameSite = typeof opt.sameSite === 'string'
|
||||
? opt.sameSite.toLowerCase() : opt.sameSite;
|
||||
|
||||
switch (sameSite) {
|
||||
case true:
|
||||
str += '; SameSite=Strict';
|
||||
break;
|
||||
case 'lax':
|
||||
str += '; SameSite=Lax';
|
||||
break;
|
||||
case 'strict':
|
||||
str += '; SameSite=Strict';
|
||||
break;
|
||||
case 'none':
|
||||
str += '; SameSite=None';
|
||||
break;
|
||||
default:
|
||||
throw new TypeError('option sameSite is invalid');
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL-decode string value. Optimized to skip native call when no %.
|
||||
*
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
|
||||
function decode (str) {
|
||||
return str.indexOf('%') !== -1
|
||||
? decodeURIComponent(str)
|
||||
: str
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if value is a Date.
|
||||
*
|
||||
* @param {*} val
|
||||
* @private
|
||||
*/
|
||||
|
||||
function isDate (val) {
|
||||
return __toString.call(val) === '[object Date]';
|
||||
}
|
||||
|
||||
/**
|
||||
* Try decoding a string using a decoding function.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {function} decode
|
||||
* @private
|
||||
*/
|
||||
|
||||
function tryDecode(str, decode) {
|
||||
try {
|
||||
return decode(str);
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "cookie",
|
||||
"description": "HTTP server cookie parsing and serialization",
|
||||
"version": "0.7.2",
|
||||
"author": "Roman Shtylman <shtylman@gmail.com>",
|
||||
"contributors": [
|
||||
"Douglas Christopher Wilson <doug@somethingdoug.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"cookie",
|
||||
"cookies"
|
||||
],
|
||||
"repository": "jshttp/cookie",
|
||||
"devDependencies": {
|
||||
"beautify-benchmark": "0.2.4",
|
||||
"benchmark": "2.1.4",
|
||||
"eslint": "8.53.0",
|
||||
"eslint-plugin-markdown": "3.0.1",
|
||||
"mocha": "10.2.0",
|
||||
"nyc": "15.1.0",
|
||||
"safe-buffer": "5.2.1",
|
||||
"top-sites": "1.1.194"
|
||||
},
|
||||
"files": [
|
||||
"HISTORY.md",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"SECURITY.md",
|
||||
"index.js"
|
||||
],
|
||||
"main": "index.js",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
},
|
||||
"scripts": {
|
||||
"bench": "node benchmark/index.js",
|
||||
"lint": "eslint .",
|
||||
"test": "mocha --reporter spec --bail --check-leaks test/",
|
||||
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
||||
"test-cov": "nyc --reporter=html --reporter=text npm test",
|
||||
"update-bench": "node scripts/update-benchmark.js"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014-2017 TJ Holowaychuk <tj@vision-media.ca>
|
||||
Copyright (c) 2018-2021 Josh Junon
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the 'Software'), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue