@nuraly/lumenjs 0.2.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -7
- package/dist/build/build-markdown.d.ts +15 -0
- package/dist/build/build-markdown.js +90 -0
- package/dist/build/build-server.js +13 -2
- package/dist/build/build.js +34 -2
- package/dist/build/scan.js +4 -1
- package/dist/build/serve-loaders.js +12 -2
- package/dist/build/serve-ssr.js +12 -3
- package/dist/build/serve-static.js +2 -1
- package/dist/build/serve.js +1 -1
- package/dist/communication/server.js +1 -0
- package/dist/communication/signaling.d.ts +2 -0
- package/dist/communication/signaling.js +41 -0
- package/dist/dev-server/plugins/vite-plugin-llms.js +1 -0
- package/dist/dev-server/plugins/vite-plugin-loaders.d.ts +4 -3
- package/dist/dev-server/plugins/vite-plugin-loaders.js +20 -5
- package/dist/dev-server/ssr-render.js +15 -3
- package/dist/editor/ai/types.d.ts +1 -1
- package/dist/editor/ai/types.js +2 -2
- package/dist/llms/generate.d.ts +15 -1
- package/dist/llms/generate.js +54 -44
- package/dist/runtime/communication.d.ts +65 -36
- package/dist/runtime/communication.js +117 -57
- package/dist/runtime/router.js +3 -3
- package/dist/runtime/webrtc.d.ts +8 -1
- package/dist/runtime/webrtc.js +49 -15
- package/dist/shared/html-to-markdown.d.ts +6 -0
- package/dist/shared/html-to-markdown.js +73 -0
- package/dist/shared/utils.js +8 -0
- package/package.json +2 -2
- package/templates/blog/pages/index.ts +3 -3
- package/templates/blog/pages/posts/[slug].ts +17 -6
- package/templates/blog/pages/tag/[tag].ts +6 -6
- package/templates/dashboard/pages/index.ts +7 -7
- package/templates/default/pages/index.ts +3 -3
package/dist/llms/generate.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface LlmsApiRoute {
|
|
|
14
14
|
}
|
|
15
15
|
export interface LlmsTxtInput {
|
|
16
16
|
title: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
baseUrl?: string;
|
|
17
19
|
pages: LlmsPage[];
|
|
18
20
|
apiRoutes: LlmsApiRoute[];
|
|
19
21
|
integrations: string[];
|
|
@@ -26,9 +28,21 @@ export interface LlmsTxtInput {
|
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
|
-
* Generate the llms.txt content
|
|
31
|
+
* Generate the llms.txt content following the llmstxt.org spec.
|
|
32
|
+
*
|
|
33
|
+
* Structure: H1 title, blockquote summary, H2 sections with
|
|
34
|
+
* markdown links to each page (linking to .md versions).
|
|
30
35
|
*/
|
|
31
36
|
export declare function generateLlmsTxt(input: LlmsTxtInput): string;
|
|
37
|
+
/**
|
|
38
|
+
* Generate llms-full.txt — all page content inlined as one markdown document.
|
|
39
|
+
*/
|
|
40
|
+
export declare function generateLlmsFullTxt(input: LlmsTxtInput & {
|
|
41
|
+
pageContents: {
|
|
42
|
+
path: string;
|
|
43
|
+
markdown: string;
|
|
44
|
+
}[];
|
|
45
|
+
}): string;
|
|
32
46
|
/**
|
|
33
47
|
* Try to resolve dynamic route entries by finding a parent/sibling index page
|
|
34
48
|
* whose loader returns an array, then calling the dynamic page's loader for each item.
|
package/dist/llms/generate.js
CHANGED
|
@@ -1,62 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generate the llms.txt content
|
|
2
|
+
* Generate the llms.txt content following the llmstxt.org spec.
|
|
3
|
+
*
|
|
4
|
+
* Structure: H1 title, blockquote summary, H2 sections with
|
|
5
|
+
* markdown links to each page (linking to .md versions).
|
|
3
6
|
*/
|
|
4
7
|
export function generateLlmsTxt(input) {
|
|
5
8
|
const lines = [];
|
|
9
|
+
const base = input.baseUrl ? input.baseUrl.replace(/\/$/, '') : '';
|
|
6
10
|
lines.push(`# ${input.title}`);
|
|
7
11
|
lines.push('');
|
|
8
|
-
lines.push(
|
|
12
|
+
lines.push(`> ${input.description || `${input.title}. Built with LumenJS.`}`);
|
|
9
13
|
lines.push('');
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
// Group pages by top-level path segment
|
|
15
|
+
const groups = new Map();
|
|
16
|
+
for (const page of input.pages) {
|
|
17
|
+
const segments = page.path.split('/').filter(Boolean);
|
|
18
|
+
const section = segments.length > 0 ? segments[0] : 'pages';
|
|
19
|
+
if (!groups.has(section))
|
|
20
|
+
groups.set(section, []);
|
|
21
|
+
groups.get(section).push(page);
|
|
22
|
+
}
|
|
23
|
+
// Pages — grouped by section with links
|
|
24
|
+
for (const [section, pages] of groups) {
|
|
25
|
+
const sectionTitle = section.charAt(0).toUpperCase() + section.slice(1);
|
|
26
|
+
lines.push(`## ${sectionTitle}`);
|
|
13
27
|
lines.push('');
|
|
14
|
-
for (const page of
|
|
15
|
-
lines.push(`### ${page.path}`);
|
|
28
|
+
for (const page of pages) {
|
|
16
29
|
const isDynamic = page.path.includes(':');
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
lines.push('- Dynamic route');
|
|
32
|
-
lines.push('');
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
else if (page.loaderData && typeof page.loaderData === 'object') {
|
|
36
|
-
// Static page with loader data
|
|
37
|
-
lines.push(flattenData(page.loaderData));
|
|
38
|
-
lines.push('');
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
// Simple page
|
|
42
|
-
const features = [];
|
|
43
|
-
if (page.hasLoader)
|
|
44
|
-
features.push('with loader data');
|
|
45
|
-
if (page.hasSubscribe)
|
|
46
|
-
features.push('with live data');
|
|
47
|
-
lines.push(`- Server-rendered page${features.length ? ' ' + features.join(', ') : ''}`);
|
|
48
|
-
lines.push('');
|
|
49
|
-
}
|
|
30
|
+
const label = page.path === '/' ? 'Home' : page.path;
|
|
31
|
+
const features = [];
|
|
32
|
+
if (isDynamic)
|
|
33
|
+
features.push('dynamic');
|
|
34
|
+
if (page.hasLoader)
|
|
35
|
+
features.push('server data');
|
|
36
|
+
if (page.hasSubscribe)
|
|
37
|
+
features.push('live data');
|
|
38
|
+
const desc = features.length > 0 ? `: ${features.join(', ')}` : '';
|
|
39
|
+
// Link to .md version for LLM-readable content (skip dynamic routes)
|
|
40
|
+
const href = !isDynamic ? `${base}${page.path}.md` : `${base}${page.path}`;
|
|
41
|
+
lines.push(`- [${label}](${href})${desc}`);
|
|
50
42
|
}
|
|
43
|
+
lines.push('');
|
|
51
44
|
}
|
|
52
45
|
// API Routes section
|
|
53
46
|
if (input.apiRoutes.length > 0) {
|
|
54
|
-
lines.push('## API
|
|
47
|
+
lines.push('## API');
|
|
55
48
|
lines.push('');
|
|
56
49
|
for (const route of input.apiRoutes) {
|
|
57
|
-
|
|
58
|
-
lines.push(`- ${method} /api/${route.path}`);
|
|
59
|
-
}
|
|
50
|
+
lines.push(`- [${route.methods.join(', ')} /api/${route.path}](${base}/api/${route.path})`);
|
|
60
51
|
}
|
|
61
52
|
lines.push('');
|
|
62
53
|
}
|
|
@@ -85,6 +76,25 @@ export function generateLlmsTxt(input) {
|
|
|
85
76
|
}
|
|
86
77
|
return lines.join('\n').trimEnd() + '\n';
|
|
87
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Generate llms-full.txt — all page content inlined as one markdown document.
|
|
81
|
+
*/
|
|
82
|
+
export function generateLlmsFullTxt(input) {
|
|
83
|
+
const lines = [];
|
|
84
|
+
lines.push(`# ${input.title}`);
|
|
85
|
+
lines.push('');
|
|
86
|
+
lines.push(`> ${input.description || `${input.title}. Built with LumenJS.`}`);
|
|
87
|
+
lines.push('');
|
|
88
|
+
for (const page of input.pageContents) {
|
|
89
|
+
lines.push('---');
|
|
90
|
+
lines.push(`source: ${page.path}`);
|
|
91
|
+
lines.push('---');
|
|
92
|
+
lines.push('');
|
|
93
|
+
lines.push(page.markdown.trim());
|
|
94
|
+
lines.push('');
|
|
95
|
+
}
|
|
96
|
+
return lines.join('\n').trimEnd() + '\n';
|
|
97
|
+
}
|
|
88
98
|
/**
|
|
89
99
|
* Flatten a loader data object into key-value text lines.
|
|
90
100
|
*/
|
|
@@ -1,26 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client-side communication SDK.
|
|
3
|
-
* Connects to the server via Socket.io and provides a clean API for chat, typing, and
|
|
3
|
+
* Connects to the server via Socket.io and provides a clean API for chat, typing, presence, and calls.
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* Connect to the communication socket.
|
|
7
|
-
*
|
|
7
|
+
* Reuses an existing socket if one is already connected.
|
|
8
8
|
*/
|
|
9
|
-
export declare function connectChat(params?: Record<string, string>): Promise<
|
|
10
|
-
/**
|
|
11
|
-
export declare function
|
|
12
|
-
/**
|
|
13
|
-
export declare function
|
|
14
|
-
/** Send a message */
|
|
15
|
-
export declare function sendMessage(conversationId: string, content: string,
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
export declare function connectChat(params?: Record<string, string>): Promise<any>;
|
|
10
|
+
/** Get the underlying socket instance (for call-service or other integrations) */
|
|
11
|
+
export declare function getSocket(): any;
|
|
12
|
+
/** Set an externally-created socket (e.g. from call-service or router) */
|
|
13
|
+
export declare function setSocket(socket: any): void;
|
|
14
|
+
/** Send a message (text, image, file, audio) */
|
|
15
|
+
export declare function sendMessage(conversationId: string, content: string, opts?: {
|
|
16
|
+
type?: string;
|
|
17
|
+
attachment?: any;
|
|
18
|
+
replyTo?: any;
|
|
19
|
+
encrypted?: boolean;
|
|
20
|
+
}): void;
|
|
21
|
+
/** Mark messages as read */
|
|
22
|
+
export declare function markRead(conversationId: string, messageIds?: string[]): void;
|
|
18
23
|
/** React to a message with an emoji (toggle) */
|
|
19
24
|
export declare function reactToMessage(messageId: string, conversationId: string, emoji: string): void;
|
|
20
25
|
/** Edit a message */
|
|
21
26
|
export declare function editMessage(messageId: string, conversationId: string, content: string): void;
|
|
22
27
|
/** Delete a message */
|
|
23
28
|
export declare function deleteMessage(messageId: string, conversationId: string): void;
|
|
29
|
+
/** Load messages for a conversation (lazy-load) */
|
|
30
|
+
export declare function loadMessages(conversationId: string): void;
|
|
31
|
+
/** Join a conversation room to receive messages */
|
|
32
|
+
export declare function joinConversation(conversationId: string): void;
|
|
33
|
+
/** Leave a conversation room */
|
|
34
|
+
export declare function leaveConversation(conversationId: string): void;
|
|
35
|
+
/** Start typing indicator */
|
|
36
|
+
export declare function startTyping(conversationId: string): void;
|
|
37
|
+
/** Stop typing indicator */
|
|
38
|
+
export declare function stopTyping(conversationId: string): void;
|
|
39
|
+
/** Update presence status */
|
|
40
|
+
export declare function updatePresence(status: 'online' | 'offline' | 'away' | 'busy'): void;
|
|
41
|
+
/** Request bulk presence sync for a list of user IDs */
|
|
42
|
+
export declare function requestPresenceSync(userIds: string[]): void;
|
|
43
|
+
/** Refresh notification/message badge counts */
|
|
44
|
+
export declare function refreshBadge(): void;
|
|
45
|
+
/** Listen for new messages */
|
|
46
|
+
export declare function onMessage(handler: (message: any) => void): () => void;
|
|
47
|
+
/** Listen for typing updates */
|
|
48
|
+
export declare function onTyping(handler: (data: {
|
|
49
|
+
conversationId: string;
|
|
50
|
+
userId: string;
|
|
51
|
+
isTyping: boolean;
|
|
52
|
+
}) => void): () => void;
|
|
53
|
+
/** Listen for presence changes */
|
|
54
|
+
export declare function onPresence(handler: (data: {
|
|
55
|
+
userId: string;
|
|
56
|
+
status: string;
|
|
57
|
+
lastSeen: string;
|
|
58
|
+
}) => void): () => void;
|
|
59
|
+
/** Listen for bulk presence sync response */
|
|
60
|
+
export declare function onPresenceSync(handler: (data: {
|
|
61
|
+
presences: Record<string, any>;
|
|
62
|
+
}) => void): () => void;
|
|
24
63
|
/** Listen for reaction updates */
|
|
25
64
|
export declare function onReactionUpdate(handler: (data: {
|
|
26
65
|
messageId: string;
|
|
@@ -37,6 +76,14 @@ export declare function onMessageDeleted(handler: (data: {
|
|
|
37
76
|
messageId: string;
|
|
38
77
|
conversationId: string;
|
|
39
78
|
}) => void): () => void;
|
|
79
|
+
/** Listen for read receipts */
|
|
80
|
+
export declare function onReadReceipt(handler: (data: any) => void): () => void;
|
|
81
|
+
/** Listen for lazy-loaded conversation messages */
|
|
82
|
+
export declare function onConversationMessages(handler: (data: {
|
|
83
|
+
conversationId: string;
|
|
84
|
+
messages: any[];
|
|
85
|
+
participants: any[];
|
|
86
|
+
}) => void): () => void;
|
|
40
87
|
/** Upload a file (returns attachment metadata) */
|
|
41
88
|
export declare function uploadFile(file: Blob, filename: string, encrypted?: boolean): Promise<{
|
|
42
89
|
id: string;
|
|
@@ -45,31 +92,9 @@ export declare function uploadFile(file: Blob, filename: string, encrypted?: boo
|
|
|
45
92
|
}>;
|
|
46
93
|
/** Fetch link previews for a text */
|
|
47
94
|
export declare function fetchLinkPreviews(text: string): Promise<any[]>;
|
|
48
|
-
/** Start typing indicator */
|
|
49
|
-
export declare function startTyping(conversationId: string): void;
|
|
50
|
-
/** Stop typing indicator */
|
|
51
|
-
export declare function stopTyping(conversationId: string): void;
|
|
52
|
-
/** Update presence status */
|
|
53
|
-
export declare function updatePresence(status: 'online' | 'offline' | 'away' | 'busy'): void;
|
|
54
|
-
/** Listen for new messages */
|
|
55
|
-
export declare function onMessage(handler: (message: any) => void): () => void;
|
|
56
|
-
/** Listen for typing updates */
|
|
57
|
-
export declare function onTyping(handler: (data: {
|
|
58
|
-
conversationId: string;
|
|
59
|
-
userId: string;
|
|
60
|
-
isTyping: boolean;
|
|
61
|
-
}) => void): () => void;
|
|
62
|
-
/** Listen for presence changes */
|
|
63
|
-
export declare function onPresence(handler: (data: {
|
|
64
|
-
userId: string;
|
|
65
|
-
status: string;
|
|
66
|
-
lastSeen: string;
|
|
67
|
-
}) => void): () => void;
|
|
68
|
-
/** Listen for read receipts */
|
|
69
|
-
export declare function onReadReceipt(handler: (data: any) => void): () => void;
|
|
70
95
|
/** Listen for incoming calls */
|
|
71
96
|
export declare function onIncomingCall(handler: (call: any) => void): () => void;
|
|
72
|
-
/** Listen for call state changes
|
|
97
|
+
/** Listen for call state changes */
|
|
73
98
|
export declare function onCallStateChanged(handler: (data: {
|
|
74
99
|
callId: string;
|
|
75
100
|
state: string;
|
|
@@ -94,11 +119,15 @@ export declare function onMediaChanged(handler: (data: {
|
|
|
94
119
|
screenShare?: boolean;
|
|
95
120
|
}) => void): () => void;
|
|
96
121
|
/** Initiate an audio or video call */
|
|
97
|
-
export declare function initiateCall(conversationId: string, type: 'audio' | 'video', calleeIds: string[]
|
|
122
|
+
export declare function initiateCall(conversationId: string, type: 'audio' | 'video', calleeIds: string[], caller?: {
|
|
123
|
+
callerName?: string;
|
|
124
|
+
callerInitials?: string;
|
|
125
|
+
callerColor?: string;
|
|
126
|
+
}): void;
|
|
98
127
|
/** Respond to an incoming call */
|
|
99
128
|
export declare function respondToCall(callId: string, action: 'accept' | 'reject'): void;
|
|
100
129
|
/** Hang up an active call */
|
|
101
|
-
export declare function hangup(callId: string, reason?: string): void;
|
|
130
|
+
export declare function hangup(callId: string, reason?: string, duration?: string | null): void;
|
|
102
131
|
/** Toggle audio/video/screenshare during a call */
|
|
103
132
|
export declare function toggleMedia(callId: string, opts: {
|
|
104
133
|
audio?: boolean;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client-side communication SDK.
|
|
3
|
-
* Connects to the server via Socket.io and provides a clean API for chat, typing, and
|
|
3
|
+
* Connects to the server via Socket.io and provides a clean API for chat, typing, presence, and calls.
|
|
4
4
|
*/
|
|
5
5
|
let _socket = null;
|
|
6
6
|
let _handlers = new Map();
|
|
7
7
|
function emit(event, data) {
|
|
8
8
|
if (!_socket)
|
|
9
|
-
|
|
9
|
+
return;
|
|
10
10
|
_socket.emit(`nk:${event}`, data);
|
|
11
11
|
}
|
|
12
12
|
function addHandler(event, handler) {
|
|
@@ -22,15 +22,19 @@ function removeHandler(event, handler) {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Connect to the communication socket.
|
|
25
|
-
*
|
|
25
|
+
* Reuses an existing socket if one is already connected.
|
|
26
26
|
*/
|
|
27
27
|
export async function connectChat(params) {
|
|
28
|
-
if (_socket)
|
|
29
|
-
return;
|
|
28
|
+
if (_socket?.connected)
|
|
29
|
+
return _socket;
|
|
30
30
|
const { io } = await import('socket.io-client');
|
|
31
31
|
_socket = io('/nk/messages', {
|
|
32
32
|
path: '/__nk_socketio/',
|
|
33
33
|
query: { ...params, __params: JSON.stringify(params || {}) },
|
|
34
|
+
reconnection: true,
|
|
35
|
+
reconnectionAttempts: Infinity,
|
|
36
|
+
reconnectionDelay: 1000,
|
|
37
|
+
reconnectionDelayMax: 10000,
|
|
34
38
|
});
|
|
35
39
|
_socket.on('nk:data', (data) => {
|
|
36
40
|
if (data?.event) {
|
|
@@ -41,22 +45,44 @@ export async function connectChat(params) {
|
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
});
|
|
48
|
+
return _socket;
|
|
44
49
|
}
|
|
45
|
-
/**
|
|
46
|
-
export function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
/** Get the underlying socket instance (for call-service or other integrations) */
|
|
51
|
+
export function getSocket() { return _socket; }
|
|
52
|
+
/** Set an externally-created socket (e.g. from call-service or router) */
|
|
53
|
+
export function setSocket(socket) {
|
|
54
|
+
if (_socket === socket)
|
|
55
|
+
return;
|
|
56
|
+
_socket = socket;
|
|
57
|
+
// Attach the nk:data handler if not already attached
|
|
58
|
+
if (_socket && !_socket.__nk_comm_attached) {
|
|
59
|
+
_socket.__nk_comm_attached = true;
|
|
60
|
+
_socket.on('nk:data', (data) => {
|
|
61
|
+
if (data?.event) {
|
|
62
|
+
const handlers = _handlers.get(data.event);
|
|
63
|
+
if (handlers) {
|
|
64
|
+
for (const h of handlers)
|
|
65
|
+
h(data.data);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
52
70
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
// ── Messages ─────────────────────────────────────────────────────
|
|
72
|
+
/** Send a message (text, image, file, audio) */
|
|
73
|
+
export function sendMessage(conversationId, content, opts) {
|
|
74
|
+
emit('message:send', {
|
|
75
|
+
conversationId,
|
|
76
|
+
content,
|
|
77
|
+
type: opts?.type || 'text',
|
|
78
|
+
...(opts?.attachment ? { attachment: opts.attachment } : {}),
|
|
79
|
+
...(opts?.replyTo ? { replyTo: opts.replyTo } : {}),
|
|
80
|
+
...(opts?.encrypted ? { encrypted: true } : {}),
|
|
81
|
+
});
|
|
56
82
|
}
|
|
57
|
-
/** Mark
|
|
58
|
-
export function markRead(conversationId,
|
|
59
|
-
emit('message:read', { conversationId,
|
|
83
|
+
/** Mark messages as read */
|
|
84
|
+
export function markRead(conversationId, messageIds) {
|
|
85
|
+
emit('message:read', { conversationId, ...(messageIds ? { messageIds } : {}) });
|
|
60
86
|
}
|
|
61
87
|
/** React to a message with an emoji (toggle) */
|
|
62
88
|
export function reactToMessage(messageId, conversationId, emoji) {
|
|
@@ -70,6 +96,61 @@ export function editMessage(messageId, conversationId, content) {
|
|
|
70
96
|
export function deleteMessage(messageId, conversationId) {
|
|
71
97
|
emit('message:delete', { messageId, conversationId });
|
|
72
98
|
}
|
|
99
|
+
/** Load messages for a conversation (lazy-load) */
|
|
100
|
+
export function loadMessages(conversationId) {
|
|
101
|
+
emit('conversation:load-messages', { conversationId });
|
|
102
|
+
}
|
|
103
|
+
/** Join a conversation room to receive messages */
|
|
104
|
+
export function joinConversation(conversationId) {
|
|
105
|
+
emit('conversation:join', { conversationId });
|
|
106
|
+
}
|
|
107
|
+
/** Leave a conversation room */
|
|
108
|
+
export function leaveConversation(conversationId) {
|
|
109
|
+
emit('conversation:leave', { conversationId });
|
|
110
|
+
}
|
|
111
|
+
// ── Typing ───────────────────────────────────────────────────────
|
|
112
|
+
/** Start typing indicator */
|
|
113
|
+
export function startTyping(conversationId) {
|
|
114
|
+
emit('typing:start', { conversationId });
|
|
115
|
+
}
|
|
116
|
+
/** Stop typing indicator */
|
|
117
|
+
export function stopTyping(conversationId) {
|
|
118
|
+
emit('typing:stop', { conversationId });
|
|
119
|
+
}
|
|
120
|
+
// ── Presence ─────────────────────────────────────────────────────
|
|
121
|
+
/** Update presence status */
|
|
122
|
+
export function updatePresence(status) {
|
|
123
|
+
emit('presence:update', { status });
|
|
124
|
+
}
|
|
125
|
+
/** Request bulk presence sync for a list of user IDs */
|
|
126
|
+
export function requestPresenceSync(userIds) {
|
|
127
|
+
emit('presence:sync', { userIds });
|
|
128
|
+
}
|
|
129
|
+
/** Refresh notification/message badge counts */
|
|
130
|
+
export function refreshBadge() {
|
|
131
|
+
emit('badge:refresh', {});
|
|
132
|
+
}
|
|
133
|
+
// ── Event Listeners ──────────────────────────────────────────────
|
|
134
|
+
/** Listen for new messages */
|
|
135
|
+
export function onMessage(handler) {
|
|
136
|
+
addHandler('message:new', handler);
|
|
137
|
+
return () => removeHandler('message:new', handler);
|
|
138
|
+
}
|
|
139
|
+
/** Listen for typing updates */
|
|
140
|
+
export function onTyping(handler) {
|
|
141
|
+
addHandler('typing:update', handler);
|
|
142
|
+
return () => removeHandler('typing:update', handler);
|
|
143
|
+
}
|
|
144
|
+
/** Listen for presence changes */
|
|
145
|
+
export function onPresence(handler) {
|
|
146
|
+
addHandler('presence:changed', handler);
|
|
147
|
+
return () => removeHandler('presence:changed', handler);
|
|
148
|
+
}
|
|
149
|
+
/** Listen for bulk presence sync response */
|
|
150
|
+
export function onPresenceSync(handler) {
|
|
151
|
+
addHandler('presence:sync', handler);
|
|
152
|
+
return () => removeHandler('presence:sync', handler);
|
|
153
|
+
}
|
|
73
154
|
/** Listen for reaction updates */
|
|
74
155
|
export function onReactionUpdate(handler) {
|
|
75
156
|
addHandler('message:reaction-update', handler);
|
|
@@ -85,6 +166,17 @@ export function onMessageDeleted(handler) {
|
|
|
85
166
|
addHandler('message:deleted', handler);
|
|
86
167
|
return () => removeHandler('message:deleted', handler);
|
|
87
168
|
}
|
|
169
|
+
/** Listen for read receipts */
|
|
170
|
+
export function onReadReceipt(handler) {
|
|
171
|
+
addHandler('read-receipt:update', handler);
|
|
172
|
+
return () => removeHandler('read-receipt:update', handler);
|
|
173
|
+
}
|
|
174
|
+
/** Listen for lazy-loaded conversation messages */
|
|
175
|
+
export function onConversationMessages(handler) {
|
|
176
|
+
addHandler('conversation:messages', handler);
|
|
177
|
+
return () => removeHandler('conversation:messages', handler);
|
|
178
|
+
}
|
|
179
|
+
// ── File Uploads & Link Previews ─────────────────────────────────
|
|
88
180
|
/** Upload a file (returns attachment metadata) */
|
|
89
181
|
export async function uploadFile(file, filename, encrypted = false) {
|
|
90
182
|
const res = await fetch('/__nk_comm/upload', {
|
|
@@ -112,45 +204,13 @@ export async function fetchLinkPreviews(text) {
|
|
|
112
204
|
const data = await res.json();
|
|
113
205
|
return data.previews || [];
|
|
114
206
|
}
|
|
115
|
-
|
|
116
|
-
export function startTyping(conversationId) {
|
|
117
|
-
emit('typing:start', { conversationId });
|
|
118
|
-
}
|
|
119
|
-
/** Stop typing indicator */
|
|
120
|
-
export function stopTyping(conversationId) {
|
|
121
|
-
emit('typing:stop', { conversationId });
|
|
122
|
-
}
|
|
123
|
-
/** Update presence status */
|
|
124
|
-
export function updatePresence(status) {
|
|
125
|
-
emit('presence:update', { status });
|
|
126
|
-
}
|
|
127
|
-
/** Listen for new messages */
|
|
128
|
-
export function onMessage(handler) {
|
|
129
|
-
addHandler('message:new', handler);
|
|
130
|
-
return () => removeHandler('message:new', handler);
|
|
131
|
-
}
|
|
132
|
-
/** Listen for typing updates */
|
|
133
|
-
export function onTyping(handler) {
|
|
134
|
-
addHandler('typing:update', handler);
|
|
135
|
-
return () => removeHandler('typing:update', handler);
|
|
136
|
-
}
|
|
137
|
-
/** Listen for presence changes */
|
|
138
|
-
export function onPresence(handler) {
|
|
139
|
-
addHandler('presence:changed', handler);
|
|
140
|
-
return () => removeHandler('presence:changed', handler);
|
|
141
|
-
}
|
|
142
|
-
/** Listen for read receipts */
|
|
143
|
-
export function onReadReceipt(handler) {
|
|
144
|
-
addHandler('read-receipt:update', handler);
|
|
145
|
-
return () => removeHandler('read-receipt:update', handler);
|
|
146
|
-
}
|
|
147
|
-
// ── Calls ─────────────────────────────────────────────────────────
|
|
207
|
+
// ── Calls ────────────────────────────────────────────────────────
|
|
148
208
|
/** Listen for incoming calls */
|
|
149
209
|
export function onIncomingCall(handler) {
|
|
150
210
|
addHandler('call:incoming', handler);
|
|
151
211
|
return () => removeHandler('call:incoming', handler);
|
|
152
212
|
}
|
|
153
|
-
/** Listen for call state changes
|
|
213
|
+
/** Listen for call state changes */
|
|
154
214
|
export function onCallStateChanged(handler) {
|
|
155
215
|
addHandler('call:state-changed', handler);
|
|
156
216
|
return () => removeHandler('call:state-changed', handler);
|
|
@@ -171,22 +231,22 @@ export function onMediaChanged(handler) {
|
|
|
171
231
|
return () => removeHandler('call:media-changed', handler);
|
|
172
232
|
}
|
|
173
233
|
/** Initiate an audio or video call */
|
|
174
|
-
export function initiateCall(conversationId, type, calleeIds) {
|
|
175
|
-
emit('call:initiate', { conversationId, type, calleeIds });
|
|
234
|
+
export function initiateCall(conversationId, type, calleeIds, caller) {
|
|
235
|
+
emit('call:initiate', { conversationId, type, calleeIds, ...caller });
|
|
176
236
|
}
|
|
177
237
|
/** Respond to an incoming call */
|
|
178
238
|
export function respondToCall(callId, action) {
|
|
179
239
|
emit('call:respond', { callId, action });
|
|
180
240
|
}
|
|
181
241
|
/** Hang up an active call */
|
|
182
|
-
export function hangup(callId, reason = 'completed') {
|
|
183
|
-
emit('call:hangup', { callId, reason });
|
|
242
|
+
export function hangup(callId, reason = 'completed', duration) {
|
|
243
|
+
emit('call:hangup', { callId, reason, ...(duration ? { duration } : {}) });
|
|
184
244
|
}
|
|
185
245
|
/** Toggle audio/video/screenshare during a call */
|
|
186
246
|
export function toggleMedia(callId, opts) {
|
|
187
247
|
emit('call:media-toggle', { callId, ...opts });
|
|
188
248
|
}
|
|
189
|
-
// ── WebRTC Signaling
|
|
249
|
+
// ── WebRTC Signaling ─────────────────────────────────────────────
|
|
190
250
|
/** Send SDP offer to a peer */
|
|
191
251
|
export function sendOffer(callId, toUserId, sdp) {
|
|
192
252
|
emit('signal:offer', { callId, fromUserId: '', toUserId, type: 'offer', sdp });
|
package/dist/runtime/router.js
CHANGED
|
@@ -170,7 +170,7 @@ export class NkRouter {
|
|
|
170
170
|
es.onmessage = (e) => {
|
|
171
171
|
const pageEl = this.findPageElement(match.route.tagName);
|
|
172
172
|
if (pageEl)
|
|
173
|
-
pageEl
|
|
173
|
+
this.spreadData(pageEl, JSON.parse(e.data));
|
|
174
174
|
};
|
|
175
175
|
this.subscriptions.push(es);
|
|
176
176
|
}
|
|
@@ -204,7 +204,7 @@ export class NkRouter {
|
|
|
204
204
|
socket.on('nk:data', (data) => {
|
|
205
205
|
const pageEl = this.findPageElement(match.route.tagName);
|
|
206
206
|
if (pageEl) {
|
|
207
|
-
pageEl
|
|
207
|
+
this.spreadData(pageEl, data);
|
|
208
208
|
injectEmit();
|
|
209
209
|
}
|
|
210
210
|
});
|
|
@@ -217,7 +217,7 @@ export class NkRouter {
|
|
|
217
217
|
es.onmessage = (e) => {
|
|
218
218
|
const layoutEl = this.outlet?.querySelector(layout.tagName);
|
|
219
219
|
if (layoutEl)
|
|
220
|
-
layoutEl
|
|
220
|
+
this.spreadData(layoutEl, JSON.parse(e.data));
|
|
221
221
|
};
|
|
222
222
|
this.subscriptions.push(es);
|
|
223
223
|
}
|
package/dist/runtime/webrtc.d.ts
CHANGED
|
@@ -66,7 +66,14 @@ export declare class GroupWebRTCManager {
|
|
|
66
66
|
getRemoteStream(userId: string): MediaStream | null;
|
|
67
67
|
getRemoteStreams(): Map<string, MediaStream>;
|
|
68
68
|
/** Acquire local media — call once before adding peers */
|
|
69
|
-
startLocalMedia(video?: boolean, audio?: boolean
|
|
69
|
+
startLocalMedia(video?: boolean, audio?: boolean, deviceIds?: {
|
|
70
|
+
audioInputId?: string | null;
|
|
71
|
+
videoInputId?: string | null;
|
|
72
|
+
}): Promise<MediaStream>;
|
|
73
|
+
/** Replace the audio track on all peer connections (for switching mic mid-call) */
|
|
74
|
+
replaceAudioTrack(newTrack: MediaStreamTrack): Promise<void>;
|
|
75
|
+
/** Replace the video track on all peer connections (for switching camera mid-call) */
|
|
76
|
+
replaceVideoTrack(newTrack: MediaStreamTrack): Promise<void>;
|
|
70
77
|
/** Create a peer connection for a remote user and optionally create an offer */
|
|
71
78
|
addPeer(userId: string, isCaller: boolean): Promise<string | void>;
|
|
72
79
|
/** Remove and close a peer connection */
|