feat: import smsreceiver workerized code with full README
This commit is contained in:
56
worker/migrate_sqlite_to_d1.py
Normal file
56
worker/migrate_sqlite_to_d1.py
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import json
|
||||
import sqlite3
|
||||
import time
|
||||
import requests
|
||||
|
||||
D1_DATABASE_ID = os.getenv('D1_DATABASE_ID', '')
|
||||
CF_API_TOKEN = os.getenv('CF_API_TOKEN', '')
|
||||
CF_ACCOUNT_ID = os.getenv('CF_ACCOUNT_ID', '')
|
||||
SQLITE_PATH = os.getenv('SQLITE_PATH', 'sms_receiver_go.db')
|
||||
BATCH_SIZE = int(os.getenv('BATCH_SIZE', '50'))
|
||||
|
||||
if not D1_DATABASE_ID or not CF_API_TOKEN or not CF_ACCOUNT_ID:
|
||||
raise SystemExit('Missing env: D1_DATABASE_ID / CF_API_TOKEN / CF_ACCOUNT_ID')
|
||||
|
||||
api = f"https://api.cloudflare.com/client/v4/accounts/{CF_ACCOUNT_ID}/d1/database/{D1_DATABASE_ID}/query"
|
||||
headers = {"Authorization": f"Bearer {CF_API_TOKEN}", "Content-Type": "application/json"}
|
||||
|
||||
conn = sqlite3.connect(SQLITE_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
TABLES = ['sms_messages', 'receive_logs']
|
||||
|
||||
|
||||
def post_query(sql, params=None):
|
||||
payload = {"sql": sql}
|
||||
if params is not None:
|
||||
payload["params"] = params
|
||||
r = requests.post(api, headers=headers, data=json.dumps(payload))
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
def migrate_table(table):
|
||||
cur = conn.cursor()
|
||||
cur.execute(f"SELECT * FROM {table}")
|
||||
rows = cur.fetchall()
|
||||
print(f"{table}: {len(rows)} rows")
|
||||
|
||||
batch = 0
|
||||
for row in rows:
|
||||
cols = row.keys()
|
||||
placeholders = ','.join(['?'] * len(cols))
|
||||
sql = f"INSERT INTO {table} ({','.join(cols)}) VALUES ({placeholders})"
|
||||
params = [row[c] for c in cols]
|
||||
post_query(sql, params)
|
||||
batch += 1
|
||||
if batch >= BATCH_SIZE:
|
||||
time.sleep(0.2)
|
||||
batch = 0
|
||||
|
||||
|
||||
for table in TABLES:
|
||||
migrate_table(table)
|
||||
|
||||
print('done')
|
||||
1531
worker/package-lock.json
generated
Normal file
1531
worker/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
worker/package.json
Normal file
17
worker/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "smsreceiver-worker-api",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wrangler dev",
|
||||
"deploy": "wrangler deploy"
|
||||
},
|
||||
"dependencies": {
|
||||
"hono": "^4.6.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.6.3",
|
||||
"wrangler": "^4.7.0"
|
||||
}
|
||||
}
|
||||
31
worker/schema.sql
Normal file
31
worker/schema.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
CREATE TABLE sms_messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
from_number TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
device_info TEXT,
|
||||
sim_info TEXT,
|
||||
sign_verified INTEGER,
|
||||
ip_address TEXT,
|
||||
created_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE receive_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
from_number TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
sign TEXT,
|
||||
sign_valid INTEGER,
|
||||
ip_address TEXT,
|
||||
status TEXT NOT NULL,
|
||||
error_message TEXT,
|
||||
created_at TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_messages_from ON sms_messages(from_number);
|
||||
CREATE INDEX idx_messages_timestamp ON sms_messages(timestamp);
|
||||
CREATE INDEX idx_messages_created ON sms_messages(created_at);
|
||||
|
||||
CREATE INDEX idx_logs_created ON receive_logs(created_at);
|
||||
CREATE INDEX idx_logs_status ON receive_logs(status);
|
||||
47
worker/src/index.ts
Normal file
47
worker/src/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Hono } from 'hono'
|
||||
import { authRoutes } from './routes/auth'
|
||||
import { messagesRoutes } from './routes/messages'
|
||||
import { logsRoutes } from './routes/logs'
|
||||
import { receiveRoutes } from './routes/receive'
|
||||
import { statsRoutes } from './routes/stats'
|
||||
|
||||
export type Env = {
|
||||
DB: D1Database
|
||||
ADMIN_USER: string
|
||||
ADMIN_PASS_HASH: string
|
||||
SESSION_SECRET: string
|
||||
API_TOKEN: string
|
||||
HMAC_SECRET: string
|
||||
SIGN_VERIFY: string
|
||||
SIGN_MAX_AGE_MS: string
|
||||
CORS_ORIGIN: string
|
||||
}
|
||||
|
||||
const app = new Hono<{ Bindings: Env }>()
|
||||
|
||||
app.use('*', async (c, next) => {
|
||||
const reqOrigin = c.req.header('Origin')
|
||||
const allowOrigin = reqOrigin || c.env.CORS_ORIGIN || '*'
|
||||
|
||||
c.header('Access-Control-Allow-Origin', allowOrigin)
|
||||
c.header('Vary', 'Origin')
|
||||
c.header('Access-Control-Allow-Credentials', 'true')
|
||||
c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||||
c.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
|
||||
|
||||
if (c.req.method === 'OPTIONS') {
|
||||
return c.body(null, 204)
|
||||
}
|
||||
|
||||
await next()
|
||||
})
|
||||
|
||||
app.route('/api/auth', authRoutes)
|
||||
app.route('/api/messages', messagesRoutes)
|
||||
app.route('/api/logs', logsRoutes)
|
||||
app.route('/api/receive', receiveRoutes)
|
||||
app.route('/api/stats', statsRoutes)
|
||||
|
||||
app.get('/api/health', (c) => c.json({ ok: true }))
|
||||
|
||||
export default app
|
||||
10
worker/src/middlewares/auth.ts
Normal file
10
worker/src/middlewares/auth.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createMiddleware } from 'hono/factory'
|
||||
import type { Env } from '../index'
|
||||
import { verifySessionCookie } from '../utils/session'
|
||||
|
||||
export const requireAuth = createMiddleware<{ Bindings: Env }>(async (c, next) => {
|
||||
const cookie = c.req.header('Cookie') || ''
|
||||
const ok = verifySessionCookie(cookie, c.env.SESSION_SECRET)
|
||||
if (!ok) return c.json({ success: false, error: '未授权' }, 401)
|
||||
await next()
|
||||
})
|
||||
33
worker/src/routes/auth.ts
Normal file
33
worker/src/routes/auth.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Hono } from 'hono'
|
||||
import type { Env } from '../index'
|
||||
import { createSessionCookie, clearSessionCookie, hashPassword } from '../utils/session'
|
||||
|
||||
export const authRoutes = new Hono<{ Bindings: Env }>()
|
||||
|
||||
authRoutes.post('/login', async (c) => {
|
||||
const body = await c.req.json().catch(() => ({})) as any
|
||||
const username = body.username || ''
|
||||
const password = body.password || ''
|
||||
|
||||
if (!username || !password) {
|
||||
return c.json({ success: false, error: '缺少用户名或密码' }, 400)
|
||||
}
|
||||
|
||||
if (username !== c.env.ADMIN_USER) {
|
||||
return c.json({ success: false, error: '用户名或密码错误' }, 401)
|
||||
}
|
||||
|
||||
const passHash = hashPassword(password, c.env.SESSION_SECRET)
|
||||
if (passHash !== c.env.ADMIN_PASS_HASH) {
|
||||
return c.json({ success: false, error: '用户名或密码错误' }, 401)
|
||||
}
|
||||
|
||||
const cookie = createSessionCookie(username, c.env.SESSION_SECRET)
|
||||
c.header('Set-Cookie', cookie)
|
||||
return c.json({ success: true })
|
||||
})
|
||||
|
||||
authRoutes.post('/logout', async (c) => {
|
||||
c.header('Set-Cookie', clearSessionCookie())
|
||||
return c.json({ success: true })
|
||||
})
|
||||
29
worker/src/routes/logs.ts
Normal file
29
worker/src/routes/logs.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Hono } from 'hono'
|
||||
import type { Env } from '../index'
|
||||
import { requireAuth } from '../middlewares/auth'
|
||||
|
||||
export const logsRoutes = new Hono<{ Bindings: Env }>()
|
||||
|
||||
logsRoutes.get('/', requireAuth, async (c) => {
|
||||
const page = Math.max(1, Number(c.req.query('page') || 1))
|
||||
const limit = Math.min(100, Math.max(1, Number(c.req.query('limit') || 20)))
|
||||
const status = (c.req.query('status') || '').trim()
|
||||
const offset = (page - 1) * limit
|
||||
|
||||
let where = ''
|
||||
const params: any[] = []
|
||||
if (status) {
|
||||
where = 'WHERE status = ?'
|
||||
params.push(status)
|
||||
}
|
||||
|
||||
const totalStmt = c.env.DB.prepare(`SELECT COUNT(*) as cnt FROM receive_logs ${where}`)
|
||||
const total = (await totalStmt.bind(...params).first() as any)?.cnt || 0
|
||||
|
||||
const stmt = c.env.DB.prepare(
|
||||
`SELECT * FROM receive_logs ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
||||
)
|
||||
const rows = await stmt.bind(...params, limit, offset).all()
|
||||
|
||||
return c.json({ success: true, data: rows.results || [], total, page, limit })
|
||||
})
|
||||
70
worker/src/routes/messages.ts
Normal file
70
worker/src/routes/messages.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Hono } from 'hono'
|
||||
import type { Env } from '../index'
|
||||
import { requireAuth } from '../middlewares/auth'
|
||||
|
||||
export const messagesRoutes = new Hono<{ Bindings: Env }>()
|
||||
|
||||
messagesRoutes.get('/', requireAuth, async (c) => {
|
||||
const page = Math.max(1, Number(c.req.query('page') || 1))
|
||||
const limit = Math.min(100, Math.max(1, Number(c.req.query('limit') || 20)))
|
||||
const q = (c.req.query('q') || '').trim()
|
||||
const from = (c.req.query('from') || '').trim()
|
||||
const startTs = Number(c.req.query('start_ts') || 0)
|
||||
const endTs = Number(c.req.query('end_ts') || 0)
|
||||
const offset = (page - 1) * limit
|
||||
|
||||
const whereParts: string[] = []
|
||||
const params: any[] = []
|
||||
|
||||
if (from) {
|
||||
whereParts.push('from_number = ?')
|
||||
params.push(from)
|
||||
}
|
||||
|
||||
if (q) {
|
||||
whereParts.push('(from_number LIKE ? OR content LIKE ?)')
|
||||
params.push(`%${q}%`, `%${q}%`)
|
||||
}
|
||||
|
||||
if (startTs > 0) {
|
||||
whereParts.push('timestamp >= ?')
|
||||
params.push(startTs)
|
||||
}
|
||||
|
||||
if (endTs > 0) {
|
||||
whereParts.push('timestamp <= ?')
|
||||
params.push(endTs)
|
||||
}
|
||||
|
||||
const where = whereParts.length ? `WHERE ${whereParts.join(' AND ')}` : ''
|
||||
|
||||
const totalStmt = c.env.DB.prepare(`SELECT COUNT(*) as cnt FROM sms_messages ${where}`)
|
||||
const total = (await totalStmt.bind(...params).first() as any)?.cnt || 0
|
||||
|
||||
const stmt = c.env.DB.prepare(
|
||||
`SELECT * FROM sms_messages ${where} ORDER BY timestamp DESC LIMIT ? OFFSET ?`
|
||||
)
|
||||
const rows = await stmt.bind(...params, limit, offset).all()
|
||||
|
||||
const fromRows = await c.env.DB.prepare(
|
||||
`SELECT DISTINCT from_number FROM sms_messages ORDER BY from_number`
|
||||
).all()
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: rows.results || [],
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
from_numbers: (fromRows.results || []).map((r: any) => r.from_number),
|
||||
})
|
||||
})
|
||||
|
||||
messagesRoutes.get('/:id', requireAuth, async (c) => {
|
||||
const id = c.req.param('id')
|
||||
const stmt = c.env.DB.prepare(`SELECT * FROM sms_messages WHERE id = ?`)
|
||||
const row = await stmt.bind(id).first()
|
||||
if (!row) return c.json({ success: false, error: '消息不存在' }, 404)
|
||||
return c.json({ success: true, data: row })
|
||||
})
|
||||
|
||||
99
worker/src/routes/receive.ts
Normal file
99
worker/src/routes/receive.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { Hono } from 'hono'
|
||||
import type { Env } from '../index'
|
||||
import { getClientIP, nowMs } from '../utils/common'
|
||||
import { verifySign } from '../utils/sign'
|
||||
import { insertMessageAndLog } from '../utils/db'
|
||||
|
||||
export const receiveRoutes = new Hono<{ Bindings: Env }>()
|
||||
|
||||
receiveRoutes.post('/', async (c) => {
|
||||
const contentType = c.req.header('content-type') || ''
|
||||
|
||||
let form: Record<string, string> = {}
|
||||
if (contentType.includes('application/json')) {
|
||||
const body = (await c.req.json().catch(() => ({}))) as any
|
||||
form = { ...body }
|
||||
} else {
|
||||
const body = await c.req.parseBody()
|
||||
for (const [k, v] of Object.entries(body)) {
|
||||
form[k] = Array.isArray(v) ? String(v[0]) : String(v ?? '')
|
||||
}
|
||||
}
|
||||
|
||||
const from = form.from || ''
|
||||
const msg = form.content || ''
|
||||
if (!from || !msg) {
|
||||
return c.json({ success: false, error: `缺少必填参数 (from: '${from}', content: '${msg}')` }, 400)
|
||||
}
|
||||
|
||||
const timestamp = form.timestamp ? Number(form.timestamp) : nowMs()
|
||||
const sign = form.sign || ''
|
||||
const device = form.device || ''
|
||||
const sim = form.sim || ''
|
||||
const token = c.req.query('token') || form.token || ''
|
||||
const ip = getClientIP(c.req.raw.headers)
|
||||
|
||||
let signValid: number | null = 1
|
||||
let status = 'success'
|
||||
let errorMessage: string | null = null
|
||||
|
||||
if (c.env.SIGN_VERIFY === 'true' && token) {
|
||||
if (c.env.API_TOKEN && token !== c.env.API_TOKEN) {
|
||||
signValid = 0
|
||||
status = 'error'
|
||||
errorMessage = '无效的 token'
|
||||
} else if (!c.env.HMAC_SECRET) {
|
||||
// 对齐旧版:token 存在但 secret 为空时,跳过签名验证
|
||||
signValid = 1
|
||||
} else {
|
||||
const maxAge = Number(c.env.SIGN_MAX_AGE_MS || 300000)
|
||||
const now = nowMs()
|
||||
const diff = now - timestamp
|
||||
if (diff > maxAge) {
|
||||
signValid = 0
|
||||
status = 'error'
|
||||
errorMessage = `时间戳过期(差异: ${(diff / 1000).toFixed(1)} 秒)`
|
||||
} else {
|
||||
const valid = verifySign(timestamp, sign, c.env.HMAC_SECRET)
|
||||
signValid = valid ? 1 : 0
|
||||
if (!valid) {
|
||||
status = 'error'
|
||||
errorMessage = '签名不匹配'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const nowIso = new Date().toISOString()
|
||||
const messageRow = {
|
||||
from_number: from,
|
||||
content: msg,
|
||||
timestamp,
|
||||
device_info: device || null,
|
||||
sim_info: sim || null,
|
||||
sign_verified: signValid,
|
||||
ip_address: ip,
|
||||
created_at: nowIso,
|
||||
}
|
||||
|
||||
const logRow = {
|
||||
from_number: from,
|
||||
content: msg,
|
||||
timestamp,
|
||||
sign: sign || null,
|
||||
sign_valid: signValid,
|
||||
ip_address: ip,
|
||||
status,
|
||||
error_message: errorMessage,
|
||||
created_at: nowIso,
|
||||
}
|
||||
|
||||
try {
|
||||
await insertMessageAndLog(c.env, messageRow, logRow)
|
||||
} catch {
|
||||
return c.json({ success: false, error: '保存消息失败' }, 500)
|
||||
}
|
||||
|
||||
return c.json({ success: true, message: '短信已接收' })
|
||||
})
|
||||
|
||||
24
worker/src/routes/stats.ts
Normal file
24
worker/src/routes/stats.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Hono } from 'hono'
|
||||
import type { Env } from '../index'
|
||||
import { requireAuth } from '../middlewares/auth'
|
||||
|
||||
export const statsRoutes = new Hono<{ Bindings: Env }>()
|
||||
|
||||
statsRoutes.get('/', requireAuth, async (c) => {
|
||||
const totalRow = await c.env.DB.prepare(`SELECT COUNT(*) as cnt FROM sms_messages`).first<{ cnt: number }>()
|
||||
const todayRow = await c.env.DB.prepare(`
|
||||
SELECT COUNT(*) as cnt
|
||||
FROM sms_messages
|
||||
WHERE datetime(timestamp/1000, 'unixepoch') >= date('now')
|
||||
`).first<{ cnt: number }>()
|
||||
const failRow = await c.env.DB.prepare(`SELECT COUNT(*) as cnt FROM receive_logs WHERE status != 'success'`).first<{ cnt: number }>()
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: {
|
||||
total: totalRow?.cnt || 0,
|
||||
today: todayRow?.cnt || 0,
|
||||
failed: failRow?.cnt || 0,
|
||||
},
|
||||
})
|
||||
})
|
||||
9
worker/src/utils/common.ts
Normal file
9
worker/src/utils/common.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export function getClientIP(headers: Headers) {
|
||||
const forwarded = headers.get('X-Forwarded-For')
|
||||
if (forwarded) return forwarded.split(',')[0].trim()
|
||||
return headers.get('CF-Connecting-IP') || ''
|
||||
}
|
||||
|
||||
export function nowMs() {
|
||||
return Date.now()
|
||||
}
|
||||
40
worker/src/utils/db.ts
Normal file
40
worker/src/utils/db.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { Env } from '../index'
|
||||
|
||||
export async function insertMessageAndLog(env: Env, msg: any, log: any) {
|
||||
const stmtMsg = env.DB.prepare(
|
||||
`INSERT INTO sms_messages (from_number, content, timestamp, device_info, sim_info, sign_verified, ip_address, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
const stmtLog = env.DB.prepare(
|
||||
`INSERT INTO receive_logs (from_number, content, timestamp, sign, sign_valid, ip_address, status, error_message, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
|
||||
const now = new Date().toISOString()
|
||||
const batch = env.DB.batch([
|
||||
stmtMsg.bind(
|
||||
msg.from_number,
|
||||
msg.content,
|
||||
msg.timestamp,
|
||||
msg.device_info,
|
||||
msg.sim_info,
|
||||
msg.sign_verified,
|
||||
msg.ip_address,
|
||||
msg.created_at || now
|
||||
),
|
||||
stmtLog.bind(
|
||||
log.from_number,
|
||||
log.content,
|
||||
log.timestamp,
|
||||
log.sign,
|
||||
log.sign_valid,
|
||||
log.ip_address,
|
||||
log.status,
|
||||
log.error_message,
|
||||
log.created_at || now
|
||||
),
|
||||
])
|
||||
|
||||
const results = await batch
|
||||
return results
|
||||
}
|
||||
39
worker/src/utils/session.ts
Normal file
39
worker/src/utils/session.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { randomBytes, createHmac, timingSafeEqual } from 'node:crypto'
|
||||
|
||||
const COOKIE_NAME = 'sms_session'
|
||||
|
||||
export function hashPassword(password: string, secret: string) {
|
||||
const h = createHmac('sha256', secret)
|
||||
h.update(password)
|
||||
return h.digest('hex')
|
||||
}
|
||||
|
||||
export function createSessionCookie(username: string, secret: string) {
|
||||
const nonce = randomBytes(16).toString('hex')
|
||||
const payload = `${username}.${Date.now()}.${nonce}`
|
||||
const sig = createHmac('sha256', secret).update(payload).digest('hex')
|
||||
return `${COOKIE_NAME}=${payload}.${sig}; HttpOnly; Path=/; SameSite=None; Secure`
|
||||
}
|
||||
|
||||
export function clearSessionCookie() {
|
||||
return `${COOKIE_NAME}=; HttpOnly; Path=/; Max-Age=0; SameSite=None; Secure`
|
||||
}
|
||||
|
||||
export function verifySessionCookie(cookieHeader: string, secret: string) {
|
||||
const cookie = parseCookie(cookieHeader, COOKIE_NAME)
|
||||
if (!cookie) return false
|
||||
const parts = cookie.split('.')
|
||||
if (parts.length < 4) return false
|
||||
const sig = parts.pop() as string
|
||||
const payload = parts.join('.')
|
||||
const expected = createHmac('sha256', secret).update(payload).digest('hex')
|
||||
return timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
|
||||
}
|
||||
|
||||
function parseCookie(header: string, key: string) {
|
||||
const items = header.split(';').map((v) => v.trim())
|
||||
for (const item of items) {
|
||||
if (item.startsWith(key + '=')) return item.slice(key.length + 1)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
14
worker/src/utils/sign.ts
Normal file
14
worker/src/utils/sign.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createHmac } from 'node:crypto'
|
||||
|
||||
export function generateSign(timestamp: number, secret: string) {
|
||||
const stringToSign = `${timestamp}\n${secret}`
|
||||
const hmac = createHmac('sha256', secret)
|
||||
hmac.update(stringToSign)
|
||||
const base64 = hmac.digest('base64')
|
||||
return encodeURIComponent(base64)
|
||||
}
|
||||
|
||||
export function verifySign(timestamp: number, sign: string, secret: string) {
|
||||
const expected = generateSign(timestamp, secret)
|
||||
return expected === sign
|
||||
}
|
||||
10
worker/tsconfig.json
Normal file
10
worker/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["@cloudflare/workers-types"]
|
||||
}
|
||||
}
|
||||
19
worker/wrangler.toml
Normal file
19
worker/wrangler.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
name = "smsreceiver-worker-api"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2026-03-22"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
|
||||
[vars]
|
||||
ADMIN_USER = "admin"
|
||||
ADMIN_PASS_HASH = "b8e450efa9c66b2b50ec8a4d7eb51213d7fa3395e107575881ad999ee92744b5"
|
||||
SESSION_SECRET = "1e81b5f9e5a695eba01e996b14871db8899b08e111cf8252df8aa4c91d1c7144"
|
||||
API_TOKEN = "default_token"
|
||||
HMAC_SECRET = ""
|
||||
SIGN_VERIFY = "true"
|
||||
SIGN_MAX_AGE_MS = "3600000"
|
||||
CORS_ORIGIN = "*"
|
||||
|
||||
[[d1_databases]]
|
||||
binding = "DB"
|
||||
database_name = "smsreceiver"
|
||||
database_id = "50bd73ba-218c-4eb6-948a-9014a8892575"
|
||||
Reference in New Issue
Block a user