Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 17 additions & 37 deletions application/static/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const getClientId = () => {

const CONFIG_DEFAULTS = {
serviceWorker: './worker.js',
pingInterval: 25000,
notificationTimeout: 3000,
syncTimeout: 2000,
};
Expand All @@ -26,11 +25,8 @@ class Application extends Emitter {
this.config = { ...CONFIG_DEFAULTS, ...config };
this.state = new Map();
this.worker = null;
const clientId = getClientId();
this.clientId = clientId;
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${location.host}`;
this.metacom = Metacom.create(url, { clientId });
this.clientId = getClientId();
this.metacom = null;
this.serviceWorker = this.config.serviceWorker;
this.online = navigator.onLine;
this.connected = false;
Expand All @@ -48,56 +44,40 @@ class Application extends Emitter {
}

#setupServiceWorker() {
const worker = navigator.serviceWorker;
worker.register(this.serviceWorker, { type: 'module' });
const ping = () => this.post({ type: 'ping' });
worker.ready.then((registration) => {
setInterval(ping, this.config.pingInterval);
const { serviceWorker } = navigator;
serviceWorker.register(this.serviceWorker, { type: 'module' });
serviceWorker.ready.then((registration) => {
this.worker = registration.active;
const data = { clientId: this.clientId };
this.post({ type: 'connect', data });
this.#setupMetacom();
});
worker.addEventListener('message', (event) => {
serviceWorker.addEventListener('message', (event) => {
const { type, data } = event.data;
this.emit(type, data);
});
this.on('status', (data) => {
this.connected = data.connected;
});
document.addEventListener('visibilitychange', () => {
this.post({ type: 'ping' });
});
}

async #setupMetacom() {
try {
await this.metacom.load('system');
const units = await this.metacom.api.system.introspect(['chat']);
this.emit('metacom-ready', { units });
} catch (err) {
this.emit('metacom-error', { error: err });
return;
}
try {
await this.metacom.load('chat');
await this.metacom.api.chat.subscribe({ room: 'sync' });
this.emit('metacom-ready');
} catch (err) {
this.emit('metacom-error', { error: err });
}
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${location.host}`;
const { clientId, worker } = this;
console.log({ clientId, worker });
this.metacom = Metacom.create(url, { clientId, worker });

await this.metacom.load(['system', 'chat']);
await this.metacom.api.chat.subscribe({ room: 'sync' });
this.connected = true;
this.emit('status', { connected: true });
this.emit('metacom-ready');
}

#setupNetworkStatus() {
window.addEventListener('online', () => {
this.online = true;
this.post({ type: 'online' });
this.emit('network', { online: true });
});

window.addEventListener('offline', () => {
this.online = false;
this.post({ type: 'offline' });
this.emit('network', { online: false });
});
}
Expand Down
72 changes: 16 additions & 56 deletions application/static/domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,9 @@ class ChatApplication extends Application {
this.on('install', () => this.showInstallButton(true));
this.on('installed', () => this.showInstallButton(false));
this.on('status', (data) => this.onStatus(data));
this.on('state', (data) => this.onState(data));
this.on('username', (data) => this.onUsername(data));
this.on('cacheUpdated', () => this.onCacheUpdated());
this.on('cacheUpdateFailed', (data) => this.onCacheUpdateFailed(data));
this.on('databaseCleared', () => this.onDatabaseCleared());
this.on('delta', (data) => this.onDelta(data));
this.on('metacom-ready', () => this.setupMetacomEvents());

document.addEventListener('visibilitychange', () => {
Expand All @@ -99,11 +96,6 @@ class ChatApplication extends Application {
const username = this.usernameInput?.value?.trim();
if (!username || username === this.username) return;
this.username = username;
if (this.syncTimeout) clearTimeout(this.syncTimeout);
this.syncTimeout = setTimeout(() => {
this.post({ type: 'username', data: this.username });
this.logger.log('Username auto-synced:', this.username);
}, this.config.syncTimeout);
}

onStatus(data) {
Expand All @@ -117,24 +109,6 @@ class ChatApplication extends Application {
}
}

onState(data) {
this.state.clear();
if (data && typeof data === 'object') {
for (const [key, value] of Object.entries(data)) {
this.state.set(key, value);
}
}
this.renderChatMessages();
this.logger.log('State updated from worker');
}

onUsername(data) {
this.username = data ?? '';
if (this.usernameInput) this.usernameInput.value = this.username;
this.logger.log('Username updated from other tab:', data);
this.showNotification('Username updated from other tab: ' + data);
}

onCacheUpdated() {
this.logger.log('Cache updated successfully');
this.showNotification('Cache updated successfully!', 'success');
Expand All @@ -154,8 +128,6 @@ class ChatApplication extends Application {
}

onDatabaseCleared() {
this.state.clear();
this.renderChatMessages();
this.logger.log('Database cleared successfully');
this.showNotification('Database cleared successfully!', 'success');
if (this.clearMessagesBtn) {
Expand Down Expand Up @@ -208,20 +180,15 @@ class ChatApplication extends Application {
}
const delta = this.addMessage(content);
const deltas = [delta];
this.post({ type: 'delta', data: deltas });
if (this.connected) {
try {
await this.metacom.api.chat.applyDelta({ deltas, room: 'sync' });
} catch (err) {
this.logger.log('Server sync failed:', err);
}
this.renderChatMessages();
try {
await this.metacom.api.chat.applyDelta({ deltas, room: 'sync' });
this.logger.log('Sent message:', content);
this.showNotification('Message sent!', 'success');
} else {
this.logger.log('Message queued (offline):', content);
this.showNotification('Message queued - will send when online', 'info');
} catch (err) {
this.logger.log('Failed to send message:', err.message);
this.showNotification('Failed to send message', 'error');
}
this.renderChatMessages();
}

renderChatMessages() {
Expand Down Expand Up @@ -277,7 +244,7 @@ class ChatApplication extends Application {
if (!reactionBtns) return;
for (const btn of reactionBtns) {
const { messageId, reaction } = btn.dataset;
btn.addEventListener('click', () => {
btn.addEventListener('click', async () => {
const record = { messageId, reaction };
const delta = { strategy: 'counter', entity: 'reaction', record };
const message = this.state.get(messageId);
Expand All @@ -287,27 +254,20 @@ class ChatApplication extends Application {
message.reactions[reaction] = count + 1;
this.renderChatMessages();
}
this.post({ type: 'delta', data: [delta] });
if (this.metacom?.api?.chat && this.connected) {
this.metacom.api.chat
.applyDelta({ deltas: [delta], room: 'sync' })
.catch(() => {});
try {
await this.metacom.api.chat.applyDelta({
deltas: [delta],
room: 'sync',
});
const msg = `${reaction} to message:${messageId}`;
this.logger.log('Added reaction:', msg);
} catch (err) {
this.logger.log('Failed to add reaction:', err.message);
}
this.logger.log('Added reaction:', reaction, 'to message:', messageId);
});
}
}

updateCache() {
this.logger.log('Requesting cache update...');
if (this.updateCacheBtn) {
this.updateCacheBtn.disabled = true;
this.updateCacheBtn.textContent = 'Updating...';
}
this.showNotification('Cache update requested', 'info');
this.post({ type: 'updateCache' });
}

showInstallButton(visible = true) {
if (visible) {
if (this.installBtn) this.installBtn.classList.remove('hidden');
Expand Down
Loading