diff --git a/README.md b/README.md index 0552706..47dea26 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,35 @@ cp .env.example .env npm install npm run dev ``` + +## Deploy test server +Target deploy: +- domain `wa.iptek.co` +- app port `127.0.0.1:3007` +- reverse proxy `nginx` + +Contoh langkah di server: +```bash +cd /var/www +git clone https://git.iptek.co/wirabasalamah/webhook-test.git wa-test-nextjs +cd wa-test-nextjs +cp .env.example .env +npm ci +npm run build +npx pm2 start ecosystem.config.cjs +npx pm2 save +``` + +Set `.env` minimal: +```bash +NEXT_PUBLIC_APP_URL=https://wa.iptek.co +WHATSAPP_VERIFY_TOKEN=... +WHATSAPP_ACCESS_TOKEN=... +WHATSAPP_PHONE_NUMBER_ID=... +WA_TEST_NUMBER=... +WA_TEST_LOGIN_USERNAME=... +WA_TEST_LOGIN_PASSWORD=... +SESSION_SECRET=... +``` + +Contoh config nginx ada di `deploy/nginx/wa.iptek.co.conf`. diff --git a/deploy/nginx/wa.iptek.co.conf b/deploy/nginx/wa.iptek.co.conf new file mode 100644 index 0000000..a90a0a2 --- /dev/null +++ b/deploy/nginx/wa.iptek.co.conf @@ -0,0 +1,19 @@ +server { + listen 80; + listen [::]:80; + server_name wa.iptek.co; + + client_max_body_size 10m; + + location / { + proxy_pass http://127.0.0.1:3007; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 60s; + } +} diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs new file mode 100644 index 0000000..3ebcf36 --- /dev/null +++ b/ecosystem.config.cjs @@ -0,0 +1,13 @@ +module.exports = { + apps: [ + { + name: 'wa-test-nextjs', + script: 'npm', + args: 'run start:3007', + cwd: '/var/www/wa-test-nextjs', + env: { + NODE_ENV: 'production', + }, + }, + ], +} diff --git a/next-env.d.ts b/next-env.d.ts index 9f14d85..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,4 +1,6 @@ /// /// +import "./.next/types/routes.d.ts"; -// This file is auto-generated by Next.js +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/package.json b/package.json index 96fac1d..dd5787f 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,13 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "start:3007": "next start -p 3007 -H 127.0.0.1", + "lint": "eslint ." }, "dependencies": { - "next": "16.0.0", - "react": "19.2.0", - "react-dom": "19.2.0" + "next": "16.2.6", + "react": "19.2.4", + "react-dom": "19.2.4" }, "devDependencies": { "typescript": "^5", @@ -19,6 +20,6 @@ "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", - "eslint-config-next": "16.0.0" + "eslint-config-next": "16.2.6" } } diff --git a/src/app/test/page.tsx b/src/app/test/page.tsx index 0f354a4..034d33f 100644 --- a/src/app/test/page.tsx +++ b/src/app/test/page.tsx @@ -8,7 +8,7 @@ export default async function TestPage({ searchParams }: { searchParams: Promise redirect('/login') } - const logs = readJsonFile('webhook-logs.json', []) + const logs = readJsonFile('webhook-logs.json', []) const state = readJsonFile>('flow-state.json', {}) const params = await searchParams diff --git a/src/app/webhook-logs/page.tsx b/src/app/webhook-logs/page.tsx index cf1235d..fb75299 100644 --- a/src/app/webhook-logs/page.tsx +++ b/src/app/webhook-logs/page.tsx @@ -8,7 +8,7 @@ export default async function WebhookLogsPage() { redirect('/login') } - const logs = readJsonFile('webhook-logs.json', []) + const logs = readJsonFile('webhook-logs.json', []) return (
diff --git a/src/lib/whatsapp.ts b/src/lib/whatsapp.ts index a90e34e..b283e3c 100644 --- a/src/lib/whatsapp.ts +++ b/src/lib/whatsapp.ts @@ -1,6 +1,25 @@ import { appendLog, readJsonFile, writeJsonFile } from './storage' type FlowState = Record +type WebhookValue = { + messages?: IncomingMessage[] + statuses?: unknown[] +} +type WebhookChange = { + value?: WebhookValue +} +type WebhookEntry = { + changes?: WebhookChange[] +} +type WebhookPayload = { + entry?: WebhookEntry[] +} +type IncomingMessage = { + from?: string + text?: { + body?: string + } +} function normalizeNumber(number?: string | null) { return (number || '').replace(/\D+/g, '') @@ -37,7 +56,7 @@ export async function sendTextMessage(to: string, message: string) { return payload } -export async function handleWebhook(payload: any) { +export async function handleWebhook(payload: WebhookPayload) { appendLog({ type: 'webhook_received', payload, created_at: new Date().toISOString() }) for (const entry of payload.entry || []) { @@ -55,7 +74,7 @@ export async function handleWebhook(payload: any) { } } -async function handleIncomingMessage(message: any) { +async function handleIncomingMessage(message: IncomingMessage) { const from = message.from || '' appendLog({ type: 'incoming_message', from, message, created_at: new Date().toISOString() }) diff --git a/tsconfig.json b/tsconfig.json index caa4325..5d998c4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": false, "skipLibCheck": true, "strict": true, @@ -11,13 +15,27 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", + "jsx": "react-jsx", "incremental": true, - "plugins": [{ "name": "next" }], + "plugins": [ + { + "name": "next" + } + ], "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] }