Bind domain access to registered node type
This commit is contained in:
parent
f3a1ee82a2
commit
8807b99d98
15 changed files with 168 additions and 63 deletions
|
|
@ -47,6 +47,7 @@ interface DomainAccessStatus {
|
|||
blockers: string[];
|
||||
domainId?: string;
|
||||
nodeId?: string;
|
||||
nodeType?: DomainNodeType;
|
||||
runtimeReady: boolean;
|
||||
stage: 'checking' | 'login-required' | 'identity-verified' | 'runtime-ready';
|
||||
trustSource?: {
|
||||
|
|
@ -179,18 +180,18 @@ export default function App() {
|
|||
}
|
||||
}, []);
|
||||
|
||||
const refreshDomainAccess = useCallback(async (domainId = 'DOM-FIFTH-0001') => {
|
||||
const refreshDomainAccess = useCallback(async (domainId = 'DOM-FIFTH-0001', nodeType: DomainNodeType = 'local-terminal') => {
|
||||
const request = ++domainAccessRequest.current;
|
||||
const server = (window as any).hololake?.server;
|
||||
if (!server?.domainAccess) {
|
||||
if (request === domainAccessRequest.current) setDomainAccess({ blockers: ['desktop_runtime_required'], domainId, runtimeReady: false, stage: 'login-required' });
|
||||
if (request === domainAccessRequest.current) setDomainAccess({ blockers: ['desktop_runtime_required'], domainId, nodeType, runtimeReady: false, stage: 'login-required' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await server.domainAccess(domainId);
|
||||
const result = await server.domainAccess(domainId, nodeType);
|
||||
if (request === domainAccessRequest.current) setDomainAccess(result);
|
||||
} catch {
|
||||
if (request === domainAccessRequest.current) setDomainAccess({ blockers: ['domain_access_probe_failed'], domainId, runtimeReady: false, stage: 'login-required' });
|
||||
if (request === domainAccessRequest.current) setDomainAccess({ blockers: ['domain_access_probe_failed'], domainId, nodeType, runtimeReady: false, stage: 'login-required' });
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -200,13 +201,17 @@ export default function App() {
|
|||
setDomainConnectionOpen(true);
|
||||
if (target) {
|
||||
setDomainAccess({ blockers: [], domainId: target.domain.stableDomainId, runtimeReady: false, stage: 'checking' });
|
||||
void refreshDomainAccess(target.domain.stableDomainId);
|
||||
void refreshDomainAccess(target.domain.stableDomainId, target.nodeType);
|
||||
}
|
||||
}, [refreshDomainAccess]);
|
||||
|
||||
const selectDomainNodeType = useCallback((nodeType: DomainNodeType) => {
|
||||
setDomainEntryTarget(current => current ? createDomainEntryTarget(current.domain.routeId, nodeType) : null);
|
||||
}, []);
|
||||
if (!domainEntryTarget) return;
|
||||
const target = createDomainEntryTarget(domainEntryTarget.domain.routeId, nodeType);
|
||||
setDomainEntryTarget(target);
|
||||
setDomainAccess({ blockers: [], domainId: target.domain.stableDomainId, nodeType, runtimeReady: false, stage: 'checking' });
|
||||
void refreshDomainAccess(target.domain.stableDomainId, nodeType);
|
||||
}, [domainEntryTarget, refreshDomainAccess]);
|
||||
|
||||
const refreshChannel = useCallback(async () => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import { projectDomainConnectionSteps, projectDomainTrustSource } from './domain
|
|||
test('code-channel account absence cannot be projected as a domain runtime login', () => {
|
||||
const steps = projectDomainConnectionSteps({
|
||||
blockers: [
|
||||
'account_node_identity_missing',
|
||||
'account_identity_missing',
|
||||
'verified_node_registration_missing',
|
||||
'verified_domain_manifest_missing',
|
||||
'scoped_session_capability_missing',
|
||||
'matching_connection_receipt_missing',
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export interface DomainAccessProjection {
|
|||
blockers: string[];
|
||||
domainId?: string;
|
||||
nodeId?: string;
|
||||
nodeType?: 'local-terminal' | 'cloud-resident';
|
||||
runtimeReady: boolean;
|
||||
stage: DomainAccessStage;
|
||||
trustSource?: {
|
||||
|
|
@ -23,7 +24,8 @@ export interface DomainConnectionStep {
|
|||
}
|
||||
|
||||
const blockerToStep: Record<string, DomainConnectionStep['id']> = {
|
||||
account_node_identity_missing: 'identity',
|
||||
account_identity_missing: 'identity',
|
||||
verified_node_registration_missing: 'identity',
|
||||
verified_domain_manifest_missing: 'manifest',
|
||||
scoped_session_capability_missing: 'capability',
|
||||
matching_connection_receipt_missing: 'receipt',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
|
|||
import test from 'node:test';
|
||||
import { canEnterSelectedDomainRuntime, createDomainEntryTarget, projectDomainRuntimeBoundary } from './domain-entry-state.js';
|
||||
|
||||
const ready = (domainId: string) => ({ blockers: [], domainId, runtimeReady: true, stage: 'runtime-ready' as const });
|
||||
const ready = (domainId: string, nodeType: 'local-terminal' | 'cloud-resident' = 'local-terminal') => ({ blockers: [], domainId, nodeType, runtimeReady: true, stage: 'runtime-ready' as const });
|
||||
|
||||
test('a generic login request does not silently target the Fifth Domain', () => {
|
||||
assert.equal(canEnterSelectedDomainRuntime(null, ready('DOM-FIFTH-0001')), false);
|
||||
|
|
@ -11,8 +11,8 @@ test('a generic login request does not silently target the Fifth Domain', () =>
|
|||
|
||||
test('the selected stable domain id must match the returned access evidence', () => {
|
||||
const fifth = createDomainEntryTarget('fifth', 'cloud-resident');
|
||||
assert.equal(canEnterSelectedDomainRuntime(fifth, ready('DOMAIN-MAIN')), false);
|
||||
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOMAIN-MAIN')), /另一个域/);
|
||||
assert.equal(canEnterSelectedDomainRuntime(fifth, ready('DOMAIN-MAIN', 'cloud-resident')), false);
|
||||
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOMAIN-MAIN', 'cloud-resident')), /另一个域/);
|
||||
});
|
||||
|
||||
test('enterprise vestibules cannot reuse the Fifth Domain renderer', () => {
|
||||
|
|
@ -25,6 +25,8 @@ test('enterprise vestibules cannot reuse the Fifth Domain renderer', () => {
|
|||
|
||||
test('the Fifth Domain opens only with matching runtime-ready evidence', () => {
|
||||
const fifth = createDomainEntryTarget('fifth', 'cloud-resident');
|
||||
assert.equal(canEnterSelectedDomainRuntime(fifth, ready('DOM-FIFTH-0001')), true);
|
||||
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOM-FIFTH-0001')), /已经匹配/);
|
||||
assert.equal(canEnterSelectedDomainRuntime(fifth, ready('DOM-FIFTH-0001', 'cloud-resident')), true);
|
||||
assert.equal(canEnterSelectedDomainRuntime(fifth, ready('DOM-FIFTH-0001', 'local-terminal')), false);
|
||||
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOM-FIFTH-0001', 'local-terminal')), /另一种节点类型/);
|
||||
assert.match(projectDomainRuntimeBoundary(fifth, ready('DOM-FIFTH-0001', 'cloud-resident')), /已经匹配/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export function createDomainEntryTarget(routeId: DomainRouteId, nodeType: Domain
|
|||
export function canEnterSelectedDomainRuntime(target: DomainEntryTarget | null, access: DomainAccessProjection): boolean {
|
||||
if (!target || !access.runtimeReady) return false;
|
||||
if (access.domainId !== target.domain.stableDomainId) return false;
|
||||
if (access.nodeType !== target.nodeType) return false;
|
||||
// The present desktop bundle contains only the Fifth Domain renderer. The four
|
||||
// enterprise domains must provide their own signed runtime package and endpoint.
|
||||
return target.domain.routeId === 'fifth';
|
||||
|
|
@ -27,6 +28,9 @@ export function projectDomainRuntimeBoundary(target: DomainEntryTarget | null, a
|
|||
if (access.domainId && access.domainId !== target.domain.stableDomainId) {
|
||||
return '回读证据属于另一个域;当前入口保持关闭。';
|
||||
}
|
||||
if (access.nodeType && access.nodeType !== target.nodeType) {
|
||||
return '回读证据属于另一种节点类型;当前入口保持关闭并重新核验。';
|
||||
}
|
||||
if (target.domain.routeId !== 'fifth') {
|
||||
return `${target.domain.displayName}的独立运行端点与主题包尚未登记;当前只能查看公开门厅。`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue