返回 slidev
useServerDetector.ts
根目录 / packages / vscode / src / composables / useServerDetector.ts
1 import type { SlidevProject } from '../projects'
2 import { createControlledPromise } from '@antfu/utils'
3 import { getPort as getPortPlease } from 'get-port-please'
4 import { computed, defineService, onScopeDispose, reactive, watch } from 'reactive-vscode'
5 import { config } from '../configs'
6 import { activeProject, askAddProject, projects, scannedProjects } from '../projects'
7 import { logger } from '../views/logger'
8
9 const versionRE = /<meta (?:name|property)="slidev:version" content="([^"]+)">/
10 const entryRE = /<meta (?:property|charset)="slidev:entry" content="([^"]+)">/
11 const RE_SLIDEV = /slidev/i
12
13 export interface DetectedServerState {
14 port: number
15 ready: boolean
16 message: string
17 compatMode: boolean
18 entry: string | null
19 pending: Promise<boolean> | null
20 }
21
22 export const useServerDetector = defineService(() => {
23 const detectedPorts = reactive(new Map<number, DetectedServerState>())
24
25 async function redetect(port: number) {
26 const state = detectedPorts.get(port) || reactive<DetectedServerState>({
27 port,
28 ready: false,
29 message: '',
30 compatMode: false,
31 entry: null,
32 pending: null,
33 })
34 detectedPorts.set(port, state)
35
36 if (state.pending)
37 return state.pending
38 const { resolve } = state.pending = createControlledPromise()
39
40 async function pingUrl(url: string) {
41 try {
42 const text = await (await fetch(url)).text()
43 if (!RE_SLIDEV.test(text))
44 return false
45 // Use verkit to compare in the future
46 state.compatMode = !text.match(versionRE)
47 const detectedEntry = text.match(entryRE)?.[1]
48 if (detectedEntry) {
49 state.entry = detectedEntry
50 logger.info(`[Slidev] Detected Slidev server entry: ${detectedEntry} on port ${port}`)
51 if (scannedProjects.value) {
52 askAddProject(detectedEntry, `A Slidev server is detected running on localhost:${port}.`)
53 }
54 }
55 return true
56 }
57 catch (err) {
58 state.message = String(err)
59 }
60 return false
61 }
62
63 // We can't use `localhost:` here
64 state.ready = await pingUrl(`http://[::1]:${port}`) || await pingUrl(`http://127.0.0.1:${port}`)
65
66 state.pending = null
67 resolve(state.ready)
68 return state.ready
69 }
70
71 const portsToDetect = computed(() => {
72 const ports = new Set([config.port])
73 for (const project of projects.values()) {
74 if (project.port.value)
75 ports.add(project.port.value)
76 }
77 return ports
78 })
79
80 watch(portsToDetect, (ports, oldPorts) => {
81 for (const port of ports) {
82 if (!oldPorts?.has(port))
83 redetect(port)
84 }
85 }, { immediate: true })
86
87 const interval = setInterval(() => {
88 const activePort = activeProject.value?.port.value
89 if (activePort) {
90 redetect(activePort)
91 }
92 }, 4000)
93 onScopeDispose(() => clearInterval(interval))
94
95 function getDetected(project: SlidevProject) {
96 const port = project.port.value || config.port
97 const detected = detectedPorts.get(port)
98 if (detected?.entry === project.entry)
99 return detected
100 for (const state of detectedPorts.values()) {
101 if (state.entry === project.entry)
102 return state
103 }
104 return null
105 }
106
107 async function allocPort() {
108 const usedPorts = [...projects.values()].map(project => project.port.value ?? 0)
109 const minPort = Math.max(3029, ...usedPorts) + 1
110 return await getPortPlease({
111 portRange: [minPort, 4000],
112 })
113 }
114
115 return {
116 redetect,
117 getDetected,
118 allocPort,
119 }
120 })
121
121 lines TYPESCRIPT