@linxin666/dsh-remote-web-ui 0.3.5 → 0.3.9

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.
Files changed (238) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +77 -468
  3. package/README.zh.md +83 -113
  4. package/lib/client.js +955 -203
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +1136 -1466
  7. package/lib/types/client/FooterRemoteEntry.d.ts +6 -7
  8. package/lib/types/client/FooterRemoteEntry.d.ts.map +1 -1
  9. package/lib/types/client/FooterRemoteEntry.js +1 -1
  10. package/lib/types/client/RemoteEntry.d.ts +7 -4
  11. package/lib/types/client/RemoteEntry.d.ts.map +1 -1
  12. package/lib/types/client/RemoteEntry.js +7 -10
  13. package/lib/types/client/RemotePanel.d.ts +2 -2
  14. package/lib/types/client/RemotePanel.d.ts.map +1 -1
  15. package/lib/types/client/RemotePanel.js +2 -2
  16. package/lib/types/client/RemoteSettingsCard.d.ts +9 -5
  17. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  18. package/lib/types/client/RemoteSettingsCard.js +56 -3
  19. package/lib/types/client/deep-link.d.ts +10 -13
  20. package/lib/types/client/deep-link.d.ts.map +1 -1
  21. package/lib/types/client/deep-link.js +14 -74
  22. package/lib/types/client/index.d.ts +2 -6
  23. package/lib/types/client/index.d.ts.map +1 -1
  24. package/lib/types/client/index.js +73 -39
  25. package/lib/types/client/locales.d.ts +26 -12
  26. package/lib/types/client/locales.d.ts.map +1 -1
  27. package/lib/types/client/locales.js +36 -22
  28. package/lib/types/client/mobile-adapt.d.ts +40 -0
  29. package/lib/types/client/mobile-adapt.d.ts.map +1 -0
  30. package/lib/types/client/mobile-adapt.js +967 -0
  31. package/lib/types/client/pair-api.d.ts +24 -6
  32. package/lib/types/client/pair-api.d.ts.map +1 -1
  33. package/lib/types/client/pair-api.js +9 -14
  34. package/lib/types/client/remote-channel.d.ts +3 -0
  35. package/lib/types/client/remote-channel.d.ts.map +1 -1
  36. package/lib/types/client/remote-channel.js +35 -3
  37. package/lib/types/client/settings-form.d.ts +35 -43
  38. package/lib/types/client/settings-form.d.ts.map +1 -1
  39. package/lib/types/client/settings-form.js +60 -53
  40. package/lib/types/firewall.d.ts +75 -0
  41. package/lib/types/firewall.d.ts.map +1 -0
  42. package/lib/types/firewall.js +184 -0
  43. package/lib/types/gate.d.ts +11 -5
  44. package/lib/types/gate.d.ts.map +1 -1
  45. package/lib/types/gate.js +11 -5
  46. package/lib/types/index.d.ts +39 -10
  47. package/lib/types/index.d.ts.map +1 -1
  48. package/lib/types/index.js +206 -58
  49. package/lib/types/inner-auth.d.ts +32 -0
  50. package/lib/types/inner-auth.d.ts.map +1 -0
  51. package/lib/types/inner-auth.js +72 -0
  52. package/lib/types/lan-bind-plan.d.ts +55 -0
  53. package/lib/types/lan-bind-plan.d.ts.map +1 -0
  54. package/lib/types/lan-bind-plan.js +60 -0
  55. package/lib/types/lan-bind.d.ts +69 -0
  56. package/lib/types/lan-bind.d.ts.map +1 -0
  57. package/lib/types/lan-bind.js +128 -0
  58. package/lib/types/loopback-proxy.d.ts +9 -2
  59. package/lib/types/loopback-proxy.d.ts.map +1 -1
  60. package/lib/types/loopback-proxy.js +24 -2
  61. package/lib/types/pairing.d.ts +15 -3
  62. package/lib/types/pairing.d.ts.map +1 -1
  63. package/lib/types/pairing.js +20 -6
  64. package/lib/types/remote-api.d.ts +30 -13
  65. package/lib/types/remote-api.d.ts.map +1 -1
  66. package/lib/types/remote-api.js +55 -42
  67. package/lib/types/remote-channel-boot.d.ts +21 -2
  68. package/lib/types/remote-channel-boot.d.ts.map +1 -1
  69. package/lib/types/remote-channel-boot.js +65 -3
  70. package/lib/types/remote-channel-rules.d.ts +8 -0
  71. package/lib/types/remote-channel-rules.d.ts.map +1 -1
  72. package/lib/types/remote-channel-rules.js +9 -2
  73. package/lib/types/remote-methods.d.ts +45 -11
  74. package/lib/types/remote-methods.d.ts.map +1 -1
  75. package/lib/types/remote-methods.js +56 -27
  76. package/lib/types/remote-presence-pet.d.ts +51 -0
  77. package/lib/types/remote-presence-pet.d.ts.map +1 -0
  78. package/lib/types/remote-presence-pet.js +101 -0
  79. package/lib/types/routes.d.ts +76 -4
  80. package/lib/types/routes.d.ts.map +1 -1
  81. package/lib/types/routes.js +336 -19
  82. package/lib/types/tunnel.d.ts.map +1 -1
  83. package/lib/types/tunnel.js +7 -1
  84. package/package.json +20 -18
  85. package/src/client/FooterRemoteEntry.tsx +9 -10
  86. package/src/client/RemoteEntry.tsx +13 -14
  87. package/src/client/RemotePanel.tsx +5 -16
  88. package/src/client/RemoteSettingsCard.tsx +72 -13
  89. package/src/client/deep-link.ts +14 -75
  90. package/src/client/index.ts +77 -39
  91. package/src/client/locales.ts +36 -22
  92. package/src/client/mobile-adapt.ts +913 -0
  93. package/src/client/pair-api.ts +26 -15
  94. package/src/client/remote-channel.ts +31 -3
  95. package/src/client/settings-form.ts +78 -92
  96. package/src/dsh-home.ts +1 -1
  97. package/src/firewall.ts +230 -0
  98. package/src/gate.ts +11 -5
  99. package/src/index.ts +235 -65
  100. package/src/inner-auth.ts +85 -0
  101. package/src/lan-bind-plan.ts +81 -0
  102. package/src/lan-bind.ts +145 -0
  103. package/src/loopback-proxy.ts +34 -0
  104. package/src/pairing.persist.test.ts +1 -1
  105. package/src/pairing.ts +20 -6
  106. package/src/remote-api.ts +67 -45
  107. package/src/remote-channel-boot.ts +70 -3
  108. package/src/remote-channel-rules.ts +17 -2
  109. package/src/remote-methods.ts +58 -27
  110. package/src/remote-presence-pet.ts +129 -0
  111. package/src/routes.ts +358 -19
  112. package/src/tunnel.ts +7 -1
  113. package/assets/apple-touch-icon.png +0 -0
  114. package/assets/mobile-icon-192.png +0 -0
  115. package/assets/mobile-icon-512.png +0 -0
  116. package/assets/mobile-service-worker.js +0 -63
  117. package/lib/mobile.js +0 -17621
  118. package/lib/mobile.js.map +0 -1
  119. package/lib/types/mobile/PairRequiredView.d.ts +0 -7
  120. package/lib/types/mobile/PairRequiredView.d.ts.map +0 -1
  121. package/lib/types/mobile/PairRequiredView.js +0 -28
  122. package/lib/types/mobile/PairRequiredView.test.d.ts +0 -2
  123. package/lib/types/mobile/PairRequiredView.test.d.ts.map +0 -1
  124. package/lib/types/mobile/PairRequiredView.test.js +0 -24
  125. package/lib/types/mobile/api.d.ts +0 -145
  126. package/lib/types/mobile/api.d.ts.map +0 -1
  127. package/lib/types/mobile/api.js +0 -103
  128. package/lib/types/mobile/index.d.ts +0 -10
  129. package/lib/types/mobile/index.d.ts.map +0 -1
  130. package/lib/types/mobile/index.js +0 -38
  131. package/lib/types/mobile/markdown.d.ts +0 -34
  132. package/lib/types/mobile/markdown.d.ts.map +0 -1
  133. package/lib/types/mobile/markdown.js +0 -285
  134. package/lib/types/mobile/markdown.test.d.ts +0 -2
  135. package/lib/types/mobile/markdown.test.d.ts.map +0 -1
  136. package/lib/types/mobile/markdown.test.js +0 -79
  137. package/lib/types/mobile/messages.d.ts +0 -141
  138. package/lib/types/mobile/messages.d.ts.map +0 -1
  139. package/lib/types/mobile/messages.js +0 -553
  140. package/lib/types/mobile/messages.test.d.ts +0 -2
  141. package/lib/types/mobile/messages.test.d.ts.map +0 -1
  142. package/lib/types/mobile/messages.test.js +0 -356
  143. package/lib/types/mobile/mobile-styles.d.ts +0 -6
  144. package/lib/types/mobile/mobile-styles.d.ts.map +0 -1
  145. package/lib/types/mobile/mobile-styles.js +0 -1438
  146. package/lib/types/mobile/mobile-theme.d.ts +0 -22
  147. package/lib/types/mobile/mobile-theme.d.ts.map +0 -1
  148. package/lib/types/mobile/mobile-theme.js +0 -70
  149. package/lib/types/mobile/mobile-theme.test.d.ts +0 -2
  150. package/lib/types/mobile/mobile-theme.test.d.ts.map +0 -1
  151. package/lib/types/mobile/mobile-theme.test.js +0 -71
  152. package/lib/types/mobile/mux.d.ts +0 -128
  153. package/lib/types/mobile/mux.d.ts.map +0 -1
  154. package/lib/types/mobile/mux.js +0 -305
  155. package/lib/types/mobile/mux.test.d.ts +0 -2
  156. package/lib/types/mobile/mux.test.d.ts.map +0 -1
  157. package/lib/types/mobile/mux.test.js +0 -267
  158. package/lib/types/mobile/pairing.d.ts +0 -31
  159. package/lib/types/mobile/pairing.d.ts.map +0 -1
  160. package/lib/types/mobile/pairing.js +0 -62
  161. package/lib/types/mobile/rpc.d.ts +0 -38
  162. package/lib/types/mobile/rpc.d.ts.map +0 -1
  163. package/lib/types/mobile/rpc.js +0 -83
  164. package/lib/types/mobile/theme-toggle.d.ts +0 -12
  165. package/lib/types/mobile/theme-toggle.d.ts.map +0 -1
  166. package/lib/types/mobile/theme-toggle.js +0 -26
  167. package/lib/types/mobile/views/App.d.ts +0 -57
  168. package/lib/types/mobile/views/App.d.ts.map +0 -1
  169. package/lib/types/mobile/views/App.js +0 -150
  170. package/lib/types/mobile/views/App.test.d.ts +0 -2
  171. package/lib/types/mobile/views/App.test.d.ts.map +0 -1
  172. package/lib/types/mobile/views/App.test.js +0 -52
  173. package/lib/types/mobile/views/ChatView.d.ts +0 -56
  174. package/lib/types/mobile/views/ChatView.d.ts.map +0 -1
  175. package/lib/types/mobile/views/ChatView.js +0 -773
  176. package/lib/types/mobile/views/ChatView.stream.test.d.ts +0 -2
  177. package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +0 -1
  178. package/lib/types/mobile/views/ChatView.stream.test.js +0 -231
  179. package/lib/types/mobile/views/ChatView.test.d.ts +0 -2
  180. package/lib/types/mobile/views/ChatView.test.d.ts.map +0 -1
  181. package/lib/types/mobile/views/ChatView.test.js +0 -718
  182. package/lib/types/mobile/views/SessionListView.d.ts +0 -27
  183. package/lib/types/mobile/views/SessionListView.d.ts.map +0 -1
  184. package/lib/types/mobile/views/SessionListView.js +0 -139
  185. package/lib/types/mobile/views/SessionListView.test.d.ts +0 -2
  186. package/lib/types/mobile/views/SessionListView.test.d.ts.map +0 -1
  187. package/lib/types/mobile/views/SessionListView.test.js +0 -167
  188. package/lib/types/mobile/views/WorkspaceView.d.ts +0 -21
  189. package/lib/types/mobile/views/WorkspaceView.d.ts.map +0 -1
  190. package/lib/types/mobile/views/WorkspaceView.js +0 -93
  191. package/lib/types/mobile/views/WorkspaceView.test.d.ts +0 -2
  192. package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +0 -1
  193. package/lib/types/mobile/views/WorkspaceView.test.js +0 -183
  194. package/lib/types/mobile-api.d.ts +0 -65
  195. package/lib/types/mobile-api.d.ts.map +0 -1
  196. package/lib/types/mobile-api.js +0 -404
  197. package/lib/types/mobile-pending.d.ts +0 -38
  198. package/lib/types/mobile-pending.d.ts.map +0 -1
  199. package/lib/types/mobile-pending.js +0 -59
  200. package/lib/types/mobile-pwa.d.ts +0 -6
  201. package/lib/types/mobile-pwa.d.ts.map +0 -1
  202. package/lib/types/mobile-pwa.js +0 -17
  203. package/lib/types/mobile-routes.d.ts +0 -13
  204. package/lib/types/mobile-routes.d.ts.map +0 -1
  205. package/lib/types/mobile-routes.js +0 -217
  206. package/lib/types/paired-model-catalog.d.ts +0 -17
  207. package/lib/types/paired-model-catalog.d.ts.map +0 -1
  208. package/lib/types/paired-model-catalog.js +0 -311
  209. package/src/mobile/PairRequiredView.test.tsx +0 -28
  210. package/src/mobile/PairRequiredView.tsx +0 -58
  211. package/src/mobile/api.ts +0 -229
  212. package/src/mobile/index.tsx +0 -41
  213. package/src/mobile/markdown.test.ts +0 -91
  214. package/src/mobile/markdown.ts +0 -291
  215. package/src/mobile/messages.test.ts +0 -402
  216. package/src/mobile/messages.ts +0 -677
  217. package/src/mobile/mobile-styles.ts +0 -1439
  218. package/src/mobile/mobile-theme.test.ts +0 -84
  219. package/src/mobile/mobile-theme.ts +0 -76
  220. package/src/mobile/mux.test.ts +0 -314
  221. package/src/mobile/mux.ts +0 -336
  222. package/src/mobile/pairing.ts +0 -77
  223. package/src/mobile/rpc.ts +0 -90
  224. package/src/mobile/theme-toggle.tsx +0 -47
  225. package/src/mobile/views/App.test.tsx +0 -62
  226. package/src/mobile/views/App.tsx +0 -213
  227. package/src/mobile/views/ChatView.stream.test.tsx +0 -263
  228. package/src/mobile/views/ChatView.test.tsx +0 -816
  229. package/src/mobile/views/ChatView.tsx +0 -1295
  230. package/src/mobile/views/SessionListView.test.tsx +0 -207
  231. package/src/mobile/views/SessionListView.tsx +0 -244
  232. package/src/mobile/views/WorkspaceView.test.tsx +0 -227
  233. package/src/mobile/views/WorkspaceView.tsx +0 -245
  234. package/src/mobile-api.ts +0 -418
  235. package/src/mobile-pending.ts +0 -88
  236. package/src/mobile-pwa.ts +0 -19
  237. package/src/mobile-routes.ts +0 -231
  238. package/src/paired-model-catalog.ts +0 -272
@@ -1,285 +0,0 @@
1
- /**
2
- * A compact GFM-subset markdown renderer for the mobile chat: headings,
3
- * paragraphs, fenced + inline code, bold/italic/strikethrough, links,
4
- * images, lists, blockquotes, hr, and tables. All HTML is escaped before
5
- * transformation — the output only ever contains the renderer's own tags.
6
- * Dependency-free on purpose (the mobile bundle stays at ~456 KB); the
7
- * escape-first + protocol allow-list design mirrors the desktop panel's
8
- * preview renderer (dsh-aionui-panel/src/client/preview/markdown.ts).
9
- * Pure and exported for tests.
10
- * @module dsh-remote-web-ui/mobile/markdown
11
- */
12
- /** HTML special-character map for {@link escapeHtml}. */
13
- const HTML_ESCAPE_MAP = {
14
- '&': '&',
15
- '<': '&lt;',
16
- '>': '&gt;',
17
- '"': '&quot;',
18
- "'": '&#39;',
19
- };
20
- /**
21
- * Escape HTML special characters. One regex pass replaces all five
22
- * characters through the map; the output is identical to five sequential
23
- * passes (each replacement string contains none of the escaped characters),
24
- * and the common no-special-character case scans the string only once
25
- * instead of five times.
26
- */
27
- export function escapeHtml(text) {
28
- return text.replace(/[&<>"']/g, char => HTML_ESCAPE_MAP[char] ?? char);
29
- }
30
- /**
31
- * Guard a raw link/image target against dangerous protocols. Returns the
32
- * (trimmed) raw string when safe, else null. Only http:, https:, mailto:,
33
- * fragment anchors (#...) and strictly relative paths are allowed; anything
34
- * with another scheme — javascript:, data:, vbscript:, etc. — or a
35
- * protocol-relative //host target (the browser resolves it against the
36
- * current scheme, reaching an arbitrary origin) is rejected so the value
37
- * never reaches dangerouslySetInnerHTML.
38
- */
39
- export function safeUrl(raw) {
40
- const trimmed = raw.trim();
41
- if (trimmed === '')
42
- return null;
43
- if (trimmed.startsWith('#'))
44
- return trimmed;
45
- if (trimmed.startsWith('//'))
46
- return null;
47
- const scheme = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(trimmed);
48
- if (scheme === null)
49
- return trimmed;
50
- const name = scheme[1].toLowerCase();
51
- return name === 'http' || name === 'https' || name === 'mailto' ? trimmed : null;
52
- }
53
- /** Find the ')' closing a link/image target, skipping nested parens. */
54
- function findCloseParen(text, from) {
55
- let depth = 0;
56
- for (let i = from; i < text.length; i += 1) {
57
- const char = text[i];
58
- if (char === '(')
59
- depth += 1;
60
- else if (char === ')') {
61
- if (depth === 0)
62
- return i;
63
- depth -= 1;
64
- }
65
- }
66
- return -1;
67
- }
68
- /** Inline pass: code spans, bold, italic, strikethrough, images, links. */
69
- export function renderInline(text) {
70
- let out = '';
71
- let i = 0;
72
- const n = text.length;
73
- while (i < n) {
74
- const char = text[i];
75
- // Fenced inline code first.
76
- if (char === '`') {
77
- const end = text.indexOf('`', i + 1);
78
- if (end !== -1) {
79
- out += '<code>' + escapeHtml(text.slice(i + 1, end)) + '</code>';
80
- i = end + 1;
81
- continue;
82
- }
83
- }
84
- // Image ![alt](src)
85
- if (char === '!' && text[i + 1] === '[') {
86
- const close = text.indexOf('](', i + 2);
87
- if (close !== -1) {
88
- const parenEnd = findCloseParen(text, close + 2);
89
- if (parenEnd !== -1) {
90
- const alt = text.slice(i + 2, close);
91
- const src = text.slice(close + 2, parenEnd);
92
- const safe = safeUrl(src);
93
- if (safe === null) {
94
- out += escapeHtml(alt);
95
- }
96
- else {
97
- const srcEsc = escapeHtml(safe).replace(/\s+/g, '%20');
98
- out += '<img alt="' + escapeHtml(alt) + '" src="' + srcEsc + '" />';
99
- }
100
- i = parenEnd + 1;
101
- continue;
102
- }
103
- }
104
- }
105
- // Link [text](href)
106
- if (char === '[') {
107
- const close = text.indexOf('](', i + 1);
108
- if (close !== -1) {
109
- const parenEnd = findCloseParen(text, close + 2);
110
- if (parenEnd !== -1) {
111
- const label = text.slice(i + 1, close);
112
- const href = text.slice(close + 2, parenEnd);
113
- const safe = safeUrl(href);
114
- if (safe === null) {
115
- out += renderInline(label);
116
- }
117
- else {
118
- out += '<a href="' + escapeHtml(safe) + '" target="_blank" rel="noopener noreferrer">' + renderInline(label) + '</a>';
119
- }
120
- i = parenEnd + 1;
121
- continue;
122
- }
123
- }
124
- }
125
- // Bold **text**
126
- if (char === '*' && text[i + 1] === '*') {
127
- const end = text.indexOf('**', i + 2);
128
- if (end !== -1) {
129
- out += '<strong>' + renderInline(text.slice(i + 2, end)) + '</strong>';
130
- i = end + 2;
131
- continue;
132
- }
133
- }
134
- // Italic *text*
135
- if (char === '*' && text[i - 1] !== '*' && text[i + 1] !== '*') {
136
- const end = text.indexOf('*', i + 1);
137
- if (end !== -1 && text[end + 1] !== '*') {
138
- out += '<em>' + renderInline(text.slice(i + 1, end)) + '</em>';
139
- i = end + 1;
140
- continue;
141
- }
142
- }
143
- // Strikethrough ~~text~~
144
- if (char === '~' && text[i + 1] === '~') {
145
- const end = text.indexOf('~~', i + 2);
146
- if (end !== -1) {
147
- out += '<del>' + renderInline(text.slice(i + 2, end)) + '</del>';
148
- i = end + 2;
149
- continue;
150
- }
151
- }
152
- out += escapeHtml(char);
153
- i += 1;
154
- }
155
- return out;
156
- }
157
- /** Render a markdown document to HTML (block pass). */
158
- export function renderMarkdown(source) {
159
- const lines = source.replace(/\r\n/g, '\n').split('\n');
160
- const out = [];
161
- let i = 0;
162
- const n = lines.length;
163
- const flushParagraph = (buffer) => {
164
- if (buffer.length === 0)
165
- return;
166
- out.push('<p>' + renderInline(buffer.join('\n')) + '</p>');
167
- buffer.length = 0;
168
- };
169
- let paragraph = [];
170
- while (i < n) {
171
- const line = lines[i];
172
- // Fenced code block.
173
- const fence = /^```([\w+-]*)\s*$/.exec(line);
174
- if (fence !== null) {
175
- flushParagraph(paragraph);
176
- const lang = fence[1] ?? '';
177
- i += 1;
178
- const code = [];
179
- while (i < n && !/^```\s*$/.test(lines[i])) {
180
- code.push(lines[i]);
181
- i += 1;
182
- }
183
- i += 1; // closing fence
184
- const langAttr = lang === '' ? '' : ' class="language-' + escapeHtml(lang) + '"';
185
- out.push('<pre' + langAttr + '><code>' + escapeHtml(code.join('\n')) + '</code></pre>');
186
- continue;
187
- }
188
- // Heading.
189
- const heading = /^(#{1,6})\s+(.*)$/.exec(line);
190
- if (heading !== null) {
191
- flushParagraph(paragraph);
192
- const level = heading[1].length;
193
- out.push('<h' + level + '>' + renderInline(heading[2] ?? '') + '</h' + level + '>');
194
- i += 1;
195
- continue;
196
- }
197
- // Horizontal rule.
198
- if (/^\s*(---+|\*\*\*+|___+)\s*$/.test(line)) {
199
- flushParagraph(paragraph);
200
- out.push('<hr />');
201
- i += 1;
202
- continue;
203
- }
204
- // Table: header row then separator row.
205
- if (line.includes('|') && i + 1 < n && /^\s*\|?[\s:|-]+\|?\s*$/.test(lines[i + 1]) && lines[i + 1].includes('-')) {
206
- flushParagraph(paragraph);
207
- const headerCells = splitTableRow(line);
208
- i += 2;
209
- const rows = [];
210
- while (i < n && lines[i].includes('|')) {
211
- rows.push(splitTableRow(lines[i]));
212
- i += 1;
213
- }
214
- out.push('<table>');
215
- out.push('<thead><tr>' + headerCells.map((cell) => '<th>' + renderInline(cell) + '</th>').join('') + '</tr></thead>');
216
- if (rows.length > 0) {
217
- out.push('<tbody>' + rows.map((row) => '<tr>' + row.map((cell) => '<td>' + renderInline(cell) + '</td>').join('') + '</tr>').join('') + '</tbody>');
218
- }
219
- out.push('</table>');
220
- continue;
221
- }
222
- // Blockquote (one level).
223
- const quote = /^>\s?(.*)$/.exec(line);
224
- if (quote !== null) {
225
- flushParagraph(paragraph);
226
- const body = [];
227
- while (i < n) {
228
- const q = /^>\s?(.*)$/.exec(lines[i]);
229
- if (q === null)
230
- break;
231
- body.push(q[1] ?? '');
232
- i += 1;
233
- }
234
- out.push('<blockquote><p>' + body.map((bodyLine) => renderInline(bodyLine)).join('<br />') + '</p></blockquote>');
235
- continue;
236
- }
237
- // Unordered list.
238
- const ul = /^\s*([-*+])\s+(.*)$/.exec(line);
239
- if (ul !== null) {
240
- flushParagraph(paragraph);
241
- const items = [];
242
- while (i < n) {
243
- const item = /^\s*([-*+])\s+(.*)$/.exec(lines[i]);
244
- if (item === null)
245
- break;
246
- items.push('<li>' + renderInline(item[2] ?? '') + '</li>');
247
- i += 1;
248
- }
249
- out.push('<ul>' + items.join('') + '</ul>');
250
- continue;
251
- }
252
- // Ordered list.
253
- const ol = /^\s*\d+[.)]\s+(.*)$/.exec(line);
254
- if (ol !== null) {
255
- flushParagraph(paragraph);
256
- const items = [];
257
- while (i < n) {
258
- const item = /^\s*\d+[.)]\s+(.*)$/.exec(lines[i]);
259
- if (item === null)
260
- break;
261
- items.push('<li>' + renderInline(item[1] ?? '') + '</li>');
262
- i += 1;
263
- }
264
- out.push('<ol>' + items.join('') + '</ol>');
265
- continue;
266
- }
267
- // Blank line: flush the paragraph.
268
- if (line.trim() === '') {
269
- flushParagraph(paragraph);
270
- i += 1;
271
- continue;
272
- }
273
- paragraph.push(line);
274
- i += 1;
275
- }
276
- flushParagraph(paragraph);
277
- return out.join('\n');
278
- }
279
- /** Split one table row into cells (respecting the leading/trailing pipes). */
280
- function splitTableRow(line) {
281
- const trimmed = line.trim();
282
- const inner = trimmed.startsWith('|') ? trimmed.slice(1) : trimmed;
283
- const withoutTrailing = inner.endsWith('|') ? inner.slice(0, -1) : inner;
284
- return withoutTrailing.split('|').map((cell) => cell.trim());
285
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=markdown.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"markdown.test.d.ts","sourceRoot":"","sources":["../../../src/mobile/markdown.test.ts"],"names":[],"mappings":""}
@@ -1,79 +0,0 @@
1
- /**
2
- * Mobile markdown renderer tests: GFM subset coverage plus the security
3
- * contract — raw HTML escapes, dangerous link/image protocols are
4
- * stripped, and code fences never parse their content.
5
- */
6
- import { describe, expect, it } from 'vitest';
7
- import { escapeHtml, renderInline, renderMarkdown, safeUrl } from "./markdown.js";
8
- describe('safeUrl', () => {
9
- it('allows http/https/mailto/fragments and scheme-less targets', () => {
10
- expect(safeUrl('https://example.com/a')).toBe('https://example.com/a');
11
- expect(safeUrl('http://example.com')).toBe('http://example.com');
12
- expect(safeUrl('mailto:a@b.c')).toBe('mailto:a@b.c');
13
- expect(safeUrl('#top')).toBe('#top');
14
- expect(safeUrl('./rel/path')).toBe('./rel/path');
15
- });
16
- it('rejects javascript:/data:/vbscript: and blanks', () => {
17
- expect(safeUrl('javascript:alert(1)')).toBeNull();
18
- expect(safeUrl('data:text/html,x')).toBeNull();
19
- expect(safeUrl('vbscript:x')).toBeNull();
20
- expect(safeUrl('')).toBeNull();
21
- expect(safeUrl(' ')).toBeNull();
22
- });
23
- it('rejects protocol-relative targets that escape the origin', () => {
24
- expect(safeUrl('//attacker.example/x')).toBeNull();
25
- expect(safeUrl(' //attacker.example/track.png')).toBeNull();
26
- });
27
- });
28
- describe('renderInline', () => {
29
- it('renders code, bold, italic, strikethrough and links', () => {
30
- expect(renderInline('`a<b`')).toBe('<code>a&lt;b</code>');
31
- expect(renderInline('**b**')).toBe('<strong>b</strong>');
32
- expect(renderInline('*i*')).toBe('<em>i</em>');
33
- expect(renderInline('~~d~~')).toBe('<del>d</del>');
34
- expect(renderInline('[x](https://e.com)')).toBe('<a href="https://e.com" target="_blank" rel="noopener noreferrer">x</a>');
35
- });
36
- it('strips unsafe link targets and renders the label as plain text', () => {
37
- expect(renderInline('[x](javascript:alert(1))')).toBe('x');
38
- expect(renderInline('![a](data:text/html,x)')).toBe('a');
39
- });
40
- it('escapes raw HTML instead of passing it through', () => {
41
- expect(renderInline('<script>alert(1)</script>')).toBe('&lt;script&gt;alert(1)&lt;/script&gt;');
42
- });
43
- });
44
- describe('renderMarkdown', () => {
45
- it('renders headings, hr, paragraphs', () => {
46
- const html = renderMarkdown('# T\n\npara\n\n---');
47
- expect(html).toContain('<h1>T</h1>');
48
- expect(html).toContain('<p>para</p>');
49
- expect(html).toContain('<hr />');
50
- });
51
- it('renders fenced code without parsing its content', () => {
52
- const html = renderMarkdown('```ts\nconst a = **bold** <x>\n```');
53
- expect(html).toBe('<pre class="language-ts"><code>const a = **bold** &lt;x&gt;</code></pre>');
54
- });
55
- it('renders lists, blockquotes and tables', () => {
56
- const html = renderMarkdown('- a\n- b\n\n1. x\n2. y\n\n> quote\n\n| h1 | h2 |\n| --- | --- |\n| c1 | c2 |');
57
- expect(html).toContain('<ul><li>a</li><li>b</li></ul>');
58
- expect(html).toContain('<ol><li>x</li><li>y</li></ol>');
59
- expect(html).toContain('<blockquote><p>quote</p></blockquote>');
60
- expect(html).toContain('<thead><tr><th>h1</th><th>h2</th></tr></thead>');
61
- expect(html).toContain('<td>c1</td><td>c2</td>');
62
- });
63
- it('normalizes CRLF and keeps user-visible text intact', () => {
64
- expect(renderMarkdown('a\r\nb')).toBe('<p>a\nb</p>');
65
- });
66
- });
67
- describe('escapeHtml', () => {
68
- it('escapes all five HTML-significant characters', () => {
69
- expect(escapeHtml('<&>"\'')).toBe('&lt;&amp;&gt;&quot;&#39;');
70
- });
71
- it('equals the five sequential passes on mixed, pre-escaped and plain input', () => {
72
- // Single-pass map replacement must never diverge from the sequential
73
- // five-pass escape (including double-escaping of literal entities).
74
- expect(escapeHtml('a & b < c > d " e \' f')).toBe('a &amp; b &lt; c &gt; d &quot; e &#39; f');
75
- expect(escapeHtml('&amp; &lt; &gt;')).toBe('&amp;amp; &amp;lt; &amp;gt;');
76
- expect(escapeHtml('plain text 123')).toBe('plain text 123');
77
- expect(escapeHtml('')).toBe('');
78
- });
79
- });
@@ -1,141 +0,0 @@
1
- /**
2
- * Message fold: collapse a session event stream into a renderable, ordered
3
- * message list for the mobile surface.
4
- *
5
- * The mobile page is an independent, self-contained bundle, so this module has
6
- * no value imports: it re-declares a local `WireEvent` (a loose envelope with a
7
- * typed `type` and a wide `data`) and folds it into {@link RenderMessage} rows.
8
- * It is a pure, side-effect-free fold — callers hold the rendered list and feed
9
- * the next batch of events in to get the next list. Caller-supplied `existing`
10
- * messages are never mutated: every change builds a fresh message object.
11
- *
12
- * The data shapes follow the host session protocol (see the dsh session
13
- * `types.ts` / `surface.ts` and the llm `types.ts` sources the reference audit
14
- * read):
15
- *
16
- * - `user/message` data = `{ id, role, content: ContentBlock[], source }`
17
- * - `assistant/message` data = `{ turn, step, message: { id, content }, usage? }`
18
- * - `assistant/chunk` data = `{ turn, step, chunk: { type: 'text-delta' | 'reasoning-delta', text } }`
19
- * - `turn/start` data = `{ turn }`
20
- * - `turn/end` data = `{ turn, reason: { kind: 'error' | ... } }`
21
- * - `tool/call` data = `{ turn, step, callId, name, arguments }`
22
- * - `session/end-seed` empty data (skipped)
23
- *
24
- * Assistant content blocks (`text` vs `reasoning`) fold into two separate
25
- * fields — `text` and `reasoning` — so the surface can show reasoning behind
26
- * a collapsed disclosure instead of dumping it into the message body. Tool
27
- * calls accumulate ordered details (`tools`) in addition to the plain
28
- * `toolSummary` name list.
29
- *
30
- * The mobile message-level aliases `message/chunk`, `message/update` and
31
- * `message/delete` are also accepted (assumed shapes documented below).
32
- *
33
- * Design notes:
34
- * - Events are applied in ascending `seq` order.
35
- * - A `seq` watermark is derived from `existing` (the max already-rendered
36
- * message seq). Events whose seq is already at or below the watermark are
37
- * skipped, which makes re-applying the same batch idempotent without
38
- * double-folding streamed chunk text.
39
- * - Create events additionally dedupe by message id, so a repeated
40
- * `user/message` / `assistant/message` replaces in place instead of duplicating.
41
- * - A pending assistant message (alive while `assistant/chunk`-style deltas
42
- * keep arriving, `pending: true`) is finalized by the matching
43
- * `assistant/message` (same id or `(turn, step)`) or closed by `turn/end`.
44
- */
45
- export interface RenderMessage {
46
- /** Stable message identity — the wire id when present, else the event seq. */
47
- readonly id: string;
48
- readonly kind: 'user' | 'assistant';
49
- /** The fully folded text (assistant chunks aggregate into their message). */
50
- readonly text: string;
51
- /**
52
- * Folded reasoning text, kept separate from `text` so the surface can
53
- * hide it behind a collapsed Think disclosure (web-UI parity).
54
- */
55
- readonly reasoning?: string;
56
- /**
57
- * Ordered tool calls of this assistant message, in first-seen order,
58
- * driving the collapsible tool disclosure (name + raw arguments).
59
- */
60
- readonly tools?: ToolCallInfo[];
61
- /** Seq of the latest event that touched this message (used for loadOlder). */
62
- readonly seq: number;
63
- /** Epoch ms of the latest touch. */
64
- readonly time: number;
65
- /** True while an assistant message is still receiving chunks (not yet closed). */
66
- readonly pending?: boolean;
67
- /** Plain-text tool call summary for this assistant message, e.g. "使用 bash / read". */
68
- readonly toolSummary?: string;
69
- /** Set when the owning turn ended in an error. */
70
- readonly failed?: boolean;
71
- /**
72
- * Token usage reported by the final assistant event. cacheReadTokens and
73
- * cacheWriteTokens are only attached when the wire carried finite values.
74
- */
75
- readonly usage?: {
76
- inputTokens: number;
77
- outputTokens: number;
78
- cacheReadTokens?: number;
79
- cacheWriteTokens?: number;
80
- };
81
- /** Context window for the model that produced this message (from request/context). */
82
- readonly contextWindow?: number;
83
- /** Wire source.kind of a user message (e.g. plugin or user). */
84
- readonly sourceKind?: string;
85
- }
86
- /** One tool call attached to an assistant message (callId dedupes repeats). */
87
- export interface ToolCallInfo {
88
- /** Tool-call id (synthetic `${name}#${seq}` when the wire omitted it). */
89
- readonly callId: string;
90
- /** Tool name, e.g. "bash". */
91
- readonly name: string;
92
- /** Raw arguments JSON, when the event carried it. */
93
- readonly arguments?: string;
94
- }
95
- /**
96
- * The session event envelope as the mobile fold sees it. `data` is kept wide
97
- * (unknown) so the fold reads fields defensively; `surfaceOp` / `sourceEventSeqs`
98
- * are envelope metadata unrelated to message rendering and are ignored here.
99
- */
100
- export interface WireEvent {
101
- readonly type: string;
102
- readonly seq: number;
103
- readonly time: number;
104
- readonly data: unknown;
105
- readonly sourceEventSeqs?: number[];
106
- readonly surfaceOp?: unknown;
107
- readonly ignorable?: true;
108
- }
109
- /**
110
- * Fold a batch of session events into a renderable message list.
111
- *
112
- * @param events - events to apply, in any order (folded by ascending seq).
113
- * @param existing - the previously rendered list (live-stream incremental tail).
114
- * @returns messages sorted by seq.
115
- */
116
- export declare function foldEvents(events: readonly WireEvent[], existing?: readonly RenderMessage[]): RenderMessage[];
117
- /**
118
- * Incremental folder for one message stream. Live chat folds one event at a
119
- * time; rebuilding the five index maps by scanning every message per event
120
- * made that path O(n) per event (O(n * events) per turn). A folder keeps the
121
- * indexes alive across folds, applies each event in O(1) map operations, and
122
- * returns the previous snapshot identity unchanged when nothing applied, so
123
- * React skips the re-render entirely. Replayed events are no-ops: the maxSeq
124
- * watermark advanced by the first application skips them, which also makes a
125
- * double-invoked React state updater harmless.
126
- */
127
- export declare class EventFolder {
128
- private state;
129
- private snapshotList;
130
- /** @param initial - seed rows (history tail load); omit for an empty stream. */
131
- constructor(initial?: readonly RenderMessage[]);
132
- /** Fold one batch incrementally; returns the current snapshot list. */
133
- fold(events: readonly WireEvent[]): RenderMessage[];
134
- /** Replace the whole stream (history reload / session switch). */
135
- seed(messages: readonly RenderMessage[]): void;
136
- /** Prepend an older history page (exact seam; no overlapping seqs). */
137
- prepend(older: readonly RenderMessage[]): void;
138
- /** Current snapshot list; a fresh copy whenever the folder changed. */
139
- snapshot(): RenderMessage[];
140
- }
141
- //# sourceMappingURL=messages.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/mobile/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IACnC,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,CAAA;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,sFAAsF;IACtF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,kDAAkD;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAC1B,CAAA;IACD,sFAAsF;IACtF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,qDAAqD;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,CAAA;CAC1B;AAyeD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,EAAE,CAE7G;AAED;;;;;;;;;GASG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,YAAY,CAA6B;IAEjD,gFAAgF;gBACpE,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE;IAI9C,uEAAuE;IACvE,IAAI,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,aAAa,EAAE;IAanD,kEAAkE;IAClE,IAAI,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI;IAK9C,uEAAuE;IACvE,OAAO,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI;IAK9C,uEAAuE;IACvE,QAAQ,IAAI,aAAa,EAAE;CAK5B"}