feat(CF): 现已支持部署到CloudFlare-Workers
This commit is contained in:
@@ -6,11 +6,9 @@
|
||||
},
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"mcp__gemini__gemini",
|
||||
"mcp__codex__codex",
|
||||
"Bash(npm run build)",
|
||||
"Bash(npm install react-globe.gl three)"
|
||||
"mcp__gemini__gemini"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -173,3 +173,6 @@ dist
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
.claude
|
||||
.idea
|
||||
.wrangler
|
||||
63
.idea/inspectionProfiles/Project_Default.xml
generated
63
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,5 +1,68 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="AliAccessStaticViaInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliArrayNamingShouldHaveBracket" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliControlFlowStatementWithoutBraces" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliDeprecation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliEqualsAvoidNull" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliLongLiteralsEndingWithLowercaseL" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliMissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliWrapperTypeEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAbstractClassShouldStartWithAbstractNaming" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAbstractMethodOrInterfaceMethodMustUseJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidApacheBeanUtilsCopy" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidCallStaticSimpleDateFormat" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidCommentBehindStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidComplexCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidConcurrentCompetitionRandom" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidDoubleOrFloatEqualCompare" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidManuallyCreateThread" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidMissUseOfMathRandom" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidNegationOperator" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidNewDateGetTime" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidPatternCompileInMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidReturnInFinally" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidStartWithDollarAndUnderLineNaming" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaAvoidUseTimer" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaBigDecimalAvoidDoubleConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaBooleanPropertyShouldNotStartWithIs" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaClassCastExceptionWithSubListToArrayList" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaClassCastExceptionWithToArray" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaClassMustHaveAuthor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaClassNamingShouldBeCamel" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaCollectionInitShouldAssignCapacity" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaCommentsMustBeJavadocFormat" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaConcurrentExceptionWithModifyOriginSubList" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaConstantFieldShouldBeUpperCase" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaCountDownShouldInFinally" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaDontModifyInForeachCircle" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaEnumConstantsMustHaveComment" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaExceptionClassShouldEndWithException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaIbatisMethodQueryForList" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaLockShouldWithTryFinally" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaLowerCamelCaseVariableNaming" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaMethodReturnWrapperType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaMethodTooLong" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaPackageNaming" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaPojoMustOverrideToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaPojoMustUsePrimitiveField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaPojoNoDefaultValue" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaRemoveCommentedCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaServiceOrDaoClassShouldEndWithImpl" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaSneakyThrowsWithoutExceptionType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaStringConcat" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaSwitchExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaTestClassShouldEndWithTestNaming" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaThreadLocalShouldRemove" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaThreadPoolCreation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaThreadShouldSetName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaTransactionMustHaveRollback" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaUndefineMagicConstant" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaUnsupportedExceptionWithModifyAsList" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaUseQuietReferenceNotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AlibabaUseRightCaseForDateFormat" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MapOrSetKeyShouldOverrideHashCodeEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
2
.idea/modules.xml
generated
2
.idea/modules.xml
generated
@@ -2,7 +2,7 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/LatencyTest.iml" filepath="$PROJECT_DIR$/.idea/LatencyTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/LatencyTest-CF.iml" filepath="$PROJECT_DIR$/.idea/LatencyTest-CF.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
2182
package-lock.json
generated
2182
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -10,8 +10,11 @@
|
||||
"build": "npm run build:client && npm run build:server",
|
||||
"build:client": "tsc -b tsconfig.client.json && vite build",
|
||||
"build:server": "tsc -b tsconfig.server.json",
|
||||
"build:worker": "npm run build:client && esbuild src/worker/index.ts --bundle --outfile=dist/worker/index.js --format=esm --platform=browser --target=es2022 --alias:@shared=./src/shared",
|
||||
"preview": "vite preview",
|
||||
"start": "node dist/server/index.js"
|
||||
"start": "node dist/server/index.js",
|
||||
"deploy": "npm run build:worker && wrangler deploy",
|
||||
"dev:worker": "wrangler dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
@@ -25,6 +28,7 @@
|
||||
"three": "^0.182.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20241218.0",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.11.30",
|
||||
@@ -32,8 +36,10 @@
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"concurrently": "^9.1.2",
|
||||
"esbuild": "^0.24.0",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^6.0.5"
|
||||
"vite": "^6.0.5",
|
||||
"wrangler": "^3.99.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ function AppContent() {
|
||||
<div className="app">
|
||||
<FloatingHeader />
|
||||
|
||||
<main className="app-main" style={{ paddingTop: '5rem' }}>
|
||||
<main className="app-main" style={{ paddingTop: '8rem' }}>
|
||||
<p className="app-description">
|
||||
{t('从全球各地测试到任意IP地址或域名的网络延迟', 'Test network latency from global locations to any IP address or domain')}
|
||||
</p>
|
||||
@@ -53,7 +53,7 @@ function AppContent() {
|
||||
</main>
|
||||
|
||||
<footer className="app-footer">
|
||||
<p>{t('© 2024 延迟测试。由 GlobalPing 提供支持。', '© 2024 Latency Test. Powered by GlobalPing.')}</p>
|
||||
<p>{t('© 2025 全球延迟测试。由 GlobalPing 提供服务支持。', '© 2025 Global Latency Test. Powered by GlobalPing.')}</p>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.floating-header {
|
||||
position: fixed;
|
||||
top: 1.25rem;
|
||||
top: 1rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 100;
|
||||
@@ -8,7 +8,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1.25rem 3rem;
|
||||
padding: 0.75rem 2rem;
|
||||
|
||||
background: var(--card-bg);
|
||||
backdrop-filter: var(--glass-blur);
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.1);
|
||||
transition: opacity var(--transition-smooth);
|
||||
width: calc(100% - 2rem);
|
||||
width: 80%;
|
||||
max-width: 1100px;
|
||||
}
|
||||
|
||||
@@ -58,8 +58,9 @@
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.floating-header {
|
||||
top: 1rem;
|
||||
top: 0.75rem;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 1rem;
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
const [theme, setTheme] = useState<Theme>(() => {
|
||||
const saved = localStorage.getItem('theme') as Theme
|
||||
if (saved) return saved
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||
return 'dark'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
250
src/worker/index.ts
Normal file
250
src/worker/index.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { TEST_NODES } from '../shared/types'
|
||||
|
||||
interface Env {
|
||||
GLOBALPING_API: string
|
||||
}
|
||||
|
||||
interface MeasurementResponse {
|
||||
id: string
|
||||
probesCount: number
|
||||
}
|
||||
|
||||
interface ProbeResult {
|
||||
probe: {
|
||||
continent: string
|
||||
country: string
|
||||
city: string
|
||||
asn: number
|
||||
network: string
|
||||
}
|
||||
result: {
|
||||
status: string
|
||||
rawOutput: string
|
||||
stats?: {
|
||||
min: number
|
||||
max: number
|
||||
avg: number
|
||||
total: number
|
||||
loss: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface MeasurementResult {
|
||||
id: string
|
||||
type: string
|
||||
status: 'in-progress' | 'finished'
|
||||
results?: ProbeResult[]
|
||||
}
|
||||
|
||||
interface BatchLatencyResult {
|
||||
nodeId: string
|
||||
latency: number | null
|
||||
success: boolean
|
||||
}
|
||||
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
}
|
||||
|
||||
function jsonResponse(data: unknown, status = 200): Response {
|
||||
return new Response(JSON.stringify(data), {
|
||||
status,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...corsHeaders,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const IP_REGEX = /^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$/
|
||||
const DOMAIN_REGEX = /^(?!:\/\/)([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/
|
||||
|
||||
function isValidTarget(target: string): boolean {
|
||||
return IP_REGEX.test(target) || DOMAIN_REGEX.test(target)
|
||||
}
|
||||
|
||||
function extractClientIp(request: Request): string | null {
|
||||
const cfConnectingIp = request.headers.get('CF-Connecting-IP')
|
||||
if (cfConnectingIp) return cfConnectingIp
|
||||
|
||||
const forwarded = request.headers.get('X-Forwarded-For')
|
||||
if (forwarded) return forwarded.split(',')[0].trim()
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async function createBatchMeasurement(target: string, env: Env): Promise<string> {
|
||||
const locations = TEST_NODES.map(node => ({
|
||||
country: node.country,
|
||||
city: node.city,
|
||||
limit: 1
|
||||
}))
|
||||
|
||||
const res = await fetch(`${env.GLOBALPING_API}/measurements`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'LatencyTest/1.0.0'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: 'ping',
|
||||
target,
|
||||
inProgressUpdates: true,
|
||||
locations,
|
||||
measurementOptions: {
|
||||
packets: 3
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await res.json().catch(() => ({})) as { error?: { message?: string } }
|
||||
throw new Error(error.error?.message || `GlobalPing API error: ${res.status}`)
|
||||
}
|
||||
|
||||
const data = await res.json() as MeasurementResponse
|
||||
return data.id
|
||||
}
|
||||
|
||||
function normalizeLocationName(value?: string | null): string {
|
||||
if (!value) return ''
|
||||
return value
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/[^a-zA-Z0-9]+/g, ' ')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
function matchProbeToNode(probe: ProbeResult['probe']): string | null {
|
||||
const candidates = TEST_NODES.filter(node => node.country === probe.country)
|
||||
if (candidates.length === 0) return null
|
||||
|
||||
const probeCity = normalizeLocationName(probe.city)
|
||||
if (probeCity) {
|
||||
for (const node of candidates) {
|
||||
const nodeCity = normalizeLocationName(node.city)
|
||||
if (nodeCity && (probeCity.includes(nodeCity) || nodeCity.includes(probeCity))) {
|
||||
return node.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (candidates.length === 1) {
|
||||
return candidates[0].id
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async function handleGetIp(request: Request): Promise<Response> {
|
||||
const ip = extractClientIp(request)
|
||||
if (!ip) {
|
||||
return jsonResponse({ error: 'Unable to determine IP' }, 500)
|
||||
}
|
||||
return jsonResponse({ ip })
|
||||
}
|
||||
|
||||
async function handleCreateMeasurement(request: Request, env: Env): Promise<Response> {
|
||||
try {
|
||||
const body = await request.json() as { target?: string }
|
||||
const { target } = body
|
||||
|
||||
if (!target || typeof target !== 'string') {
|
||||
return jsonResponse({ error: 'target is required' }, 400)
|
||||
}
|
||||
|
||||
const trimmedTarget = target.trim().toLowerCase()
|
||||
|
||||
if (!isValidTarget(trimmedTarget)) {
|
||||
return jsonResponse({ error: 'Invalid target. Please enter a valid IP address or domain name.' }, 400)
|
||||
}
|
||||
|
||||
const measurementId = await createBatchMeasurement(trimmedTarget, env)
|
||||
return jsonResponse({ measurementId })
|
||||
} catch (error) {
|
||||
console.error('Batch measurement creation error:', error)
|
||||
return jsonResponse({ error: 'Failed to create measurement' }, 500)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGetMeasurement(measurementId: string, env: Env): Promise<Response> {
|
||||
try {
|
||||
const fetchRes = await fetch(`${env.GLOBALPING_API}/measurements/${measurementId}`, {
|
||||
headers: {
|
||||
'User-Agent': 'LatencyTest/1.0.0'
|
||||
}
|
||||
})
|
||||
|
||||
if (!fetchRes.ok) {
|
||||
return jsonResponse({ error: 'Failed to get measurement' }, fetchRes.status)
|
||||
}
|
||||
|
||||
const data = await fetchRes.json() as MeasurementResult
|
||||
const results: BatchLatencyResult[] = []
|
||||
const matchedNodes = new Set<string>()
|
||||
|
||||
if (data.results) {
|
||||
for (const probeResult of data.results) {
|
||||
const result = probeResult.result
|
||||
const nodeId = matchProbeToNode(probeResult.probe)
|
||||
|
||||
if (nodeId && !matchedNodes.has(nodeId)) {
|
||||
matchedNodes.add(nodeId)
|
||||
if (result.status === 'finished') {
|
||||
const latency = result.stats?.avg != null ? Math.round(result.stats.avg) : null
|
||||
results.push({ nodeId, latency, success: latency !== null })
|
||||
} else {
|
||||
results.push({ nodeId, latency: null, success: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const node of TEST_NODES) {
|
||||
if (!matchedNodes.has(node.id)) {
|
||||
results.push({ nodeId: node.id, latency: null, success: false })
|
||||
}
|
||||
}
|
||||
|
||||
return jsonResponse({
|
||||
status: data.status,
|
||||
results
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Get measurement error:', error)
|
||||
return jsonResponse({ error: 'Failed to get measurement' }, 500)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const url = new URL(request.url)
|
||||
const path = url.pathname
|
||||
|
||||
// Handle CORS preflight
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: corsHeaders })
|
||||
}
|
||||
|
||||
// API routes
|
||||
if (path === '/api/ip' && request.method === 'GET') {
|
||||
return handleGetIp(request)
|
||||
}
|
||||
|
||||
if (path === '/api/latency/batch' && request.method === 'POST') {
|
||||
return handleCreateMeasurement(request, env)
|
||||
}
|
||||
|
||||
const batchMatch = path.match(/^\/api\/latency\/batch\/([a-zA-Z0-9-]+)$/)
|
||||
if (batchMatch && request.method === 'GET') {
|
||||
return handleGetMeasurement(batchMatch[1], env)
|
||||
}
|
||||
|
||||
// Serve static files for other routes (handled by Cloudflare Pages/Workers Sites)
|
||||
return new Response('Not Found', { status: 404 })
|
||||
}
|
||||
}
|
||||
20
tsconfig.worker.json
Normal file
20
tsconfig.worker.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022"],
|
||||
"types": ["@cloudflare/workers-types"],
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"isolatedModules": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"paths": {
|
||||
"@shared/*": ["./src/shared/*"]
|
||||
},
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["src/worker/**/*", "src/shared/**/*"]
|
||||
}
|
||||
9
wrangler.toml
Normal file
9
wrangler.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
name = "latency-test"
|
||||
main = "dist/worker/index.js"
|
||||
compatibility_date = "2025-12-22"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
|
||||
assets = { directory = "./dist/client" }
|
||||
|
||||
[vars]
|
||||
GLOBALPING_API = "https://api.globalping.io/v1"
|
||||
Reference in New Issue
Block a user