172 lines
4.3 KiB
TypeScript
172 lines
4.3 KiB
TypeScript
"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 };
|
|
}
|