ui布局自适应
This commit is contained in:
1 parent
98f44890f8
commit
3587129b90
40 files changed
+929
-142
No files matched your search
@@ -29,8 +29,8 @@ async function buildMac() {
|
||||
run(process.env.CXX || 'clang++', [
|
||||
'-std=c++17', '-O2', '-Wall', '-Wextra', '-Werror', '-fobjc-arc',
|
||||
'-arch', 'arm64', '-arch', 'x86_64', '-mmacosx-version-min=11.0',
|
||||
...commonSources, join(native, 'app_macos.mm'),
|
||||
'-framework', 'AppKit', '-framework', 'Foundation', '-o', executable,
|
||||
...commonSources, join(native, 'local_certificate.mm'), join(native, 'app_macos.mm'),
|
||||
'-framework', 'AppKit', '-framework', 'Foundation', '-framework', 'Security', '-o', executable,
|
||||
])
|
||||
await chmod(executable, 0o755)
|
||||
await writeFile(join(app, 'Contents', 'Info.plist'), `<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { spawn } from 'node:child_process'
|
||||
import { mkdtemp, rm, stat } from 'node:fs/promises'
|
||||
import { createServer } from 'node:net'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import https from 'node:https'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
const projectDir = resolve(import.meta.dirname, '..')
|
||||
|
||||
function freePort() {
|
||||
return new Promise((resolvePort, reject) => {
|
||||
const server = createServer()
|
||||
server.once('error', reject)
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const { port } = server.address()
|
||||
server.close((error) => error ? reject(error) : resolvePort(port))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function requestJson(url, options, ca) {
|
||||
return new Promise((resolveRequest, reject) => {
|
||||
const request = https.request(url, { ...options, ca }, (response) => {
|
||||
const chunks = []
|
||||
response.on('data', (chunk) => chunks.push(chunk))
|
||||
response.on('end', () => {
|
||||
try {
|
||||
resolveRequest({
|
||||
status: response.statusCode,
|
||||
headers: response.headers,
|
||||
body: JSON.parse(Buffer.concat(chunks).toString('utf8')),
|
||||
})
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
request.once('error', reject)
|
||||
request.end()
|
||||
})
|
||||
}
|
||||
|
||||
async function waitForConnector(url, ca) {
|
||||
const deadline = Date.now() + 10_000
|
||||
let lastError
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const response = await requestJson(url, { headers: { Origin: 'https://safari.test' } }, ca)
|
||||
if (response.status === 200) return response
|
||||
lastError = new Error(`健康检查返回 ${response.status}`)
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
}
|
||||
await new Promise((resolveDelay) => setTimeout(resolveDelay, 150))
|
||||
}
|
||||
throw lastError || new Error('HTTPS 连接器启动超时')
|
||||
}
|
||||
|
||||
const temp = await mkdtemp(join(tmpdir(), 'spine-connector-tls-test-'))
|
||||
const [httpPort, httpsPort] = await Promise.all([freePort(), freePort()])
|
||||
const certificateDir = join(temp, 'tls')
|
||||
const processHandle = spawn(process.execPath, [join(projectDir, 'connector', 'spine-connector.mjs')], {
|
||||
cwd: projectDir,
|
||||
env: {
|
||||
...process.env,
|
||||
SPINE_CONNECTOR_PORT: String(httpPort),
|
||||
SPINE_CONNECTOR_TLS_PORT: String(httpsPort),
|
||||
SPINE_CONNECTOR_CONFIG: join(temp, 'config.json'),
|
||||
SPINE_CONNECTOR_TLS_DIR: certificateDir,
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
})
|
||||
let output = ''
|
||||
processHandle.stdout.on('data', (chunk) => { output += chunk })
|
||||
processHandle.stderr.on('data', (chunk) => { output += chunk })
|
||||
|
||||
try {
|
||||
const ca = await readFile(join(certificateDir, 'ca.pem')).catch(() => null)
|
||||
|| await new Promise((resolveCa, reject) => {
|
||||
const timer = setInterval(async () => {
|
||||
try {
|
||||
const value = await readFile(join(certificateDir, 'ca.pem'))
|
||||
clearInterval(timer)
|
||||
resolveCa(value)
|
||||
} catch {
|
||||
// The connector creates the certificate asynchronously during startup.
|
||||
}
|
||||
}, 100)
|
||||
setTimeout(() => {
|
||||
clearInterval(timer)
|
||||
reject(new Error(`TLS 证书未生成\n${output}`))
|
||||
}, 10_000)
|
||||
})
|
||||
const response = await waitForConnector(`https://127.0.0.1:${httpsPort}/v1/health`, ca)
|
||||
if (!response.body.secureAvailable) throw new Error('健康检查未报告 HTTPS 可用')
|
||||
if (response.headers['access-control-allow-origin'] !== 'https://safari.test') throw new Error('HTTPS CORS 来源不正确')
|
||||
if (!String(response.body.pairUrl).startsWith(`https://127.0.0.1:${httpsPort}/pair`)) throw new Error('授权链接未使用 HTTPS')
|
||||
for (const name of ['server-key.pem', 'identity.p12']) {
|
||||
if (((await stat(join(certificateDir, name))).mode & 0o077) !== 0) throw new Error(`${name} 权限过宽`)
|
||||
}
|
||||
console.log(`连接器 HTTPS 验证通过:https://127.0.0.1:${httpsPort}`)
|
||||
} finally {
|
||||
if (!processHandle.killed) processHandle.kill('SIGTERM')
|
||||
await new Promise((resolveExit) => processHandle.once('exit', resolveExit)).catch(() => {})
|
||||
await rm(temp, { recursive: true, force: true })
|
||||
}
|
||||
Reference in new issue
Block a user