mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-09 04:36:15 +02:00
refactor: cleanup
This commit is contained in:
@@ -237,6 +237,24 @@ export function preflight(profile: BrowserProfile): PreflightResult {
|
||||
return ELIGIBLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this profile could be launched on a remote host.
|
||||
*
|
||||
* A strict subset of {@link preflight}: a remote session needs the profile to
|
||||
* exist in cloud storage in a form a host can read, and nothing more. The bot's
|
||||
* extra requirement — an exit node — exists because a night of unattended
|
||||
* traffic from a datacenter address is worse for the profile than not warming
|
||||
* it, and that reasoning does not apply to a session the user is driving.
|
||||
*
|
||||
* Mirrors `remote_launch_profile_rules` in `api_server.rs`, which is
|
||||
* authoritative; this only avoids offering an action that would be refused.
|
||||
*/
|
||||
export function canLaunchRemotely(profile: BrowserProfile): boolean {
|
||||
const syncMode = profile.sync_mode ?? "Disabled";
|
||||
if (syncMode === "Disabled" || syncMode === "Encrypted") return false;
|
||||
return resolvedOs(profile) !== null;
|
||||
}
|
||||
|
||||
export function preflightReason(t: TFunction, result: PreflightResult): string {
|
||||
switch (result.code) {
|
||||
case "syncOff":
|
||||
|
||||
@@ -101,6 +101,7 @@ import { useBrowserState } from "@/hooks/use-browser-state";
|
||||
import { useCloudAuth } from "@/hooks/use-cloud-auth";
|
||||
import { cookieBotScopeFor, useCookieBot } from "@/hooks/use-cookie-bot";
|
||||
import { useProxyEvents } from "@/hooks/use-proxy-events";
|
||||
import { useRemoteHandoff } from "@/hooks/use-remote-handoff";
|
||||
import { useScrollFade } from "@/hooks/use-scroll-fade";
|
||||
import { useTableSorting } from "@/hooks/use-table-sorting";
|
||||
import { useTeamLocks } from "@/hooks/use-team-locks";
|
||||
@@ -121,6 +122,7 @@ import {
|
||||
import { DNS_BLOCKLIST_LEVELS } from "@/lib/dns-blocklist-levels";
|
||||
import { canUseCookieBot } from "@/lib/entitlements";
|
||||
import { formatRelativeTime } from "@/lib/flag-utils";
|
||||
import type { RemoteHandoffState } from "@/lib/remote-sessions";
|
||||
import { showErrorToast, showSuccessToast } from "@/lib/toast-utils";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type {
|
||||
@@ -273,6 +275,15 @@ interface TableMeta {
|
||||
isProfileLockedByAnother: (profileId: string) => boolean;
|
||||
getProfileLockEmail: (profileId: string) => string | undefined;
|
||||
|
||||
// Remote execution.
|
||||
//
|
||||
// `getRemoteHandoff` is the authoritative answer to "can this be opened
|
||||
// here", read from the same store the backend gate reads. The team-lock cache
|
||||
// above cannot serve it: it refreshes on a 30-second poll and says nothing at
|
||||
// all about a session that has finished but whose work has not been pulled
|
||||
// back yet.
|
||||
getRemoteHandoff: (profileId: string) => RemoteHandoffState | null;
|
||||
|
||||
// Synchronizer
|
||||
getProfileSyncInfo: (profileId: string) =>
|
||||
| {
|
||||
@@ -1585,6 +1596,10 @@ export function ProfilesDataTable({
|
||||
const { vpnConfigs } = useVpnEvents();
|
||||
const { user } = useCloudAuth();
|
||||
const { isProfileLocked, getLockInfo } = useTeamLocks(user?.id);
|
||||
// Which profiles cannot be opened on this computer, and why. Event-driven and
|
||||
// read from the backend's own gate, so the button state and the refusal the
|
||||
// backend would give can never disagree.
|
||||
const { handoffFor } = useRemoteHandoff();
|
||||
|
||||
// Cookie Bot. Enrolments and live runs both live server-side, so the table
|
||||
// reads them from the shared store rather than from BrowserProfile.
|
||||
@@ -2445,6 +2460,9 @@ export function ProfilesDataTable({
|
||||
getProfileLockEmail: (profileId: string) =>
|
||||
getLockInfo(profileId)?.lockedByEmail,
|
||||
|
||||
// Remote execution
|
||||
getRemoteHandoff: handoffFor,
|
||||
|
||||
// Synchronizer
|
||||
getProfileSyncInfo: getProfileSyncInfo ?? (() => undefined),
|
||||
onLaunchWithSync:
|
||||
@@ -2524,6 +2542,7 @@ export function ProfilesDataTable({
|
||||
handleCreateCountryProxy,
|
||||
isProfileLocked,
|
||||
getLockInfo,
|
||||
handoffFor,
|
||||
getProfileSyncInfo,
|
||||
onLaunchWithSync,
|
||||
cookieBotUnlocked,
|
||||
@@ -2725,20 +2744,37 @@ export function ProfilesDataTable({
|
||||
cell: ({ row, table }) => {
|
||||
const meta = table.options.meta as TableMeta;
|
||||
const profile = row.original;
|
||||
const handoff = meta.getRemoteHandoff(profile.id);
|
||||
// A profile open on the fleet IS running, and the button has to say
|
||||
// so: it is the control that stops it, and stopping now reaches the
|
||||
// remote browser rather than looking for a local process that was
|
||||
// never there.
|
||||
const isRunningRemotely = handoff === "running";
|
||||
const isPendingRemotePull = handoff === "pending_sync";
|
||||
const isRunning =
|
||||
meta.isClient && meta.runningProfiles.has(profile.id);
|
||||
(meta.isClient && meta.runningProfiles.has(profile.id)) ||
|
||||
isRunningRemotely;
|
||||
const isLaunching = meta.launchingProfiles.has(profile.id);
|
||||
const isStopping = meta.stoppingProfiles.has(profile.id);
|
||||
const isLockedByAnother = meta.isProfileLockedByAnother(profile.id);
|
||||
const isSyncing = meta.syncStatuses[profile.id]?.status === "syncing";
|
||||
const canLaunch =
|
||||
meta.browserState.canLaunchProfile(profile) &&
|
||||
!isLockedByAnother &&
|
||||
!isSyncing;
|
||||
// A remote session holds the profile lock under its own holder id, so
|
||||
// `isLockedByAnother` is true for the user's OWN fleet session. That
|
||||
// must not disable the control that stops it.
|
||||
const canLaunch = isRunningRemotely
|
||||
? true
|
||||
: meta.browserState.canLaunchProfile(profile) &&
|
||||
!isPendingRemotePull &&
|
||||
!isLockedByAnother &&
|
||||
!isSyncing;
|
||||
const lockEmail = meta.getProfileLockEmail(profile.id);
|
||||
const tooltipContent = isLockedByAnother
|
||||
? meta.t("sync.team.cannotLaunchLocked", { email: lockEmail })
|
||||
: meta.browserState.getLaunchTooltipContent(profile);
|
||||
const tooltipContent = isRunningRemotely
|
||||
? meta.t("profiles.remote.runningTooltip")
|
||||
: isPendingRemotePull
|
||||
? meta.t("profiles.remote.pendingSyncTooltip")
|
||||
: isLockedByAnother
|
||||
? meta.t("sync.team.cannotLaunchLocked", { email: lockEmail })
|
||||
: meta.browserState.getLaunchTooltipContent(profile);
|
||||
|
||||
const handleProfileStop = async (profile: BrowserProfile) => {
|
||||
meta.setStoppingProfiles((prev: Set<string>) =>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
getRemoteHandoffStates,
|
||||
onRemoteHandoffChanged,
|
||||
type RemoteHandoffState,
|
||||
} from "@/lib/remote-sessions";
|
||||
|
||||
/**
|
||||
* Which profiles cannot be opened on this computer right now.
|
||||
*
|
||||
* Reads the same store the backend launch gate reads, so the button this
|
||||
* disables and the refusal the backend would produce can never disagree. That
|
||||
* matters more than it sounds: the previous signal was the profile-lock cache,
|
||||
* which refreshes on a 30-second server poll and only refetches on this
|
||||
* device's own lock events. A profile running on the fleet therefore looked
|
||||
* launchable for up to half a minute, and a profile whose finished session had
|
||||
* not been pulled back looked launchable indefinitely.
|
||||
*
|
||||
* Updates arrive as an event rather than a poll because every transition that
|
||||
* can change this already emits one.
|
||||
*/
|
||||
export function useRemoteHandoff() {
|
||||
const [states, setStates] = useState<Record<string, RemoteHandoffState>>({});
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
try {
|
||||
setStates(await getRemoteHandoffStates());
|
||||
} catch (error) {
|
||||
// Not signed in, or the app is still starting. The backend gate still
|
||||
// applies; the button is simply not pre-disabled.
|
||||
console.warn("Could not read remote handoff state:", error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void refresh();
|
||||
const unlisten = onRemoteHandoffChanged(setStates);
|
||||
return () => {
|
||||
void unlisten.then((off) => {
|
||||
off();
|
||||
});
|
||||
};
|
||||
}, [refresh]);
|
||||
|
||||
const handoffFor = useCallback(
|
||||
(profileId: string): RemoteHandoffState | null => states[profileId] ?? null,
|
||||
[states],
|
||||
);
|
||||
|
||||
return { handoffStates: states, handoffFor, refreshHandoff: refresh };
|
||||
}
|
||||
@@ -345,6 +345,10 @@
|
||||
"nameDesc": "Name (Z–A)",
|
||||
"newest": "Newest first",
|
||||
"oldest": "Oldest first"
|
||||
},
|
||||
"remote": {
|
||||
"runningTooltip": "Running on a remote machine. Stop it to bring the profile back here.",
|
||||
"pendingSyncTooltip": "Downloading what the remote session changed. Available again when it finishes."
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
@@ -1880,7 +1884,12 @@
|
||||
"cookieBotUnsupportedPlatform": "The cookie bot cannot run {{platform}} profiles. Only Windows and macOS profiles are supported.",
|
||||
"cookieBotRequiresExitNode": "Attach a proxy or VPN first. Without one the run would come from a datacenter address, which damages the profile's identity.",
|
||||
"unknownCode": "Something went wrong: {{code}}",
|
||||
"cookieBotTouchFingerprintUnsupported": "This profile claims a touch device, which the bot cannot drive. Use a desktop fingerprint."
|
||||
"cookieBotTouchFingerprintUnsupported": "This profile claims a touch device, which the bot cannot drive. Use a desktop fingerprint.",
|
||||
"profileRunningRemotely": "This profile is running on a remote machine. Stop the remote session first.",
|
||||
"profileRemoteSyncPending": "A remote session just finished. Waiting for its changes to download before this profile can open here.",
|
||||
"profileLockedByMember": "This profile is in use by {{email}}.",
|
||||
"profileLockedElsewhere": "This profile is in use on another device.",
|
||||
"profileLockUnavailable": "Could not check whether this profile is in use elsewhere. Check your connection and try again."
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Profiles",
|
||||
|
||||
@@ -345,6 +345,10 @@
|
||||
"nameDesc": "Nombre (Z–A)",
|
||||
"newest": "Más recientes primero",
|
||||
"oldest": "Más antiguos primero"
|
||||
},
|
||||
"remote": {
|
||||
"runningTooltip": "Ejecutándose en una máquina remota. Deténlo para recuperar el perfil aquí.",
|
||||
"pendingSyncTooltip": "Descargando lo que cambió la sesión remota. Disponible de nuevo cuando termine."
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
@@ -1887,7 +1891,12 @@
|
||||
"cookieBotUnsupportedPlatform": "Cookie Bot no puede ejecutar perfiles de {{platform}}. Solo se admiten perfiles de Windows y macOS.",
|
||||
"cookieBotRequiresExitNode": "Asigna primero un proxy o una VPN. Sin ninguno, la ejecución saldría desde una dirección de centro de datos, lo que daña la identidad del perfil.",
|
||||
"unknownCode": "Algo salió mal: {{code}}",
|
||||
"cookieBotTouchFingerprintUnsupported": "Este perfil declara un dispositivo táctil, que el bot no puede controlar. Usa una huella de escritorio."
|
||||
"cookieBotTouchFingerprintUnsupported": "Este perfil declara un dispositivo táctil, que el bot no puede controlar. Usa una huella de escritorio.",
|
||||
"profileRunningRemotely": "Este perfil se está ejecutando en una máquina remota. Detén primero la sesión remota.",
|
||||
"profileRemoteSyncPending": "Una sesión remota acaba de terminar. Esperando a que se descarguen sus cambios antes de abrir este perfil aquí.",
|
||||
"profileLockedByMember": "Este perfil está siendo usado por {{email}}.",
|
||||
"profileLockedElsewhere": "Este perfil está en uso en otro dispositivo.",
|
||||
"profileLockUnavailable": "No se pudo comprobar si este perfil está en uso en otro lugar. Revisa tu conexión e inténtalo de nuevo."
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Perfiles",
|
||||
|
||||
@@ -345,6 +345,10 @@
|
||||
"nameDesc": "Nom (Z–A)",
|
||||
"newest": "Plus récents d’abord",
|
||||
"oldest": "Plus anciens d’abord"
|
||||
},
|
||||
"remote": {
|
||||
"runningTooltip": "En cours d'exécution sur une machine distante. Arrêtez-la pour récupérer le profil ici.",
|
||||
"pendingSyncTooltip": "Téléchargement des modifications de la session distante. De nouveau disponible une fois terminé."
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
@@ -1887,7 +1891,12 @@
|
||||
"cookieBotUnsupportedPlatform": "Cookie Bot ne peut pas exécuter de profils {{platform}}. Seuls les profils Windows et macOS sont pris en charge.",
|
||||
"cookieBotRequiresExitNode": "Associez d'abord un proxy ou un VPN. Sans cela, l'exécution proviendrait d'une adresse de centre de données, ce qui abîme l'identité du profil.",
|
||||
"unknownCode": "Une erreur est survenue : {{code}}",
|
||||
"cookieBotTouchFingerprintUnsupported": "Ce profil déclare un appareil tactile, que le bot ne peut pas piloter. Utilisez une empreinte de bureau."
|
||||
"cookieBotTouchFingerprintUnsupported": "Ce profil déclare un appareil tactile, que le bot ne peut pas piloter. Utilisez une empreinte de bureau.",
|
||||
"profileRunningRemotely": "Ce profil s'exécute sur une machine distante. Arrêtez d'abord la session distante.",
|
||||
"profileRemoteSyncPending": "Une session distante vient de se terminer. Ses modifications doivent être téléchargées avant d'ouvrir ce profil ici.",
|
||||
"profileLockedByMember": "Ce profil est utilisé par {{email}}.",
|
||||
"profileLockedElsewhere": "Ce profil est utilisé sur un autre appareil.",
|
||||
"profileLockUnavailable": "Impossible de vérifier si ce profil est utilisé ailleurs. Vérifiez votre connexion et réessayez."
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Profils",
|
||||
|
||||
@@ -345,6 +345,10 @@
|
||||
"nameDesc": "名前 (Z→A)",
|
||||
"newest": "新しい順",
|
||||
"oldest": "古い順"
|
||||
},
|
||||
"remote": {
|
||||
"runningTooltip": "リモートマシンで実行中です。停止するとプロファイルがここに戻ります。",
|
||||
"pendingSyncTooltip": "リモートセッションの変更をダウンロード中です。完了すると再び使用できます。"
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
@@ -1880,7 +1884,12 @@
|
||||
"cookieBotUnsupportedPlatform": "Cookie Bot は {{platform}} のプロファイルを実行できません。対応しているのは Windows と macOS のプロファイルのみです。",
|
||||
"cookieBotRequiresExitNode": "先にプロキシまたは VPN を設定してください。設定しないと通信がデータセンターのアドレスから出て、プロファイルの信頼性を損ないます。",
|
||||
"unknownCode": "エラーが発生しました: {{code}}",
|
||||
"cookieBotTouchFingerprintUnsupported": "このプロファイルはタッチ端末を名乗っており、ボットは操作できません。デスクトップのフィンガープリントをお使いください。"
|
||||
"cookieBotTouchFingerprintUnsupported": "このプロファイルはタッチ端末を名乗っており、ボットは操作できません。デスクトップのフィンガープリントをお使いください。",
|
||||
"profileRunningRemotely": "このプロファイルはリモートマシンで実行中です。先にリモートセッションを停止してください。",
|
||||
"profileRemoteSyncPending": "リモートセッションが終了しました。この profile をここで開く前に、変更のダウンロードを待っています。",
|
||||
"profileLockedByMember": "このプロファイルは {{email}} が使用中です。",
|
||||
"profileLockedElsewhere": "このプロファイルは別のデバイスで使用中です。",
|
||||
"profileLockUnavailable": "このプロファイルが他で使用中か確認できませんでした。接続を確認して再試行してください。"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "プロファイル",
|
||||
|
||||
@@ -345,6 +345,10 @@
|
||||
"nameDesc": "이름 (Z→A)",
|
||||
"newest": "최신순",
|
||||
"oldest": "오래된순"
|
||||
},
|
||||
"remote": {
|
||||
"runningTooltip": "원격 머신에서 실행 중입니다. 중지하면 프로필이 여기로 돌아옵니다.",
|
||||
"pendingSyncTooltip": "원격 세션이 변경한 내용을 내려받는 중입니다. 완료되면 다시 사용할 수 있습니다."
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
@@ -1880,7 +1884,12 @@
|
||||
"cookieBotUnsupportedPlatform": "Cookie Bot은 {{platform}} 프로필을 실행할 수 없습니다. Windows와 macOS 프로필만 지원합니다.",
|
||||
"cookieBotRequiresExitNode": "먼저 프록시나 VPN을 연결하세요. 없으면 실행 트래픽이 데이터센터 주소에서 나가 프로필 신뢰도를 해칩니다.",
|
||||
"unknownCode": "문제가 발생했습니다: {{code}}",
|
||||
"cookieBotTouchFingerprintUnsupported": "이 프로필은 터치 기기를 표방하며, 봇이 조작할 수 없습니다. 데스크톱 지문을 사용하세요."
|
||||
"cookieBotTouchFingerprintUnsupported": "이 프로필은 터치 기기를 표방하며, 봇이 조작할 수 없습니다. 데스크톱 지문을 사용하세요.",
|
||||
"profileRunningRemotely": "이 프로필은 원격 머신에서 실행 중입니다. 먼저 원격 세션을 중지하세요.",
|
||||
"profileRemoteSyncPending": "원격 세션이 방금 끝났습니다. 이 프로필을 여기서 열기 전에 변경 사항을 내려받는 중입니다.",
|
||||
"profileLockedByMember": "이 프로필은 {{email}} 님이 사용 중입니다.",
|
||||
"profileLockedElsewhere": "이 프로필은 다른 기기에서 사용 중입니다.",
|
||||
"profileLockUnavailable": "이 프로필이 다른 곳에서 사용 중인지 확인할 수 없습니다. 연결을 확인한 뒤 다시 시도하세요."
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "프로필",
|
||||
|
||||
@@ -345,6 +345,10 @@
|
||||
"nameDesc": "Nome (Z–A)",
|
||||
"newest": "Mais recentes primeiro",
|
||||
"oldest": "Mais antigos primeiro"
|
||||
},
|
||||
"remote": {
|
||||
"runningTooltip": "Em execução numa máquina remota. Pare-a para trazer o perfil de volta para aqui.",
|
||||
"pendingSyncTooltip": "A transferir o que a sessão remota alterou. Disponível novamente quando terminar."
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
@@ -1887,7 +1891,12 @@
|
||||
"cookieBotUnsupportedPlatform": "O Cookie Bot não pode executar perfis de {{platform}}. Somente perfis Windows e macOS são suportados.",
|
||||
"cookieBotRequiresExitNode": "Anexe primeiro um proxy ou VPN. Sem isso, a execução sairia de um endereço de data center, o que prejudica a identidade do perfil.",
|
||||
"unknownCode": "Algo deu errado: {{code}}",
|
||||
"cookieBotTouchFingerprintUnsupported": "Este perfil declara um dispositivo de toque, que o bot não consegue controlar. Use uma impressão digital de computador."
|
||||
"cookieBotTouchFingerprintUnsupported": "Este perfil declara um dispositivo de toque, que o bot não consegue controlar. Use uma impressão digital de computador.",
|
||||
"profileRunningRemotely": "Este perfil está em execução numa máquina remota. Pare primeiro a sessão remota.",
|
||||
"profileRemoteSyncPending": "Uma sessão remota acabou de terminar. A aguardar a transferência das alterações antes de abrir este perfil aqui.",
|
||||
"profileLockedByMember": "Este perfil está a ser utilizado por {{email}}.",
|
||||
"profileLockedElsewhere": "Este perfil está a ser utilizado noutro dispositivo.",
|
||||
"profileLockUnavailable": "Não foi possível verificar se este perfil está a ser utilizado noutro local. Verifique a ligação e tente novamente."
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Perfis",
|
||||
|
||||
@@ -345,6 +345,10 @@
|
||||
"nameDesc": "Имя (Я–А)",
|
||||
"newest": "Сначала новые",
|
||||
"oldest": "Сначала старые"
|
||||
},
|
||||
"remote": {
|
||||
"runningTooltip": "Выполняется на удалённой машине. Остановите, чтобы вернуть профиль сюда.",
|
||||
"pendingSyncTooltip": "Загружаются изменения удалённого сеанса. Профиль снова будет доступен по завершении."
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
@@ -1894,7 +1898,12 @@
|
||||
"cookieBotUnsupportedPlatform": "Cookie Bot не может запускать профили {{platform}}. Поддерживаются только профили Windows и macOS.",
|
||||
"cookieBotRequiresExitNode": "Сначала назначьте прокси или VPN. Без них трафик пойдёт с адреса дата-центра, а это вредит репутации профиля.",
|
||||
"unknownCode": "Что-то пошло не так: {{code}}",
|
||||
"cookieBotTouchFingerprintUnsupported": "Этот профиль выдаёт себя за сенсорное устройство, которым бот управлять не может. Используйте настольный отпечаток."
|
||||
"cookieBotTouchFingerprintUnsupported": "Этот профиль выдаёт себя за сенсорное устройство, которым бот управлять не может. Используйте настольный отпечаток.",
|
||||
"profileRunningRemotely": "Этот профиль запущен на удалённой машине. Сначала остановите удалённый сеанс.",
|
||||
"profileRemoteSyncPending": "Удалённый сеанс только что завершился. Дождитесь загрузки его изменений, прежде чем открывать профиль здесь.",
|
||||
"profileLockedByMember": "Этот профиль используется пользователем {{email}}.",
|
||||
"profileLockedElsewhere": "Этот профиль используется на другом устройстве.",
|
||||
"profileLockUnavailable": "Не удалось проверить, используется ли профиль где-то ещё. Проверьте подключение и попробуйте снова."
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Профили",
|
||||
|
||||
@@ -345,6 +345,10 @@
|
||||
"nameDesc": "Ad (Z–A)",
|
||||
"newest": "Önce en yeni",
|
||||
"oldest": "Önce en eski"
|
||||
},
|
||||
"remote": {
|
||||
"runningTooltip": "Uzak bir makinede çalışıyor. Profili buraya geri getirmek için durdurun.",
|
||||
"pendingSyncTooltip": "Uzak oturumun değiştirdikleri indiriliyor. Bittiğinde yeniden kullanılabilir olacak."
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
@@ -1880,7 +1884,12 @@
|
||||
"cookieBotUnsupportedPlatform": "Cookie Bot, {{platform}} profillerini çalıştıramaz. Yalnızca Windows ve macOS profilleri desteklenir.",
|
||||
"cookieBotRequiresExitNode": "Önce bir proxy veya VPN ekleyin. Aksi hâlde çalışma bir veri merkezi adresinden çıkar ve bu, profilin kimliğine zarar verir.",
|
||||
"unknownCode": "Bir sorun oluştu: {{code}}",
|
||||
"cookieBotTouchFingerprintUnsupported": "Bu profil dokunmatik bir cihaz olduğunu bildiriyor ve bot bunu süremez. Masaüstü parmak izi kullanın."
|
||||
"cookieBotTouchFingerprintUnsupported": "Bu profil dokunmatik bir cihaz olduğunu bildiriyor ve bot bunu süremez. Masaüstü parmak izi kullanın.",
|
||||
"profileRunningRemotely": "Bu profil uzak bir makinede çalışıyor. Önce uzak oturumu durdurun.",
|
||||
"profileRemoteSyncPending": "Uzak oturum az önce bitti. Bu profili burada açmadan önce değişikliklerinin inmesi bekleniyor.",
|
||||
"profileLockedByMember": "Bu profil {{email}} tarafından kullanılıyor.",
|
||||
"profileLockedElsewhere": "Bu profil başka bir cihazda kullanılıyor.",
|
||||
"profileLockUnavailable": "Bu profilin başka bir yerde kullanılıp kullanılmadığı denetlenemedi. Bağlantınızı kontrol edip yeniden deneyin."
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Profiller",
|
||||
|
||||
@@ -345,6 +345,10 @@
|
||||
"nameDesc": "Tên (Z–A)",
|
||||
"newest": "Mới nhất trước",
|
||||
"oldest": "Cũ nhất trước"
|
||||
},
|
||||
"remote": {
|
||||
"runningTooltip": "Đang chạy trên máy từ xa. Dừng lại để đưa hồ sơ về đây.",
|
||||
"pendingSyncTooltip": "Đang tải về những gì phiên từ xa đã thay đổi. Sẽ dùng lại được khi hoàn tất."
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
@@ -1880,7 +1884,12 @@
|
||||
"cookieBotUnsupportedPlatform": "Cookie Bot không chạy được hồ sơ {{platform}}. Chỉ hỗ trợ hồ sơ Windows và macOS.",
|
||||
"cookieBotRequiresExitNode": "Hãy gán proxy hoặc VPN trước. Nếu không, lần chạy sẽ đi ra từ địa chỉ trung tâm dữ liệu, gây hại cho danh tính hồ sơ.",
|
||||
"unknownCode": "Đã xảy ra lỗi: {{code}}",
|
||||
"cookieBotTouchFingerprintUnsupported": "Hồ sơ này khai báo là thiết bị cảm ứng, bot không điều khiển được. Hãy dùng vân tay máy tính để bàn."
|
||||
"cookieBotTouchFingerprintUnsupported": "Hồ sơ này khai báo là thiết bị cảm ứng, bot không điều khiển được. Hãy dùng vân tay máy tính để bàn.",
|
||||
"profileRunningRemotely": "Hồ sơ này đang chạy trên máy từ xa. Hãy dừng phiên từ xa trước.",
|
||||
"profileRemoteSyncPending": "Một phiên từ xa vừa kết thúc. Đang chờ tải các thay đổi về trước khi mở hồ sơ này tại đây.",
|
||||
"profileLockedByMember": "Hồ sơ này đang được {{email}} sử dụng.",
|
||||
"profileLockedElsewhere": "Hồ sơ này đang được sử dụng trên thiết bị khác.",
|
||||
"profileLockUnavailable": "Không thể kiểm tra hồ sơ này có đang được dùng ở nơi khác hay không. Hãy kiểm tra kết nối và thử lại."
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Profile",
|
||||
|
||||
@@ -345,6 +345,10 @@
|
||||
"nameDesc": "名称 (Z–A)",
|
||||
"newest": "最新优先",
|
||||
"oldest": "最早优先"
|
||||
},
|
||||
"remote": {
|
||||
"runningTooltip": "正在远程计算机上运行。停止后配置文件会回到本机。",
|
||||
"pendingSyncTooltip": "正在下载远程会话所做的更改。完成后即可再次使用。"
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
@@ -1880,7 +1884,12 @@
|
||||
"cookieBotUnsupportedPlatform": "Cookie Bot 无法运行 {{platform}} 配置文件。仅支持 Windows 和 macOS 配置文件。",
|
||||
"cookieBotRequiresExitNode": "请先绑定代理或 VPN。否则运行会从数据中心地址发出,损害配置文件的身份。",
|
||||
"unknownCode": "出现问题: {{code}}",
|
||||
"cookieBotTouchFingerprintUnsupported": "该配置文件声称是触摸设备,机器人无法操作。请使用桌面端指纹。"
|
||||
"cookieBotTouchFingerprintUnsupported": "该配置文件声称是触摸设备,机器人无法操作。请使用桌面端指纹。",
|
||||
"profileRunningRemotely": "该配置文件正在远程计算机上运行。请先停止远程会话。",
|
||||
"profileRemoteSyncPending": "远程会话刚刚结束。正在等待其更改下载完成后才能在此打开该配置文件。",
|
||||
"profileLockedByMember": "该配置文件正在被 {{email}} 使用。",
|
||||
"profileLockedElsewhere": "该配置文件正在另一台设备上使用。",
|
||||
"profileLockUnavailable": "无法检查该配置文件是否正在别处使用。请检查网络连接后重试。"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "配置文件",
|
||||
|
||||
@@ -78,6 +78,11 @@ export type BackendErrorCode =
|
||||
| "REMOTE_SESSION_CONFLICT"
|
||||
| "REMOTE_SYNC_IN_PROGRESS"
|
||||
| "REMOTE_HOURS_EXHAUSTED"
|
||||
| "PROFILE_RUNNING_REMOTELY"
|
||||
| "PROFILE_REMOTE_SYNC_PENDING"
|
||||
| "PROFILE_LOCKED_BY_MEMBER"
|
||||
| "PROFILE_LOCKED_ELSEWHERE"
|
||||
| "PROFILE_LOCK_UNAVAILABLE"
|
||||
| "NOT_TEAM_MEMBER"
|
||||
| "COOKIE_BOT_NOT_ENTITLED"
|
||||
| "COOKIE_BOT_NOT_ENROLLED"
|
||||
@@ -314,6 +319,18 @@ export function translateBackendError(t: TFunction, err: unknown): string {
|
||||
granted: parsed.params?.granted ?? "0",
|
||||
used: parsed.params?.used ?? "0",
|
||||
});
|
||||
case "PROFILE_RUNNING_REMOTELY":
|
||||
return t("backendErrors.profileRunningRemotely");
|
||||
case "PROFILE_REMOTE_SYNC_PENDING":
|
||||
return t("backendErrors.profileRemoteSyncPending");
|
||||
case "PROFILE_LOCKED_BY_MEMBER":
|
||||
return t("backendErrors.profileLockedByMember", {
|
||||
email: parsed.params?.email ?? "",
|
||||
});
|
||||
case "PROFILE_LOCKED_ELSEWHERE":
|
||||
return t("backendErrors.profileLockedElsewhere");
|
||||
case "PROFILE_LOCK_UNAVAILABLE":
|
||||
return t("backendErrors.profileLockUnavailable");
|
||||
case "NOT_TEAM_MEMBER":
|
||||
return t("backendErrors.notTeamMember");
|
||||
case "COOKIE_BOT_NOT_ENTITLED":
|
||||
|
||||
@@ -47,6 +47,19 @@ export interface RemoteSessionEnded {
|
||||
billed_seconds: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a profile cannot be opened on this computer right now.
|
||||
*
|
||||
* - `running`: a browser is open on the fleet holding this profile.
|
||||
* - `pending_sync`: a session has finished and what it wrote is still being
|
||||
* pulled down. Opening the local copy now would make the local files look
|
||||
* newer than the host's push, and the next sync would then upload the stale
|
||||
* copy over the session's work and delete the rest of it.
|
||||
*
|
||||
* Both states are temporary and neither is an error.
|
||||
*/
|
||||
export type RemoteHandoffState = "running" | "pending_sync";
|
||||
|
||||
/**
|
||||
* States a session cannot leave under its own steam.
|
||||
*
|
||||
@@ -83,8 +96,31 @@ export const REMOTE_SESSION_EVENTS = {
|
||||
snapshot: "remote-session-snapshot",
|
||||
/** Stream connectivity. Payload: `RemoteSessionStreamStatus`. */
|
||||
stream: "remote-session-stream",
|
||||
/**
|
||||
* The set of profiles that cannot be launched locally changed.
|
||||
* Payload: `Record<profileId, RemoteHandoffState>`.
|
||||
*/
|
||||
handoff: "remote-handoff-changed",
|
||||
} as const;
|
||||
|
||||
/** Which profiles are blocked from launching locally, and why. */
|
||||
export function getRemoteHandoffStates(): Promise<
|
||||
Record<string, RemoteHandoffState>
|
||||
> {
|
||||
return invoke<Record<string, RemoteHandoffState>>(
|
||||
"get_remote_handoff_states",
|
||||
);
|
||||
}
|
||||
|
||||
export function onRemoteHandoffChanged(
|
||||
handler: (states: Record<string, RemoteHandoffState>) => void,
|
||||
): Promise<UnlistenFn> {
|
||||
return listen<Record<string, RemoteHandoffState>>(
|
||||
REMOTE_SESSION_EVENTS.handoff,
|
||||
(event) => handler(event.payload),
|
||||
);
|
||||
}
|
||||
|
||||
export function listRemoteSessions(): Promise<RemoteSessionState[]> {
|
||||
return invoke<RemoteSessionState[]>("list_remote_sessions");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user