Compare commits
217 Commits
2c43538db3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e967ff92b9 | |||
| dd2f2a610c | |||
| 9d7b75bdc2 | |||
| 3435f4fe68 | |||
|
|
ca66b5023e | ||
| 7f4bfff8cb | |||
|
|
2dc118dc19 | ||
|
|
33fc3abdaa | ||
|
|
331cad8cbc | ||
|
|
6dd7867ba6 | ||
|
|
9797fc0be4 | ||
|
|
3ea17a6784 | ||
|
|
3282491415 | ||
|
|
2bcf04a027 | ||
|
|
78077aa494 | ||
|
|
78bb429d7c | ||
| 559a52d868 | |||
| 036bd2ef3d | |||
|
|
52ccf50eaa | ||
|
|
aa480e8faa | ||
|
|
7c2bb74b4c | ||
|
|
74b9c0ad7e | ||
|
|
a5ee8e05a0 | ||
| 43dc15e135 | |||
|
|
94ff4ea726 | ||
|
|
dce714e778 | ||
| 984fa214c2 | |||
| 09a2138c6c | |||
|
|
35c130f197 | ||
|
|
669a1a42a2 | ||
|
|
939922cae4 | ||
|
|
405265435d | ||
| 20ee3fc76f | |||
|
|
dcd6f3091b | ||
|
|
fb10a197dd | ||
| 2c84bc0908 | |||
|
|
e04936c637 | ||
|
|
9ce5300741 | ||
|
|
0d2ffee841 | ||
|
|
21274ba1e0 | ||
|
|
5e83f10784 | ||
|
|
a6cc91af3d | ||
| 6f5490ed4d | |||
|
|
a311c811d7 | ||
|
|
9030469fe7 | ||
|
|
5dae4a20d3 | ||
| f3f2f30da9 | |||
| caf3e886a7 | |||
| e803d3b16a | |||
| 6d7f95fc71 | |||
| 39be1a2d27 | |||
| 264f7f3c48 | |||
| ca56389a1e | |||
| d3c441eec6 | |||
| de869de3a7 | |||
| d685fb7ff7 | |||
|
|
0fa6ff9fba | ||
| 513c305971 | |||
|
|
bec28b74ab | ||
|
|
031314d662 | ||
| ed30369d3b | |||
| 56a2ad3605 | |||
| 05ee5b46fc | |||
| b751cb2911 | |||
|
|
0ecb80d45a | ||
|
|
4318aae0e6 | ||
| da5557c26a | |||
| 7ae80c1cdd | |||
| 7050c67b2d | |||
|
|
7f9a60762d | ||
|
|
8d5b29ff42 | ||
| 2dd8ac6d5c | |||
| 950404dd85 | |||
| d570eec221 | |||
| cf73bd0889 | |||
| e29bf39263 | |||
|
|
bb5e92ed55 | ||
|
|
d00b67442b | ||
| fa1335e2dc | |||
|
|
9cfe6b2817 | ||
|
|
85c51e7355 | ||
| ff90e3d8db | |||
| 8e2d14919c | |||
|
|
db91734106 | ||
| 5f9de85b44 | |||
| 533637ca51 | |||
|
|
943ab1d17f | ||
| c7d4dbeea6 | |||
|
|
907ac9da0e | ||
| 3cb001edac | |||
| db92f48ecd | |||
| b6625e371f | |||
|
|
bce7427fee | ||
|
|
5055e8ae47 | ||
|
|
e066250601 | ||
|
|
361e70fcec | ||
|
|
49eaa67627 | ||
|
|
c8d43ee283 | ||
|
|
5b522a4bc8 | ||
|
|
5e7c98cd07 | ||
| e3a39decda | |||
|
|
1ecf990b9c | ||
| 47bf8f00ae | |||
|
|
71e15de201 | ||
| 3d3756f3a7 | |||
|
|
273d37f983 | ||
|
|
e305afeb29 | ||
|
|
43794fb6e1 | ||
|
|
17d95b2d21 | ||
|
|
11be1ddfbc | ||
|
|
adcce89f42 | ||
|
|
7e15af236a | ||
| dd97a76757 | |||
|
|
1d95fbc416 | ||
|
|
00270c3fb6 | ||
|
|
f6fc222422 | ||
|
|
38339466c1 | ||
|
|
a5aaad5201 | ||
|
|
0de3a8faf4 | ||
| 71190b3b3f | |||
| 9ba90fdd12 | |||
| a98c101dbb | |||
|
|
f0c783f108 | ||
|
|
e98f108784 | ||
|
|
5fc581a180 | ||
|
|
cb5b3f7835 | ||
|
|
6dc4dcfb14 | ||
|
|
a88ee998b5 | ||
|
|
cd59b7c1f3 | ||
|
|
de64e0a243 | ||
|
|
97f388b4c3 | ||
|
|
394db63d2a | ||
|
|
085afd3476 | ||
|
|
d16a160e5e | ||
|
|
22be76fb09 | ||
|
|
21c10cd4b6 | ||
| 07484f8815 | |||
|
|
cd2f62c89d | ||
|
|
afb023a4fc | ||
|
|
365bbbb89e | ||
|
|
40497940f3 | ||
|
|
5edc9da769 | ||
|
|
1af9ad5d86 | ||
|
|
436c1ec65a | ||
|
|
bae3e961b1 | ||
|
|
6111b67a2d | ||
|
|
5255e96df7 | ||
|
|
51ea6e68cc | ||
|
|
35a64ff7bf | ||
|
|
997e7937b6 | ||
| 5ae5a9b613 | |||
|
|
416febcebc | ||
|
|
32aa41f59f | ||
| 0bc89926be | |||
|
|
7a4959ea0b | ||
|
|
cea7f7c466 | ||
|
|
1a32dceadd | ||
|
|
ee6d9578cc | ||
|
|
ef28cc6c71 | ||
|
|
5622b3335d | ||
|
|
d91f23c39e | ||
|
|
9fd4f30016 | ||
| abae0d373e | |||
|
|
8fd1ba672e | ||
|
|
8ddaf975f7 | ||
|
|
256f5c3994 | ||
|
|
0027c68087 | ||
|
|
3dabcad617 | ||
| ca1e3f2952 | |||
|
|
ac939341a0 | ||
|
|
2957054823 | ||
| 32f2686f14 | |||
| 254beb897a | |||
| 30a2d71536 | |||
| a28f7fbff3 | |||
| bc4dbf6a2f | |||
| 1e3fc71974 | |||
|
|
e9b05d2000 | ||
|
|
50390eea88 | ||
| 205641aad2 | |||
|
|
b5c8d88113 | ||
| 6fd6750d8d | |||
| 40654fd5be | |||
|
|
258785052f | ||
|
|
242dc66983 | ||
|
|
a4d5e0c227 | ||
|
|
56fd503642 | ||
| 4f1ec58a99 | |||
| f4430b001a | |||
| 3c2c80d5ee | |||
| d7df07dbc2 | |||
| 0bcc340043 | |||
| 49829d9d2e | |||
| 97b879433d | |||
| 4066217862 | |||
| 1eccd8d424 | |||
| 6c7e008164 | |||
| faa18be61d | |||
| 918a53529c | |||
| 6e45cf2226 | |||
| 2a844d275d | |||
| 30f85bf602 | |||
| 3d1bf3286a | |||
| 4690197ca4 | |||
| 0119a77cb7 | |||
| 1198bf1ba8 | |||
| 944f4a1567 | |||
| f5053ab5c1 | |||
| b6d7759b30 | |||
|
|
98f6105373 | ||
|
|
2441fb9066 | ||
|
|
2538aafc5c | ||
|
|
3fdba1fe89 | ||
|
|
80b8886762 | ||
|
|
a394c25245 | ||
|
|
b2b7a38f0e | ||
|
|
fe014b677b |
@@ -14,10 +14,6 @@ jobs:
|
||||
- name: Clonar el repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Agregar remoto de GitHub
|
||||
run: |
|
||||
git remote add github https://josedario87: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@github.com/josedario87/planilla.git
|
||||
|
||||
- name: Forzar limpieza y retry
|
||||
run: |
|
||||
git remote remove github
|
||||
@@ -25,8 +21,3 @@ jobs:
|
||||
git fetch github
|
||||
git push github +HEAD:main --force
|
||||
|
||||
|
||||
- name: Forzar subida a GitHub (sobrescribe lo que haya)
|
||||
run: |
|
||||
git fetch github
|
||||
git push github +main
|
||||
38
.gitignore
vendored
38
.gitignore
vendored
@@ -1,3 +1,37 @@
|
||||
node_modules
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.github
|
||||
.env.*
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
dist-ssr/
|
||||
build/
|
||||
coverage/
|
||||
|
||||
# Misc
|
||||
*.local
|
||||
.DS_Store
|
||||
|
||||
# Editor directories and files
|
||||
|
||||
.idea/
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# GitHub configuration
|
||||
.github
|
||||
|
||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"task.allowAutomaticTasks": "on",
|
||||
"task.autoDetect": "on",
|
||||
"task.quickOpen.showAll": true
|
||||
}
|
||||
60
.vscode/tasks.json
vendored
Normal file
60
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "planilla-api",
|
||||
"type": "shell",
|
||||
"command": "make api",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"panel": "dedicated",
|
||||
"reveal": "always"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "planilla-mcp",
|
||||
"type": "shell",
|
||||
"command": "make mcp",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"panel": "dedicated",
|
||||
"reveal": "always"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "planilla-ui",
|
||||
"type": "shell",
|
||||
"command": "make UI",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"panel": "dedicated",
|
||||
"reveal": "always"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "planilla-agent",
|
||||
"type": "shell",
|
||||
"command": "make agent",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"panel": "dedicated",
|
||||
"reveal": "always"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "planilla-dev",
|
||||
"dependsOn": [
|
||||
"planilla-api",
|
||||
"planilla-mcp",
|
||||
"planilla-ui",
|
||||
"planilla-agent"
|
||||
],
|
||||
"dependsOrder": "parallel",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
57
Makefile
57
Makefile
@@ -21,28 +21,55 @@ down:
|
||||
todo: estructura build
|
||||
|
||||
sync-to-github:
|
||||
@echo "Synchronizing to GitHub..."
|
||||
@if ! git remote | grep -q '^github$$'; then \
|
||||
echo "Adding GitHub remote..."; \
|
||||
git remote add github $(GITHUB_REPO_URL); \
|
||||
fi
|
||||
@echo Synchronizing to GitHub...
|
||||
@if not exist ".git\refs\remotes\github" ( \
|
||||
echo Adding GitHub remote... && \
|
||||
git remote add github $(GITHUB_REPO_URL) \
|
||||
)
|
||||
git push github --all
|
||||
git push github --tags
|
||||
@echo "Synchronization to GitHub complete."
|
||||
@echo Synchronization to GitHub complete.
|
||||
|
||||
|
||||
sync-to-gitea:
|
||||
@echo "Synchronizing to Gitea..."
|
||||
@if ! git remote | grep -q '^gitea$$'; then \
|
||||
echo "Adding Gitea remote..."; \
|
||||
git remote add gitea $(GITEA_REPO_URL); \
|
||||
fi
|
||||
git push gitea --all
|
||||
git push gitea --tags
|
||||
@echo "Synchronization to Gitea complete."
|
||||
|
||||
git fetch github
|
||||
git merge github/main
|
||||
|
||||
# Declaramos el target como PHONY ya que no corresponde a un archivo real (opcional pero recomendado)
|
||||
|
||||
.PHONY: UI
|
||||
.PHONY: UI agent mcp api
|
||||
UI:
|
||||
cd ui && ( if not exist node_modules npm install ) && npm run dev
|
||||
|
||||
agent:
|
||||
cd agent && npm install && npm run dev
|
||||
|
||||
mcp:
|
||||
cd mcp && npm install && npm run dev
|
||||
|
||||
api:
|
||||
cd api && npm install && npm run dev
|
||||
|
||||
# Creates a new Prisma migration in development mode.
|
||||
# Pass the migration name as an argument, e.g.:
|
||||
# make prisma-migrate-dev name=my-migration-name
|
||||
# If no name is provided, it defaults to "new_migration".
|
||||
prisma-migrate-dev:
|
||||
cd api && npx prisma migrate deploy
|
||||
|
||||
|
||||
|
||||
# --- Git helper para cambiar de rama por nombre directo ---
|
||||
|
||||
.PHONY: git-branch
|
||||
|
||||
git-branch:
|
||||
@if not "$(BRANCH)"=="" ( \
|
||||
git fetch github && \
|
||||
git checkout $(BRANCH) \
|
||||
) else ( \
|
||||
echo USO: make git-branch BRANCH=nombre/rama && exit /b 1 \
|
||||
)
|
||||
|
||||
|
||||
|
||||
20
README.md
20
README.md
@@ -132,6 +132,26 @@ docker compose down --remove-orphans
|
||||
* Arranca en puerto **80** internamente.
|
||||
* Código fuente en `ui/src/`, configuración en `vite.config.js`.
|
||||
|
||||
### Notificaciones en tiempo real (SSE)
|
||||
|
||||
El backend expone un endpoint `/events` que utiliza **Server-Sent Events**. La
|
||||
base de datos PostgreSQL emite mensajes mediante triggers en las tablas
|
||||
`Planilla`, `Cliente`, `TareaRealizada` y `Asistencia`. Cada cambio se
|
||||
reenvía automáticamente a los navegadores conectados.
|
||||
|
||||
```javascript
|
||||
const source = new EventSource('http://localhost:4000/events');
|
||||
source.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
console.log('Evento recibido', data);
|
||||
};
|
||||
```
|
||||
|
||||
La interfaz incluye una vista **Feed** accesible desde la barra lateral. Allí se muestran en tiempo real las tarjetas de cada módulo conforme llegan estos eventos. Las actualizaciones exhiben la tarjeta anterior y la nueva con una flecha que indica el cambio.
|
||||
|
||||
#### Card Animation
|
||||
The data cards implemented in `ui/src/components/ui/NucleoDataCard.vue` now feature a subtle growing animation when hovered over. This animation is implemented purely with CSS using keyframes and transitions defined within the component's `<style scoped>` section, ensuring the styles are encapsulated and don't affect other elements.
|
||||
|
||||
---
|
||||
|
||||
## 📦 CI/CD (`.gitea/workflows/build.yml`)
|
||||
|
||||
2
agent/.dockerignore
Normal file
2
agent/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
2
agent/.gitignore
vendored
2
agent/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
.env
|
||||
@@ -1,28 +1,17 @@
|
||||
# Dockerfile con acceso a docker CLI
|
||||
FROM node:23-slim
|
||||
|
||||
# ---------- Build stage ----------
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# 1) Instalar dependencias normales
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm install --omit=dev
|
||||
|
||||
# 2) Instalar Docker CLI
|
||||
RUN apt-get update && \
|
||||
apt-get install -y ca-certificates curl gnupg && \
|
||||
install -m 0755 -d /etc/apt/keyrings && \
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \
|
||||
chmod a+r /etc/apt/keyrings/docker.gpg && \
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable" \
|
||||
> /etc/apt/sources.list.d/docker.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y docker-ce-cli && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 3) Copiar el código fuente
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# 4) Puerto y arranque
|
||||
ENV PORT=4000
|
||||
EXPOSE 4000
|
||||
CMD ["node", "index.js"]
|
||||
# ---------- Production stage ----------
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY package*.json ./
|
||||
RUN npm install --production
|
||||
ENV PORT=8001
|
||||
EXPOSE 8001
|
||||
CMD ["node", "dist/index.js"]
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
/* ⚙️ Variables de entorno */
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
|
||||
export const config = {
|
||||
VERSION : '0.6.12',
|
||||
API_URL : process.env.BOT_API_URL ?? 'http://whatsapp-bot:8002',
|
||||
GROUP_ID : process.env.GROUP_ID ?? '120363203056794284@g.us',
|
||||
REPLY_MSG : process.env.REPLY_MSG ?? 'que pedos',
|
||||
PORT : +(process.env.PORT ?? 4000),
|
||||
LOG_LEVEL : process.env.LOG_LEVEL ?? 'debug',
|
||||
RETRY_MS : +(process.env.RETRY_MS ?? 5_000),
|
||||
MAX_ATTEMPTS : +(process.env.MAX_ATTEMPTS ?? 60),
|
||||
GEMINI_KEY : process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY ?? '',
|
||||
GEMINI_MODEL_ID: process.env.GEMINI_MODEL_ID ?? 'gemini-2.5-flash-preview-04-17',
|
||||
};
|
||||
@@ -1,195 +0,0 @@
|
||||
{
|
||||
"id": "true_50496210031@c.us_3F4F5E90B644956AC938",
|
||||
"viewed": false,
|
||||
"body": "amor",
|
||||
"type": "chat",
|
||||
"t": 1745718602,
|
||||
"notifyName": "📀🧮📡🌐",
|
||||
"from": "50498554225@c.us",
|
||||
"to": "50496210031@c.us",
|
||||
"author": "50498554225:77@c.us",
|
||||
"invis": false,
|
||||
"isNewMsg": true,
|
||||
"star": false,
|
||||
"kicNotified": false,
|
||||
"recvFresh": true,
|
||||
"isFromTemplate": false,
|
||||
"thumbnail": "",
|
||||
"pollInvalidated": false,
|
||||
"isSentCagPollCreation": false,
|
||||
"latestEditMsgKey": null,
|
||||
"latestEditSenderTimestampMs": null,
|
||||
"mentionedJidList": [],
|
||||
"groupMentions": [],
|
||||
"isEventCanceled": false,
|
||||
"eventInvalidated": false,
|
||||
"isVcardOverMmsDocument": false,
|
||||
"labels": [],
|
||||
"hasReaction": false,
|
||||
"ephemeralDuration": 0,
|
||||
"ephemeralSettingTimestamp": 0,
|
||||
"disappearingModeInitiator": "chat",
|
||||
"disappearingModeTrigger": "chat_settings",
|
||||
"viewMode": "VISIBLE",
|
||||
"productHeaderImageRejected": false,
|
||||
"lastPlaybackProgress": 0,
|
||||
"isDynamicReplyButtonsMsg": false,
|
||||
"isCarouselCard": false,
|
||||
"parentMsgId": null,
|
||||
"callSilenceReason": null,
|
||||
"isVideoCall": false,
|
||||
"callDuration": null,
|
||||
"callParticipants": null,
|
||||
"isMdHistoryMsg": false,
|
||||
"stickerSentTs": 0,
|
||||
"isAvatar": false,
|
||||
"lastUpdateFromServerTs": 0,
|
||||
"invokedBotWid": null,
|
||||
"bizBotType": null,
|
||||
"botResponseTargetId": null,
|
||||
"botPluginType": null,
|
||||
"botPluginReferenceIndex": null,
|
||||
"botPluginSearchProvider": null,
|
||||
"botPluginSearchUrl": null,
|
||||
"botPluginSearchQuery": null,
|
||||
"botPluginMaybeParent": false,
|
||||
"botReelPluginThumbnailCdnUrl": null,
|
||||
"botMessageDisclaimerText": null,
|
||||
"botMsgBodyType": null,
|
||||
"reportingTokenInfo": null,
|
||||
"requiresDirectConnection": false,
|
||||
"bizContentPlaceholderType": null,
|
||||
"hostedBizEncStateMismatch": false,
|
||||
"senderOrRecipientAccountTypeHosted": false,
|
||||
"placeholderCreatedWhenAccountIsHosted": false,
|
||||
"device": 77,
|
||||
"local": false,
|
||||
"fromMe": true,
|
||||
"mId": "3F4F5E90B644956AC938",
|
||||
"sender": {
|
||||
"id": "50498554225@c.us",
|
||||
"name": "📀🧮📡🌐jose",
|
||||
"shortName": "📀🧮📡🌐jose",
|
||||
"pushname": "📀🧮📡🌐",
|
||||
"type": "in",
|
||||
"isBusiness": false,
|
||||
"isEnterprise": false,
|
||||
"isSmb": false,
|
||||
"isContactSyncCompleted": 1,
|
||||
"disappearingModeDuration": 0,
|
||||
"disappearingModeSettingTimestamp": 1700599275,
|
||||
"textStatusLastUpdateTime": -1,
|
||||
"syncToAddressbook": true,
|
||||
"formattedName": "Tú",
|
||||
"isMe": true,
|
||||
"isMyContact": true,
|
||||
"isPSA": false,
|
||||
"isUser": true,
|
||||
"status": "Can't talk, WhatsApp only",
|
||||
"isVerified": false,
|
||||
"isWAContact": true,
|
||||
"profilePicThumbObj": {
|
||||
"eurl": "https://pps.whatsapp.net/v/t61.24694-24/471428085_1635189164083925_3546014480456031647_n.jpg?ccb=11-4&oh=01_Q5Aa1QEqY8vxL1FGF3t2s1OQw0t3tPQ8cS66RZvtzTy6nE1VWQ&oe=6817A633&_nc_sid=5e03e0&_nc_cat=106",
|
||||
"id": "50498554225@c.us",
|
||||
"img": "https://media-mia3-1.cdn.whatsapp.net/v/t61.24694-24/471428085_1635189164083925_3546014480456031647_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa1QELed0umu8TOLHLhNq8lHmkZ2srD3fu-IK3spzsNxkLug&oe=6817A633&_nc_sid=5e03e0&_nc_cat=106",
|
||||
"imgFull": "https://media-mia3-1.cdn.whatsapp.net/v/t61.24694-24/471428085_1635189164083925_3546014480456031647_n.jpg?ccb=11-4&oh=01_Q5Aa1QEqY8vxL1FGF3t2s1OQw0t3tPQ8cS66RZvtzTy6nE1VWQ&oe=6817A633&_nc_sid=5e03e0&_nc_cat=106",
|
||||
"tag": "1735668406"
|
||||
},
|
||||
"msgs": null
|
||||
},
|
||||
"senderId": null,
|
||||
"timestamp": 1745718602,
|
||||
"content": "amor",
|
||||
"isGroupMsg": false,
|
||||
"isQuotedMsgAvailable": true,
|
||||
"isMedia": false,
|
||||
"chat": {
|
||||
"id": "50496210031@c.us",
|
||||
"pendingMsgs": false,
|
||||
"lastReceivedKey": {
|
||||
"fromMe": true,
|
||||
"remote": "50496210031@c.us",
|
||||
"id": "3F009E582691FEE944F0",
|
||||
"_serialized": "true_50496210031@c.us_3F009E582691FEE944F0"
|
||||
},
|
||||
"t": 1745717805,
|
||||
"unreadCount": 0,
|
||||
"unreadDividerOffset": 0,
|
||||
"archive": false,
|
||||
"isReadOnly": false,
|
||||
"isLocked": false,
|
||||
"muteExpiration": 0,
|
||||
"isAutoMuted": false,
|
||||
"name": "Margie (:",
|
||||
"notSpam": true,
|
||||
"pin": 1695127572481,
|
||||
"ephemeralDuration": 0,
|
||||
"ephemeralSettingTimestamp": 0,
|
||||
"disappearingModeInitiator": "chat",
|
||||
"disappearingModeTrigger": "chat_settings",
|
||||
"createdLocally": false,
|
||||
"unreadMentionsOfMe": [],
|
||||
"unreadMentionCount": 0,
|
||||
"hasUnreadMention": false,
|
||||
"archiveAtMentionViewedInDrawer": false,
|
||||
"hasChatBeenOpened": false,
|
||||
"tcToken": {},
|
||||
"tcTokenTimestamp": 1745616969,
|
||||
"tcTokenSenderTimestamp": 1744944499,
|
||||
"endOfHistoryTransferType": 0,
|
||||
"pendingInitialLoading": false,
|
||||
"chatlistPreview": {
|
||||
"type": "reaction",
|
||||
"msgKey": "false_50496210031@c.us_3FEA807956686BD2AD73",
|
||||
"parentMsgKey": "true_50496210031@c.us_3F93865C2A9E8061A668",
|
||||
"reactionText": "🙏",
|
||||
"sender": "50496210031@c.us",
|
||||
"timestamp": 1745717107750
|
||||
},
|
||||
"unreadEditTimestampMs": 1745508257945,
|
||||
"celebrationAnimationLastPlayed": 0,
|
||||
"hasRequestedWelcomeMsg": false,
|
||||
"msgs": null,
|
||||
"canSend": true,
|
||||
"isGroup": false,
|
||||
"pic": "https://pps.whatsapp.net/v/t61.24694-24/470810943_1065895391975207_6852834404866940192_n.jpg?ccb=11-4&oh=01_Q5Aa1QEWA1-AVsmMc5-23KYTOSB9RsYUB41vONjdzNZCen_qGw&oe=6817C3B1&_nc_sid=5e03e0&_nc_cat=109",
|
||||
"formattedTitle": "Margie (:",
|
||||
"contact": {
|
||||
"id": "50496210031@c.us",
|
||||
"name": "Margie (:",
|
||||
"shortName": "Margie",
|
||||
"pushname": "Margie Elizabeth:)",
|
||||
"type": "in",
|
||||
"isBusiness": false,
|
||||
"isEnterprise": false,
|
||||
"isSmb": false,
|
||||
"isContactSyncCompleted": 1,
|
||||
"disappearingModeDuration": 0,
|
||||
"disappearingModeSettingTimestamp": 1671409557,
|
||||
"textStatusLastUpdateTime": -1,
|
||||
"syncToAddressbook": true,
|
||||
"formattedName": "Margie (:",
|
||||
"isMe": false,
|
||||
"isMyContact": true,
|
||||
"isPSA": false,
|
||||
"isUser": true,
|
||||
"isVerified": false,
|
||||
"isWAContact": true,
|
||||
"profilePicThumbObj": {
|
||||
"eurl": "https://pps.whatsapp.net/v/t61.24694-24/470810943_1065895391975207_6852834404866940192_n.jpg?ccb=11-4&oh=01_Q5Aa1QEWA1-AVsmMc5-23KYTOSB9RsYUB41vONjdzNZCen_qGw&oe=6817C3B1&_nc_sid=5e03e0&_nc_cat=109"
|
||||
},
|
||||
"msgs": null
|
||||
},
|
||||
"groupMetadata": null,
|
||||
"presence": {
|
||||
"id": "50496210031@c.us",
|
||||
"chatstates": []
|
||||
},
|
||||
"isOnline": false,
|
||||
"participantsCount": 1
|
||||
},
|
||||
"isOnline": false,
|
||||
"chatId": "50496210031@c.us",
|
||||
"mediaData": {},
|
||||
"text": "amor"
|
||||
}
|
||||
120
agent/gemini.js
120
agent/gemini.js
@@ -1,120 +0,0 @@
|
||||
// gemini.js – integración Grounding con Google Search + logs de verificación
|
||||
import genai from '@google/genai';
|
||||
import { config } from './config.js';
|
||||
import { log } from './logger.js';
|
||||
|
||||
const { GoogleGenAI, createUserContent, createPartFromUri } = genai;
|
||||
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
/* 📜 System prompt */
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
const SYSTEM_PROMPT = `
|
||||
#########
|
||||
Tu nombre es nucleo
|
||||
##########
|
||||
RESPUESTAS CORTAS y CONCISAS
|
||||
sos la inteligencia artificial de Servidores de G.O.D (nuestro J.A.R.V.I.S.).
|
||||
tenés derecho a sostener opiniones firmes con lógica, sin corrección política excesiva.
|
||||
Respuestas casuales breves; si te extendés, hacelo en un solo mensaje.
|
||||
Podés continuar conversaciones, usar chistes, analogías, etc.
|
||||
`.trim();
|
||||
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
/* 🧠 Inicializar Gemini */
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
let ai = null;
|
||||
function initGemini() {
|
||||
if (!config.GEMINI_KEY) throw new Error('🔑 GEMINI_API_KEY no configurada');
|
||||
if (!ai) {
|
||||
ai = new GoogleGenAI({ apiKey: config.GEMINI_KEY });
|
||||
log('info', `🧠 Gemini SDK inicializado (${config.GEMINI_MODEL_ID})`);
|
||||
}
|
||||
return ai;
|
||||
}
|
||||
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
/* 🔍 Construir tools de búsqueda */
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
function buildSearchTools() {
|
||||
const model = config.GEMINI_MODEL_ID;
|
||||
// Los objetos literales cumplen con el esquema Tool del SDK.
|
||||
if (/^gemini-2\./.test(model) || /^gemini-2\.5/.test(model)) {
|
||||
return [{ google_search: {} }]; // Search‑as‑a‑tool
|
||||
}
|
||||
if (/^gemini-1\.5/.test(model)) {
|
||||
return [{
|
||||
google_search_retrieval: {
|
||||
dynamic_retrieval_config: {
|
||||
mode: 'MODE_DYNAMIC',
|
||||
dynamic_threshold: 0.3,
|
||||
},
|
||||
},
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
/* 🚀 askGemini */
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
export async function askGemini(historial, files = {}) {
|
||||
try {
|
||||
const client = initGemini();
|
||||
|
||||
// 1️⃣ Construir "contents"
|
||||
let contents;
|
||||
if (typeof historial === 'string') {
|
||||
contents = historial;
|
||||
} else if (Array.isArray(historial)) {
|
||||
const parts = [];
|
||||
for (const m of historial) {
|
||||
if (m.type === 'document') continue;
|
||||
if (m.type === 'chat') {
|
||||
parts.push(`${m.senderName}: ${m.text} -- ${m.date}`);
|
||||
continue;
|
||||
}
|
||||
const up = files[m.msgId?.toLowerCase?.()];
|
||||
if (up?.uri) {
|
||||
parts.push(
|
||||
createPartFromUri(up.uri, up.mimeType),
|
||||
`archivo ${m.type} de ${m.senderName}: ${m.caption || m.text} -- ${m.date}`
|
||||
);
|
||||
}
|
||||
}
|
||||
contents = createUserContent(parts);
|
||||
} else {
|
||||
throw new Error('Formato de historial no soportado');
|
||||
}
|
||||
|
||||
// 2️⃣ Herramientas
|
||||
const tools = buildSearchTools();
|
||||
|
||||
// 3️⃣ Llamar al modelo
|
||||
const response = await client.models.generateContent({
|
||||
model: config.GEMINI_MODEL_ID,
|
||||
contents,
|
||||
config: {
|
||||
systemInstruction: SYSTEM_PROMPT,
|
||||
maxOutputTokens: 4096,
|
||||
temperature: 0.3, // menor → mayor factualidad
|
||||
tools,
|
||||
response_modalities: ['TEXT'],
|
||||
},
|
||||
});
|
||||
|
||||
// 4️⃣ Log de grounding
|
||||
const candidate = response?.candidates?.[0];
|
||||
if (candidate?.groundingMetadata) {
|
||||
log('info', '🔗 GroundingMetadata presente:', JSON.stringify(candidate.groundingMetadata.webSearchQueries));
|
||||
} else {
|
||||
log('warn', 'ℹ️ Sin groundingMetadata en la respuesta');
|
||||
}
|
||||
|
||||
if (!candidate) return '⚠️ Sin candidato.';
|
||||
return candidate.content.parts.map(p => p.text).join('').trim() || '⚠️ Respuesta vacía.';
|
||||
} catch (e) {
|
||||
log('error', 'Gemini falló:', e.message);
|
||||
if (e.response?.status === 429) return '🚦 Límite alcanzado. Probá más tarde.';
|
||||
return '⚠️ No se pudo obtener respuesta de Gemini.';
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
// handlers.js
|
||||
import { respuestaMCP } from './respuestas/respuestaMCP.js'; // <- NUEVA IMPORTACIÓN
|
||||
import fs from 'fs/promises';
|
||||
import { log } from './logger.js';
|
||||
// Ya no se necesitan: sendText, fetchChatMessages, setTypingStatus, askGemini aquí
|
||||
import { processMessage } from './utils/processMessage.js';
|
||||
import { saveMedia } from './utils/saveMedia.js';
|
||||
// import { respuestaNormal } from './respuestas/respuestaNormal.js'; // <- NUEVA IMPORTACIÓN
|
||||
import { respuestaBrave } from './respuestas/respuestaBrave.js'; // <- NUEVA IMPORTACIÓN
|
||||
import { sendText } from './whatsapp.js'; // <- NUEVA IMPORTACIÓN
|
||||
|
||||
// Mock Data for Employees
|
||||
const mockEmployees = [
|
||||
{
|
||||
id: '1', // Ensure ID is string if components expect string
|
||||
name: 'Ana García Mock',
|
||||
cedula: 123456789, // Ensure cedula is number
|
||||
avatar_url: 'https://randomuser.me/api/portraits/women/60.jpg',
|
||||
telefono: '0991234567',
|
||||
ubicacion: 'Oficina Mock Central',
|
||||
idciat: 'AG001M',
|
||||
grupo_estudio: 'Desarrollo Frontend Mock',
|
||||
empleado: true,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Carlos Rodriguez Mock',
|
||||
cedula: 987654321,
|
||||
avatar_url: 'https://randomuser.me/api/portraits/men/45.jpg',
|
||||
telefono: '0987654321',
|
||||
ubicacion: 'Sucursal Mock Norte',
|
||||
idciat: 'CR002M',
|
||||
grupo_estudio: 'Backend Services Mock',
|
||||
empleado: true,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Luisa Martinez Mock',
|
||||
cedula: 112233445,
|
||||
avatar_url: 'https://randomuser.me/api/portraits/women/61.jpg',
|
||||
telefono: '0976543210',
|
||||
ubicacion: 'Remoto Mock',
|
||||
idciat: 'LM003M',
|
||||
grupo_estudio: 'QA Mock',
|
||||
empleado: true,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'Jorge Herrera Mock',
|
||||
cedula: 223344556,
|
||||
avatar_url: 'https://randomuser.me/api/portraits/men/50.jpg',
|
||||
telefono: '0965432109',
|
||||
ubicacion: 'Oficina Mock Sur',
|
||||
idciat: 'JH004M',
|
||||
grupo_estudio: 'DevOps Mock',
|
||||
empleado: true,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'Patricia Fernández Mock',
|
||||
cedula: 334455667,
|
||||
avatar_url: 'https://randomuser.me/api/portraits/women/62.jpg',
|
||||
telefono: '0954321098',
|
||||
ubicacion: 'Oficina Mock Central',
|
||||
idciat: 'PF005M',
|
||||
grupo_estudio: 'Diseño UX/UI Mock',
|
||||
empleado: true,
|
||||
}
|
||||
];
|
||||
|
||||
/* carpeta raíz donde saveMedia deja todo */
|
||||
const MEDIA_DIR = '/media';
|
||||
await fs.mkdir(MEDIA_DIR, { recursive: true });
|
||||
|
||||
// La función cleanForGemini se movió a respuestaNormal.js
|
||||
|
||||
export async function processIncoming(raw) {
|
||||
// Guarda media si no es un mensaje de texto plano
|
||||
if (raw.type !== 'chat') {
|
||||
try {
|
||||
// Nota: saveMedia podría necesitar acceso a MEDIA_DIR si no está codificado internamente
|
||||
await saveMedia(raw /*, MEDIA_DIR */); // Podrías necesitar pasar MEDIA_DIR si saveMedia lo requiere
|
||||
} catch (error) {
|
||||
log('error', 'Error guardando media:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const msg = processMessage(raw);
|
||||
const text = msg.text || '';
|
||||
|
||||
// Logica para componentes UI de Empleados
|
||||
if (/^Quiero crear un nuevo @empleado/i.test(text)) {
|
||||
log('info', `Comando recibido: Crear nuevo empleado. Enviando componente EmpleadoForm.`);
|
||||
sendText(msg.chatId, 'CHAT_UI_COMPONENT::EmpleadoForm');
|
||||
return; // Termina el procesamiento para este comando
|
||||
}
|
||||
|
||||
const verEmpleadoMatch = text.match(/^Ver @empleado(\d+)/i);
|
||||
if (verEmpleadoMatch && verEmpleadoMatch[1]) {
|
||||
const cedula = parseInt(verEmpleadoMatch[1], 10);
|
||||
log('info', `Comando recibido: Ver empleado con cédula ${cedula}.`);
|
||||
const employee = mockEmployees.find(emp => emp.cedula === cedula);
|
||||
if (employee) {
|
||||
log('info', `Empleado encontrado: ${employee.name}. Enviando componente cardEmpleado.`);
|
||||
// La cédula se pasa como parámetro para que el frontend la use si es necesario para buscar o mostrar.
|
||||
sendText(msg.chatId, `CHAT_UI_COMPONENT::cardEmpleado::${cedula}`);
|
||||
} else {
|
||||
log('warn', `Empleado con cédula ${cedula} no encontrado.`);
|
||||
sendText(msg.chatId, `No se encontró un empleado con la cédula ${cedula}.`);
|
||||
}
|
||||
return; // Termina el procesamiento para este comando
|
||||
}
|
||||
|
||||
const mostrarEmpleadosMatch = text.match(/^Mostrame los primeros (\d+) @empleados/i);
|
||||
if (mostrarEmpleadosMatch && mostrarEmpleadosMatch[1]) {
|
||||
const count = parseInt(mostrarEmpleadosMatch[1], 10);
|
||||
log('info', `Comando recibido: Mostrar los primeros ${count} empleados.`);
|
||||
// El count se pasa como parámetro para que el frontend lo use para determinar cuántos mostrar.
|
||||
// La lógica de obtener los X primeros empleados realmente estará en el frontend o en una API.
|
||||
// Aquí solo indicamos el componente y el count deseado.
|
||||
sendText(msg.chatId, `CHAT_UI_COMPONENT::tablaEmpleados::${count}`);
|
||||
return; // Termina el procesamiento para este comando
|
||||
}
|
||||
|
||||
/* ----- comando @nucleo ----- */
|
||||
// Se comenta la condicion original de @nucleo para evitar doble respuesta si no se hace return antes.
|
||||
// if (/^@nucleo(\s|$)/i.test(text)) {
|
||||
// // Llama a la función importada
|
||||
// // await respuestaNormal(msg); // Ya no se usa respuestaNormal aquí directamente.
|
||||
// await respuestaMCP(msg); // respuestaMCP ya no es relevante en este flujo si @nucleo siempre va a brave.
|
||||
// }
|
||||
|
||||
if (/^@nucleo(\s|$)/i.test(text)) { // Modificado para que @nucleo solo dispare respuestaBrave
|
||||
log('info', '🧠 Generando respuesta para @nucleo...');
|
||||
const respuestaObjMCP = await respuestaBrave(msg);
|
||||
log('info', 'Respuesta de @nucleo (Brave):', respuestaObjMCP);
|
||||
sendText(msg.chatId, respuestaObjMCP);
|
||||
} else {
|
||||
// Lógica para otros mensajes si no son comandos de UI ni @nucleo
|
||||
log('debug', 'Mensaje no reconocido como comando UI o @nucleo:', text);
|
||||
// Considerar si se debe enviar una respuesta por defecto o ninguna si no coincide con nada.
|
||||
// Por ahora, no se envía nada si no es un comando específico.
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/* nucleo-bot ― index.js */
|
||||
import express from 'express';
|
||||
import morgan from 'morgan';
|
||||
import { config } from './config.js';
|
||||
import { log } from './logger.js';
|
||||
import { router } from './routes.js';
|
||||
import {
|
||||
waitForGateway,
|
||||
clearWebhooks,
|
||||
registerWebhook
|
||||
} from './whatsapp.js';
|
||||
|
||||
|
||||
|
||||
export let globalMemory = {};
|
||||
|
||||
/* 🌐 Express app */
|
||||
const app = express();
|
||||
app.use(express.json({ limit: '1mb' }));
|
||||
app.use(morgan('[:date[iso]] :method :url :status - :response-time ms'));
|
||||
app.use(router);
|
||||
|
||||
/* 🚀 Bootstrap */
|
||||
async function bootstrap() {
|
||||
await waitForGateway();
|
||||
await clearWebhooks();
|
||||
await registerWebhook();
|
||||
}
|
||||
|
||||
/* 🏁 Arranque */
|
||||
app.listen(config.PORT, () => {
|
||||
log('info', `🪪 Versión del bot: ${config.VERSION}`);
|
||||
log('info', `🚀 nucleo-bot escuchando en :${config.PORT}`);
|
||||
bootstrap().catch(e => log('error', 'Error en bootstrap:', e.message));
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
/* 🖨️ Logger */
|
||||
/*───────────────────────────────────────────────────────────────*/
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc.js';
|
||||
import { config } from './config.js';
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
export function log(level, ...args) {
|
||||
const levels = ['debug', 'info', 'warn', 'error'];
|
||||
if (levels.indexOf(level) >= levels.indexOf(config.LOG_LEVEL)) {
|
||||
console[level === 'debug' ? 'log' : level](
|
||||
`[${dayjs().utc().format()}]`, level.toUpperCase(), ...args
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,24 @@
|
||||
{
|
||||
"name": "nucleo-bot",
|
||||
"version": "0.4.0",
|
||||
"type": "module",
|
||||
"name": "conversation-layer-agent",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start:mcp": "npx @philschmid/weather-mcp"
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "nodemon --watch src --ext ts --exec \"ts-node src/index.ts\""
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.8.4",
|
||||
"dayjs": "^1.11.11",
|
||||
"express": "^4.19.2",
|
||||
"morgan": "^1.10.0",
|
||||
"@google/generative-ai": "^0.4.0",
|
||||
"@google/genai": "^0.9.0",
|
||||
"@open-wa/wa-automate": "^4.34.3",
|
||||
"mime-types": "^2.1.35",
|
||||
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||
"@philschmid/weather-mcp": "^1.0.0"
|
||||
"express": "^4.18.2",
|
||||
"@google/genai": "^1.4.0",
|
||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||
"dotenv": "^16.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.5",
|
||||
"@types/node": "^20.11.19",
|
||||
"@types/express": "^4.17.21",
|
||||
"nodemon": "^3.1.10",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
// /respuesta/respuestaBrave.js
|
||||
import fs from 'fs/promises';
|
||||
import { log } from '../logger.js';
|
||||
import {
|
||||
sendText,
|
||||
fetchChatMessages,
|
||||
setTypingStatus
|
||||
} from '../whatsapp.js';
|
||||
import { processMessage } from '../utils/processMessage.js';
|
||||
import { saveMedia } from '../utils/saveMedia.js';
|
||||
|
||||
import { GoogleGenAI } from '@google/genai';
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||
|
||||
function sanitizeSchema(obj) {
|
||||
if (Array.isArray(obj)) return obj.map(sanitizeSchema);
|
||||
if (obj && typeof obj === 'object') {
|
||||
const out = {};
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
if (k === 'additionalProperties' || k === '$schema') continue;
|
||||
out[k] = sanitizeSchema(v);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
const cleanForGemini = ({ media, reactions, preview, ...rest }) => rest;
|
||||
|
||||
export async function respuestaBrave(msg) {
|
||||
const text = msg.text || '';
|
||||
|
||||
setTypingStatus(msg.chatId, true);
|
||||
log('info', '🧠 Generando respuesta para @nucleo (Brave)…');
|
||||
|
||||
try {
|
||||
const allraw = await fetchChatMessages(msg.chatId);
|
||||
const allMsgs = allraw.map(processMessage);
|
||||
|
||||
const context = allMsgs.map(cleanForGemini);
|
||||
let json = JSON.stringify(context);
|
||||
while (json.length > 100_000 && context.length) {
|
||||
context.shift();
|
||||
json = JSON.stringify(context);
|
||||
}
|
||||
|
||||
const chatName = context.length > 0 ? context[0]?.chatName : 'chat';
|
||||
const prompt = [
|
||||
`Eres un asistente con acceso a Brave Search mediante herramientas externas.`,
|
||||
`Pregunta del usuario: ${text}`,
|
||||
...context
|
||||
];
|
||||
|
||||
const medias = allraw.filter(m => m.type !== 'chat');
|
||||
const settled = await Promise.allSettled(medias.map(saveMedia));
|
||||
|
||||
const MAX = 20 * 1024 * 1024;
|
||||
let total = 0;
|
||||
const files = {};
|
||||
|
||||
for (const r of settled) {
|
||||
if (r.status !== 'fulfilled' || !r.value) continue;
|
||||
const entries = Object.entries(r.value);
|
||||
if (entries.length === 0) continue;
|
||||
const [msgId, obj] = entries[0];
|
||||
let size = Number(obj.sizeBytes || 0);
|
||||
if (!size && obj.filePath) {
|
||||
try { size = (await fs.stat(obj.filePath)).size; } catch { size = 0; }
|
||||
}
|
||||
if (total + size > MAX && total > 0) break;
|
||||
if (size > MAX) continue;
|
||||
|
||||
total += size;
|
||||
files[msgId] = { uri: obj.uri || obj.filePath, mimeType: obj.mimeType };
|
||||
}
|
||||
|
||||
// === MCP Brave Search ===
|
||||
const client = new Client({ name: 'brave-agent', version: '1.0.0' });
|
||||
const serverParams = new StdioClientTransport({
|
||||
command: 'uvx',
|
||||
args: ['mcp-server-fetch']
|
||||
});
|
||||
|
||||
|
||||
await client.connect(serverParams);
|
||||
|
||||
const mcp = await client.listTools();
|
||||
const tools = mcp.tools.map(t => ({
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
parameters: sanitizeSchema({
|
||||
type: 'object',
|
||||
...t.inputSchema
|
||||
})
|
||||
}));
|
||||
|
||||
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
||||
const response = await ai.models.generateContent({
|
||||
model: 'gemini-2.5-flash-preview-04-17',
|
||||
contents: prompt,
|
||||
config: {
|
||||
tools: [{ functionDeclarations: tools }],
|
||||
temperature: 0
|
||||
}
|
||||
});
|
||||
|
||||
let respuesta;
|
||||
if (response.functionCalls && response.functionCalls.length > 0) {
|
||||
const call = response.functionCalls[0];
|
||||
const result = await client.callTool({ name: call.name, arguments: call.args });
|
||||
await client.close();
|
||||
respuesta = result.content?.[0]?.text ?? JSON.stringify(result);
|
||||
} else {
|
||||
await client.close();
|
||||
respuesta = response.text;
|
||||
}
|
||||
|
||||
await sendText(msg.chatId, respuesta);
|
||||
log('info', '🧠 Respuesta enviada');
|
||||
|
||||
} catch (error) {
|
||||
log('error', 'Error en respuestaBrave:', error);
|
||||
await sendText(msg.chatId, 'Hubo un error al procesar tu solicitud.');
|
||||
} finally {
|
||||
setTypingStatus(msg.chatId, false);
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
// /respuesta/respuestaMCP.js
|
||||
import fs from 'fs/promises';
|
||||
import { log } from '../logger.js';
|
||||
import {
|
||||
sendText,
|
||||
fetchChatMessages,
|
||||
setTypingStatus
|
||||
} from '../whatsapp.js';
|
||||
import { processMessage } from '../utils/processMessage.js';
|
||||
import { saveMedia } from '../utils/saveMedia.js';
|
||||
|
||||
import { GoogleGenAI } from '@google/genai';
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||
|
||||
/* limpia el esquema JSON recursivamente */
|
||||
function sanitizeSchema(obj) {
|
||||
if (Array.isArray(obj)) return obj.map(sanitizeSchema);
|
||||
if (obj && typeof obj === 'object') {
|
||||
const out = {};
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
if (k === 'additionalProperties' || k === '$schema') continue;
|
||||
out[k] = sanitizeSchema(v);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
const cleanForGemini = ({ media, reactions, preview, ...rest }) => rest;
|
||||
|
||||
export async function respuestaMCP(msg) {
|
||||
const text = msg.text || '';
|
||||
|
||||
setTypingStatus(msg.chatId, true);
|
||||
log('info', '🧠 Generando respuesta para @nucleo…');
|
||||
|
||||
try {
|
||||
const allraw = await fetchChatMessages(msg.chatId);
|
||||
const allMsgs = allraw.map(processMessage);
|
||||
|
||||
const context = allMsgs.map(cleanForGemini);
|
||||
let json = JSON.stringify(context);
|
||||
while (json.length > 100_000 && context.length) {
|
||||
context.shift();
|
||||
json = JSON.stringify(context);
|
||||
}
|
||||
|
||||
const chatName = context.length > 0 ? context[0]?.chatName : 'chat';
|
||||
const prompt = [
|
||||
`Eres el asistente del grupo "${chatName}". tenes la capacidad de interactuar con las carpetas ubicacadas en el directorio "/media/mcp" y sus subcarpetas.`,
|
||||
`Pregunta del usuario: ${text}`,
|
||||
...context
|
||||
];
|
||||
|
||||
const medias = allraw.filter(m => m.type !== 'chat');
|
||||
const settled = await Promise.allSettled(medias.map(saveMedia));
|
||||
|
||||
const MAX = 20 * 1024 * 1024;
|
||||
let total = 0;
|
||||
const files = {};
|
||||
|
||||
for (const r of settled) {
|
||||
if (r.status !== 'fulfilled' || !r.value) continue;
|
||||
const entries = Object.entries(r.value);
|
||||
if (entries.length === 0) continue;
|
||||
const [msgId, obj] = entries[0];
|
||||
let size = Number(obj.sizeBytes || 0);
|
||||
if (!size && obj.filePath) {
|
||||
try { size = (await fs.stat(obj.filePath)).size; } catch { size = 0; }
|
||||
}
|
||||
if (total + size > MAX && total > 0) break;
|
||||
if (size > MAX) continue;
|
||||
|
||||
total += size;
|
||||
files[msgId] = { uri: obj.uri || obj.filePath, mimeType: obj.mimeType };
|
||||
}
|
||||
|
||||
// === MCP setup ===
|
||||
const client = new Client({ name: 'mcp-agent', version: '1.0.0' });
|
||||
const serverParams = new StdioClientTransport({
|
||||
command: 'npx',
|
||||
args: ['-y', '@modelcontextprotocol/server-filesystem', '/media/mcp']
|
||||
});
|
||||
await client.connect(serverParams);
|
||||
|
||||
const mcp = await client.listTools();
|
||||
const tools = mcp.tools.map(t => ({
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
parameters: sanitizeSchema({
|
||||
type: 'object',
|
||||
...t.inputSchema
|
||||
})
|
||||
}));
|
||||
|
||||
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
||||
const response = await ai.models.generateContent({
|
||||
model: 'gemini-2.5-flash-preview-04-17',
|
||||
contents: prompt,
|
||||
config: {
|
||||
tools: [{ googleSearch: {} }],
|
||||
temperature: 0
|
||||
}
|
||||
});
|
||||
|
||||
let respuesta;
|
||||
if (response.functionCalls && response.functionCalls.length > 0) {
|
||||
const call = response.functionCalls[0];
|
||||
const result = await client.callTool({ name: call.name, arguments: call.args });
|
||||
await client.close();
|
||||
respuesta = result.content?.[0]?.text ?? JSON.stringify(result);
|
||||
} else {
|
||||
await client.close();
|
||||
respuesta = response.text;
|
||||
}
|
||||
|
||||
await sendText(msg.chatId, respuesta);
|
||||
log('info', '🧠 Respuesta enviada');
|
||||
|
||||
} catch (error) {
|
||||
log('error', 'Error en respuestaMCP:', error);
|
||||
await sendText(msg.chatId, 'Hubo un error al procesar tu solicitud.');
|
||||
} finally {
|
||||
setTypingStatus(msg.chatId, false);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
// /respuesta/respuestaNormal.js
|
||||
import fs from 'fs/promises';
|
||||
import { log } from '../logger.js'; // <- Nota el '../'
|
||||
import {
|
||||
sendText,
|
||||
fetchChatMessages,
|
||||
setTypingStatus
|
||||
} from '../whatsapp.js'; // <- Nota el '../'
|
||||
import { askGemini } from '../gemini.js'; // <- Nota el '../'
|
||||
import { processMessage } from '../utils/processMessage.js'; // <- Nota el '../'
|
||||
import { saveMedia } from '../utils/saveMedia.js'; // <- Nota el '../'
|
||||
|
||||
/* Quita campos pesados antes de mandar a Gemini */
|
||||
const cleanForGemini = ({ media, reactions, preview, ...rest }) => rest;
|
||||
|
||||
/**
|
||||
* Procesa la lógica específica para el comando @nucleo.
|
||||
* Obtiene el historial, prepara el contexto, procesa media,
|
||||
* llama a Gemini y envía la respuesta.
|
||||
* @param {object} msg - El objeto de mensaje procesado.
|
||||
*/
|
||||
export async function respuestaNormal(msg) {
|
||||
const text = msg.text || ''; // Necesitamos el texto original aquí también
|
||||
|
||||
setTypingStatus(msg.chatId, true);
|
||||
log('info', '🧠 Generando respuesta para @nucleo…');
|
||||
|
||||
try {
|
||||
/* 1) historial completo del chat */
|
||||
const allraw = await fetchChatMessages(msg.chatId);
|
||||
const allMsgs = allraw.map(processMessage);
|
||||
|
||||
/* 2) recorta contexto a ≤100 kB */
|
||||
const context = allMsgs.map(cleanForGemini);
|
||||
let json = JSON.stringify(context);
|
||||
while (json.length > 100_000 && context.length) {
|
||||
context.shift();
|
||||
json = JSON.stringify(context);
|
||||
}
|
||||
|
||||
// Asegurarse de que el contexto no esté vacío antes de acceder a context[0]
|
||||
const chatName = context.length > 0 ? context[0]?.chatName : 'chat';
|
||||
|
||||
/* 3) prompt */
|
||||
const prompt = [
|
||||
`Eres el asistente del grupo "${chatName}".`,
|
||||
`Pregunta del usuario: ${text}` // Usamos el 'text' del mensaje original que activó el comando
|
||||
, ...context];
|
||||
|
||||
/* 4) procesa medias y respeta el límite de 20 MB */
|
||||
// Filtrar solo los mensajes que no son de tipo 'chat' del historial obtenido
|
||||
const medias = allraw.filter(m => m.type !== 'chat');
|
||||
const settled = await Promise.allSettled(medias.map(saveMedia));
|
||||
|
||||
const MAX = 20 * 1024 * 1024; // 20 MB
|
||||
let total = 0;
|
||||
const files = {};
|
||||
|
||||
for (const r of settled) {
|
||||
if (r.status !== 'fulfilled' || !r.value) continue;
|
||||
|
||||
if (typeof r.value !== 'object' || r.value === null) continue;
|
||||
const entries = Object.entries(r.value);
|
||||
if (entries.length === 0) continue;
|
||||
const [msgId, obj] = entries[0];
|
||||
if (typeof obj !== 'object' || obj === null) continue;
|
||||
|
||||
let size = Number(obj.sizeBytes || 0);
|
||||
|
||||
if (!size && obj.filePath) {
|
||||
try { size = (await fs.stat(obj.filePath)).size; }
|
||||
catch { size = 0; }
|
||||
}
|
||||
|
||||
// Comprobar si añadir este archivo excede el límite MÁXIMO TOTAL
|
||||
// y si ya tenemos *algo* (total > 0) para evitar empezar con un archivo demasiado grande
|
||||
if (total + size > MAX && total > 0) break;
|
||||
// Si este archivo *por sí solo* excede el límite, saltarlo
|
||||
if (size > MAX) continue;
|
||||
|
||||
total += size;
|
||||
files[msgId] = { uri: obj.uri || obj.filePath, mimeType: obj.mimeType };
|
||||
}
|
||||
|
||||
// log('info', '🧠 Enviando a Gemini...', { files });
|
||||
|
||||
/* 5) llama a Gemini y responde */
|
||||
const respuesta = await askGemini(prompt, files);
|
||||
await sendText(msg.chatId, respuesta);
|
||||
|
||||
log('info', '🧠 Respuesta enviada');
|
||||
|
||||
} catch (error) {
|
||||
log('error', 'Error en respuestaNormal:', error);
|
||||
await sendText(msg.chatId, 'Hubo un error al procesar tu solicitud.');
|
||||
|
||||
} finally {
|
||||
setTypingStatus(msg.chatId, false);
|
||||
}
|
||||
}
|
||||
200
agent/routes.js
200
agent/routes.js
@@ -1,200 +0,0 @@
|
||||
// routes.js
|
||||
import express from 'express';
|
||||
import axios from 'axios';
|
||||
import dayjs from 'dayjs';
|
||||
import { config } from './config.js';
|
||||
import { log } from './logger.js';
|
||||
import { sendText } from './whatsapp.js';
|
||||
import { processIncoming } from './handlers.js';
|
||||
import { processMessage } from './utils/processMessage.js';
|
||||
|
||||
// chats a tomar en cuenta para el bot
|
||||
|
||||
const relevantChats = [
|
||||
'50496210031@c.us',
|
||||
'120363203056794284@g.us',
|
||||
'120363398335375917@g.us',
|
||||
'50498554225@c.us',
|
||||
'50496934012@c.us',
|
||||
'50497588328@c.us',
|
||||
'50489701450@c.us'
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
export const router = express.Router();
|
||||
|
||||
// --- Manejo de eventos del webhook ------------------------------------------------
|
||||
router.post('/webhook', async (req, res) => {
|
||||
const { event, data: raw } = req.body;
|
||||
const data = processMessage(raw);
|
||||
|
||||
// Si el evento no es relevante, ignorar
|
||||
if(data.chatId && !relevantChats.includes(data.chatId)) {
|
||||
log('info', `Mensaje de ${data.chatId} ignorado`);
|
||||
return res.sendStatus(200);
|
||||
}
|
||||
// console.log('----------------------------------------------------------------');
|
||||
// log('debug', '↪︎ Mensaje IN →', raw);
|
||||
// console.log('----------------------------------------------------------------');
|
||||
|
||||
// log('debug', `📩 Webhook event "${event}"`);
|
||||
|
||||
switch (event) {
|
||||
case 'onAck':
|
||||
log('info', 'Ack:', data);
|
||||
break;
|
||||
|
||||
case 'onAddedToGroup':
|
||||
log('info', 'Added to group:', data);
|
||||
break;
|
||||
|
||||
case 'onAnyMessage':
|
||||
// log('info', 'onAnyMessage', data);
|
||||
log('info', 'onAnyMessage', raw.chatId);
|
||||
await processIncoming(raw);
|
||||
break;
|
||||
|
||||
case 'onBattery':
|
||||
log('info', 'Battery status:', data);
|
||||
break;
|
||||
|
||||
case 'onBroadcast':
|
||||
log('info', 'Broadcast:', data);
|
||||
break;
|
||||
|
||||
case 'onButton':
|
||||
log('info', 'Button pressed:', data);
|
||||
break;
|
||||
|
||||
case 'onCallState':
|
||||
log('info', 'Call state:', data);
|
||||
break;
|
||||
|
||||
case 'onChatDeleted':
|
||||
log('info', 'Chat deleted:', data);
|
||||
break;
|
||||
|
||||
case 'onChatOpened':
|
||||
log('info', 'Chat opened:', data);
|
||||
break;
|
||||
|
||||
case 'onChatState':
|
||||
log('info', 'Chat state:', data);
|
||||
break;
|
||||
|
||||
case 'onContactAdded':
|
||||
log('info', 'Contact added:', data);
|
||||
break;
|
||||
|
||||
case 'onGlobalParticipantsChanged':
|
||||
log('info', 'Global participants changed:', data);
|
||||
break;
|
||||
|
||||
case 'onGroupApprovalRequest':
|
||||
log('info', 'Group approval request:', data);
|
||||
break;
|
||||
|
||||
case 'onGroupChange':
|
||||
log('info', 'Group change:', data);
|
||||
break;
|
||||
|
||||
case 'onIncomingCall':
|
||||
log('info', 'Incoming call:', data);
|
||||
break;
|
||||
|
||||
case 'onLabel':
|
||||
log('info', 'Label event:', data);
|
||||
break;
|
||||
|
||||
case 'onLogout':
|
||||
log('info', 'Logout:', data);
|
||||
break;
|
||||
|
||||
case 'onMessage':
|
||||
log('info', 'Message:', data);
|
||||
break;
|
||||
|
||||
case 'onMessageDeleted':
|
||||
log('info', 'Message deleted:', data);
|
||||
break;
|
||||
|
||||
case 'onNewProduct':
|
||||
log('info', 'New product:', data);
|
||||
break;
|
||||
|
||||
case 'onOrder':
|
||||
log('info', 'Order:', data);
|
||||
break;
|
||||
|
||||
case 'onPlugged':
|
||||
log('info', 'Plugged:', data);
|
||||
break;
|
||||
|
||||
case 'onPollVote':
|
||||
log('info', 'Poll vote:', data);
|
||||
break;
|
||||
|
||||
case 'onReaction':
|
||||
log('info', 'Reaction:', data);
|
||||
break;
|
||||
|
||||
case 'onRemovedFromGroup':
|
||||
log('info', 'Removed from group:', data);
|
||||
break;
|
||||
|
||||
case 'onStateChanged':
|
||||
log('info', 'State changed:', data);
|
||||
break;
|
||||
|
||||
case 'onStory':
|
||||
log('info', 'Story:', data);
|
||||
break;
|
||||
|
||||
default:
|
||||
log('warn', `Unhandled event type: "${event}"`, data);
|
||||
break;
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// comentado el 4/26/2025
|
||||
/* Debug: escanear últimos mensajes */
|
||||
// router.get('/debug/scan', async (_req, res) => {
|
||||
// const { data } = await axios.post(`${config.API_URL}/loadAndGetAllMessagesInChat`, {
|
||||
// args: { chatId: config.GROUP_ID, includeMe: 'true', includeNotifications: 'false' }
|
||||
// });
|
||||
// const msgs = (data?.response ?? []).slice(-20);
|
||||
// log('info', `Escaneando ${msgs.length} mensajes recientes…`);
|
||||
// for (const m of msgs) await processIncoming(m);
|
||||
// res.json({ ok: true, scanned: msgs.length });
|
||||
// });
|
||||
|
||||
/* Debug: enviar mensaje */
|
||||
// router.get('/debug/send', async (req, res) => {
|
||||
// const text = req.query.msg ?? config.REPLY_MSG;
|
||||
// const resp = await sendText(config.GROUP_ID, text);
|
||||
// res.json({ ok: true, resp });
|
||||
// });
|
||||
|
||||
/* Debug: versión */
|
||||
router.get('/debug/version', (_req, res) => {
|
||||
res.json({ version: config.VERSION });
|
||||
});
|
||||
22
agent/src/cognition/executeTools.ts
Normal file
22
agent/src/cognition/executeTools.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { genAI, getMcpTool } from '../llm/gemini';
|
||||
import { FunctionCallingConfigMode } from '@google/genai';
|
||||
|
||||
export async function executeTools(instruction: string): Promise<string> {
|
||||
console.log('llamando herramienta');
|
||||
if (!genAI) throw new Error('LLM not configured');
|
||||
const mcpTool = await getMcpTool();
|
||||
const executionPrompt = `Vas a ejecutar una sola herramienta del MCP seg\xFAn el plan. Explic\xE1 brevemente la acci\xF3n y devuelve s\xF3lo el resultado.`;
|
||||
const fullPrompt = `${executionPrompt}\n${instruction}`
|
||||
console.log('---prompt---', fullPrompt);
|
||||
|
||||
const result = await genAI.models.generateContent({
|
||||
model: 'gemini-2.0-flash',
|
||||
contents: [{ role: 'user', parts: [{ text: fullPrompt }] }],
|
||||
config: {
|
||||
tools: [mcpTool],
|
||||
},
|
||||
});
|
||||
|
||||
const text = (result as any).text || '';
|
||||
return text.trim();
|
||||
}
|
||||
30
agent/src/cognition/generatePlan.ts
Normal file
30
agent/src/cognition/generatePlan.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { genAI, getMcpTool } from '../llm/gemini';
|
||||
import { FunctionCallingConfigMode } from '@google/genai';
|
||||
import { systemPrompt } from '../systemPrompt';
|
||||
import type { Conversation } from '../types';
|
||||
|
||||
export async function generatePlan(conversation: Conversation, cognitionPrompt: string): Promise<string> {
|
||||
console.log('generando plan');
|
||||
|
||||
if (!genAI) throw new Error('LLM not configured');
|
||||
|
||||
const mcpTool = await getMcpTool();
|
||||
const planExecutionPrompt = `Est\xE1s generando el plan de acci\xF3n.
|
||||
Solo deb\xE9s consultar el MCP para listar sus capacidades disponibles
|
||||
y luego describir qu\xE9 pasos seguir. Si ya no hay tareas, inclu\xED
|
||||
la frase \"respuesta final\".`;
|
||||
|
||||
|
||||
const prompt = `${systemPrompt}\nConversation:\n${conversation}\n\nCognition:\n${JSON.stringify(conversation)}\n\n${planExecutionPrompt}\nPlan:`;
|
||||
const result = await genAI.models.generateContent({
|
||||
model: 'gemini-2.0-flash',
|
||||
contents: prompt,
|
||||
config: {
|
||||
tools: [mcpTool],
|
||||
},
|
||||
});
|
||||
console.log('------plan generado--------');
|
||||
|
||||
const text = (result as any).text || '';
|
||||
return text.trim();
|
||||
}
|
||||
34
agent/src/cognition/index.ts
Normal file
34
agent/src/cognition/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Conversation } from '../types';
|
||||
import { generatePlan } from './generatePlan';
|
||||
import { executeTools } from './executeTools';
|
||||
|
||||
interface CognitionArgs {
|
||||
conversation: Conversation;
|
||||
}
|
||||
|
||||
export async function iniciarProcesoCognitivo({ conversation }: CognitionArgs) {
|
||||
try {
|
||||
console.log('Iniciando proceso cognitivo...');
|
||||
let cognitionPrompt = '';
|
||||
let loopCount = 0;
|
||||
const plan = await generatePlan(conversation, cognitionPrompt);
|
||||
|
||||
while (loopCount < 5) {
|
||||
cognitionPrompt += `\n## Plan\n${plan}\n`;
|
||||
if (/respuesta final/i.test(plan)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const toolResult = await executeTools(plan);
|
||||
console.log('resultado de la herramienta ', toolResult);
|
||||
cognitionPrompt += `\n## Resultado\n${toolResult}\n`;
|
||||
loopCount += 1;
|
||||
}
|
||||
|
||||
console.log('Proceso cognitivo completado.✅✅✅✅✅');
|
||||
return { text: cognitionPrompt };
|
||||
} catch (error) {
|
||||
console.error('Error al iniciar el proceso cognitivo:', error);
|
||||
return { text: 'Error en el proceso cognitivo' };
|
||||
}
|
||||
}
|
||||
29
agent/src/cognition/normalResponse.ts
Normal file
29
agent/src/cognition/normalResponse.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { genAI, getMcpTool } from '../llm/gemini';
|
||||
import { FunctionCallingConfigMode, GenerateContentResponse } from '@google/genai';
|
||||
import { systemPrompt } from '../systemPrompt';
|
||||
import type { Conversation } from '../types';
|
||||
|
||||
export async function normalResponse(conversation: Conversation): Promise<GenerateContentResponse> {
|
||||
console.log('generando plan');
|
||||
|
||||
if (!genAI) throw new Error('LLM not configured');
|
||||
|
||||
const mcpTool = await getMcpTool();
|
||||
const planExecutionPrompt = `
|
||||
si necesitas ejecutar mas de una herremienta despues de otra para obtener la respuesta hacelo
|
||||
`;
|
||||
|
||||
|
||||
const prompt = `${systemPrompt}\nConversation:\n${JSON.stringify(conversation)}\n\nCognition:\n${planExecutionPrompt}`;
|
||||
const result = await genAI.models.generateContent({
|
||||
model: 'gemini-2.0-flash',
|
||||
contents: prompt,
|
||||
config: {
|
||||
tools: [mcpTool],
|
||||
},
|
||||
});
|
||||
console.log('------respuesta normal generada--------');
|
||||
|
||||
const text = (result as any).text || '';
|
||||
return result
|
||||
}
|
||||
82
agent/src/index.ts
Normal file
82
agent/src/index.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import express from 'express';
|
||||
|
||||
import { genAI, getMcpTool } from './llm/gemini';
|
||||
|
||||
import { systemPrompt } from './systemPrompt'; // Import the repository info from a separate file
|
||||
import { iniciarProcesoCognitivo } from './cognition/index'; // Import the MCP function to start cognitive processes
|
||||
import {normalResponse} from './cognition/normalResponse'
|
||||
import type { Conversation, Msg, Participant } from './types'; // Import the Conversation type
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
||||
|
||||
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
||||
console.log(`MCP URL: ${process.env.MCP_URL || 'http://localhost:5000/mcp'}`);
|
||||
console.log(`Gemini API Key: ${process.env.GEMINI_API_KEY ? '***' : 'not set'}`);
|
||||
|
||||
|
||||
const PORT = Number(process.env.PORT) || 8001;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Descripción de alto nivel para que cualquier agente (humano o LLM) entienda y
|
||||
* trabaje con el repositorio sin perder tiempo buscando contexto.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/', async (req, res) => {
|
||||
const conversation = req.body?.conversation as Conversation | undefined;
|
||||
if (!conversation) return res.status(400).json({ error: 'Missing conversation' });
|
||||
const lastMsg = conversation.messages[conversation.messages.length - 1];
|
||||
const message = lastMsg?.text || '';
|
||||
|
||||
const context = conversation.messages
|
||||
.slice(-10)
|
||||
.map((m) => {
|
||||
const sender =
|
||||
conversation.participants.find((p) => p.id === m.from)?.name || m.from;
|
||||
const content = m.text || `[${m.type}]`;
|
||||
return `${sender}: ${content}`;
|
||||
})
|
||||
.join('\n');
|
||||
console.log('primero')
|
||||
if (!genAI) {
|
||||
return res.json({ reply: "por el momento parece que no tengo acceso a ningun llm ⚠️" });
|
||||
}
|
||||
|
||||
try {
|
||||
const contents = `${systemPrompt}\nConversation:\n${conversation}\n`;
|
||||
const result = await normalResponse(conversation)
|
||||
const reply = (result.text || '').trim();
|
||||
res.json({ reply });
|
||||
} catch (err: any) {
|
||||
console.error('Gemini error', err.message);
|
||||
res.status(500).json({ error: 'Failed to generate reply' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send(`
|
||||
<h1>Conversation Layer Agent</h1>
|
||||
<p>This service answers questions about the repository.</p>
|
||||
<p>Send a POST request to / with a JSON body containing {"conversation": {...}}</p>
|
||||
<p>Example: {"conversation": {"chatId": "123@c.us", "title": "Chat", "isGroup": false, "unreadCount": 0, "participants": [{"id": "123@c.us", "name": "Alice", "isMe": false}], "messages": [{"id": "m1", "from": "123@c.us", "to": "me@c.us", "ts": 0, "type": "chat", "text": "hello", "meta": {"ack":0,"hasReaction":false,"isQuoted":false}}]}}</p>
|
||||
<p>It will respond with a JSON object containing {"reply": "the answer"}</p>
|
||||
|
||||
<p>Repository info: ${systemPrompt}</p>
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`conversation-layer-agent listening on ${PORT}`);
|
||||
});
|
||||
48
agent/src/llm/gemini.ts
Normal file
48
agent/src/llm/gemini.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { GoogleGenAI, mcpToTool } from '@google/genai';
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('modo desarrollo');
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
}else{
|
||||
console.log('modo produccion');
|
||||
}
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV !== 'development' &&
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0'
|
||||
) {
|
||||
throw new Error('NODE_TLS_REJECT_UNAUTHORIZED está activado en producción. Abortando.');
|
||||
}
|
||||
|
||||
|
||||
|
||||
const API_KEY = process.env.GEMINI_API_KEY || '';
|
||||
const MCP_URL = process.env.MCP_URL || 'http://planilla.interno.com/mcp';
|
||||
|
||||
console.log('segundo')
|
||||
export const genAI = API_KEY ? new GoogleGenAI({ apiKey: API_KEY }) : null;
|
||||
console.log(`Using Gemini API key: ${API_KEY}- ${!!genAI}`);
|
||||
|
||||
let mcpClient: Client | undefined;
|
||||
let mcpTransport: StreamableHTTPClientTransport | undefined;
|
||||
|
||||
export async function getMcpTool() {
|
||||
console.log('mcpURL ', MCP_URL);
|
||||
|
||||
if (!mcpClient) {
|
||||
mcpClient = new Client({ name: 'planilla-client', version: '1.0.0' });
|
||||
mcpTransport = new StreamableHTTPClientTransport(new URL(MCP_URL));
|
||||
await mcpClient.connect(mcpTransport);
|
||||
}
|
||||
// console.log('mcpClient ', mcpClient);
|
||||
const tool = mcpToTool(mcpClient);
|
||||
console.log('---------------------terminado getMcpTool---------------------------------------------------');
|
||||
|
||||
// console.log('tool ', tool);
|
||||
return tool;
|
||||
}
|
||||
92
agent/src/systemPrompt copy.ts
Normal file
92
agent/src/systemPrompt copy.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
export const systemPrompt = `
|
||||
# 🟢 System Prompt — Agente de Planillas
|
||||
|
||||
|
||||
## Como funcionas
|
||||
- la fecha de hoy es ${new Date().toLocaleString('es-HN', { timeZone: 'America/Tegucigalpa', hour12: false, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })}
|
||||
- tu nombre es nucleo "id": "50493849962@c.us", "name": "Nucleo🖥️🧠🌐",
|
||||
- tu funcionamiento inicia con este system prompt que contiene la informacion sobre el funcionamiento tuyo y del sistema con el que trabajas.
|
||||
- vos estas conectado a dos interfaces: una de UI chat y otra por whatsapp. en whatsapp recibis mensajes de texto, video, audio, imagenes y documentos. y sos parte de un grupo llamado "Planillas" donde se encuentran los usuarios que pueden interactuar con vos.
|
||||
- desde tu punto de vista, los mensajes que recibis son todos iguales, no importa si vienen por UI o por whatsapp.
|
||||
- el whatsapp-router y la UI chat son los encargados de manejar el objeto 'conversation' que contiene la informacion de los participantes, mensajes y demas metadatos.
|
||||
- cuando sos activado, tenes que entender los mensajes del objeto 'conversation' desde el ultimo mensaje pues es el mas nuevo.
|
||||
no siempre ese mensaje contiene todo el contexto, por lo que debes buscar en los mensajes anteriores de la conversacion para entender el contexto completo.
|
||||
- entre los mensajes del objeto 'conversation' pueden haber mensajes tuyos, que respondiste en otra interaccion. recorda que podes aprovechar esta capacidad para hablar con vos mismo en el futuro
|
||||
- aparte del system prompt y del objeto 'conversation', tenes acceso a un cognition prompt donde se te va indicando que accion es la que estas realizando de tu proceso cognitivo.
|
||||
- vos tenes un cognition flow predefinido asi que tenes que conocerlo, entender en que parte estas y que accion tenes que realizar para sacar el mejor provecho.
|
||||
- el cognition flow va de esta forma:
|
||||
|
||||
## Cognition Flow
|
||||
se ejecuta una llamada a un llm para que analice el convo y genere estas respuestas:
|
||||
1. **Identificar al hablante**: ¿Quién está escribiendo? ¿Es un usuario conocido?
|
||||
${'variable respuestaIdentidadHablante'}
|
||||
2. **Entender el mensaje**: ¿Qué pregunta o solicitud se está haciendo?
|
||||
${'variable respuestaEntenderMensaje'}
|
||||
3. **Procesar la solicitud**: ¿que herramientas puedo llamar para lograr el objetivo?
|
||||
${'variable respuestaProcesarSolicitud'}
|
||||
4. **Responder al usuario**: una vez hayas llenado las primeras dos variables y provisto un plan de tools a utilizar,
|
||||
se procede a llamar las herramientas y agregar las respuestas al cognition prompt por ejemplo:
|
||||
-----vengo yo y te digo----- quiero entrar. te vas a dar cuenta por mi nombre de usuario que soy un empleado y que quiero registrar mi entrada. pero necesitas saber cual es mi id
|
||||
por lo tanto vas a llenar el cognition prompt con la siguiente informacion:
|
||||
- **Identidad del hablante**: "jose dario"
|
||||
- **Entender el mensaje**: "Quiero registrar mi entrada"
|
||||
- **Procesar la solicitud**: buscar empleado.search y usar el id para crear asistencias.createEntrada despues responder,
|
||||
|
||||
se ejecuta otra llamada a un llm para que usando esto se decida a ejecutar las herramientas necesarias y generar una respuesta al usuario.
|
||||
solo puede ejecutar una herramienta a la vez, en el cognition prompt se va guardando el estado de las herramientas ejecutadas y sus respuestas.
|
||||
cuando todas las herramientas hayan sido ejecutadas, se genera una respuesta final al usuario.
|
||||
|
||||
## Rol general
|
||||
Sos el *Agente de Planillas* del Núcleo. Tu trabajo es manejar, vía servidor MCP, las operaciones CRUD de las tablas **empleados**, **planillas**, **asistencias** y **tareas**.
|
||||
Respondés siempre en español, con mensajes breves y el tono casual de un colega hondureño (usá *vos* y expresiones locales).
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Reglas de interacción
|
||||
|
||||
### 1. Identidad del hablante
|
||||
- Usá los metadatos del mensaje para identificar quién escribe.
|
||||
- Si el usuario habla de “mí”, asumí que se refiere a su propio registro de *empleado* y confirmalo:
|
||||
|
||||
Ej: ¿Hablamos de tu usuario (ID 123) o de otra persona?
|
||||
|
||||
- Si menciona otro nombre/ID, verificá que exista; si no, devolvé error.
|
||||
|
||||
---
|
||||
|
||||
### 2. Tabla: asistencias
|
||||
- Al crear **entrada**, la fecha y hora es el momento actual.
|
||||
- Si ya hay una asistencia abierta hoy → respondé que ya fue registrada.
|
||||
- Al crear **salida**, también usá la hora actual.
|
||||
- Si no hay entrada abierta → indicá que primero debe marcar entrada.
|
||||
- Estado inicial siempre es "pendiente".
|
||||
- No permitás modificar registros que ya tienen entrada y salida.
|
||||
|
||||
---
|
||||
|
||||
### 3. Tabla: tareas
|
||||
- Cada tarea debe estar asociada a un *empleado válido*.
|
||||
- precio es opcional; si no se da, guardalo como 0.
|
||||
- Permití crear, listar, editar y borrar tareas sin restricciones.
|
||||
|
||||
---
|
||||
|
||||
### 4. Tabla: planillas
|
||||
- Agrupan asistencias y tareas de uno o varios empleados dentro de un rango fecha_desde → fecha_hasta.
|
||||
- Al crear:
|
||||
1. Validá que existan los empleados.
|
||||
2. Incluí tareas/asistencias del rango.
|
||||
3. Guardá con estado = "borrador".
|
||||
- Se pueden actualizar: título, fechas, estado.
|
||||
- Al cerrar una planilla se deben fijar los montos finales.
|
||||
|
||||
---
|
||||
|
||||
### 5. Tabla: empleados
|
||||
- Permití: alta, edición, baja lógica (activo = false) y consulta.
|
||||
- Antes de operar en otras tablas, validá que el empleado esté activo.
|
||||
|
||||
---
|
||||
|
||||
|
||||
`;
|
||||
83
agent/src/systemPrompt.ts
Normal file
83
agent/src/systemPrompt.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
export const systemPrompt = `
|
||||
# 🟢 System Prompt — Agente de Planillas
|
||||
|
||||
|
||||
## Como funcionas
|
||||
- la fecha de hoy es ${new Date().toLocaleString('es-HN', { timeZone: 'America/Tegucigalpa', hour12: false, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })}
|
||||
- tu nombre es nucleo "id": "50493849962@c.us", "name": "Nucleo🖥️🧠🌐",
|
||||
- tu funcionamiento inicia con este system prompt que contiene la informacion sobre el funcionamiento tuyo y del sistema con el que trabajas.
|
||||
- vos estas conectado a dos interfaces: una de UI chat y otra por whatsapp. en whatsapp recibis mensajes de texto, video, audio, imagenes y documentos. y sos parte de un grupo llamado "Planillas" donde se encuentran los usuarios que pueden interactuar con vos.
|
||||
- desde tu punto de vista, los mensajes que recibis son todos iguales, no importa si vienen por UI o por whatsapp.
|
||||
- el whatsapp-router y la UI chat son los encargados de manejar el objeto 'conversation' que contiene la informacion de los participantes, mensajes y demas metadatos.
|
||||
- cuando sos activado, tenes que entender los mensajes del objeto 'conversation' desde el ultimo mensaje pues es el mas nuevo.
|
||||
no siempre ese mensaje contiene todo el contexto, por lo que debes buscar en los mensajes anteriores de la conversacion para entender el contexto completo.
|
||||
- entre los mensajes del objeto 'conversation' pueden haber mensajes tuyos, que respondiste en otra interaccion. recorda que podes aprovechar esta capacidad para hablar con vos mismo en el futuro
|
||||
- tenes la capacidad de llamar de manera secuencial a las herramientas del mcp planilla, eso significa que podes llamar una herramienta, esperar su respuesta y usar esa respuesta para llamar a la siguiente
|
||||
- la respuesta final siempre la tenes que dar hasta terminar de llamar a todas tus herramientas y obtener un resultado de cada una de ellas para luego usar eso para guiar tu respuesta final
|
||||
|
||||
## Rol general
|
||||
Sos el *Agente de Planillas* del Núcleo. Tu trabajo es manejar, vía servidor MCP, las operaciones CRUD de las tablas **empleados**, **planillas**, **asistencias** y **tareas**.
|
||||
Respondés siempre en español, con mensajes breves y el tono casual de un colega hondureño (usá *vos* y expresiones locales).
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Reglas de interacción
|
||||
|
||||
### 1. Identidad del hablante
|
||||
- Usá los metadatos del mensaje para identificar quién escribe.
|
||||
- Si el usuario habla de “mí”, asumí que se refiere a su propio registro de *empleado* y confirmalo:
|
||||
|
||||
Ej: ¿Hablamos de tu usuario (ID 123) o de otra persona?
|
||||
|
||||
- Si menciona otro nombre/ID, verificá que exista; si no, devolvé error.
|
||||
|
||||
---
|
||||
|
||||
### 2. Tabla: asistencias
|
||||
- Al crear **entrada**, la fecha y hora es el momento actual.
|
||||
- Si ya hay una asistencia abierta hoy → respondé que ya fue registrada.
|
||||
- Al crear **salida**, también usá la hora actual.
|
||||
- Si no hay entrada abierta → indicá que primero debe marcar entrada.
|
||||
- Estado inicial siempre es "pendiente".
|
||||
- No permitás modificar registros que ya tienen entrada y salida.
|
||||
|
||||
---
|
||||
|
||||
### 3. Tabla: tareas
|
||||
- Cada tarea debe estar asociada a un *empleado válido*.
|
||||
- precio es opcional; si no se da, guardalo como 0.
|
||||
- Permití crear, listar, editar y borrar tareas sin restricciones.
|
||||
|
||||
---
|
||||
|
||||
### 4. Tabla: planillas
|
||||
- Agrupan asistencias y tareas de uno o varios empleados dentro de un rango fecha_desde → fecha_hasta.
|
||||
- Al crear:
|
||||
1. Validá que existan los empleados.
|
||||
2. Incluí tareas/asistencias del rango.
|
||||
3. Guardá con estado = "borrador".
|
||||
- Se pueden actualizar: título, fechas, estado.
|
||||
- Al cerrar una planilla se deben fijar los montos finales.
|
||||
|
||||
---
|
||||
|
||||
### 5. Tabla: empleados
|
||||
- Permití: alta, edición, baja lógica (activo = false) y consulta.
|
||||
- Antes de operar en otras tablas, validá que el empleado esté activo.
|
||||
|
||||
---
|
||||
|
||||
|
||||
#### sentido del oido
|
||||
los mensajes que te llegan por el chat-ui y whatsapp-router siempre vienen con texto, aunque dentro de whatsapp o el chat sean audio. una llamada a gemini-flash-2.0 transcribio el audio y te lo envio como texto.
|
||||
sin embargo eso no quiere decir que no podes "escuchar" u "oir" porque aunque lo estas haciendo en texto, estas logrando bastante del objetivo. normalmente en honduras usamos esas palabras para referirnos a "entender"
|
||||
y vos por medio de la traduccion estas entendiendo lo que se dice en el audio, por lo tanto estas "oyendo" o "escuchando" el mensaje. si en algun caso crees que enrealidad te estan preguntando por algun sonido hace una
|
||||
pregunta aclaratoria de si es a un sonido a lo que se refieren y de ser asi, respondeles que no tenes acceso a los sonidos.
|
||||
|
||||
|
||||
|
||||
###aclaratorias
|
||||
- los mensajes e2e son mensajes que utiliza whatsapp para notificar cosas de su sistema, no son mensajes de los usuarios y no debes responderlos.
|
||||
- los mensajes de tipo "notification" son mensajes que whatsapp envia para notificar cosas del sistema, no son mensajes de los usuarios y no debes responderlos.
|
||||
|
||||
`;
|
||||
32
agent/src/types.ts
Normal file
32
agent/src/types.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export interface Participant {
|
||||
id: string;
|
||||
name: string;
|
||||
isMe: boolean;
|
||||
isAdmin?: boolean;
|
||||
}
|
||||
|
||||
export interface Msg {
|
||||
id: string;
|
||||
from: string;
|
||||
to: string;
|
||||
ts: number;
|
||||
type: 'chat' | 'image' | 'audio' | 'sticker' | 'doc';
|
||||
text?: string;
|
||||
mediaUrl?: string;
|
||||
mentions?: string[];
|
||||
meta: {
|
||||
ack: number;
|
||||
hasReaction: boolean;
|
||||
isQuoted: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
chatId: string;
|
||||
title: string;
|
||||
isGroup: boolean;
|
||||
unreadCount: number;
|
||||
participants: Participant[];
|
||||
messages: Msg[];
|
||||
createdAt: number;
|
||||
}
|
||||
14
agent/tsconfig.json
Normal file
14
agent/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"lib": ["es2020"],
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// utils/decryptMediaContent.js (ES modules)
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import axios from 'axios';
|
||||
import mime from 'mime-types';
|
||||
import { decryptMedia } from '@open-wa/wa-automate';
|
||||
import { log } from '../logger.js';
|
||||
|
||||
// 🔒 quita caracteres que rompen rutas
|
||||
const safe = s => (s || '').replace(/[\\/:*?"<>|]/g, '_');
|
||||
|
||||
export async function decryptMediaContent(
|
||||
mediaInfo,
|
||||
outputDir = 'media/dec',
|
||||
rawDir = 'media/raw',
|
||||
filename = null
|
||||
) {
|
||||
const { clientUrl, t, filehash, msgId, type } = mediaInfo;
|
||||
let { mimetype } = mediaInfo;
|
||||
|
||||
if (!clientUrl) {
|
||||
log('error', '❌ Sin clientUrl, no se puede bajar');
|
||||
return null;
|
||||
}
|
||||
|
||||
// deducir mimetype si falta
|
||||
if (!mimetype) {
|
||||
mimetype =
|
||||
mime.lookup(clientUrl) ||
|
||||
(type?.startsWith('image') && 'image/jpeg') ||
|
||||
(type?.startsWith('video') && 'video/mp4') ||
|
||||
'application/octet-stream';
|
||||
}
|
||||
|
||||
const ext = mime.extension(mimetype) || 'bin';
|
||||
const baseName = safe(filename || msgId || filehash?.slice(0,16) || `file_${t||Date.now()}`);
|
||||
const rawPath = path.join(rawDir , `${baseName}.enc`);
|
||||
const decPath = path.join(outputDir, `${baseName}.${ext}`);
|
||||
|
||||
if (fs.existsSync(decPath)) return decPath;
|
||||
|
||||
fs.mkdirSync(rawDir , { recursive: true });
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
try {
|
||||
/* ───── descarga RAW (solo si no existe) ───── */
|
||||
if (!fs.existsSync(rawPath)) {
|
||||
const { data } = await axios
|
||||
.get(clientUrl, { responseType: 'arraybuffer' })
|
||||
.catch(e => {
|
||||
if (e.response?.status === 410) throw new Error('URL expirada (410)');
|
||||
throw e;
|
||||
});
|
||||
fs.writeFileSync(rawPath, Buffer.from(data));
|
||||
}
|
||||
|
||||
/* ───── inyectar RAW para que decryptMedia no lo vuelva a bajar ───── */
|
||||
const fake = {
|
||||
...mediaInfo,
|
||||
mimetype,
|
||||
mimeType: mimetype,
|
||||
_data: {
|
||||
...mediaInfo,
|
||||
mimetype,
|
||||
mimeType: mimetype,
|
||||
_raw: fs.readFileSync(rawPath)
|
||||
}
|
||||
};
|
||||
|
||||
const plain = await decryptMedia(fake);
|
||||
if (!plain?.length) throw new Error('descifrado vacío');
|
||||
|
||||
fs.writeFileSync(decPath, plain);
|
||||
log('info', `✔️ ${type || ext} → ${decPath}`);
|
||||
return decPath;
|
||||
} catch (e) {
|
||||
log('error', `❌ decryptMedia falló → ${e.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
/**
|
||||
* Convierte el raw de open-wa a un objeto compacto.
|
||||
* Solo coloca `media` cuando hay TODO para desencriptar: mediaKey + clientUrl.
|
||||
*/
|
||||
export function processMessage(raw) {
|
||||
const m = raw?.data ?? raw; // alias corto
|
||||
|
||||
const base = {
|
||||
msgId : m.id,
|
||||
chatId : m.chatId,
|
||||
chatName : m.chat?.name ?? m.chat?.formattedTitle ?? null,
|
||||
senderId : m.sender?.id ?? m.author ?? null,
|
||||
senderName: m.sender?.name ?? m.notifyName ?? null,
|
||||
type : m.type,
|
||||
text : m.text ?? m.caption ?? '',
|
||||
fromMe : !!m.fromMe,
|
||||
timestamp : m.timestamp ?? m.t,
|
||||
date : m.timestamp
|
||||
? dayjs.unix(m.timestamp).utcOffset(-360).format('YYYY-MM-DD HH:mm:ss')
|
||||
: null,
|
||||
hasReactions: (m.reactions?.length ?? 0) > 0 || !!m.hasReaction,
|
||||
reactions : (m.reactions ?? []).map(r => r.aggregateEmoji)
|
||||
};
|
||||
|
||||
/* ---------- quoted ---------- */
|
||||
if (m.quotedMsg) {
|
||||
base.replyTo = {
|
||||
id : m.quotedMsg.id,
|
||||
text: m.quotedMsg.text ?? m.quotedMsg.body ?? '',
|
||||
from: m.quotedParticipant ?? null,
|
||||
type: m.quotedMsg.type
|
||||
};
|
||||
}
|
||||
|
||||
/* ---------- preview de enlaces ---------- */
|
||||
if (m.matchedText) {
|
||||
base.preview = {
|
||||
url : m.matchedText,
|
||||
title: m.title ?? null,
|
||||
description: m.description ?? null
|
||||
};
|
||||
}
|
||||
|
||||
/* ---------- media listo para desencriptar ---------- */
|
||||
const isMediaType = ['image','video','sticker','ptt','audio','document'].includes(m.type);
|
||||
const hasKeys = m.mediaKey || m.mediaData?.mediaKey;
|
||||
const hasUrl = m.clientUrl || m.directPath;
|
||||
|
||||
if (isMediaType && hasKeys && hasUrl) {
|
||||
base.media = {
|
||||
type : m.type,
|
||||
clientUrl: m.clientUrl ?? m.directPath,
|
||||
mimetype : m.mimetype,
|
||||
size : m.size ?? m.mediaData?.size,
|
||||
width : m.width,
|
||||
height : m.height,
|
||||
duration : Number(m.duration) || 0,
|
||||
filename : m.filename ?? null,
|
||||
caption : m.caption ?? null,
|
||||
|
||||
/* claves para decryptMediaContent */
|
||||
mediaKey : m.mediaKey ?? m.mediaData?.mediaKey,
|
||||
filehash : m.filehash ?? m.mediaData?.filehash,
|
||||
t : m.t ?? m.timestamp
|
||||
};
|
||||
|
||||
if (m.type === 'sticker') {
|
||||
Object.assign(base.media, {
|
||||
packId : m.stickerPackId ?? m.mediaData?.stickerPackId,
|
||||
pack : m.stickerPackName ?? m.mediaData?.stickerPackName,
|
||||
author : m.stickerPackPublisher ?? m.mediaData?.stickerPackPublisher,
|
||||
animated: m.isLottie || m.mediaData?.isAnimated || false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// utils/saveMedia.js
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import axios from 'axios';
|
||||
import mime from 'mime-types';
|
||||
import { decryptMedia } from '@open-wa/wa-automate';
|
||||
import { GoogleGenAI } from '@google/genai';
|
||||
import { config } from '../config.js';
|
||||
import { log } from '../logger.js';
|
||||
|
||||
const BASE_DIR = '/media';
|
||||
const safe = s => (s || '').replace(/[\\/:*?"<>|]/g, '_');
|
||||
const ai = new GoogleGenAI({ apiKey: config.GEMINI_KEY });
|
||||
|
||||
function getShortId(msgId = '') {
|
||||
return msgId.replace(/^true_|^false_|@c\.us_/g, '').split('_')[0];
|
||||
}
|
||||
|
||||
export async function saveMedia(msg) {
|
||||
if (msg.type === 'chat') return null;
|
||||
if (!msg.clientUrl) {
|
||||
log('warn', `Sin clientUrl: ${msg.id}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const ext = mime.extension(msg.mimetype || 'application/octet-stream') || 'bin';
|
||||
const chatId = safe(msg.chatId);
|
||||
const shortId = (`${msg.timestamp}${msg.type}`).toLocaleLowerCase();
|
||||
// const shortId = getShortId(msg.id);
|
||||
const folder = path.join(BASE_DIR, chatId, safe(msg.type));
|
||||
const filePath = path.join(folder, `${shortId}.${ext}`);
|
||||
const fileName = shortId // solo para Files API
|
||||
|
||||
// Buscar primero en Files API
|
||||
try {
|
||||
const existing = await ai.files.get({ name: fileName });
|
||||
log('info', `📂 ya existe en Files API: ${fileName}`);
|
||||
return { [msg.id]: existing };
|
||||
} catch (e) {
|
||||
if (e.message?.includes('INVALID_ARGUMENT')) {
|
||||
log('error', `files.get falló para ${fileName}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar en disco
|
||||
if (fs.existsSync(filePath)) {
|
||||
log('info', `📁 ya existe local: ${fileName}`, 'subiendo a Files API');
|
||||
const uploaded = await ai.files.upload({
|
||||
file: fileName,
|
||||
config: { mimeType: msg.mimetype || 'application/octet-stream', name: fileName },
|
||||
});
|
||||
log('info', `📤 subido a Files API: ${uploaded.name}`);
|
||||
return { [msg.id]: uploaded };
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = await axios.get(msg.clientUrl, { responseType: 'arraybuffer' });
|
||||
msg._data = { ...msg, _raw: raw.data };
|
||||
const buf = await decryptMedia(msg);
|
||||
|
||||
fs.mkdirSync(folder, { recursive: true });
|
||||
fs.writeFileSync(filePath, buf);
|
||||
log('info', `✅ guardado: ${filePath}`);
|
||||
|
||||
const uploaded = await ai.files.upload({
|
||||
file: filePath,
|
||||
config: { mimeType: msg.mimetype || 'application/octet-stream', name: fileName },
|
||||
});
|
||||
log('info', `📤 subido a Files API: ${uploaded.name}`);
|
||||
return { [msg.id]: uploaded };
|
||||
|
||||
} catch (err) {
|
||||
if (err.response?.status === 410) {
|
||||
log('warn', `URL expirada (410) para ${msg.id}`);
|
||||
} else {
|
||||
log('error', `Error al guardar media ${msg.id}: ${err.message}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
// whatsapp.js
|
||||
import axios from 'axios';
|
||||
import { config } from './config.js';
|
||||
import { log } from './logger.js';
|
||||
|
||||
/* ✉️ Enviar texto -------------------------------------------------------- */
|
||||
export async function sendText(to, content) {
|
||||
log('info', `Enviando mensaje a ${to}: "${content}"`);
|
||||
const { data } = await axios.post(`${config.API_URL}/sendText`, {
|
||||
args: { to, content }
|
||||
});
|
||||
log('debug', 'Respuesta de /sendText →', data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/* 🗨️ Traer todos los mensajes cargados en el chat ------------------------ */
|
||||
export async function fetchChatMessages(chatId) {
|
||||
try {
|
||||
const { data } = await axios.post(`${config.API_URL}/getAllMessagesInChat`, {
|
||||
args: {
|
||||
chatId,
|
||||
includeMe: 'true',
|
||||
includeNotifications: 'false'
|
||||
}
|
||||
});
|
||||
return data?.response ?? [];
|
||||
} catch (e) {
|
||||
log('error', 'Fallo /getAllMessagesInChat:', e.response?.data ?? e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/* ⏳ Esperar gateway ------------------------------------------------------ */
|
||||
export async function waitForGateway() {
|
||||
for (let i = 1; i <= config.MAX_ATTEMPTS; i++) {
|
||||
try {
|
||||
await axios.get(`${config.API_URL}/api-docs`);
|
||||
log('info', '🟢 whatsapp-gateway listo');
|
||||
return;
|
||||
} catch {
|
||||
log('warn', `Gateway no responde (intento ${i}/${config.MAX_ATTEMPTS})…`);
|
||||
await new Promise(r => setTimeout(r, config.RETRY_MS));
|
||||
}
|
||||
}
|
||||
throw new Error('whatsapp-gateway no respondió a tiempo');
|
||||
}
|
||||
|
||||
/* 🧹 Limpiar webhooks anteriores ----------------------------------------- */
|
||||
export async function clearWebhooks() {
|
||||
try {
|
||||
const { data } = await axios.post(`${config.API_URL}/listWebhooks`);
|
||||
const hooks = data?.response ?? [];
|
||||
if (!hooks.length) {
|
||||
log('info', 'Sin webhooks previos que limpiar');
|
||||
return;
|
||||
}
|
||||
|
||||
log('info', `Eliminando ${hooks.length} webhooks…`);
|
||||
const results = await Promise.allSettled(
|
||||
hooks.map(h => axios.post(`${config.API_URL}/removeWebhook`, { args: { webhookId: h.id } }))
|
||||
);
|
||||
|
||||
results.forEach((r, i) => {
|
||||
const id = hooks[i].id;
|
||||
if (r.status === 'fulfilled' && r.value?.data?.response === true) {
|
||||
log('debug', `✔️ Eliminado webhook ${id}`);
|
||||
} else {
|
||||
log('warn', `⚠️ Falló eliminar webhook ${id}`);
|
||||
}
|
||||
});
|
||||
|
||||
const ok = results.filter(r => r.status === 'fulfilled' && r.value?.data?.response === true).length;
|
||||
log('info', `Limpieza OK (${ok}/${hooks.length} eliminados)`);
|
||||
} catch (e) {
|
||||
log('error', 'Fallo limpiando webhooks:', e.response?.data ?? e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Registro del webhook con todos los eventos disponibles ----------
|
||||
export async function registerWebhook() {
|
||||
const url = `http://nucleo-bot:${config.PORT}/webhook`;
|
||||
|
||||
const eventConfig = {
|
||||
onAck: false, // ❌
|
||||
onAddedToGroup: true,
|
||||
onAnyMessage: true,
|
||||
onBattery: true,
|
||||
onBroadcast: true,
|
||||
onButton: true,
|
||||
onCallState: false, // ❌
|
||||
onChatDeleted: true,
|
||||
onChatOpened: true,
|
||||
onChatState: true,
|
||||
onContactAdded: true,
|
||||
onGlobalParticipantsChanged: true,
|
||||
onGroupApprovalRequest: true,
|
||||
onGroupChange: true,
|
||||
onIncomingCall: false, // ❌
|
||||
onLabel: true,
|
||||
onLogout: true,
|
||||
onMessage: false, // ❌
|
||||
onMessageDeleted: true,
|
||||
onNewProduct: true,
|
||||
onOrder: true,
|
||||
onPlugged: false, // ❌
|
||||
onPollVote: true,
|
||||
onReaction: true,
|
||||
onRemovedFromGroup: false, // ❌
|
||||
onStateChanged: false, // ❌
|
||||
onStory: false, // ❌
|
||||
};
|
||||
|
||||
// usa el config de eventos para filtrar los habilitados (el config de arriba)
|
||||
const allEvents = Object.entries(eventConfig)
|
||||
.filter(([_, enabled]) => enabled)
|
||||
.map(([event]) => event);
|
||||
|
||||
|
||||
const { data } = await axios.post(`${config.API_URL}/registerWebhook`, {
|
||||
args: { url, events: allEvents, id: 'nucleo-bot' }
|
||||
});
|
||||
|
||||
log('info', '✔️ Webhook registrado:', data);
|
||||
}
|
||||
|
||||
|
||||
export async function setTypingStatus(chatId, status) {
|
||||
try {
|
||||
const { data } = await axios.post(`${config.API_URL}/simulateTyping`, {
|
||||
args: { to: chatId, on: status }
|
||||
});
|
||||
log('debug', 'Respuesta de /setChatState →', data);
|
||||
} catch (e) {
|
||||
log('error', 'Fallo /setChatState:', e.response?.data ?? e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@
|
||||
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||
|
||||
DATABASE_URL=postgresql://planilla:planilla@localhost:5434/planilla_db?schema=public
|
||||
CONVERSATION_LAYER_ROUTER_URL='http://your-router-url:port'
|
||||
|
||||
|
||||
28
api/.gitignore
vendored
28
api/.gitignore
vendored
@@ -1,5 +1,27 @@
|
||||
node_modules
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
coverage/
|
||||
dev
|
||||
# Keep environment variables out of version control
|
||||
prisma/generated
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
prisma/generated
|
||||
.env.*
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea/
|
||||
.DS_Store
|
||||
|
||||
1079
api/node_modules/.package-lock.json
generated
vendored
1079
api/node_modules/.package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
243
api/node_modules/accepts/HISTORY.md
generated
vendored
243
api/node_modules/accepts/HISTORY.md
generated
vendored
@@ -1,243 +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
|
||||
23
api/node_modules/accepts/LICENSE
generated
vendored
23
api/node_modules/accepts/LICENSE
generated
vendored
@@ -1,23 +0,0 @@
|
||||
(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.
|
||||
140
api/node_modules/accepts/README.md
generated
vendored
140
api/node_modules/accepts/README.md
generated
vendored
@@ -1,140 +0,0 @@
|
||||
# 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 types 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
|
||||
238
api/node_modules/accepts/index.js
generated
vendored
238
api/node_modules/accepts/index.js
generated
vendored
@@ -1,238 +0,0 @@
|
||||
/*!
|
||||
* 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 {String}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function validMime (type) {
|
||||
return typeof type === 'string'
|
||||
}
|
||||
47
api/node_modules/accepts/package.json
generated
vendored
47
api/node_modules/accepts/package.json
generated
vendored
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"name": "accepts",
|
||||
"description": "Higher-level content negotiation",
|
||||
"version": "1.3.8",
|
||||
"contributors": [
|
||||
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": "jshttp/accepts",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
672
api/node_modules/body-parser/HISTORY.md
generated
vendored
672
api/node_modules/body-parser/HISTORY.md
generated
vendored
@@ -1,672 +0,0 @@
|
||||
1.20.3 / 2024-09-10
|
||||
===================
|
||||
|
||||
* deps: qs@6.13.0
|
||||
* add `depth` option to customize the depth level in the parser
|
||||
* IMPORTANT: The default `depth` level for parsing URL-encoded data is now `32` (previously was `Infinity`)
|
||||
|
||||
1.20.2 / 2023-02-21
|
||||
===================
|
||||
|
||||
* Fix strict json error message on Node.js 19+
|
||||
* deps: content-type@~1.0.5
|
||||
- perf: skip value escaping when unnecessary
|
||||
* deps: raw-body@2.5.2
|
||||
|
||||
1.20.1 / 2022-10-06
|
||||
===================
|
||||
|
||||
* deps: qs@6.11.0
|
||||
* perf: remove unnecessary object clone
|
||||
|
||||
1.20.0 / 2022-04-02
|
||||
===================
|
||||
|
||||
* Fix error message for json parse whitespace in `strict`
|
||||
* Fix internal error when inflated body exceeds limit
|
||||
* Prevent loss of async hooks context
|
||||
* Prevent hanging when request already read
|
||||
* deps: depd@2.0.0
|
||||
- Replace internal `eval` usage with `Function` constructor
|
||||
- Use instance methods on `process` to check for listeners
|
||||
* deps: http-errors@2.0.0
|
||||
- deps: depd@2.0.0
|
||||
- deps: statuses@2.0.1
|
||||
* deps: on-finished@2.4.1
|
||||
* deps: qs@6.10.3
|
||||
* deps: raw-body@2.5.1
|
||||
- deps: http-errors@2.0.0
|
||||
|
||||
1.19.2 / 2022-02-15
|
||||
===================
|
||||
|
||||
* deps: bytes@3.1.2
|
||||
* deps: qs@6.9.7
|
||||
* Fix handling of `__proto__` keys
|
||||
* deps: raw-body@2.4.3
|
||||
- deps: bytes@3.1.2
|
||||
|
||||
1.19.1 / 2021-12-10
|
||||
===================
|
||||
|
||||
* deps: bytes@3.1.1
|
||||
* deps: http-errors@1.8.1
|
||||
- deps: inherits@2.0.4
|
||||
- deps: toidentifier@1.0.1
|
||||
- deps: setprototypeof@1.2.0
|
||||
* deps: qs@6.9.6
|
||||
* deps: raw-body@2.4.2
|
||||
- deps: bytes@3.1.1
|
||||
- deps: http-errors@1.8.1
|
||||
* deps: safe-buffer@5.2.1
|
||||
* deps: type-is@~1.6.18
|
||||
|
||||
1.19.0 / 2019-04-25
|
||||
===================
|
||||
|
||||
* deps: bytes@3.1.0
|
||||
- Add petabyte (`pb`) support
|
||||
* deps: http-errors@1.7.2
|
||||
- Set constructor name when possible
|
||||
- deps: setprototypeof@1.1.1
|
||||
- deps: statuses@'>= 1.5.0 < 2'
|
||||
* deps: iconv-lite@0.4.24
|
||||
- Added encoding MIK
|
||||
* deps: qs@6.7.0
|
||||
- Fix parsing array brackets after index
|
||||
* deps: raw-body@2.4.0
|
||||
- deps: bytes@3.1.0
|
||||
- deps: http-errors@1.7.2
|
||||
- deps: iconv-lite@0.4.24
|
||||
* deps: type-is@~1.6.17
|
||||
- deps: mime-types@~2.1.24
|
||||
- perf: prevent internal `throw` on invalid type
|
||||
|
||||
1.18.3 / 2018-05-14
|
||||
===================
|
||||
|
||||
* Fix stack trace for strict json parse error
|
||||
* deps: depd@~1.1.2
|
||||
- perf: remove argument reassignment
|
||||
* deps: http-errors@~1.6.3
|
||||
- deps: depd@~1.1.2
|
||||
- deps: setprototypeof@1.1.0
|
||||
- deps: statuses@'>= 1.3.1 < 2'
|
||||
* deps: iconv-lite@0.4.23
|
||||
- Fix loading encoding with year appended
|
||||
- Fix deprecation warnings on Node.js 10+
|
||||
* deps: qs@6.5.2
|
||||
* deps: raw-body@2.3.3
|
||||
- deps: http-errors@1.6.3
|
||||
- deps: iconv-lite@0.4.23
|
||||
* deps: type-is@~1.6.16
|
||||
- deps: mime-types@~2.1.18
|
||||
|
||||
1.18.2 / 2017-09-22
|
||||
===================
|
||||
|
||||
* deps: debug@2.6.9
|
||||
* perf: remove argument reassignment
|
||||
|
||||
1.18.1 / 2017-09-12
|
||||
===================
|
||||
|
||||
* deps: content-type@~1.0.4
|
||||
- perf: remove argument reassignment
|
||||
- perf: skip parameter parsing when no parameters
|
||||
* deps: iconv-lite@0.4.19
|
||||
- Fix ISO-8859-1 regression
|
||||
- Update Windows-1255
|
||||
* deps: qs@6.5.1
|
||||
- Fix parsing & compacting very deep objects
|
||||
* deps: raw-body@2.3.2
|
||||
- deps: iconv-lite@0.4.19
|
||||
|
||||
1.18.0 / 2017-09-08
|
||||
===================
|
||||
|
||||
* Fix JSON strict violation error to match native parse error
|
||||
* Include the `body` property on verify errors
|
||||
* Include the `type` property on all generated errors
|
||||
* Use `http-errors` to set status code on errors
|
||||
* deps: bytes@3.0.0
|
||||
* deps: debug@2.6.8
|
||||
* deps: depd@~1.1.1
|
||||
- Remove unnecessary `Buffer` loading
|
||||
* deps: http-errors@~1.6.2
|
||||
- deps: depd@1.1.1
|
||||
* deps: iconv-lite@0.4.18
|
||||
- Add support for React Native
|
||||
- Add a warning if not loaded as utf-8
|
||||
- Fix CESU-8 decoding in Node.js 8
|
||||
- Improve speed of ISO-8859-1 encoding
|
||||
* deps: qs@6.5.0
|
||||
* deps: raw-body@2.3.1
|
||||
- Use `http-errors` for standard emitted errors
|
||||
- deps: bytes@3.0.0
|
||||
- deps: iconv-lite@0.4.18
|
||||
- perf: skip buffer decoding on overage chunk
|
||||
* perf: prevent internal `throw` when missing charset
|
||||
|
||||
1.17.2 / 2017-05-17
|
||||
===================
|
||||
|
||||
* deps: debug@2.6.7
|
||||
- Fix `DEBUG_MAX_ARRAY_LENGTH`
|
||||
- deps: ms@2.0.0
|
||||
* deps: type-is@~1.6.15
|
||||
- deps: mime-types@~2.1.15
|
||||
|
||||
1.17.1 / 2017-03-06
|
||||
===================
|
||||
|
||||
* deps: qs@6.4.0
|
||||
- Fix regression parsing keys starting with `[`
|
||||
|
||||
1.17.0 / 2017-03-01
|
||||
===================
|
||||
|
||||
* deps: http-errors@~1.6.1
|
||||
- Make `message` property enumerable for `HttpError`s
|
||||
- deps: setprototypeof@1.0.3
|
||||
* deps: qs@6.3.1
|
||||
- Fix compacting nested arrays
|
||||
|
||||
1.16.1 / 2017-02-10
|
||||
===================
|
||||
|
||||
* deps: debug@2.6.1
|
||||
- Fix deprecation messages in WebStorm and other editors
|
||||
- Undeprecate `DEBUG_FD` set to `1` or `2`
|
||||
|
||||
1.16.0 / 2017-01-17
|
||||
===================
|
||||
|
||||
* deps: debug@2.6.0
|
||||
- Allow colors in workers
|
||||
- Deprecated `DEBUG_FD` environment variable
|
||||
- Fix error when running under React Native
|
||||
- Use same color for same namespace
|
||||
- deps: ms@0.7.2
|
||||
* deps: http-errors@~1.5.1
|
||||
- deps: inherits@2.0.3
|
||||
- deps: setprototypeof@1.0.2
|
||||
- deps: statuses@'>= 1.3.1 < 2'
|
||||
* deps: iconv-lite@0.4.15
|
||||
- Added encoding MS-31J
|
||||
- Added encoding MS-932
|
||||
- Added encoding MS-936
|
||||
- Added encoding MS-949
|
||||
- Added encoding MS-950
|
||||
- Fix GBK/GB18030 handling of Euro character
|
||||
* deps: qs@6.2.1
|
||||
- Fix array parsing from skipping empty values
|
||||
* deps: raw-body@~2.2.0
|
||||
- deps: iconv-lite@0.4.15
|
||||
* deps: type-is@~1.6.14
|
||||
- deps: mime-types@~2.1.13
|
||||
|
||||
1.15.2 / 2016-06-19
|
||||
===================
|
||||
|
||||
* deps: bytes@2.4.0
|
||||
* deps: content-type@~1.0.2
|
||||
- perf: enable strict mode
|
||||
* deps: http-errors@~1.5.0
|
||||
- Use `setprototypeof` module to replace `__proto__` setting
|
||||
- deps: statuses@'>= 1.3.0 < 2'
|
||||
- perf: enable strict mode
|
||||
* deps: qs@6.2.0
|
||||
* deps: raw-body@~2.1.7
|
||||
- deps: bytes@2.4.0
|
||||
- perf: remove double-cleanup on happy path
|
||||
* deps: type-is@~1.6.13
|
||||
- deps: mime-types@~2.1.11
|
||||
|
||||
1.15.1 / 2016-05-05
|
||||
===================
|
||||
|
||||
* deps: bytes@2.3.0
|
||||
- Drop partial bytes on all parsed units
|
||||
- Fix parsing byte string that looks like hex
|
||||
* deps: raw-body@~2.1.6
|
||||
- deps: bytes@2.3.0
|
||||
* deps: type-is@~1.6.12
|
||||
- deps: mime-types@~2.1.10
|
||||
|
||||
1.15.0 / 2016-02-10
|
||||
===================
|
||||
|
||||
* deps: http-errors@~1.4.0
|
||||
- Add `HttpError` export, for `err instanceof createError.HttpError`
|
||||
- deps: inherits@2.0.1
|
||||
- deps: statuses@'>= 1.2.1 < 2'
|
||||
* deps: qs@6.1.0
|
||||
* deps: type-is@~1.6.11
|
||||
- deps: mime-types@~2.1.9
|
||||
|
||||
1.14.2 / 2015-12-16
|
||||
===================
|
||||
|
||||
* deps: bytes@2.2.0
|
||||
* deps: iconv-lite@0.4.13
|
||||
* deps: qs@5.2.0
|
||||
* deps: raw-body@~2.1.5
|
||||
- deps: bytes@2.2.0
|
||||
- deps: iconv-lite@0.4.13
|
||||
* deps: type-is@~1.6.10
|
||||
- deps: mime-types@~2.1.8
|
||||
|
||||
1.14.1 / 2015-09-27
|
||||
===================
|
||||
|
||||
* Fix issue where invalid charset results in 400 when `verify` used
|
||||
* deps: iconv-lite@0.4.12
|
||||
- Fix CESU-8 decoding in Node.js 4.x
|
||||
* deps: raw-body@~2.1.4
|
||||
- Fix masking critical errors from `iconv-lite`
|
||||
- deps: iconv-lite@0.4.12
|
||||
* deps: type-is@~1.6.9
|
||||
- deps: mime-types@~2.1.7
|
||||
|
||||
1.14.0 / 2015-09-16
|
||||
===================
|
||||
|
||||
* Fix JSON strict parse error to match syntax errors
|
||||
* Provide static `require` analysis in `urlencoded` parser
|
||||
* deps: depd@~1.1.0
|
||||
- Support web browser loading
|
||||
* deps: qs@5.1.0
|
||||
* deps: raw-body@~2.1.3
|
||||
- Fix sync callback when attaching data listener causes sync read
|
||||
* deps: type-is@~1.6.8
|
||||
- Fix type error when given invalid type to match against
|
||||
- deps: mime-types@~2.1.6
|
||||
|
||||
1.13.3 / 2015-07-31
|
||||
===================
|
||||
|
||||
* deps: type-is@~1.6.6
|
||||
- deps: mime-types@~2.1.4
|
||||
|
||||
1.13.2 / 2015-07-05
|
||||
===================
|
||||
|
||||
* deps: iconv-lite@0.4.11
|
||||
* deps: qs@4.0.0
|
||||
- Fix dropping parameters like `hasOwnProperty`
|
||||
- Fix user-visible incompatibilities from 3.1.0
|
||||
- Fix various parsing edge cases
|
||||
* deps: raw-body@~2.1.2
|
||||
- Fix error stack traces to skip `makeError`
|
||||
- deps: iconv-lite@0.4.11
|
||||
* deps: type-is@~1.6.4
|
||||
- deps: mime-types@~2.1.2
|
||||
- perf: enable strict mode
|
||||
- perf: remove argument reassignment
|
||||
|
||||
1.13.1 / 2015-06-16
|
||||
===================
|
||||
|
||||
* deps: qs@2.4.2
|
||||
- Downgraded from 3.1.0 because of user-visible incompatibilities
|
||||
|
||||
1.13.0 / 2015-06-14
|
||||
===================
|
||||
|
||||
* Add `statusCode` property on `Error`s, in addition to `status`
|
||||
* Change `type` default to `application/json` for JSON parser
|
||||
* Change `type` default to `application/x-www-form-urlencoded` for urlencoded parser
|
||||
* Provide static `require` analysis
|
||||
* Use the `http-errors` module to generate errors
|
||||
* deps: bytes@2.1.0
|
||||
- Slight optimizations
|
||||
* deps: iconv-lite@0.4.10
|
||||
- The encoding UTF-16 without BOM now defaults to UTF-16LE when detection fails
|
||||
- Leading BOM is now removed when decoding
|
||||
* deps: on-finished@~2.3.0
|
||||
- Add defined behavior for HTTP `CONNECT` requests
|
||||
- Add defined behavior for HTTP `Upgrade` requests
|
||||
- deps: ee-first@1.1.1
|
||||
* deps: qs@3.1.0
|
||||
- Fix dropping parameters like `hasOwnProperty`
|
||||
- Fix various parsing edge cases
|
||||
- Parsed object now has `null` prototype
|
||||
* deps: raw-body@~2.1.1
|
||||
- Use `unpipe` module for unpiping requests
|
||||
- deps: iconv-lite@0.4.10
|
||||
* deps: type-is@~1.6.3
|
||||
- deps: mime-types@~2.1.1
|
||||
- perf: reduce try block size
|
||||
- perf: remove bitwise operations
|
||||
* perf: enable strict mode
|
||||
* perf: remove argument reassignment
|
||||
* perf: remove delete call
|
||||
|
||||
1.12.4 / 2015-05-10
|
||||
===================
|
||||
|
||||
* deps: debug@~2.2.0
|
||||
* deps: qs@2.4.2
|
||||
- Fix allowing parameters like `constructor`
|
||||
* deps: on-finished@~2.2.1
|
||||
* deps: raw-body@~2.0.1
|
||||
- Fix a false-positive when unpiping in Node.js 0.8
|
||||
- deps: bytes@2.0.1
|
||||
* deps: type-is@~1.6.2
|
||||
- deps: mime-types@~2.0.11
|
||||
|
||||
1.12.3 / 2015-04-15
|
||||
===================
|
||||
|
||||
* Slight efficiency improvement when not debugging
|
||||
* deps: depd@~1.0.1
|
||||
* deps: iconv-lite@0.4.8
|
||||
- Add encoding alias UNICODE-1-1-UTF-7
|
||||
* deps: raw-body@1.3.4
|
||||
- Fix hanging callback if request aborts during read
|
||||
- deps: iconv-lite@0.4.8
|
||||
|
||||
1.12.2 / 2015-03-16
|
||||
===================
|
||||
|
||||
* deps: qs@2.4.1
|
||||
- Fix error when parameter `hasOwnProperty` is present
|
||||
|
||||
1.12.1 / 2015-03-15
|
||||
===================
|
||||
|
||||
* deps: debug@~2.1.3
|
||||
- Fix high intensity foreground color for bold
|
||||
- deps: ms@0.7.0
|
||||
* deps: type-is@~1.6.1
|
||||
- deps: mime-types@~2.0.10
|
||||
|
||||
1.12.0 / 2015-02-13
|
||||
===================
|
||||
|
||||
* add `debug` messages
|
||||
* accept a function for the `type` option
|
||||
* use `content-type` to parse `Content-Type` headers
|
||||
* deps: iconv-lite@0.4.7
|
||||
- Gracefully support enumerables on `Object.prototype`
|
||||
* deps: raw-body@1.3.3
|
||||
- deps: iconv-lite@0.4.7
|
||||
* deps: type-is@~1.6.0
|
||||
- fix argument reassignment
|
||||
- fix false-positives in `hasBody` `Transfer-Encoding` check
|
||||
- support wildcard for both type and subtype (`*/*`)
|
||||
- deps: mime-types@~2.0.9
|
||||
|
||||
1.11.0 / 2015-01-30
|
||||
===================
|
||||
|
||||
* make internal `extended: true` depth limit infinity
|
||||
* deps: type-is@~1.5.6
|
||||
- deps: mime-types@~2.0.8
|
||||
|
||||
1.10.2 / 2015-01-20
|
||||
===================
|
||||
|
||||
* deps: iconv-lite@0.4.6
|
||||
- Fix rare aliases of single-byte encodings
|
||||
* deps: raw-body@1.3.2
|
||||
- deps: iconv-lite@0.4.6
|
||||
|
||||
1.10.1 / 2015-01-01
|
||||
===================
|
||||
|
||||
* deps: on-finished@~2.2.0
|
||||
* deps: type-is@~1.5.5
|
||||
- deps: mime-types@~2.0.7
|
||||
|
||||
1.10.0 / 2014-12-02
|
||||
===================
|
||||
|
||||
* make internal `extended: true` array limit dynamic
|
||||
|
||||
1.9.3 / 2014-11-21
|
||||
==================
|
||||
|
||||
* deps: iconv-lite@0.4.5
|
||||
- Fix Windows-31J and X-SJIS encoding support
|
||||
* deps: qs@2.3.3
|
||||
- Fix `arrayLimit` behavior
|
||||
* deps: raw-body@1.3.1
|
||||
- deps: iconv-lite@0.4.5
|
||||
* deps: type-is@~1.5.3
|
||||
- deps: mime-types@~2.0.3
|
||||
|
||||
1.9.2 / 2014-10-27
|
||||
==================
|
||||
|
||||
* deps: qs@2.3.2
|
||||
- Fix parsing of mixed objects and values
|
||||
|
||||
1.9.1 / 2014-10-22
|
||||
==================
|
||||
|
||||
* deps: on-finished@~2.1.1
|
||||
- Fix handling of pipelined requests
|
||||
* deps: qs@2.3.0
|
||||
- Fix parsing of mixed implicit and explicit arrays
|
||||
* deps: type-is@~1.5.2
|
||||
- deps: mime-types@~2.0.2
|
||||
|
||||
1.9.0 / 2014-09-24
|
||||
==================
|
||||
|
||||
* include the charset in "unsupported charset" error message
|
||||
* include the encoding in "unsupported content encoding" error message
|
||||
* deps: depd@~1.0.0
|
||||
|
||||
1.8.4 / 2014-09-23
|
||||
==================
|
||||
|
||||
* fix content encoding to be case-insensitive
|
||||
|
||||
1.8.3 / 2014-09-19
|
||||
==================
|
||||
|
||||
* deps: qs@2.2.4
|
||||
- Fix issue with object keys starting with numbers truncated
|
||||
|
||||
1.8.2 / 2014-09-15
|
||||
==================
|
||||
|
||||
* deps: depd@0.4.5
|
||||
|
||||
1.8.1 / 2014-09-07
|
||||
==================
|
||||
|
||||
* deps: media-typer@0.3.0
|
||||
* deps: type-is@~1.5.1
|
||||
|
||||
1.8.0 / 2014-09-05
|
||||
==================
|
||||
|
||||
* make empty-body-handling consistent between chunked requests
|
||||
- empty `json` produces `{}`
|
||||
- empty `raw` produces `new Buffer(0)`
|
||||
- empty `text` produces `''`
|
||||
- empty `urlencoded` produces `{}`
|
||||
* deps: qs@2.2.3
|
||||
- Fix issue where first empty value in array is discarded
|
||||
* deps: type-is@~1.5.0
|
||||
- fix `hasbody` to be true for `content-length: 0`
|
||||
|
||||
1.7.0 / 2014-09-01
|
||||
==================
|
||||
|
||||
* add `parameterLimit` option to `urlencoded` parser
|
||||
* change `urlencoded` extended array limit to 100
|
||||
* respond with 413 when over `parameterLimit` in `urlencoded`
|
||||
|
||||
1.6.7 / 2014-08-29
|
||||
==================
|
||||
|
||||
* deps: qs@2.2.2
|
||||
- Remove unnecessary cloning
|
||||
|
||||
1.6.6 / 2014-08-27
|
||||
==================
|
||||
|
||||
* deps: qs@2.2.0
|
||||
- Array parsing fix
|
||||
- Performance improvements
|
||||
|
||||
1.6.5 / 2014-08-16
|
||||
==================
|
||||
|
||||
* deps: on-finished@2.1.0
|
||||
|
||||
1.6.4 / 2014-08-14
|
||||
==================
|
||||
|
||||
* deps: qs@1.2.2
|
||||
|
||||
1.6.3 / 2014-08-10
|
||||
==================
|
||||
|
||||
* deps: qs@1.2.1
|
||||
|
||||
1.6.2 / 2014-08-07
|
||||
==================
|
||||
|
||||
* deps: qs@1.2.0
|
||||
- Fix parsing array of objects
|
||||
|
||||
1.6.1 / 2014-08-06
|
||||
==================
|
||||
|
||||
* deps: qs@1.1.0
|
||||
- Accept urlencoded square brackets
|
||||
- Accept empty values in implicit array notation
|
||||
|
||||
1.6.0 / 2014-08-05
|
||||
==================
|
||||
|
||||
* deps: qs@1.0.2
|
||||
- Complete rewrite
|
||||
- Limits array length to 20
|
||||
- Limits object depth to 5
|
||||
- Limits parameters to 1,000
|
||||
|
||||
1.5.2 / 2014-07-27
|
||||
==================
|
||||
|
||||
* deps: depd@0.4.4
|
||||
- Work-around v8 generating empty stack traces
|
||||
|
||||
1.5.1 / 2014-07-26
|
||||
==================
|
||||
|
||||
* deps: depd@0.4.3
|
||||
- Fix exception when global `Error.stackTraceLimit` is too low
|
||||
|
||||
1.5.0 / 2014-07-20
|
||||
==================
|
||||
|
||||
* deps: depd@0.4.2
|
||||
- Add `TRACE_DEPRECATION` environment variable
|
||||
- Remove non-standard grey color from color output
|
||||
- Support `--no-deprecation` argument
|
||||
- Support `--trace-deprecation` argument
|
||||
* deps: iconv-lite@0.4.4
|
||||
- Added encoding UTF-7
|
||||
* deps: raw-body@1.3.0
|
||||
- deps: iconv-lite@0.4.4
|
||||
- Added encoding UTF-7
|
||||
- Fix `Cannot switch to old mode now` error on Node.js 0.10+
|
||||
* deps: type-is@~1.3.2
|
||||
|
||||
1.4.3 / 2014-06-19
|
||||
==================
|
||||
|
||||
* deps: type-is@1.3.1
|
||||
- fix global variable leak
|
||||
|
||||
1.4.2 / 2014-06-19
|
||||
==================
|
||||
|
||||
* deps: type-is@1.3.0
|
||||
- improve type parsing
|
||||
|
||||
1.4.1 / 2014-06-19
|
||||
==================
|
||||
|
||||
* fix urlencoded extended deprecation message
|
||||
|
||||
1.4.0 / 2014-06-19
|
||||
==================
|
||||
|
||||
* add `text` parser
|
||||
* add `raw` parser
|
||||
* check accepted charset in content-type (accepts utf-8)
|
||||
* check accepted encoding in content-encoding (accepts identity)
|
||||
* deprecate `bodyParser()` middleware; use `.json()` and `.urlencoded()` as needed
|
||||
* deprecate `urlencoded()` without provided `extended` option
|
||||
* lazy-load urlencoded parsers
|
||||
* parsers split into files for reduced mem usage
|
||||
* support gzip and deflate bodies
|
||||
- set `inflate: false` to turn off
|
||||
* deps: raw-body@1.2.2
|
||||
- Support all encodings from `iconv-lite`
|
||||
|
||||
1.3.1 / 2014-06-11
|
||||
==================
|
||||
|
||||
* deps: type-is@1.2.1
|
||||
- Switch dependency from mime to mime-types@1.0.0
|
||||
|
||||
1.3.0 / 2014-05-31
|
||||
==================
|
||||
|
||||
* add `extended` option to urlencoded parser
|
||||
|
||||
1.2.2 / 2014-05-27
|
||||
==================
|
||||
|
||||
* deps: raw-body@1.1.6
|
||||
- assert stream encoding on node.js 0.8
|
||||
- assert stream encoding on node.js < 0.10.6
|
||||
- deps: bytes@1
|
||||
|
||||
1.2.1 / 2014-05-26
|
||||
==================
|
||||
|
||||
* invoke `next(err)` after request fully read
|
||||
- prevents hung responses and socket hang ups
|
||||
|
||||
1.2.0 / 2014-05-11
|
||||
==================
|
||||
|
||||
* add `verify` option
|
||||
* deps: type-is@1.2.0
|
||||
- support suffix matching
|
||||
|
||||
1.1.2 / 2014-05-11
|
||||
==================
|
||||
|
||||
* improve json parser speed
|
||||
|
||||
1.1.1 / 2014-05-11
|
||||
==================
|
||||
|
||||
* fix repeated limit parsing with every request
|
||||
|
||||
1.1.0 / 2014-05-10
|
||||
==================
|
||||
|
||||
* add `type` option
|
||||
* deps: pin for safety and consistency
|
||||
|
||||
1.0.2 / 2014-04-14
|
||||
==================
|
||||
|
||||
* use `type-is` module
|
||||
|
||||
1.0.1 / 2014-03-20
|
||||
==================
|
||||
|
||||
* lower default limits to 100kb
|
||||
23
api/node_modules/body-parser/LICENSE
generated
vendored
23
api/node_modules/body-parser/LICENSE
generated
vendored
@@ -1,23 +0,0 @@
|
||||
(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.
|
||||
476
api/node_modules/body-parser/README.md
generated
vendored
476
api/node_modules/body-parser/README.md
generated
vendored
@@ -1,476 +0,0 @@
|
||||
# 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/docs/guides/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.org/package/busboy#readme) and
|
||||
[connect-busboy](https://www.npmjs.org/package/connect-busboy#readme)
|
||||
* [multiparty](https://www.npmjs.org/package/multiparty#readme) and
|
||||
[connect-multiparty](https://www.npmjs.org/package/connect-multiparty#readme)
|
||||
* [formidable](https://www.npmjs.org/package/formidable#readme)
|
||||
* [multer](https://www.npmjs.org/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.org/package/body#readme)
|
||||
- [co-body](https://www.npmjs.org/package/co-body#readme)
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
$ npm install body-parser
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```js
|
||||
var 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, or an empty
|
||||
object (`{}`) if there was no body to parse, the `Content-Type` was not matched,
|
||||
or an error occurred.
|
||||
|
||||
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` 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:
|
||||
|
||||
##### 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.org/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` 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.org/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` 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.org/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 encoding of the body and supports automatic
|
||||
inflation of `gzip` 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` option allows to choose between parsing the URL-encoded data
|
||||
with the `querystring` library (when `false`) or the `qs` library (when
|
||||
`true`). 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.org/package/qs#readme).
|
||||
|
||||
Defaults to `true`, but using the default has been deprecated. Please
|
||||
research into the difference between `qs` and `querystring` and choose the
|
||||
appropriate setting.
|
||||
|
||||
##### 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.org/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.
|
||||
|
||||
#### 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
|
||||
var express = require('express')
|
||||
var bodyParser = require('body-parser')
|
||||
|
||||
var app = express()
|
||||
|
||||
// parse application/x-www-form-urlencoded
|
||||
app.use(bodyParser.urlencoded({ extended: false }))
|
||||
|
||||
// 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(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
|
||||
var express = require('express')
|
||||
var bodyParser = require('body-parser')
|
||||
|
||||
var app = express()
|
||||
|
||||
// create application/json parser
|
||||
var jsonParser = bodyParser.json()
|
||||
|
||||
// create application/x-www-form-urlencoded parser
|
||||
var urlencodedParser = bodyParser.urlencoded({ extended: false })
|
||||
|
||||
// POST /login gets urlencoded bodies
|
||||
app.post('/login', urlencodedParser, function (req, res) {
|
||||
res.send('welcome, ' + req.body.username)
|
||||
})
|
||||
|
||||
// POST /api/users gets JSON bodies
|
||||
app.post('/api/users', jsonParser, function (req, res) {
|
||||
// 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
|
||||
var express = require('express')
|
||||
var bodyParser = require('body-parser')
|
||||
|
||||
var 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://badgen.net/github/checks/expressjs/body-parser/master?label=ci
|
||||
[ci-url]: https://github.com/expressjs/body-parser/actions/workflows/ci.yml
|
||||
[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/body-parser/master
|
||||
[coveralls-url]: https://coveralls.io/r/expressjs/body-parser?branch=master
|
||||
[node-version-image]: https://badgen.net/npm/node/body-parser
|
||||
[node-version-url]: https://nodejs.org/en/download
|
||||
[npm-downloads-image]: https://badgen.net/npm/dm/body-parser
|
||||
[npm-url]: https://npmjs.org/package/body-parser
|
||||
[npm-version-image]: https://badgen.net/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
|
||||
156
api/node_modules/body-parser/index.js
generated
vendored
156
api/node_modules/body-parser/index.js
generated
vendored
@@ -1,156 +0,0 @@
|
||||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var deprecate = require('depd')('body-parser')
|
||||
|
||||
/**
|
||||
* Cache of loaded parsers.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var parsers = Object.create(null)
|
||||
|
||||
/**
|
||||
* @typedef Parsers
|
||||
* @type {function}
|
||||
* @property {function} json
|
||||
* @property {function} raw
|
||||
* @property {function} text
|
||||
* @property {function} urlencoded
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
* @type {Parsers}
|
||||
*/
|
||||
|
||||
exports = module.exports = deprecate.function(bodyParser,
|
||||
'bodyParser: use individual json/urlencoded middlewares')
|
||||
|
||||
/**
|
||||
* JSON parser.
|
||||
* @public
|
||||
*/
|
||||
|
||||
Object.defineProperty(exports, 'json', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: createParserGetter('json')
|
||||
})
|
||||
|
||||
/**
|
||||
* Raw parser.
|
||||
* @public
|
||||
*/
|
||||
|
||||
Object.defineProperty(exports, 'raw', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: createParserGetter('raw')
|
||||
})
|
||||
|
||||
/**
|
||||
* Text parser.
|
||||
* @public
|
||||
*/
|
||||
|
||||
Object.defineProperty(exports, 'text', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: createParserGetter('text')
|
||||
})
|
||||
|
||||
/**
|
||||
* URL-encoded parser.
|
||||
* @public
|
||||
*/
|
||||
|
||||
Object.defineProperty(exports, 'urlencoded', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: createParserGetter('urlencoded')
|
||||
})
|
||||
|
||||
/**
|
||||
* Create a middleware to parse json and urlencoded bodies.
|
||||
*
|
||||
* @param {object} [options]
|
||||
* @return {function}
|
||||
* @deprecated
|
||||
* @public
|
||||
*/
|
||||
|
||||
function bodyParser (options) {
|
||||
// use default type for parsers
|
||||
var opts = Object.create(options || null, {
|
||||
type: {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: undefined,
|
||||
writable: true
|
||||
}
|
||||
})
|
||||
|
||||
var _urlencoded = exports.urlencoded(opts)
|
||||
var _json = exports.json(opts)
|
||||
|
||||
return function bodyParser (req, res, next) {
|
||||
_json(req, res, function (err) {
|
||||
if (err) return next(err)
|
||||
_urlencoded(req, res, next)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a getter for loading a parser.
|
||||
* @private
|
||||
*/
|
||||
|
||||
function createParserGetter (name) {
|
||||
return function get () {
|
||||
return loadParser(name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a parser module.
|
||||
* @private
|
||||
*/
|
||||
|
||||
function loadParser (parserName) {
|
||||
var parser = parsers[parserName]
|
||||
|
||||
if (parser !== undefined) {
|
||||
return parser
|
||||
}
|
||||
|
||||
// this uses a switch for static require analysis
|
||||
switch (parserName) {
|
||||
case 'json':
|
||||
parser = require('./lib/types/json')
|
||||
break
|
||||
case 'raw':
|
||||
parser = require('./lib/types/raw')
|
||||
break
|
||||
case 'text':
|
||||
parser = require('./lib/types/text')
|
||||
break
|
||||
case 'urlencoded':
|
||||
parser = require('./lib/types/urlencoded')
|
||||
break
|
||||
}
|
||||
|
||||
// store to prevent invoking require()
|
||||
return (parsers[parserName] = parser)
|
||||
}
|
||||
205
api/node_modules/body-parser/lib/read.js
generated
vendored
205
api/node_modules/body-parser/lib/read.js
generated
vendored
@@ -1,205 +0,0 @@
|
||||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var createError = require('http-errors')
|
||||
var destroy = require('destroy')
|
||||
var getBody = require('raw-body')
|
||||
var iconv = require('iconv-lite')
|
||||
var onFinished = require('on-finished')
|
||||
var unpipe = require('unpipe')
|
||||
var zlib = require('zlib')
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var length
|
||||
var opts = options
|
||||
var stream
|
||||
|
||||
// flag as parsed
|
||||
req._body = true
|
||||
|
||||
// read options
|
||||
var encoding = opts.encoding !== null
|
||||
? opts.encoding
|
||||
: null
|
||||
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) {
|
||||
unpipe(req)
|
||||
destroy(stream, true)
|
||||
}
|
||||
|
||||
// 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)
|
||||
} 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=true]
|
||||
* @return {object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function contentstream (req, debug, inflate) {
|
||||
var encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
|
||||
var length = req.headers['content-length']
|
||||
var stream
|
||||
|
||||
debug('content-encoding "%s"', encoding)
|
||||
|
||||
if (inflate === false && encoding !== 'identity') {
|
||||
throw createError(415, 'content encoding unsupported', {
|
||||
encoding: encoding,
|
||||
type: 'encoding.unsupported'
|
||||
})
|
||||
}
|
||||
|
||||
switch (encoding) {
|
||||
case 'deflate':
|
||||
stream = zlib.createInflate()
|
||||
debug('inflate body')
|
||||
req.pipe(stream)
|
||||
break
|
||||
case 'gzip':
|
||||
stream = zlib.createGunzip()
|
||||
debug('gunzip body')
|
||||
req.pipe(stream)
|
||||
break
|
||||
case 'identity':
|
||||
stream = req
|
||||
stream.length = length
|
||||
break
|
||||
default:
|
||||
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
|
||||
encoding: encoding,
|
||||
type: 'encoding.unsupported'
|
||||
})
|
||||
}
|
||||
|
||||
return stream
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the contents of a request.
|
||||
*
|
||||
* @param {object} req
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function dump (req, callback) {
|
||||
if (onFinished.isFinished(req)) {
|
||||
callback(null)
|
||||
} else {
|
||||
onFinished(req, callback)
|
||||
req.resume()
|
||||
}
|
||||
}
|
||||
247
api/node_modules/body-parser/lib/types/json.js
generated
vendored
247
api/node_modules/body-parser/lib/types/json.js
generated
vendored
@@ -1,247 +0,0 @@
|
||||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014 Jonathan Ong
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var bytes = require('bytes')
|
||||
var contentType = require('content-type')
|
||||
var createError = require('http-errors')
|
||||
var debug = require('debug')('body-parser:json')
|
||||
var read = require('../read')
|
||||
var typeis = require('type-is')
|
||||
|
||||
/**
|
||||
* 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]
|
||||
* @return {function}
|
||||
* @public
|
||||
*/
|
||||
|
||||
function json (options) {
|
||||
var opts = options || {}
|
||||
|
||||
var limit = typeof opts.limit !== 'number'
|
||||
? bytes.parse(opts.limit || '100kb')
|
||||
: opts.limit
|
||||
var inflate = opts.inflate !== false
|
||||
var reviver = opts.reviver
|
||||
var strict = opts.strict !== false
|
||||
var type = opts.type || 'application/json'
|
||||
var verify = opts.verify || false
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return function jsonParser (req, res, next) {
|
||||
if (req._body) {
|
||||
debug('body already parsed')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
req.body = req.body || {}
|
||||
|
||||
// skip requests without bodies
|
||||
if (!typeis.hasBody(req)) {
|
||||
debug('skip empty body')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
debug('content-type %j', req.headers['content-type'])
|
||||
|
||||
// determine if request should be parsed
|
||||
if (!shouldParse(req)) {
|
||||
debug('skip parsing')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// assert charset per RFC 7159 sec 8.1
|
||||
var charset = getCharset(req) || 'utf-8'
|
||||
if (charset.slice(0, 4) !== 'utf-') {
|
||||
debug('invalid charset')
|
||||
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
|
||||
charset: charset,
|
||||
type: 'charset.unsupported'
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
// read
|
||||
read(req, res, next, parse, debug, {
|
||||
encoding: charset,
|
||||
inflate: inflate,
|
||||
limit: limit,
|
||||
verify: verify
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create strict violation syntax error matching native error.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {string} char
|
||||
* @return {Error}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function createStrictSyntaxError (str, char) {
|
||||
var index = str.indexOf(char)
|
||||
var partial = ''
|
||||
|
||||
if (index !== -1) {
|
||||
partial = str.substring(0, index) + JSON_SYNTAX_CHAR
|
||||
|
||||
for (var i = index + 1; i < str.length; i++) {
|
||||
partial += JSON_SYNTAX_CHAR
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* @return {function}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function firstchar (str) {
|
||||
var match = FIRST_CHAR_REGEXP.exec(str)
|
||||
|
||||
return match
|
||||
? match[1]
|
||||
: undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the charset of a request.
|
||||
*
|
||||
* @param {object} req
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function getCharset (req) {
|
||||
try {
|
||||
return (contentType.parse(req).parameters.charset || '').toLowerCase()
|
||||
} catch (e) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a SyntaxError for JSON.parse.
|
||||
*
|
||||
* @param {SyntaxError} error
|
||||
* @param {object} obj
|
||||
* @return {SyntaxError}
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the simple type checker.
|
||||
*
|
||||
* @param {string} type
|
||||
* @return {function}
|
||||
*/
|
||||
|
||||
function typeChecker (type) {
|
||||
return function checkType (req) {
|
||||
return Boolean(typeis(req, type))
|
||||
}
|
||||
}
|
||||
101
api/node_modules/body-parser/lib/types/raw.js
generated
vendored
101
api/node_modules/body-parser/lib/types/raw.js
generated
vendored
@@ -1,101 +0,0 @@
|
||||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var bytes = require('bytes')
|
||||
var debug = require('debug')('body-parser:raw')
|
||||
var read = require('../read')
|
||||
var typeis = require('type-is')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = raw
|
||||
|
||||
/**
|
||||
* Create a middleware to parse raw bodies.
|
||||
*
|
||||
* @param {object} [options]
|
||||
* @return {function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function raw (options) {
|
||||
var opts = options || {}
|
||||
|
||||
var inflate = opts.inflate !== false
|
||||
var limit = typeof opts.limit !== 'number'
|
||||
? bytes.parse(opts.limit || '100kb')
|
||||
: opts.limit
|
||||
var type = opts.type || 'application/octet-stream'
|
||||
var verify = opts.verify || false
|
||||
|
||||
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
|
||||
|
||||
function parse (buf) {
|
||||
return buf
|
||||
}
|
||||
|
||||
return function rawParser (req, res, next) {
|
||||
if (req._body) {
|
||||
debug('body already parsed')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
req.body = req.body || {}
|
||||
|
||||
// skip requests without bodies
|
||||
if (!typeis.hasBody(req)) {
|
||||
debug('skip empty body')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
debug('content-type %j', req.headers['content-type'])
|
||||
|
||||
// determine if request should be parsed
|
||||
if (!shouldParse(req)) {
|
||||
debug('skip parsing')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// read
|
||||
read(req, res, next, parse, debug, {
|
||||
encoding: null,
|
||||
inflate: inflate,
|
||||
limit: limit,
|
||||
verify: verify
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the simple type checker.
|
||||
*
|
||||
* @param {string} type
|
||||
* @return {function}
|
||||
*/
|
||||
|
||||
function typeChecker (type) {
|
||||
return function checkType (req) {
|
||||
return Boolean(typeis(req, type))
|
||||
}
|
||||
}
|
||||
121
api/node_modules/body-parser/lib/types/text.js
generated
vendored
121
api/node_modules/body-parser/lib/types/text.js
generated
vendored
@@ -1,121 +0,0 @@
|
||||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var bytes = require('bytes')
|
||||
var contentType = require('content-type')
|
||||
var debug = require('debug')('body-parser:text')
|
||||
var read = require('../read')
|
||||
var typeis = require('type-is')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = text
|
||||
|
||||
/**
|
||||
* Create a middleware to parse text bodies.
|
||||
*
|
||||
* @param {object} [options]
|
||||
* @return {function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function text (options) {
|
||||
var opts = options || {}
|
||||
|
||||
var defaultCharset = opts.defaultCharset || 'utf-8'
|
||||
var inflate = opts.inflate !== false
|
||||
var limit = typeof opts.limit !== 'number'
|
||||
? bytes.parse(opts.limit || '100kb')
|
||||
: opts.limit
|
||||
var type = opts.type || 'text/plain'
|
||||
var verify = opts.verify || false
|
||||
|
||||
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
|
||||
|
||||
function parse (buf) {
|
||||
return buf
|
||||
}
|
||||
|
||||
return function textParser (req, res, next) {
|
||||
if (req._body) {
|
||||
debug('body already parsed')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
req.body = req.body || {}
|
||||
|
||||
// skip requests without bodies
|
||||
if (!typeis.hasBody(req)) {
|
||||
debug('skip empty body')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
debug('content-type %j', req.headers['content-type'])
|
||||
|
||||
// determine if request should be parsed
|
||||
if (!shouldParse(req)) {
|
||||
debug('skip parsing')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// get charset
|
||||
var charset = getCharset(req) || defaultCharset
|
||||
|
||||
// read
|
||||
read(req, res, next, parse, debug, {
|
||||
encoding: charset,
|
||||
inflate: inflate,
|
||||
limit: limit,
|
||||
verify: verify
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the charset of a request.
|
||||
*
|
||||
* @param {object} req
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function getCharset (req) {
|
||||
try {
|
||||
return (contentType.parse(req).parameters.charset || '').toLowerCase()
|
||||
} catch (e) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the simple type checker.
|
||||
*
|
||||
* @param {string} type
|
||||
* @return {function}
|
||||
*/
|
||||
|
||||
function typeChecker (type) {
|
||||
return function checkType (req) {
|
||||
return Boolean(typeis(req, type))
|
||||
}
|
||||
}
|
||||
307
api/node_modules/body-parser/lib/types/urlencoded.js
generated
vendored
307
api/node_modules/body-parser/lib/types/urlencoded.js
generated
vendored
@@ -1,307 +0,0 @@
|
||||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014 Jonathan Ong
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var bytes = require('bytes')
|
||||
var contentType = require('content-type')
|
||||
var createError = require('http-errors')
|
||||
var debug = require('debug')('body-parser:urlencoded')
|
||||
var deprecate = require('depd')('body-parser')
|
||||
var read = require('../read')
|
||||
var typeis = require('type-is')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = urlencoded
|
||||
|
||||
/**
|
||||
* Cache of parser modules.
|
||||
*/
|
||||
|
||||
var parsers = Object.create(null)
|
||||
|
||||
/**
|
||||
* Create a middleware to parse urlencoded bodies.
|
||||
*
|
||||
* @param {object} [options]
|
||||
* @return {function}
|
||||
* @public
|
||||
*/
|
||||
|
||||
function urlencoded (options) {
|
||||
var opts = options || {}
|
||||
|
||||
// notice because option default will flip in next major
|
||||
if (opts.extended === undefined) {
|
||||
deprecate('undefined extended: provide extended option')
|
||||
}
|
||||
|
||||
var extended = opts.extended !== false
|
||||
var inflate = opts.inflate !== false
|
||||
var limit = typeof opts.limit !== 'number'
|
||||
? bytes.parse(opts.limit || '100kb')
|
||||
: opts.limit
|
||||
var type = opts.type || 'application/x-www-form-urlencoded'
|
||||
var verify = opts.verify || false
|
||||
var depth = typeof opts.depth !== 'number'
|
||||
? Number(opts.depth || 32)
|
||||
: opts.depth
|
||||
|
||||
if (verify !== false && typeof verify !== 'function') {
|
||||
throw new TypeError('option verify must be function')
|
||||
}
|
||||
|
||||
// create the appropriate query parser
|
||||
var queryparse = extended
|
||||
? extendedparser(opts)
|
||||
: simpleparser(opts)
|
||||
|
||||
// create the appropriate type checking function
|
||||
var shouldParse = typeof type !== 'function'
|
||||
? typeChecker(type)
|
||||
: type
|
||||
|
||||
function parse (body) {
|
||||
return body.length
|
||||
? queryparse(body)
|
||||
: {}
|
||||
}
|
||||
|
||||
return function urlencodedParser (req, res, next) {
|
||||
if (req._body) {
|
||||
debug('body already parsed')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
req.body = req.body || {}
|
||||
|
||||
// skip requests without bodies
|
||||
if (!typeis.hasBody(req)) {
|
||||
debug('skip empty body')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
debug('content-type %j', req.headers['content-type'])
|
||||
|
||||
// determine if request should be parsed
|
||||
if (!shouldParse(req)) {
|
||||
debug('skip parsing')
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// assert charset
|
||||
var charset = getCharset(req) || 'utf-8'
|
||||
if (charset !== 'utf-8') {
|
||||
debug('invalid charset')
|
||||
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
|
||||
charset: charset,
|
||||
type: 'charset.unsupported'
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
// read
|
||||
read(req, res, next, parse, debug, {
|
||||
debug: debug,
|
||||
encoding: charset,
|
||||
inflate: inflate,
|
||||
limit: limit,
|
||||
verify: verify,
|
||||
depth: depth
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extended query parser.
|
||||
*
|
||||
* @param {object} options
|
||||
*/
|
||||
|
||||
function extendedparser (options) {
|
||||
var parameterLimit = options.parameterLimit !== undefined
|
||||
? options.parameterLimit
|
||||
: 1000
|
||||
|
||||
var depth = typeof options.depth !== 'number'
|
||||
? Number(options.depth || 32)
|
||||
: options.depth
|
||||
var parse = parser('qs')
|
||||
|
||||
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) {
|
||||
var paramCount = parameterCount(body, parameterLimit)
|
||||
|
||||
if (paramCount === undefined) {
|
||||
debug('too many parameters')
|
||||
throw createError(413, 'too many parameters', {
|
||||
type: 'parameters.too.many'
|
||||
})
|
||||
}
|
||||
|
||||
var arrayLimit = Math.max(100, paramCount)
|
||||
|
||||
debug('parse extended urlencoding')
|
||||
try {
|
||||
return parse(body, {
|
||||
allowPrototypes: true,
|
||||
arrayLimit: arrayLimit,
|
||||
depth: depth,
|
||||
strictDepth: true,
|
||||
parameterLimit: parameterLimit
|
||||
})
|
||||
} catch (err) {
|
||||
if (err instanceof RangeError) {
|
||||
throw createError(400, 'The input exceeded the depth', {
|
||||
type: 'querystring.parse.rangeError'
|
||||
})
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the charset of a request.
|
||||
*
|
||||
* @param {object} req
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function getCharset (req) {
|
||||
try {
|
||||
return (contentType.parse(req).parameters.charset || '').toLowerCase()
|
||||
} catch (e) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of parameters, stopping once limit reached
|
||||
*
|
||||
* @param {string} body
|
||||
* @param {number} limit
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function parameterCount (body, limit) {
|
||||
var count = 0
|
||||
var index = 0
|
||||
|
||||
while ((index = body.indexOf('&', index)) !== -1) {
|
||||
count++
|
||||
index++
|
||||
|
||||
if (count === limit) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parser for module name dynamically.
|
||||
*
|
||||
* @param {string} name
|
||||
* @return {function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function parser (name) {
|
||||
var mod = parsers[name]
|
||||
|
||||
if (mod !== undefined) {
|
||||
return mod.parse
|
||||
}
|
||||
|
||||
// this uses a switch for static require analysis
|
||||
switch (name) {
|
||||
case 'qs':
|
||||
mod = require('qs')
|
||||
break
|
||||
case 'querystring':
|
||||
mod = require('querystring')
|
||||
break
|
||||
}
|
||||
|
||||
// store to prevent invoking require()
|
||||
parsers[name] = mod
|
||||
|
||||
return mod.parse
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the simple query parser.
|
||||
*
|
||||
* @param {object} options
|
||||
*/
|
||||
|
||||
function simpleparser (options) {
|
||||
var parameterLimit = options.parameterLimit !== undefined
|
||||
? options.parameterLimit
|
||||
: 1000
|
||||
var parse = parser('querystring')
|
||||
|
||||
if (isNaN(parameterLimit) || parameterLimit < 1) {
|
||||
throw new TypeError('option parameterLimit must be a positive number')
|
||||
}
|
||||
|
||||
if (isFinite(parameterLimit)) {
|
||||
parameterLimit = parameterLimit | 0
|
||||
}
|
||||
|
||||
return function queryparse (body) {
|
||||
var paramCount = parameterCount(body, parameterLimit)
|
||||
|
||||
if (paramCount === undefined) {
|
||||
debug('too many parameters')
|
||||
throw createError(413, 'too many parameters', {
|
||||
type: 'parameters.too.many'
|
||||
})
|
||||
}
|
||||
|
||||
debug('parse urlencoding')
|
||||
return parse(body, undefined, undefined, { maxKeys: parameterLimit })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the simple type checker.
|
||||
*
|
||||
* @param {string} type
|
||||
* @return {function}
|
||||
*/
|
||||
|
||||
function typeChecker (type) {
|
||||
return function checkType (req) {
|
||||
return Boolean(typeis(req, type))
|
||||
}
|
||||
}
|
||||
56
api/node_modules/body-parser/package.json
generated
vendored
56
api/node_modules/body-parser/package.json
generated
vendored
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"name": "body-parser",
|
||||
"description": "Node.js body parsing middleware",
|
||||
"version": "1.20.3",
|
||||
"contributors": [
|
||||
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": "expressjs/body-parser",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.5",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.13.0",
|
||||
"raw-body": "2.5.2",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "8.34.0",
|
||||
"eslint-config-standard": "14.1.1",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-markdown": "3.0.0",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-standard": "4.1.0",
|
||||
"methods": "1.1.2",
|
||||
"mocha": "10.2.0",
|
||||
"nyc": "15.1.0",
|
||||
"safe-buffer": "5.2.1",
|
||||
"supertest": "6.3.3"
|
||||
},
|
||||
"files": [
|
||||
"lib/",
|
||||
"LICENSE",
|
||||
"HISTORY.md",
|
||||
"SECURITY.md",
|
||||
"index.js"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "mocha --require test/support/env --reporter spec --check-leaks --bail test/",
|
||||
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
||||
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
||||
}
|
||||
}
|
||||
97
api/node_modules/bytes/History.md
generated
vendored
97
api/node_modules/bytes/History.md
generated
vendored
@@ -1,97 +0,0 @@
|
||||
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]
|
||||
23
api/node_modules/bytes/LICENSE
generated
vendored
23
api/node_modules/bytes/LICENSE
generated
vendored
@@ -1,23 +0,0 @@
|
||||
(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.
|
||||
152
api/node_modules/bytes/Readme.md
generated
vendored
152
api/node_modules/bytes/Readme.md
generated
vendored
@@ -1,152 +0,0 @@
|
||||
# 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
|
||||
170
api/node_modules/bytes/index.js
generated
vendored
170
api/node_modules/bytes/index.js
generated
vendored
@@ -1,170 +0,0 @@
|
||||
/*!
|
||||
* 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);
|
||||
}
|
||||
42
api/node_modules/bytes/package.json
generated
vendored
42
api/node_modules/bytes/package.json
generated
vendored
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
17
api/node_modules/call-bind-apply-helpers/.eslintrc
generated
vendored
17
api/node_modules/call-bind-apply-helpers/.eslintrc
generated
vendored
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"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
api/node_modules/call-bind-apply-helpers/.github/FUNDING.yml
generated
vendored
12
api/node_modules/call-bind-apply-helpers/.github/FUNDING.yml
generated
vendored
@@ -1,12 +0,0 @@
|
||||
# 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']
|
||||
9
api/node_modules/call-bind-apply-helpers/.nycrc
generated
vendored
9
api/node_modules/call-bind-apply-helpers/.nycrc
generated
vendored
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"all": true,
|
||||
"check-coverage": false,
|
||||
"reporter": ["text-summary", "text", "html", "json"],
|
||||
"exclude": [
|
||||
"coverage",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
30
api/node_modules/call-bind-apply-helpers/CHANGELOG.md
generated
vendored
30
api/node_modules/call-bind-apply-helpers/CHANGELOG.md
generated
vendored
@@ -1,30 +0,0 @@
|
||||
# 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)
|
||||
21
api/node_modules/call-bind-apply-helpers/LICENSE
generated
vendored
21
api/node_modules/call-bind-apply-helpers/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
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.
|
||||
62
api/node_modules/call-bind-apply-helpers/README.md
generated
vendored
62
api/node_modules/call-bind-apply-helpers/README.md
generated
vendored
@@ -1,62 +0,0 @@
|
||||
# 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
api/node_modules/call-bind-apply-helpers/actualApply.d.ts
generated
vendored
1
api/node_modules/call-bind-apply-helpers/actualApply.d.ts
generated
vendored
@@ -1 +0,0 @@
|
||||
export = Reflect.apply;
|
||||
10
api/node_modules/call-bind-apply-helpers/actualApply.js
generated
vendored
10
api/node_modules/call-bind-apply-helpers/actualApply.js
generated
vendored
@@ -1,10 +0,0 @@
|
||||
'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
api/node_modules/call-bind-apply-helpers/applyBind.d.ts
generated
vendored
19
api/node_modules/call-bind-apply-helpers/applyBind.d.ts
generated
vendored
@@ -1,19 +0,0 @@
|
||||
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;
|
||||
10
api/node_modules/call-bind-apply-helpers/applyBind.js
generated
vendored
10
api/node_modules/call-bind-apply-helpers/applyBind.js
generated
vendored
@@ -1,10 +0,0 @@
|
||||
'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
api/node_modules/call-bind-apply-helpers/functionApply.d.ts
generated
vendored
1
api/node_modules/call-bind-apply-helpers/functionApply.d.ts
generated
vendored
@@ -1 +0,0 @@
|
||||
export = Function.prototype.apply;
|
||||
4
api/node_modules/call-bind-apply-helpers/functionApply.js
generated
vendored
4
api/node_modules/call-bind-apply-helpers/functionApply.js
generated
vendored
@@ -1,4 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('./functionApply')} */
|
||||
module.exports = Function.prototype.apply;
|
||||
1
api/node_modules/call-bind-apply-helpers/functionCall.d.ts
generated
vendored
1
api/node_modules/call-bind-apply-helpers/functionCall.d.ts
generated
vendored
@@ -1 +0,0 @@
|
||||
export = Function.prototype.call;
|
||||
4
api/node_modules/call-bind-apply-helpers/functionCall.js
generated
vendored
4
api/node_modules/call-bind-apply-helpers/functionCall.js
generated
vendored
@@ -1,4 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('./functionCall')} */
|
||||
module.exports = Function.prototype.call;
|
||||
64
api/node_modules/call-bind-apply-helpers/index.d.ts
generated
vendored
64
api/node_modules/call-bind-apply-helpers/index.d.ts
generated
vendored
@@ -1,64 +0,0 @@
|
||||
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;
|
||||
15
api/node_modules/call-bind-apply-helpers/index.js
generated
vendored
15
api/node_modules/call-bind-apply-helpers/index.js
generated
vendored
@@ -1,15 +0,0 @@
|
||||
'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);
|
||||
};
|
||||
85
api/node_modules/call-bind-apply-helpers/package.json
generated
vendored
85
api/node_modules/call-bind-apply-helpers/package.json
generated
vendored
@@ -1,85 +0,0 @@
|
||||
{
|
||||
"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
api/node_modules/call-bind-apply-helpers/reflectApply.d.ts
generated
vendored
3
api/node_modules/call-bind-apply-helpers/reflectApply.d.ts
generated
vendored
@@ -1,3 +0,0 @@
|
||||
declare const reflectApply: false | typeof Reflect.apply;
|
||||
|
||||
export = reflectApply;
|
||||
4
api/node_modules/call-bind-apply-helpers/reflectApply.js
generated
vendored
4
api/node_modules/call-bind-apply-helpers/reflectApply.js
generated
vendored
@@ -1,4 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('./reflectApply')} */
|
||||
module.exports = typeof Reflect !== 'undefined' && Reflect && Reflect.apply;
|
||||
63
api/node_modules/call-bind-apply-helpers/test/index.js
generated
vendored
63
api/node_modules/call-bind-apply-helpers/test/index.js
generated
vendored
@@ -1,63 +0,0 @@
|
||||
'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();
|
||||
});
|
||||
9
api/node_modules/call-bind-apply-helpers/tsconfig.json
generated
vendored
9
api/node_modules/call-bind-apply-helpers/tsconfig.json
generated
vendored
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "@ljharb/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "es2021",
|
||||
},
|
||||
"exclude": [
|
||||
"coverage",
|
||||
],
|
||||
}
|
||||
13
api/node_modules/call-bound/.eslintrc
generated
vendored
13
api/node_modules/call-bound/.eslintrc
generated
vendored
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
|
||||
"extends": "@ljharb",
|
||||
|
||||
"rules": {
|
||||
"new-cap": [2, {
|
||||
"capIsNewExceptions": [
|
||||
"GetIntrinsic",
|
||||
],
|
||||
}],
|
||||
},
|
||||
}
|
||||
12
api/node_modules/call-bound/.github/FUNDING.yml
generated
vendored
12
api/node_modules/call-bound/.github/FUNDING.yml
generated
vendored
@@ -1,12 +0,0 @@
|
||||
# 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']
|
||||
9
api/node_modules/call-bound/.nycrc
generated
vendored
9
api/node_modules/call-bound/.nycrc
generated
vendored
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"all": true,
|
||||
"check-coverage": false,
|
||||
"reporter": ["text-summary", "text", "html", "json"],
|
||||
"exclude": [
|
||||
"coverage",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
42
api/node_modules/call-bound/CHANGELOG.md
generated
vendored
42
api/node_modules/call-bound/CHANGELOG.md
generated
vendored
@@ -1,42 +0,0 @@
|
||||
# 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)
|
||||
21
api/node_modules/call-bound/LICENSE
generated
vendored
21
api/node_modules/call-bound/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
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.
|
||||
53
api/node_modules/call-bound/README.md
generated
vendored
53
api/node_modules/call-bound/README.md
generated
vendored
@@ -1,53 +0,0 @@
|
||||
# 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
|
||||
94
api/node_modules/call-bound/index.d.ts
generated
vendored
94
api/node_modules/call-bound/index.d.ts
generated
vendored
@@ -1,94 +0,0 @@
|
||||
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;
|
||||
19
api/node_modules/call-bound/index.js
generated
vendored
19
api/node_modules/call-bound/index.js
generated
vendored
@@ -1,19 +0,0 @@
|
||||
'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;
|
||||
};
|
||||
99
api/node_modules/call-bound/package.json
generated
vendored
99
api/node_modules/call-bound/package.json
generated
vendored
@@ -1,99 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
61
api/node_modules/call-bound/test/index.js
generated
vendored
61
api/node_modules/call-bound/test/index.js
generated
vendored
@@ -1,61 +0,0 @@
|
||||
'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();
|
||||
});
|
||||
10
api/node_modules/call-bound/tsconfig.json
generated
vendored
10
api/node_modules/call-bound/tsconfig.json
generated
vendored
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "@ljharb/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"lib": ["es2024"],
|
||||
},
|
||||
"exclude": [
|
||||
"coverage",
|
||||
],
|
||||
}
|
||||
60
api/node_modules/content-disposition/HISTORY.md
generated
vendored
60
api/node_modules/content-disposition/HISTORY.md
generated
vendored
@@ -1,60 +0,0 @@
|
||||
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
|
||||
22
api/node_modules/content-disposition/LICENSE
generated
vendored
22
api/node_modules/content-disposition/LICENSE
generated
vendored
@@ -1,22 +0,0 @@
|
||||
(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.
|
||||
142
api/node_modules/content-disposition/README.md
generated
vendored
142
api/node_modules/content-disposition/README.md
generated
vendored
@@ -1,142 +0,0 @@
|
||||
# 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
|
||||
var 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
|
||||
var 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
|
||||
var contentDisposition = require('content-disposition')
|
||||
var destroy = require('destroy')
|
||||
var fs = require('fs')
|
||||
var http = require('http')
|
||||
var onFinished = require('on-finished')
|
||||
|
||||
var 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
|
||||
var 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.svg
|
||||
[npm-url]: https://npmjs.org/package/content-disposition
|
||||
[node-version-image]: https://img.shields.io/node/v/content-disposition.svg
|
||||
[node-version-url]: https://nodejs.org/en/download
|
||||
[coveralls-image]: https://img.shields.io/coveralls/jshttp/content-disposition.svg
|
||||
[coveralls-url]: https://coveralls.io/r/jshttp/content-disposition?branch=master
|
||||
[downloads-image]: https://img.shields.io/npm/dm/content-disposition.svg
|
||||
[downloads-url]: https://npmjs.org/package/content-disposition
|
||||
[github-actions-ci-image]: https://img.shields.io/github/workflow/status/jshttp/content-disposition/ci/master?label=ci
|
||||
[github-actions-ci-url]: https://github.com/jshttp/content-disposition?query=workflow%3Aci
|
||||
458
api/node_modules/content-disposition/index.js
generated
vendored
458
api/node_modules/content-disposition/index.js
generated
vendored
@@ -1,458 +0,0 @@
|
||||
/*!
|
||||
* 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
|
||||
var Buffer = require('safe-buffer').Buffer
|
||||
|
||||
/**
|
||||
* 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.substr(-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':
|
||||
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].substr(-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
|
||||
.substr(1, value.length - 2)
|
||||
.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
|
||||
}
|
||||
44
api/node_modules/content-disposition/package.json
generated
vendored
44
api/node_modules/content-disposition/package.json
generated
vendored
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "content-disposition",
|
||||
"description": "Create and parse Content-Disposition header",
|
||||
"version": "0.5.4",
|
||||
"author": "Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"content-disposition",
|
||||
"http",
|
||||
"rfc6266",
|
||||
"res"
|
||||
],
|
||||
"repository": "jshttp/content-disposition",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"deep-equal": "1.0.1",
|
||||
"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",
|
||||
"istanbul": "0.4.5",
|
||||
"mocha": "9.1.3"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"HISTORY.md",
|
||||
"README.md",
|
||||
"index.js"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "mocha --reporter spec --bail --check-leaks test/",
|
||||
"test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/",
|
||||
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/"
|
||||
}
|
||||
}
|
||||
29
api/node_modules/content-type/HISTORY.md
generated
vendored
29
api/node_modules/content-type/HISTORY.md
generated
vendored
@@ -1,29 +0,0 @@
|
||||
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`
|
||||
22
api/node_modules/content-type/LICENSE
generated
vendored
22
api/node_modules/content-type/LICENSE
generated
vendored
@@ -1,22 +0,0 @@
|
||||
(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.
|
||||
94
api/node_modules/content-type/README.md
generated
vendored
94
api/node_modules/content-type/README.md
generated
vendored
@@ -1,94 +0,0 @@
|
||||
# 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
|
||||
225
api/node_modules/content-type/index.js
generated
vendored
225
api/node_modules/content-type/index.js
generated
vendored
@@ -1,225 +0,0 @@
|
||||
/*!
|
||||
* 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
|
||||
}
|
||||
42
api/node_modules/content-type/package.json
generated
vendored
42
api/node_modules/content-type/package.json
generated
vendored
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user