Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aba40c8cb7 | ||
|
|
f16a8a9255 | ||
|
|
fe051d390d | ||
|
|
e764abb32a | ||
|
|
e884d5691e |
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "automatic",
|
||||
"java.compile.nullAnalysis.mode": "automatic"
|
||||
}
|
||||
2
app.json
2
app.json
@@ -38,7 +38,7 @@
|
||||
[
|
||||
"expo-camera",
|
||||
{
|
||||
"cameraPermission": "用于扫描 PC 端绑定二维码(含 EasyTier 组网信息)。"
|
||||
"cameraPermission": "用于扫描 PC 端绑定二维码(baseUrl/token)。"
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
@@ -6,82 +6,117 @@ import {
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Linking,
|
||||
Alert,
|
||||
Platform,
|
||||
} from 'react-native'
|
||||
import { CameraView, useCameraPermissions } from 'expo-camera'
|
||||
import * as Clipboard from 'expo-clipboard'
|
||||
import { CameraView, useCameraPermissions } from 'expo-camera'
|
||||
import { Ionicons } from '@expo/vector-icons'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { Ionicons } from '@expo/vector-icons'
|
||||
|
||||
import { useBind } from '@/context/BindContext'
|
||||
import { DEFAULT_EASYTIER_PEER_URLS, parseBindPayload, type BindPayload } from '@/lib/types'
|
||||
import {
|
||||
checkVpnPermissionGranted,
|
||||
isExpoEasyTierVpnAvailable,
|
||||
saveAndPrepareTunnel,
|
||||
} from '@/lib/easyTierVpn'
|
||||
import { parseBindPayload, type BindPayload } from '@/lib/types'
|
||||
import { theme } from '@/constants/theme'
|
||||
|
||||
const ET_RELEASE = 'https://github.com/EasyTier/EasyTier/releases'
|
||||
const DEFAULT_BOOTSTRAP_PEER = DEFAULT_EASYTIER_PEER_URLS[0]
|
||||
function normalizeHostInput(hostInput: string): { baseUrl: string; port: number } | null {
|
||||
const s = hostInput.trim()
|
||||
if (!s) return null
|
||||
|
||||
if (/^https?:\/\//i.test(s)) {
|
||||
try {
|
||||
const u = new URL(s)
|
||||
const port = Number(u.port || (u.protocol === 'https:' ? 443 : 80))
|
||||
if (!Number.isFinite(port)) return null
|
||||
return { baseUrl: u.origin, port }
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const noPath = s.split('/')[0]
|
||||
const i = noPath.lastIndexOf(':')
|
||||
if (i <= 0) return null
|
||||
const host = noPath.slice(0, i).trim()
|
||||
const port = Number(noPath.slice(i + 1))
|
||||
if (!host || !Number.isFinite(port) || port <= 0) return null
|
||||
return { baseUrl: `http://${host}:${port}`, port }
|
||||
}
|
||||
|
||||
export default function BindScreen() {
|
||||
const { bind, setBind, clear, ready } = useBind()
|
||||
const [paste, setPaste] = useState('')
|
||||
const [msg, setMsg] = useState('')
|
||||
const [hostText, setHostText] = useState('')
|
||||
const [scanOn, setScanOn] = useState(false)
|
||||
const [perm, requestPerm] = useCameraPermissions()
|
||||
const scannedRef = useRef(false)
|
||||
const [etBusy, setEtBusy] = useState(false)
|
||||
|
||||
const etEmbedded = Platform.OS === 'android' && isExpoEasyTierVpnAvailable()
|
||||
useEffect(() => {
|
||||
if (!scanOn) scannedRef.current = false
|
||||
}, [scanOn])
|
||||
|
||||
useEffect(() => {
|
||||
if (!bind?.baseUrl) {
|
||||
setHostText('')
|
||||
return
|
||||
}
|
||||
const v = bind.baseUrl.replace(/^https?:\/\//i, '')
|
||||
setHostText(v)
|
||||
}, [bind?.baseUrl])
|
||||
|
||||
const applyPayload = useCallback(
|
||||
async (p: BindPayload | null) => {
|
||||
if (!p) {
|
||||
setMsg('无法解析,请确认扫描的是 PC 端“生成绑定二维码”的 JSON。')
|
||||
return
|
||||
}
|
||||
async (p: BindPayload) => {
|
||||
await setBind(p)
|
||||
setMsg(
|
||||
etEmbedded
|
||||
? '配对已保存。EasyTier 参数已写入本机,可继续申请 VPN 权限。'
|
||||
: '配对已保存。请使用开发构建或正式 APK 完成 Android 原生组网。'
|
||||
)
|
||||
setScanOn(false)
|
||||
scannedRef.current = false
|
||||
setMsg('已导入绑定(含 token)。可在下方修改 IP/域名与端口后再点「保存」。')
|
||||
},
|
||||
[etEmbedded, setBind],
|
||||
[setBind],
|
||||
)
|
||||
|
||||
const onBarCode = useCallback(
|
||||
async ({ data }: { data: string }) => {
|
||||
const onBarcodeScanned = useCallback(
|
||||
(result: { data: string }) => {
|
||||
if (scannedRef.current) return
|
||||
const p = parseBindPayload(result.data)
|
||||
if (!p) {
|
||||
setMsg('二维码内容不是有效的绑定 JSON(需 v1、baseUrl 等字段)。')
|
||||
return
|
||||
}
|
||||
scannedRef.current = true
|
||||
const p = parseBindPayload(data)
|
||||
await applyPayload(p)
|
||||
void (async () => {
|
||||
await applyPayload(p)
|
||||
setScanOn(false)
|
||||
})()
|
||||
},
|
||||
[applyPayload],
|
||||
)
|
||||
|
||||
const onPasteApply = useCallback(async () => {
|
||||
const p = parseBindPayload(paste.trim())
|
||||
await applyPayload(p)
|
||||
}, [paste, applyPayload])
|
||||
|
||||
const copyEtJson = useCallback(async () => {
|
||||
if (!bind) return
|
||||
const chunk = {
|
||||
networkName: bind.easytier.networkName,
|
||||
networkSecret: bind.easytier.networkSecret,
|
||||
peerUrls: bind.easytier.peerUrls,
|
||||
const onPasteJson = useCallback(async () => {
|
||||
const raw = (await Clipboard.getStringAsync()).trim()
|
||||
const p = parseBindPayload(raw)
|
||||
if (!p) {
|
||||
setMsg('剪贴板内容不是有效的绑定 JSON。')
|
||||
return
|
||||
}
|
||||
await Clipboard.setStringAsync(JSON.stringify(chunk, null, 2))
|
||||
setMsg('已复制 EasyTier 组网 JSON。')
|
||||
}, [bind])
|
||||
await applyPayload(p)
|
||||
}, [applyPayload])
|
||||
|
||||
const onSaveHost = useCallback(async () => {
|
||||
const n = normalizeHostInput(hostText)
|
||||
if (!n) {
|
||||
setMsg('请输入 `ip/域名:端口`(或带 http:// 的 baseUrl)。')
|
||||
return
|
||||
}
|
||||
const p: BindPayload = { v: 1, baseUrl: n.baseUrl, port: n.port, token: bind?.token ?? '' }
|
||||
await setBind(p)
|
||||
setMsg('已保存绑定地址;未改动的 token 已保留。')
|
||||
}, [hostText, bind?.token, setBind])
|
||||
|
||||
const onResetHost = useCallback(async () => {
|
||||
setMsg('')
|
||||
Alert.alert('清除配对', '确定要删除当前绑定地址吗?', [
|
||||
{ text: '取消', style: 'cancel' },
|
||||
{ text: '清除', style: 'destructive', onPress: () => void clear() },
|
||||
])
|
||||
}, [clear])
|
||||
|
||||
const copyFull = useCallback(async () => {
|
||||
if (!bind) return
|
||||
@@ -91,52 +126,49 @@ export default function BindScreen() {
|
||||
|
||||
const testConn = useCallback(async () => {
|
||||
if (!bind) return
|
||||
const url = `${bind.baseUrl}/health`
|
||||
setMsg('正在测试 API 连接…')
|
||||
const ac = new AbortController()
|
||||
const t = setTimeout(() => ac.abort(), 15000)
|
||||
try {
|
||||
const r = await fetch(`${bind.baseUrl}/health`, {
|
||||
const r = await fetch(url, {
|
||||
signal: ac.signal,
|
||||
headers: bind.token ? { Authorization: `Bearer ${bind.token}` } : undefined,
|
||||
})
|
||||
const j = await r.json().catch(() => ({}))
|
||||
setMsg('')
|
||||
Alert.alert('连接测试', r.ok ? `OK: ${JSON.stringify(j)}` : `HTTP ${r.status}`)
|
||||
} catch (e) {
|
||||
Alert.alert('连接失败', e instanceof Error ? e.message : String(e))
|
||||
const isAbort = e instanceof Error && e.name === 'AbortError'
|
||||
setMsg('')
|
||||
Alert.alert(
|
||||
isAbort ? '连接超时' : '连接失败',
|
||||
isAbort
|
||||
? '15 秒内无响应。请确认手机能访问绑定里的 baseUrl:端口(或域名:端口),PC 上 web2 服务已启动,且防火墙/网络路由允许(换网后可能需要重新扫码或改地址)。'
|
||||
: e instanceof Error
|
||||
? e.message
|
||||
: String(e),
|
||||
)
|
||||
} finally {
|
||||
clearTimeout(t)
|
||||
}
|
||||
}, [bind])
|
||||
|
||||
const retryEmbeddedTunnel = useCallback(async () => {
|
||||
if (!bind || !etEmbedded) return
|
||||
setEtBusy(true)
|
||||
try {
|
||||
const r = await saveAndPrepareTunnel(bind.easytier)
|
||||
if (r.needsVpnPermission) {
|
||||
setMsg('请在系统弹窗中允许本应用建立 VPN,授权后再点“检查 VPN 权限”。')
|
||||
} else if (r.ok && r.vpnPermissionGranted) {
|
||||
setMsg(
|
||||
'VPN 权限已就绪,EasyTier 参数也已保存。桌面端已默认附带官方共享引导节点,外网下更容易发现对端。'
|
||||
)
|
||||
} else {
|
||||
setMsg(`组网准备结果:${JSON.stringify(r)}`)
|
||||
const openScan = useCallback(async () => {
|
||||
setMsg('')
|
||||
if (Platform.OS === 'web') {
|
||||
setMsg('网页版请使用「从剪贴板导入 JSON」。')
|
||||
return
|
||||
}
|
||||
if (!perm?.granted) {
|
||||
const r = await requestPerm()
|
||||
if (!r.granted) {
|
||||
setMsg('需要相机权限才能扫码。')
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
setMsg(e instanceof Error ? e.message : String(e))
|
||||
} finally {
|
||||
setEtBusy(false)
|
||||
}
|
||||
}, [bind, etEmbedded])
|
||||
|
||||
const checkVpn = useCallback(async () => {
|
||||
if (!etEmbedded) return
|
||||
setEtBusy(true)
|
||||
try {
|
||||
const ok = await checkVpnPermissionGranted()
|
||||
setMsg(
|
||||
ok
|
||||
? 'VPN 权限已授予。若仍无法访问 API,请确认当前安装包已包含完整原生 EasyTier 数据面。'
|
||||
: '尚未授予 VPN 权限,请先在系统设置中允许本应用建立 VPN。'
|
||||
)
|
||||
} finally {
|
||||
setEtBusy(false)
|
||||
}
|
||||
}, [etEmbedded])
|
||||
setScanOn(true)
|
||||
}, [perm?.granted, requestPerm])
|
||||
|
||||
if (!ready) {
|
||||
return (
|
||||
@@ -146,26 +178,21 @@ export default function BindScreen() {
|
||||
)
|
||||
}
|
||||
|
||||
const bootstrapPeer = bind?.easytier.peerUrls[0] ?? DEFAULT_BOOTSTRAP_PEER
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.safe} edges={['bottom']}>
|
||||
<ScrollView contentContainerStyle={styles.scroll} keyboardShouldPersistTaps="handled">
|
||||
<Text style={styles.h1}>远程配对</Text>
|
||||
<Text style={styles.lead}>
|
||||
在 PC 控制台“网络 - 远程控制”生成二维码后,用手机扫码导入 `baseUrl`、`token` 和 EasyTier
|
||||
参数。桌面端现在会默认附带 EasyTier 官方共享引导节点,户外网络下更容易发现对端。
|
||||
PC 端生成二维码后扫码导入;导入后可在下方编辑 `ip/域名:端口` 再保存,token 会保留。也可仅手动填写地址(需自行保证与 PC 上 token 一致)。
|
||||
</Text>
|
||||
|
||||
{!!msg && <Text style={styles.msgBanner}>{msg}</Text>}
|
||||
|
||||
{bind && (
|
||||
<LinearGradient colors={['#1e3a5f', theme.bgCard]} style={styles.card}>
|
||||
<Text style={styles.cardTitle}>当前已绑定</Text>
|
||||
<Text style={styles.mono}>{bind.baseUrl}</Text>
|
||||
<Text style={styles.mutedSmall}>组网名 · {bind.easytier.networkName}</Text>
|
||||
<Text style={styles.mutedSmall}>引导节点 · {bootstrapPeer}</Text>
|
||||
{bind.easytier.peerUrls.length > 1 && (
|
||||
<Text style={styles.mutedSmall}>额外引导节点 · {bind.easytier.peerUrls.length - 1} 个</Text>
|
||||
)}
|
||||
<Text style={styles.mutedSmall}>token:{bind.token ? '已保存' : '无'}</Text>
|
||||
<View style={styles.row}>
|
||||
<Pressable style={styles.btn} onPress={() => void testConn()}>
|
||||
<Text style={styles.btnText}>测试 API</Text>
|
||||
@@ -182,110 +209,72 @@ export default function BindScreen() {
|
||||
<Text style={styles.btnText}>清除</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
{etEmbedded ? (
|
||||
<View style={styles.etRow}>
|
||||
<Pressable
|
||||
style={[styles.btnSecondary, etBusy && styles.btnDisabled]}
|
||||
disabled={etBusy}
|
||||
onPress={() => void retryEmbeddedTunnel()}
|
||||
>
|
||||
<Text style={styles.btnSecondaryText}>重新应用组网参数</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={[styles.btnSecondary, etBusy && styles.btnDisabled]}
|
||||
disabled={etBusy}
|
||||
onPress={() => void checkVpn()}
|
||||
>
|
||||
<Text style={styles.btnSecondaryText}>检查 VPN 权限</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.mutedSmall}>
|
||||
{Platform.OS === 'ios'
|
||||
? 'iOS 仍需单独接入 Network Extension 与 EasyTier 原生库。'
|
||||
: '当前运行环境未加载原生模块,例如 Expo Go。请使用开发构建或正式 APK。'}
|
||||
</Text>
|
||||
)}
|
||||
</LinearGradient>
|
||||
)}
|
||||
|
||||
<View style={styles.block}>
|
||||
<Text style={styles.h2}>扫码或导入 JSON</Text>
|
||||
<Text style={styles.body}>与 PC 端「远程控制」面板中的二维码内容一致(含 baseUrl、port、token)。</Text>
|
||||
<View style={styles.etRow}>
|
||||
<Pressable style={styles.btnPrimary} onPress={() => void openScan()}>
|
||||
<Ionicons name="qr-code-outline" size={20} color="#fff" />
|
||||
<Text style={styles.btnPrimaryText}> 扫二维码</Text>
|
||||
</Pressable>
|
||||
<Pressable style={styles.btnSecondary} onPress={() => void onPasteJson()}>
|
||||
<Text style={styles.btnSecondaryText}>从剪贴板导入 JSON</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{scanOn && Platform.OS !== 'web' && (
|
||||
<View style={styles.camWrap}>
|
||||
<CameraView
|
||||
style={styles.camera}
|
||||
facing="back"
|
||||
barcodeScannerSettings={{ barcodeTypes: ['qr'] }}
|
||||
onBarcodeScanned={onBarcodeScanned}
|
||||
/>
|
||||
<Pressable style={styles.camClose} onPress={() => setScanOn(false)}>
|
||||
<Text style={styles.btnText}>关闭相机</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.block}>
|
||||
<Text style={styles.h2}>绑定地址(可编辑)</Text>
|
||||
<Text style={styles.body}>
|
||||
格式:`ip/域名:端口`(如 `192.168.21.222:8001`)。保存时会保留当前已存储的 token;若仅手填地址且从未扫码,请确保 token 与 PC 一致。
|
||||
</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="ip/域名:端口"
|
||||
placeholderTextColor={theme.muted}
|
||||
value={hostText}
|
||||
onChangeText={setHostText}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
/>
|
||||
<View style={styles.row}>
|
||||
<Pressable style={styles.btn} onPress={() => void onSaveHost()}>
|
||||
<Text style={styles.btnText}>保存</Text>
|
||||
</Pressable>
|
||||
<Pressable style={[styles.btn, styles.btnDanger]} onPress={() => void onResetHost()}>
|
||||
<Text style={styles.btnText}>删除/重置</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.block}>
|
||||
<Text style={styles.h2}>说明</Text>
|
||||
<Text style={styles.body}>
|
||||
推流和 FFmpeg 只在 PC 执行;手机只负责保存控制通道地址与 EasyTier 参数。现在桌面端会默认把
|
||||
`tcp://public.easytier.top:11010` 写进 `peerUrls`,减少没有公网 IP 时的发现失败。
|
||||
</Text>
|
||||
<Text style={styles.body}>
|
||||
当前 Android 原生模块已经能保存组网参数并申请 VPN 权限;如果你要完全在 App 内跑 EasyTier
|
||||
数据面,下一步还需要把 EasyTier Android 原生库继续接进来。
|
||||
</Text>
|
||||
<Pressable onPress={() => void Linking.openURL(ET_RELEASE)} style={styles.linkRow}>
|
||||
<Ionicons name="open-outline" size={18} color={theme.accent} />
|
||||
<Text style={styles.link}>EasyTier 官方发布与文档</Text>
|
||||
</Pressable>
|
||||
<Text style={styles.body}>推流、转码在 PC 侧执行;手机保存远程控制用的 baseUrl 与 token。</Text>
|
||||
{bind && (
|
||||
<View style={styles.etActions}>
|
||||
<Pressable style={styles.btnSecondary} onPress={() => void copyEtJson()}>
|
||||
<Text style={styles.btnSecondaryText}>复制组网 JSON</Text>
|
||||
</Pressable>
|
||||
<Pressable style={styles.btnSecondary} onPress={() => void copyFull()}>
|
||||
<Text style={styles.btnSecondaryText}>复制完整绑定 JSON</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.block}>
|
||||
<Text style={styles.h2}>扫码</Text>
|
||||
{!scanOn ? (
|
||||
<Pressable
|
||||
style={styles.btnPrimary}
|
||||
onPress={async () => {
|
||||
const r = await requestPerm()
|
||||
if (!r.granted) {
|
||||
setMsg('需要相机权限才能扫码。')
|
||||
return
|
||||
}
|
||||
scannedRef.current = false
|
||||
setScanOn(true)
|
||||
}}
|
||||
>
|
||||
<Ionicons name="qr-code-outline" size={22} color="#fff" style={{ marginRight: 8 }} />
|
||||
<Text style={styles.btnPrimaryText}>打开相机扫描 PC 二维码</Text>
|
||||
</Pressable>
|
||||
) : perm?.granted ? (
|
||||
<View style={styles.camWrap}>
|
||||
<CameraView
|
||||
style={styles.camera}
|
||||
facing="back"
|
||||
onBarcodeScanned={onBarCode}
|
||||
barcodeScannerSettings={{ barcodeTypes: ['qr'] }}
|
||||
/>
|
||||
<Pressable style={styles.camClose} onPress={() => setScanOn(false)}>
|
||||
<Text style={styles.btnPrimaryText}>关闭相机</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.muted}>没有相机权限。</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.block}>
|
||||
<Text style={styles.h2}>手动粘贴 JSON</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
multiline
|
||||
placeholder="粘贴 PC 端显示的绑定 JSON"
|
||||
placeholderTextColor={theme.muted}
|
||||
value={paste}
|
||||
onChangeText={setPaste}
|
||||
/>
|
||||
<Pressable style={styles.btnPrimary} onPress={() => void onPasteApply()}>
|
||||
<Text style={styles.btnPrimaryText}>解析并保存</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{!!msg && <Text style={styles.msg}>{msg}</Text>}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
)
|
||||
@@ -321,7 +310,6 @@ const styles = StyleSheet.create({
|
||||
borderWidth: 1,
|
||||
borderColor: theme.border,
|
||||
},
|
||||
btnDisabled: { opacity: 0.55 },
|
||||
btnDanger: { backgroundColor: 'rgba(245,101,101,0.15)' },
|
||||
btnText: { color: theme.text, fontWeight: '600', fontSize: 14 },
|
||||
btnPrimary: {
|
||||
@@ -331,6 +319,8 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: '#3d7eff',
|
||||
paddingVertical: 14,
|
||||
borderRadius: 10,
|
||||
flex: 1,
|
||||
minWidth: '45%',
|
||||
},
|
||||
btnPrimaryText: { color: '#fff', fontWeight: '700', fontSize: 15 },
|
||||
btnSecondary: {
|
||||
@@ -346,21 +336,29 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
btnSecondaryText: { color: theme.text, fontWeight: '600', fontSize: 13 },
|
||||
input: {
|
||||
minHeight: 120,
|
||||
minHeight: 52,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.border,
|
||||
borderRadius: 10,
|
||||
padding: 12,
|
||||
color: theme.text,
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
marginBottom: 12,
|
||||
backgroundColor: theme.bgCard,
|
||||
},
|
||||
camWrap: { borderRadius: 12, overflow: 'hidden', backgroundColor: '#000' },
|
||||
camWrap: { borderRadius: 12, overflow: 'hidden', backgroundColor: '#000', marginBottom: 18 },
|
||||
camera: { height: 280, width: '100%' },
|
||||
camClose: { padding: 12, alignItems: 'center', backgroundColor: '#111' },
|
||||
linkRow: { flexDirection: 'row', alignItems: 'center', gap: 6, marginVertical: 8 },
|
||||
link: { color: theme.accent, fontSize: 14 },
|
||||
msg: { color: theme.success, fontSize: 14, marginTop: 8 },
|
||||
msgBanner: {
|
||||
color: theme.success,
|
||||
fontSize: 14,
|
||||
lineHeight: 22,
|
||||
marginBottom: 14,
|
||||
padding: 12,
|
||||
borderRadius: 10,
|
||||
backgroundColor: 'rgba(61,126,255,0.12)',
|
||||
borderWidth: 1,
|
||||
borderColor: theme.border,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Alert,
|
||||
RefreshControl,
|
||||
Linking,
|
||||
Switch,
|
||||
} from 'react-native'
|
||||
import { Picker } from '@react-native-picker/picker'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
@@ -23,6 +24,27 @@ import {
|
||||
proChannelPm2Process,
|
||||
} from '@/lib/youtubeConfigUtils'
|
||||
|
||||
function statusTintBackground(variant: string): string {
|
||||
switch (variant) {
|
||||
case 'online':
|
||||
return 'rgba(61, 214, 140, 0.1)'
|
||||
case 'stopped':
|
||||
case 'error':
|
||||
return 'rgba(245, 101, 101, 0.1)'
|
||||
case 'warn':
|
||||
return 'rgba(246, 173, 85, 0.12)'
|
||||
case 'offline':
|
||||
return 'rgba(139, 146, 158, 0.11)'
|
||||
case 'loading':
|
||||
return 'rgba(91, 157, 255, 0.12)'
|
||||
case 'unknown':
|
||||
case 'empty':
|
||||
return 'rgba(91, 157, 255, 0.09)'
|
||||
default:
|
||||
return 'rgba(139, 146, 158, 0.1)'
|
||||
}
|
||||
}
|
||||
|
||||
function ctrlStyle(action: string): object {
|
||||
switch (action) {
|
||||
case 'start':
|
||||
@@ -183,6 +205,7 @@ export default function RecordScreen() {
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
|
||||
const statusColor = useMemo(() => variantColor[r.statusMeta.variant] ?? theme.muted, [r.statusMeta.variant])
|
||||
const statusBg = useMemo(() => statusTintBackground(r.statusMeta.variant), [r.statusMeta.variant])
|
||||
|
||||
const onRefresh = useCallback(async () => {
|
||||
if (!bind) return
|
||||
@@ -197,7 +220,7 @@ export default function RecordScreen() {
|
||||
<View style={styles.fatalWrap}>
|
||||
<Text style={styles.err}>无法连接:{r.bootError}</Text>
|
||||
<Text style={styles.fatalHint}>
|
||||
常见于 WiFi / EasyTier 瞬时断连;若「远程」里测试 API 正常,多半是任务列表请求失败,请重试。
|
||||
常见于网络瞬时断连;若「远程」里测试 API 正常,多半是任务列表请求失败,请重试。
|
||||
</Text>
|
||||
<Pressable style={styles.retryBtn} onPress={() => void r.refreshProcessList()}>
|
||||
<Text style={styles.retryBtnText}>重试加载任务列表</Text>
|
||||
@@ -227,11 +250,6 @@ export default function RecordScreen() {
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{unbound && (
|
||||
<View style={styles.banner}>
|
||||
<Text style={styles.bannerText}>未绑定 PC:在「远程」页扫码后,此处会加载任务与实时数据。</Text>
|
||||
</View>
|
||||
)}
|
||||
{bind && (
|
||||
<View style={styles.banner}>
|
||||
<Text style={styles.bannerText}>
|
||||
@@ -240,17 +258,61 @@ export default function RecordScreen() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.toolbar}>
|
||||
<View style={[styles.pickerWrap, unbound && styles.pickerDisabled]}>
|
||||
<View style={styles.toolbarCard}>
|
||||
<View style={styles.toolbarRow}>
|
||||
<View style={styles.toolbarLeft}>
|
||||
<View
|
||||
style={[
|
||||
styles.statusChip,
|
||||
{ borderLeftColor: statusColor, backgroundColor: statusBg },
|
||||
]}
|
||||
>
|
||||
<View style={[styles.statusDotOuter, { borderColor: statusColor }]}>
|
||||
<View style={[styles.statusDotInner, { backgroundColor: statusColor }]} />
|
||||
</View>
|
||||
<Text style={styles.statusLineText} numberOfLines={1}>
|
||||
{r.statusMeta.line}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[styles.proCluster, unbound && styles.proClusterDisabled]}>
|
||||
<View style={styles.proLabelRow}>
|
||||
<Text style={styles.proLabelZh}>多频道</Text>
|
||||
<View style={[styles.proBadge, r.multiChannelPro && styles.proBadgeOn]}>
|
||||
<Text style={styles.proBadgeText}>PRO</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.proSwitchWrap}>
|
||||
<Switch
|
||||
value={r.multiChannelPro}
|
||||
disabled={unbound}
|
||||
onValueChange={(v) => {
|
||||
if (unbound) return
|
||||
void r.togglePro(v)
|
||||
}}
|
||||
trackColor={{
|
||||
false: 'rgba(255,255,255,0.12)',
|
||||
true: 'rgba(91, 157, 255, 0.45)',
|
||||
}}
|
||||
thumbColor={r.multiChannelPro ? '#7eb6ff' : '#5c6370'}
|
||||
ios_backgroundColor="rgba(255,255,255,0.1)"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{bind ? (
|
||||
<View style={[styles.pickerWrap, r.emptyHint && styles.pickerDisabled, styles.pickerRowBelow]}>
|
||||
<Picker
|
||||
enabled={!unbound && !r.emptyHint}
|
||||
selectedValue={unbound ? '' : r.currentProcess}
|
||||
enabled={!r.emptyHint}
|
||||
selectedValue={r.currentProcess}
|
||||
onValueChange={(v) => r.setCurrentProcess(String(v))}
|
||||
style={styles.picker}
|
||||
dropdownIconColor={theme.text}
|
||||
>
|
||||
{unbound || !r.entries.length ? (
|
||||
<Picker.Item label="(未配对)" value="" color={theme.muted} />
|
||||
{!r.entries.length ? (
|
||||
<Picker.Item label="(无任务)" value="" color={theme.muted} />
|
||||
) : (
|
||||
r.entries.map((e) => (
|
||||
<Picker.Item key={e.pm2} label={e.label} value={e.pm2} color={theme.text} />
|
||||
@@ -258,28 +320,7 @@ export default function RecordScreen() {
|
||||
)}
|
||||
</Picker>
|
||||
</View>
|
||||
<Pressable
|
||||
style={[styles.proRow, unbound && styles.pickerDisabled]}
|
||||
disabled={unbound}
|
||||
onPress={() => {
|
||||
if (unbound) {
|
||||
Alert.alert('未绑定', '配对后可切换多频道 Pro。')
|
||||
return
|
||||
}
|
||||
void r.togglePro(!r.multiChannelPro)
|
||||
}}
|
||||
>
|
||||
<Text style={styles.proLabel}>多频道直播(Pro)</Text>
|
||||
<Text style={[styles.proVal, r.multiChannelPro && { color: theme.accent }]}>
|
||||
{r.multiChannelPro ? '开' : '关'}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<View style={[styles.pill, { borderColor: statusColor }]}>
|
||||
<View style={[styles.dot, { backgroundColor: statusColor }]} />
|
||||
<Text style={[styles.pillText, { color: statusColor }]}>{r.statusMeta.line}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{bind && r.emptyHint ? (
|
||||
<View style={styles.emptyCard}>
|
||||
@@ -678,7 +719,90 @@ const styles = StyleSheet.create({
|
||||
marginBottom: 14,
|
||||
},
|
||||
bannerText: { fontSize: 13, color: theme.text, lineHeight: 20 },
|
||||
toolbar: { marginBottom: 12 },
|
||||
toolbarCard: {
|
||||
marginBottom: 12,
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255,255,255,0.07)',
|
||||
backgroundColor: theme.bgElevated,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
toolbarRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 10,
|
||||
},
|
||||
toolbarLeft: { flex: 1, minWidth: 0, marginRight: 6 },
|
||||
statusChip: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 12,
|
||||
paddingLeft: 11,
|
||||
borderRadius: 12,
|
||||
borderLeftWidth: 3,
|
||||
borderTopWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderTopColor: 'rgba(255,255,255,0.06)',
|
||||
borderRightColor: 'rgba(255,255,255,0.06)',
|
||||
borderBottomColor: 'rgba(255,255,255,0.06)',
|
||||
},
|
||||
statusDotOuter: {
|
||||
width: 14,
|
||||
height: 14,
|
||||
borderRadius: 7,
|
||||
borderWidth: 2,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
statusDotInner: { width: 6, height: 6, borderRadius: 3 },
|
||||
statusLineText: {
|
||||
flex: 1,
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
color: theme.text,
|
||||
letterSpacing: 0.2,
|
||||
},
|
||||
proCluster: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexShrink: 0,
|
||||
gap: 10,
|
||||
paddingLeft: 10,
|
||||
paddingVertical: 4,
|
||||
paddingRight: 4,
|
||||
borderRadius: 12,
|
||||
backgroundColor: 'rgba(91, 157, 255, 0.06)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(91, 157, 255, 0.18)',
|
||||
},
|
||||
proClusterDisabled: { opacity: 0.45 },
|
||||
proLabelRow: { flexDirection: 'row', alignItems: 'center', gap: 6 },
|
||||
proLabelZh: { fontSize: 13, fontWeight: '600', color: theme.text },
|
||||
proBadge: {
|
||||
paddingHorizontal: 7,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 6,
|
||||
backgroundColor: 'rgba(255,255,255,0.08)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255,255,255,0.1)',
|
||||
},
|
||||
proBadgeOn: {
|
||||
backgroundColor: 'rgba(91, 157, 255, 0.22)',
|
||||
borderColor: 'rgba(91, 157, 255, 0.45)',
|
||||
},
|
||||
proBadgeText: {
|
||||
fontSize: 10,
|
||||
fontWeight: '800',
|
||||
color: theme.accent,
|
||||
letterSpacing: 0.6,
|
||||
},
|
||||
proSwitchWrap: { transform: [{ scale: 0.92 }] },
|
||||
unpairedText: { fontSize: 15, fontWeight: '600', color: theme.muted },
|
||||
proGrid: { gap: 10 },
|
||||
proSwitchRow: {
|
||||
padding: 12,
|
||||
@@ -712,32 +836,8 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
pickerDisabled: { opacity: 0.65 },
|
||||
picker: { color: theme.text },
|
||||
proRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: 12,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 12,
|
||||
backgroundColor: theme.bgCard,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.border,
|
||||
},
|
||||
proLabel: { color: theme.text, fontWeight: '600' },
|
||||
proVal: { color: theme.muted, fontWeight: '600' },
|
||||
pill: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
alignSelf: 'flex-start',
|
||||
gap: 8,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
borderRadius: 999,
|
||||
borderWidth: 1,
|
||||
marginBottom: 14,
|
||||
},
|
||||
pickerRowBelow: { marginTop: 0 },
|
||||
dot: { width: 8, height: 8, borderRadius: 4 },
|
||||
pillText: { fontSize: 13, fontWeight: '600' },
|
||||
emptyCard: {
|
||||
padding: 20,
|
||||
borderRadius: 12,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { View, Text, ScrollView, StyleSheet, RefreshControl } from 'react-native'
|
||||
import { View, Text, ScrollView, StyleSheet, RefreshControl, Pressable } from 'react-native'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
|
||||
import { UserAgreementModal } from '@/components/UserAgreementModal'
|
||||
import { useBind } from '@/context/BindContext'
|
||||
import { apiFetch } from '@/lib/api'
|
||||
import type { SystemMetricsPayload } from '@/lib/pcTypes'
|
||||
@@ -20,6 +21,7 @@ export default function SystemScreen() {
|
||||
const [data, setData] = useState<SystemMetricsPayload | null>(null)
|
||||
const [err, setErr] = useState<string | null>(null)
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const [agreementOpen, setAgreementOpen] = useState(false)
|
||||
|
||||
const load = useCallback(async () => {
|
||||
if (!bind) {
|
||||
@@ -147,8 +149,12 @@ export default function SystemScreen() {
|
||||
<View style={styles.cardMuted}>
|
||||
<Text style={styles.cardTitle}>客户端与系统集成</Text>
|
||||
<Text style={styles.muted}>开机启动、路径与 Electron 集成仅在桌面客户端可用。</Text>
|
||||
<Pressable style={styles.agreementRow} onPress={() => setAgreementOpen(true)} hitSlop={8}>
|
||||
<Text style={styles.agreementLink}>用户协议</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<UserAgreementModal visible={agreementOpen} onClose={() => setAgreementOpen(false)} />
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
@@ -205,4 +211,6 @@ const styles = StyleSheet.create({
|
||||
ffSec: { marginTop: 12 },
|
||||
ffLine: { fontSize: 12, color: theme.muted, marginBottom: 4, fontFamily: 'monospace' },
|
||||
muted: { fontSize: 13, color: theme.muted, lineHeight: 20 },
|
||||
agreementRow: { marginTop: 10, alignSelf: 'flex-start' },
|
||||
agreementLink: { fontSize: 12, color: theme.accent, textDecorationLine: 'underline' },
|
||||
})
|
||||
|
||||
@@ -20,26 +20,34 @@ export const unstable_settings = {
|
||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
/** 避免部分真机/构建环境下字体加载挂起或失败时永远停在 Expo 默认靶心启动图 */
|
||||
const SPLASH_FALLBACK_MS = 4000;
|
||||
|
||||
export default function RootLayout() {
|
||||
const [loaded, error] = useFonts({
|
||||
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
|
||||
});
|
||||
|
||||
// Expo Router uses Error Boundaries to catch errors in the navigation tree.
|
||||
useEffect(() => {
|
||||
if (error) throw error;
|
||||
if (error) {
|
||||
console.warn('[expo-font]', error);
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync();
|
||||
void SplashScreen.hideAsync();
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
useEffect(() => {
|
||||
const t = setTimeout(() => {
|
||||
void SplashScreen.hideAsync();
|
||||
}, SPLASH_FALLBACK_MS);
|
||||
return () => clearTimeout(t);
|
||||
}, []);
|
||||
|
||||
// 不再在字体未就绪时 return null,否则 hideAsync 后会出现纯白屏且仍无界面
|
||||
return <RootLayoutNav />;
|
||||
}
|
||||
|
||||
|
||||
136
components/UserAgreementModal.tsx
Normal file
136
components/UserAgreementModal.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { Modal, View, Text, ScrollView, Pressable, StyleSheet } from 'react-native'
|
||||
|
||||
import { USER_AGREEMENT_MARKDOWN } from '@/lib/userAgreement'
|
||||
import { theme } from '@/constants/theme'
|
||||
|
||||
function renderAgreementBody(md: string) {
|
||||
const lines = md.split('\n')
|
||||
const out: ReactNode[] = []
|
||||
lines.forEach((line, i) => {
|
||||
if (line.startsWith('# ')) {
|
||||
out.push(
|
||||
<Text key={i} style={styles.h1}>
|
||||
{line.slice(2)}
|
||||
</Text>
|
||||
)
|
||||
return
|
||||
}
|
||||
if (line.startsWith('## ')) {
|
||||
out.push(
|
||||
<Text key={i} style={styles.h2}>
|
||||
{line.slice(3)}
|
||||
</Text>
|
||||
)
|
||||
return
|
||||
}
|
||||
if (line.startsWith('* ')) {
|
||||
out.push(
|
||||
<Text key={i} style={styles.li}>
|
||||
• {line.slice(2)}
|
||||
</Text>
|
||||
)
|
||||
return
|
||||
}
|
||||
if (line.trim() === '') {
|
||||
out.push(<View key={i} style={styles.gap} />)
|
||||
return
|
||||
}
|
||||
out.push(
|
||||
<Text key={i} style={styles.p}>
|
||||
{line}
|
||||
</Text>
|
||||
)
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
type Props = {
|
||||
visible: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const AGREEMENT_BODY = USER_AGREEMENT_MARKDOWN.replace(/^# 用户使用协议\n+/, '')
|
||||
|
||||
export function UserAgreementModal({ visible, onClose }: Props) {
|
||||
return (
|
||||
<Modal visible={visible} animationType="slide" transparent onRequestClose={onClose}>
|
||||
<View style={styles.backdrop}>
|
||||
<View style={styles.sheet}>
|
||||
<Text style={styles.sheetTitle}>用户使用协议</Text>
|
||||
<ScrollView style={styles.scroll} contentContainerStyle={styles.scrollInner}>
|
||||
{renderAgreementBody(AGREEMENT_BODY)}
|
||||
</ScrollView>
|
||||
<Pressable style={styles.closeBtn} onPress={onClose}>
|
||||
<Text style={styles.closeBtnText}>关闭</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
backdrop: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.55)',
|
||||
justifyContent: 'center',
|
||||
padding: 16,
|
||||
},
|
||||
sheet: {
|
||||
maxHeight: '88%',
|
||||
borderRadius: 14,
|
||||
backgroundColor: theme.bgCard,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(124,92,255,0.22)',
|
||||
paddingTop: 14,
|
||||
paddingHorizontal: 14,
|
||||
paddingBottom: 12,
|
||||
},
|
||||
sheetTitle: {
|
||||
fontSize: 17,
|
||||
fontWeight: '700',
|
||||
color: theme.text,
|
||||
marginBottom: 10,
|
||||
textAlign: 'center',
|
||||
},
|
||||
scroll: { flexGrow: 0 },
|
||||
scrollInner: { paddingBottom: 8 },
|
||||
h1: {
|
||||
fontSize: 17,
|
||||
fontWeight: '700',
|
||||
color: theme.text,
|
||||
marginBottom: 8,
|
||||
},
|
||||
h2: {
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
color: theme.text,
|
||||
marginTop: 10,
|
||||
marginBottom: 6,
|
||||
},
|
||||
p: {
|
||||
fontSize: 13,
|
||||
color: theme.muted,
|
||||
lineHeight: 21,
|
||||
marginBottom: 4,
|
||||
},
|
||||
li: {
|
||||
fontSize: 13,
|
||||
color: theme.muted,
|
||||
lineHeight: 21,
|
||||
marginBottom: 4,
|
||||
paddingLeft: 4,
|
||||
},
|
||||
gap: { height: 6 },
|
||||
closeBtn: {
|
||||
marginTop: 10,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 10,
|
||||
backgroundColor: theme.bgElevated,
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: theme.border,
|
||||
},
|
||||
closeBtnText: { fontSize: 15, fontWeight: '600', color: theme.accent },
|
||||
})
|
||||
@@ -1,6 +1,4 @@
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import { isExpoEasyTierVpnAvailable, saveAndPrepareTunnel } from '@/lib/easyTierVpn'
|
||||
import type { BindPayload } from '@/lib/types'
|
||||
import { clearBindPayload, loadBindPayload, saveBindPayload } from '@/lib/storage'
|
||||
|
||||
@@ -25,24 +23,10 @@ export function BindProvider({ children }: { children: React.ReactNode }) {
|
||||
})()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!ready || !bind) return
|
||||
if (Platform.OS === 'android' && isExpoEasyTierVpnAvailable()) {
|
||||
void saveAndPrepareTunnel(bind.easytier).catch(() => {})
|
||||
}
|
||||
}, [ready, bind])
|
||||
|
||||
const setBind = useCallback(async (p: BindPayload | null) => {
|
||||
setBindState(p)
|
||||
if (p) await saveBindPayload(p)
|
||||
else await clearBindPayload()
|
||||
if (p && Platform.OS === 'android' && isExpoEasyTierVpnAvailable()) {
|
||||
try {
|
||||
await saveAndPrepareTunnel(p.easytier)
|
||||
} catch {
|
||||
/* 组网内核未就绪或用户未授权 VPN 时不阻断配对 */
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const clear = useCallback(async () => {
|
||||
|
||||
125
electron/agreement.html
Normal file
125
electron/agreement.html
Normal file
@@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>用户使用协议</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
color: #e8e6f2;
|
||||
background: #14121a;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
h1 {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 12px;
|
||||
color: #f2f0fa;
|
||||
}
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 14px 0 8px;
|
||||
color: #e8e6f2;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 8px;
|
||||
color: #a39bb8;
|
||||
}
|
||||
ul {
|
||||
margin: 0 0 10px;
|
||||
padding-left: 18px;
|
||||
color: #a39bb8;
|
||||
}
|
||||
li { margin-bottom: 4px; }
|
||||
.scroll {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 16px 18px 12px;
|
||||
}
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 12px 16px 16px;
|
||||
border-top: 1px solid rgba(124, 92, 255, 0.25);
|
||||
background: #1a1722;
|
||||
}
|
||||
button {
|
||||
flex: 1;
|
||||
padding: 11px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(124, 92, 255, 0.35);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.primary {
|
||||
background: linear-gradient(135deg, #5c4dff, #3d7eff);
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
.ghost {
|
||||
background: transparent;
|
||||
color: #c4bdd9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="scroll">
|
||||
<h1>用户使用协议</h1>
|
||||
<p>欢迎使用本软件(以下简称「本软件」)。在使用本软件前,请您仔细阅读并理解本协议。</p>
|
||||
|
||||
<h2>1. 软件性质说明</h2>
|
||||
<p>本软件为通用的视频处理与推流工具,支持用户对自有或合法授权的视频源进行处理与分发。本软件不提供任何第三方平台内容的解析、抓取或分发服务。</p>
|
||||
|
||||
<h2>2. 用户责任</h2>
|
||||
<p>用户在使用本软件过程中,应确保:</p>
|
||||
<ul>
|
||||
<li>所使用的视频源具有合法使用权或授权;</li>
|
||||
<li>不侵犯任何第三方的著作权、邻接权或其他合法权益;</li>
|
||||
<li>不违反相关法律法规及平台规则。</li>
|
||||
</ul>
|
||||
<p>由用户上传、处理、推流的所有内容,均由用户自行承担全部法律责任。</p>
|
||||
|
||||
<h2>3. 禁止行为</h2>
|
||||
<p>用户不得利用本软件从事以下行为:</p>
|
||||
<ul>
|
||||
<li>未经授权抓取、转播、传播第三方平台内容;</li>
|
||||
<li>侵犯他人版权、肖像权、隐私权;</li>
|
||||
<li>从事违法违规内容传播。</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. 责任限制</h2>
|
||||
<p>本软件仅提供技术工具服务,不参与用户内容的获取、编辑或分发过程。</p>
|
||||
<p>因用户行为导致的任何法律责任或纠纷,均由用户自行承担,与本软件无关。</p>
|
||||
|
||||
<h2>5. 权限与服务</h2>
|
||||
<p>部分功能属于付费服务(如 Pro/VIP),用户购买后仅获得使用权限,不涉及软件所有权转移。</p>
|
||||
|
||||
<h2>6. 协议变更</h2>
|
||||
<p>本软件有权根据业务发展对本协议进行调整,并通过软件或网站进行公告。</p>
|
||||
<p>如您不同意本协议,请立即停止使用本软件。</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button type="button" class="ghost" id="refuse">不同意并退出</button>
|
||||
<button type="button" class="primary" id="accept">同意并继续</button>
|
||||
</div>
|
||||
<script>
|
||||
const api = window.agreementApi
|
||||
document.getElementById('accept').addEventListener('click', () => {
|
||||
if (api) api.accept()
|
||||
})
|
||||
document.getElementById('refuse').addEventListener('click', () => {
|
||||
if (api) api.refuse()
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
91
electron/main.js
Normal file
91
electron/main.js
Normal file
@@ -0,0 +1,91 @@
|
||||
const { app, BrowserWindow, ipcMain } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const AGREEMENT_FILE = 'user-agreement-accepted.json'
|
||||
|
||||
function agreementPath() {
|
||||
return path.join(app.getPath('userData'), AGREEMENT_FILE)
|
||||
}
|
||||
|
||||
function hasAcceptedAgreement() {
|
||||
try {
|
||||
return fs.existsSync(agreementPath())
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function markAgreementAccepted() {
|
||||
fs.writeFileSync(
|
||||
agreementPath(),
|
||||
JSON.stringify({ v: 1, acceptedAt: Date.now() }, null, 2),
|
||||
'utf8'
|
||||
)
|
||||
}
|
||||
|
||||
/** 占位主窗口;合并到实际桌面端时请替换为真实 loadURL / loadFile */
|
||||
function createMainWindow() {
|
||||
const win = new BrowserWindow({
|
||||
width: 960,
|
||||
height: 640,
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
},
|
||||
})
|
||||
win.loadURL('about:blank')
|
||||
return win
|
||||
}
|
||||
|
||||
function showAgreementGate(onAccepted) {
|
||||
const win = new BrowserWindow({
|
||||
width: 520,
|
||||
height: 680,
|
||||
show: false,
|
||||
resizable: true,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
},
|
||||
})
|
||||
|
||||
win.loadFile(path.join(__dirname, 'agreement.html'))
|
||||
win.once('ready-to-show', () => win.show())
|
||||
|
||||
ipcMain.once('agreement-accept', () => {
|
||||
markAgreementAccepted()
|
||||
ipcMain.removeAllListeners('agreement-refuse')
|
||||
win.close()
|
||||
onAccepted()
|
||||
})
|
||||
|
||||
ipcMain.once('agreement-refuse', () => {
|
||||
ipcMain.removeAllListeners('agreement-accept')
|
||||
app.quit()
|
||||
})
|
||||
|
||||
win.on('closed', () => {
|
||||
if (!hasAcceptedAgreement()) {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
if (!hasAcceptedAgreement()) {
|
||||
showAgreementGate(() => createMainWindow())
|
||||
} else {
|
||||
createMainWindow()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
913
electron/package-lock.json
generated
Normal file
913
electron/package-lock.json
generated
Normal file
@@ -0,0 +1,913 @@
|
||||
{
|
||||
"name": "d2y-electron-shell",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "d2y-electron-shell",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"mobile": "file:.."
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^33.4.1"
|
||||
}
|
||||
},
|
||||
"..": {
|
||||
"name": "mobile",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^15.1.1",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
"@react-native-picker/picker": "^2.11.1",
|
||||
"@react-navigation/native": "^7.1.33",
|
||||
"expo": "~55.0.8",
|
||||
"expo-camera": "~17.0.9",
|
||||
"expo-clipboard": "~8.0.7",
|
||||
"expo-constants": "~55.0.9",
|
||||
"expo-font": "~55.0.4",
|
||||
"expo-linear-gradient": "~15.0.7",
|
||||
"expo-linking": "~55.0.8",
|
||||
"expo-modules-core": "~55.0.17",
|
||||
"expo-router": "~55.0.7",
|
||||
"expo-splash-screen": "~55.0.12",
|
||||
"expo-status-bar": "~55.0.4",
|
||||
"expo-symbols": "~55.0.5",
|
||||
"expo-web-browser": "~55.0.10",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-native": "0.83.2",
|
||||
"react-native-reanimated": "4.2.1",
|
||||
"react-native-safe-area-context": "~5.6.2",
|
||||
"react-native-screens": "~4.23.0",
|
||||
"react-native-web": "~0.21.0",
|
||||
"react-native-worklets": "0.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "~19.2.2",
|
||||
"typescript": "~5.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@electron/get": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz",
|
||||
"integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"env-paths": "^2.2.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"got": "^11.8.5",
|
||||
"progress": "^2.0.3",
|
||||
"semver": "^6.2.0",
|
||||
"sumchecker": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"global-agent": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
||||
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@szmarczak/http-timer": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
|
||||
"integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"defer-to-connect": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cacheable-request": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
|
||||
"integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/http-cache-semantics": "*",
|
||||
"@types/keyv": "^3.1.4",
|
||||
"@types/node": "*",
|
||||
"@types/responselike": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/http-cache-semantics": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
||||
"integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/keyv": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
|
||||
"integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.37",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
|
||||
"integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/responselike": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
|
||||
"integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yauzl": {
|
||||
"version": "2.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
||||
"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/boolean": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
|
||||
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/cacheable-lookup": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
|
||||
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cacheable-request": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
|
||||
"integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clone-response": "^1.0.2",
|
||||
"get-stream": "^5.1.0",
|
||||
"http-cache-semantics": "^4.0.0",
|
||||
"keyv": "^4.0.0",
|
||||
"lowercase-keys": "^2.0.0",
|
||||
"normalize-url": "^6.0.1",
|
||||
"responselike": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/clone-response": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
|
||||
"integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-response": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-response/node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/defer-to-connect": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
|
||||
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/define-properties": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
|
||||
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.0",
|
||||
"object-keys": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
|
||||
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "33.4.11",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz",
|
||||
"integrity": "sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^20.9.0",
|
||||
"extract-zip": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"electron": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.20.55"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/env-paths": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
||||
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-error": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/extract-zip": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
||||
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"get-stream": "^5.1.0",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"bin": {
|
||||
"extract-zip": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.17.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@types/yauzl": "^2.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6 <7 || >=8"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
|
||||
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pump": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/global-agent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
|
||||
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"boolean": "^3.0.1",
|
||||
"es6-error": "^4.1.1",
|
||||
"matcher": "^3.0.0",
|
||||
"roarr": "^2.15.3",
|
||||
"semver": "^7.3.2",
|
||||
"serialize-error": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/global-agent/node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/globalthis": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
|
||||
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"define-properties": "^1.2.1",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/got": {
|
||||
"version": "11.8.6",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
|
||||
"integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sindresorhus/is": "^4.0.0",
|
||||
"@szmarczak/http-timer": "^4.0.5",
|
||||
"@types/cacheable-request": "^6.0.1",
|
||||
"@types/responselike": "^1.0.0",
|
||||
"cacheable-lookup": "^5.0.3",
|
||||
"cacheable-request": "^7.0.2",
|
||||
"decompress-response": "^6.0.0",
|
||||
"http2-wrapper": "^1.0.0-beta.5.2",
|
||||
"lowercase-keys": "^2.0.0",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"responselike": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/got?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
||||
"integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/http2-wrapper": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
|
||||
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"quick-lru": "^5.1.1",
|
||||
"resolve-alpn": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lowercase-keys": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
|
||||
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/matcher": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
|
||||
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mobile": {
|
||||
"resolved": "..",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/normalize-url": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
|
||||
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/object-keys": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/p-cancelable": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
|
||||
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
|
||||
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/quick-lru": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
|
||||
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-alpn": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
|
||||
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/responselike": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
|
||||
"integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lowercase-keys": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/roarr": {
|
||||
"version": "2.15.4",
|
||||
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
|
||||
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"boolean": "^3.0.1",
|
||||
"detect-node": "^2.0.4",
|
||||
"globalthis": "^1.0.1",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"semver-compare": "^1.0.0",
|
||||
"sprintf-js": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/semver-compare": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
|
||||
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/serialize-error": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
|
||||
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"type-fest": "^0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/sumchecker": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
|
||||
"integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"debug": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yauzl": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-crc32": "~0.2.3",
|
||||
"fd-slicer": "~1.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
electron/package.json
Normal file
15
electron/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "d2y-electron-shell",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^33.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"mobile": "file:.."
|
||||
}
|
||||
}
|
||||
6
electron/preload.js
Normal file
6
electron/preload.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld('agreementApi', {
|
||||
accept: () => ipcRenderer.send('agreement-accept'),
|
||||
refuse: () => ipcRenderer.send('agreement-refuse'),
|
||||
})
|
||||
@@ -1,36 +0,0 @@
|
||||
import { requireOptionalNativeModule } from 'expo-modules-core'
|
||||
|
||||
export type EasyTierConfigInput = {
|
||||
networkName: string
|
||||
networkSecret: string
|
||||
peerUrls: string[]
|
||||
}
|
||||
|
||||
type NativeModule = {
|
||||
isAvailable: () => boolean
|
||||
saveAndPrepareTunnel: (configJson: string) => Promise<Record<string, unknown>>
|
||||
checkVpnPermissionGranted: () => Promise<Record<string, unknown>>
|
||||
}
|
||||
|
||||
const Native = requireOptionalNativeModule<NativeModule>('ExpoEasyTierVpn')
|
||||
|
||||
export function isExpoEasyTierVpnAvailable(): boolean {
|
||||
try {
|
||||
return Native?.isAvailable?.() === true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveAndPrepareTunnel(config: EasyTierConfigInput): Promise<Record<string, unknown>> {
|
||||
if (!Native?.saveAndPrepareTunnel) {
|
||||
return { ok: false, reason: 'native_module_missing' }
|
||||
}
|
||||
return Native.saveAndPrepareTunnel(JSON.stringify(config))
|
||||
}
|
||||
|
||||
export async function checkVpnPermissionGranted(): Promise<boolean> {
|
||||
if (!Native?.checkVpnPermissionGranted) return false
|
||||
const r = await Native.checkVpnPermissionGranted()
|
||||
return r?.granted === true
|
||||
}
|
||||
37
lib/types.ts
37
lib/types.ts
@@ -1,52 +1,21 @@
|
||||
export type EasyTierConfig = {
|
||||
networkName: string
|
||||
networkSecret: string
|
||||
peerUrls: string[]
|
||||
}
|
||||
|
||||
export const DEFAULT_EASYTIER_PEER_URLS = ['tcp://public.easytier.top:11010']
|
||||
|
||||
function normalizePeerUrls(value: unknown): string[] {
|
||||
const seen = new Set<string>()
|
||||
const next = Array.isArray(value) ? value.map(String) : []
|
||||
const normalized = next
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => {
|
||||
if (!item || seen.has(item)) return false
|
||||
seen.add(item)
|
||||
return true
|
||||
})
|
||||
return normalized.length > 0 ? normalized : [...DEFAULT_EASYTIER_PEER_URLS]
|
||||
}
|
||||
|
||||
/** 与 Electron `remote:get-bind-payload` 输出一致,供扫码导入 */
|
||||
export type BindPayload = {
|
||||
v: 1
|
||||
baseUrl: string
|
||||
port: number
|
||||
token: string
|
||||
easytier: EasyTierConfig
|
||||
token?: string
|
||||
}
|
||||
|
||||
export function parseBindPayload(raw: string): BindPayload | null {
|
||||
try {
|
||||
const j = JSON.parse(raw) as Record<string, unknown>
|
||||
if (j.v !== 1) return null
|
||||
const et = j.easytier as Record<string, unknown> | undefined
|
||||
if (!et || typeof j.baseUrl !== 'string' || typeof j.token !== 'string') return null
|
||||
const networkName = String(et.networkName ?? '').trim()
|
||||
const networkSecret = String(et.networkSecret ?? '').trim()
|
||||
if (!networkName || !networkSecret) return null
|
||||
if (typeof j.baseUrl !== 'string') return null
|
||||
return {
|
||||
v: 1,
|
||||
baseUrl: String(j.baseUrl).replace(/\/$/, ''),
|
||||
port: typeof j.port === 'number' ? j.port : Number(j.port) || 8001,
|
||||
token: String(j.token),
|
||||
easytier: {
|
||||
networkName,
|
||||
networkSecret,
|
||||
peerUrls: normalizePeerUrls(et.peerUrls),
|
||||
},
|
||||
token: j.token == null ? '' : String(j.token),
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
|
||||
42
lib/userAgreement.ts
Normal file
42
lib/userAgreement.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/** 用户使用协议(与 electron/agreement.html 内容保持一致) */
|
||||
export const USER_AGREEMENT_MARKDOWN = `# 用户使用协议
|
||||
|
||||
欢迎使用本软件(以下简称「本软件」)。在使用本软件前,请您仔细阅读并理解本协议。
|
||||
|
||||
## 1. 软件性质说明
|
||||
|
||||
本软件为通用的视频处理与推流工具,支持用户对自有或合法授权的视频源进行处理与分发。本软件不提供任何第三方平台内容的解析、抓取或分发服务。
|
||||
|
||||
## 2. 用户责任
|
||||
|
||||
用户在使用本软件过程中,应确保:
|
||||
|
||||
* 所使用的视频源具有合法使用权或授权;
|
||||
* 不侵犯任何第三方的著作权、邻接权或其他合法权益;
|
||||
* 不违反相关法律法规及平台规则。
|
||||
|
||||
由用户上传、处理、推流的所有内容,均由用户自行承担全部法律责任。
|
||||
|
||||
## 3. 禁止行为
|
||||
|
||||
用户不得利用本软件从事以下行为:
|
||||
|
||||
* 未经授权抓取、转播、传播第三方平台内容;
|
||||
* 侵犯他人版权、肖像权、隐私权;
|
||||
* 从事违法违规内容传播。
|
||||
|
||||
## 4. 责任限制
|
||||
|
||||
本软件仅提供技术工具服务,不参与用户内容的获取、编辑或分发过程。
|
||||
因用户行为导致的任何法律责任或纠纷,均由用户自行承担,与本软件无关。
|
||||
|
||||
## 5. 权限与服务
|
||||
|
||||
部分功能属于付费服务(如 Pro/VIP),用户购买后仅获得使用权限,不涉及软件所有权转移。
|
||||
|
||||
## 6. 协议变更
|
||||
|
||||
本软件有权根据业务发展对本协议进行调整,并通过软件或网站进行公告。
|
||||
|
||||
如您不同意本协议,请立即停止使用本软件。
|
||||
`
|
||||
@@ -1,19 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'expo-module-gradle-plugin'
|
||||
}
|
||||
|
||||
group = 'expo.modules.easytier'
|
||||
version = '1.0.0'
|
||||
|
||||
android {
|
||||
namespace "expo.modules.easytier"
|
||||
defaultConfig {
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.13.1'
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
o/bundleLibRuntimeToDirDebug
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
o/bundleLibRuntimeToDirDebug
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
o/classes
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
o/classes
|
||||
Binary file not shown.
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Automatically generated file. DO NOT MODIFY
|
||||
*/
|
||||
package expo.modules.easytier;
|
||||
|
||||
public final class BuildConfig {
|
||||
public static final boolean DEBUG = Boolean.parseBoolean("true");
|
||||
public static final String LIBRARY_PACKAGE_NAME = "expo.modules.easytier";
|
||||
public static final String BUILD_TYPE = "debug";
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="expo.modules.easytier" >
|
||||
|
||||
<uses-sdk android:minSdkVersion="24" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
</manifest>
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "AAPT_FRIENDLY_MERGED_MANIFESTS",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "expo.modules.easytier",
|
||||
"variantName": "debug",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"outputFile": "AndroidManifest.xml"
|
||||
}
|
||||
],
|
||||
"elementType": "File"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
aarFormatVersion=1.0
|
||||
aarMetadataVersion=1.0
|
||||
minCompileSdk=1
|
||||
minCompileSdkExtension=0
|
||||
minAndroidGradlePluginVersion=1.0.0
|
||||
coreLibraryDesugaringEnabled=false
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
#Wed Mar 25 12:15:20 CDT 2026
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merger version="3"><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\mobile\modules\expo-easytier-vpn\android\src\main\res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main" generated-set="main$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\mobile\modules\expo-easytier-vpn\android\src\main\res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\mobile\modules\expo-easytier-vpn\android\src\debug\res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug" generated-set="debug$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\mobile\modules\expo-easytier-vpn\android\src\debug\res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="generated$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\mobile\modules\expo-easytier-vpn\android\build\generated\res\resValues\debug"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="generated" generated-set="generated$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\mobile\modules\expo-easytier-vpn\android\build\generated\res\resValues\debug"/></dataSet><mergedItems/></merger>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\admin\Desktop\d2yapp\modules\expo-easytier-vpn\android\src\main\assets"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\admin\Desktop\d2yapp\modules\expo-easytier-vpn\android\src\debug\assets"/></dataSet><dataSet config="generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\admin\Desktop\d2yapp\modules\expo-easytier-vpn\android\build\intermediates\shader_assets\debug\compileDebugShaders\out"/></dataSet></merger>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\admin\Desktop\d2yapp\modules\expo-easytier-vpn\android\src\main\jniLibs"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\admin\Desktop\d2yapp\modules\expo-easytier-vpn\android\src\debug\jniLibs"/></dataSet></merger>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\admin\Desktop\d2yapp\modules\expo-easytier-vpn\android\src\main\shaders"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\admin\Desktop\d2yapp\modules\expo-easytier-vpn\android\src\debug\shaders"/></dataSet></merger>
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
R_DEF: Internal format may change without notice
|
||||
local
|
||||
@@ -1,11 +0,0 @@
|
||||
1<?xml version="1.0" encoding="utf-8"?>
|
||||
2<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
3 package="expo.modules.easytier" >
|
||||
4
|
||||
5 <uses-sdk android:minSdkVersion="24" />
|
||||
6
|
||||
7 <uses-permission android:name="android.permission.INTERNET" />
|
||||
7-->C:\mobile\modules\expo-easytier-vpn\android\src\main\AndroidManifest.xml:2:3-65
|
||||
7-->C:\mobile\modules\expo-easytier-vpn\android\src\main\AndroidManifest.xml:2:20-62
|
||||
8
|
||||
9</manifest>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="expo.modules.easytier" >
|
||||
|
||||
<uses-sdk android:minSdkVersion="24" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
</manifest>
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
0 Warning/Error
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
expo.modules.easytier
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user