feat(registry): Add hot-reload websocket, embedded UI bundling, and multi-platform Deno build tasks
Introduce a ReloadSocketManager and client ReloadService for automatic page reloads when the server restarts. Serve UI assets from an embedded generated file and add Deno tasks to bundle the UI and compile native binaries for multiple platforms. Also update dev watch workflow and ignore generated embedded UI file.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { ReloadService } from './core/services/reload.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -7,4 +8,7 @@ import { RouterOutlet } from '@angular/router';
|
||||
imports: [RouterOutlet],
|
||||
template: `<router-outlet />`,
|
||||
})
|
||||
export class AppComponent {}
|
||||
export class AppComponent {
|
||||
// Inject to trigger instantiation for hot reload
|
||||
private reloadService = inject(ReloadService);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||
import { routes } from './app.routes';
|
||||
import { authInterceptor } from './core/interceptors/auth.interceptor';
|
||||
import { ReloadService } from './core/services/reload.service';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideExperimentalZonelessChangeDetection(),
|
||||
provideRouter(routes),
|
||||
provideHttpClient(withInterceptors([authInterceptor])),
|
||||
ReloadService,
|
||||
],
|
||||
};
|
||||
|
||||
86
ui/src/app/core/services/reload.service.ts
Normal file
86
ui/src/app/core/services/reload.service.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Service for automatic page reload when server restarts.
|
||||
* Connects to WebSocket endpoint and monitors server instance ID.
|
||||
* When server restarts with new ID, page automatically reloads.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ReloadService {
|
||||
private ws: WebSocket | null = null;
|
||||
private instanceId = signal<string | null>(null);
|
||||
private reconnectAttempts = 0;
|
||||
private maxReconnectAttempts = 10;
|
||||
private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
constructor() {
|
||||
this.connect();
|
||||
}
|
||||
|
||||
private connect(): void {
|
||||
// Clean up any existing connection
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
|
||||
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${protocol}//${location.host}/ws/reload`;
|
||||
|
||||
try {
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('[ReloadService] Connected to server');
|
||||
this.reconnectAttempts = 0;
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'instance') {
|
||||
const currentId = this.instanceId();
|
||||
if (currentId !== null && currentId !== data.id) {
|
||||
// Server restarted with new ID - reload page
|
||||
console.log('[ReloadService] Server restarted, reloading...');
|
||||
location.reload();
|
||||
} else {
|
||||
console.log(`[ReloadService] Instance ID: ${data.id}`);
|
||||
}
|
||||
this.instanceId.set(data.id);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[ReloadService] Failed to parse message:', e);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
console.log('[ReloadService] Connection closed');
|
||||
this.scheduleReconnect();
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('[ReloadService] WebSocket error:', error);
|
||||
this.ws?.close();
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('[ReloadService] Failed to create WebSocket:', e);
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleReconnect(): void {
|
||||
if (this.reconnectTimeout) {
|
||||
clearTimeout(this.reconnectTimeout);
|
||||
}
|
||||
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 10000);
|
||||
this.reconnectAttempts++;
|
||||
console.log(`[ReloadService] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
||||
this.reconnectTimeout = setTimeout(() => this.connect(), delay);
|
||||
} else {
|
||||
console.log('[ReloadService] Max reconnect attempts reached');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user