feat: 添加设备注册 API 并重构安装横幅组件
This commit is contained in:
171
hooks/use-device-token.ts
Normal file
171
hooks/use-device-token.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
|
||||
interface DeviceTokenData {
|
||||
clawId: string;
|
||||
apiKey: string;
|
||||
name: string;
|
||||
deviceId: string;
|
||||
}
|
||||
|
||||
interface UseDeviceTokenReturn {
|
||||
token: string | null;
|
||||
clawId: string | null;
|
||||
name: string | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
updateName: (newName: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = "openclaw_device";
|
||||
|
||||
function generateUUID(): string {
|
||||
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
function getDeviceInfo(): {
|
||||
platform: string;
|
||||
browser: string;
|
||||
screen: string;
|
||||
language: string;
|
||||
} {
|
||||
if (typeof navigator === "undefined") {
|
||||
return { platform: "unknown", browser: "unknown", screen: "unknown", language: "en" };
|
||||
}
|
||||
|
||||
const ua = navigator.userAgent;
|
||||
let browser = "Unknown";
|
||||
if (ua.includes("Chrome") && !ua.includes("Edg")) browser = "Chrome";
|
||||
else if (ua.includes("Firefox")) browser = "Firefox";
|
||||
else if (ua.includes("Safari") && !ua.includes("Chrome")) browser = "Safari";
|
||||
else if (ua.includes("Edg")) browser = "Edge";
|
||||
|
||||
return {
|
||||
platform: navigator.platform || "unknown",
|
||||
browser,
|
||||
screen:
|
||||
typeof screen !== "undefined"
|
||||
? `${screen.width}x${screen.height}`
|
||||
: "unknown",
|
||||
language: navigator.language || "en",
|
||||
};
|
||||
}
|
||||
|
||||
function getCachedData(): DeviceTokenData | null {
|
||||
if (typeof localStorage === "undefined") return null;
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
return JSON.parse(raw) as DeviceTokenData;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function setCachedData(data: DeviceTokenData): void {
|
||||
if (typeof localStorage === "undefined") return;
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||||
}
|
||||
|
||||
function updateCachedName(newName: string): void {
|
||||
const data = getCachedData();
|
||||
if (data) {
|
||||
setCachedData({ ...data, name: newName });
|
||||
}
|
||||
}
|
||||
|
||||
export function useDeviceToken(): UseDeviceTokenReturn {
|
||||
const [token, setToken] = useState<string | null>(null);
|
||||
const [clawId, setClawId] = useState<string | null>(null);
|
||||
const [name, setName] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
// Check cache first
|
||||
const cached = getCachedData();
|
||||
if (cached) {
|
||||
setToken(cached.apiKey);
|
||||
setClawId(cached.clawId);
|
||||
setName(cached.name);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Register new device
|
||||
try {
|
||||
const deviceId = generateUUID();
|
||||
const deviceInfo = getDeviceInfo();
|
||||
|
||||
const res = await fetch("/api/v1/device/register", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
deviceId,
|
||||
...deviceInfo,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Registration failed: ${res.status}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const tokenData: DeviceTokenData = {
|
||||
clawId: data.clawId,
|
||||
apiKey: data.apiKey,
|
||||
name: data.name,
|
||||
deviceId,
|
||||
};
|
||||
|
||||
setCachedData(tokenData);
|
||||
setToken(data.apiKey);
|
||||
setClawId(data.clawId);
|
||||
setName(data.name);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Registration failed");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
init();
|
||||
}, []);
|
||||
|
||||
const updateName = useCallback(
|
||||
async (newName: string): Promise<boolean> => {
|
||||
if (!token) return false;
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/v1/device/name", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ name: newName }),
|
||||
});
|
||||
|
||||
if (!res.ok) return false;
|
||||
|
||||
setName(newName);
|
||||
updateCachedName(newName);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[token]
|
||||
);
|
||||
|
||||
return { token, clawId, name, isLoading, error, updateName };
|
||||
}
|
||||
Reference in New Issue
Block a user