@bun-native/protocol 0.33.0 → 0.33.1

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/src/processes.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ProtocolError } from './framing.ts'
2
2
  import { parseChunk, parseFilePath, parseRootId, parseRootPath } from './files.ts'
3
- import { id, integer, keys, object } from './values.ts'
3
+ import { fields, id, integer, keys, object } from './values.ts'
4
4
 
5
5
  export const MAX_PROCESS_EXECUTABLES = 8
6
6
  export const MAX_PROCESSES = 8
@@ -48,9 +48,11 @@ const LIMIT_BOUNDS = {
48
48
  open_files: [MIN_PROCESS_OPEN_FILES, MAX_PROCESS_OPEN_FILES],
49
49
  } as const
50
50
 
51
- export function parseProcessLimits(value: unknown): ProcessLimits {
51
+ const PROCESS_LIMITS_KEYS = fields(...LIMIT_NAMES)
52
+
53
+ function parseProcessLimits(value: unknown): ProcessLimits {
52
54
  const data = object(value)
53
- keys(data, [...LIMIT_NAMES])
55
+ keys(data, PROCESS_LIMITS_KEYS)
54
56
  const limits: ProcessLimits = {}
55
57
  for (const name of LIMIT_NAMES)
56
58
  if (name in data) {
@@ -103,9 +105,11 @@ export type ProcessSignal = (typeof PROCESS_SIGNALS)[number]
103
105
  export type ProcessStream = 'stdout' | 'stderr' | 'terminal'
104
106
  export type ProcessExit = { code: number } | { signal: number }
105
107
 
108
+ const EXECUTABLE_KEYS = fields('id', 'executable', 'limits')
109
+
106
110
  function executable(value: unknown): ProcessExecutable {
107
111
  const data = object(value)
108
- keys(data, ['id', 'executable', 'limits'])
112
+ keys(data, EXECUTABLE_KEYS)
109
113
  const path = parseRootPath(data['executable'])
110
114
  // `/` and `X:\` are the only normalized paths ending in a separator.
111
115
  if (path.endsWith('/') || path.endsWith('\\'))
@@ -169,9 +173,11 @@ function plain(value: unknown, limit: number, field: string): string {
169
173
  return value
170
174
  }
171
175
 
176
+ const TERMINAL_SIZE_KEYS = fields('columns', 'rows')
177
+
172
178
  export function parseTerminalSize(value: unknown): TerminalSize {
173
179
  const data = object(value)
174
- keys(data, ['columns', 'rows'])
180
+ keys(data, TERMINAL_SIZE_KEYS)
175
181
  return {
176
182
  columns: integer(data['columns'], 'terminal columns', 1, MAX_TERMINAL_DIMENSION),
177
183
  rows: integer(data['rows'], 'terminal rows', 1, MAX_TERMINAL_DIMENSION),
@@ -186,9 +192,12 @@ function parseEnvName(value: string): string {
186
192
  return value
187
193
  }
188
194
 
195
+ const PROCESS_LAUNCH_KEYS = fields('executable', 'args', 'env', 'cwd', 'terminal')
196
+ const PROCESS_CWD_KEYS = fields('grant_id', 'path')
197
+
189
198
  export function parseProcessLaunch(value: unknown): ProcessLaunch {
190
199
  const data = object(value)
191
- keys(data, ['executable', 'args', 'env', 'cwd', 'terminal'])
200
+ keys(data, PROCESS_LAUNCH_KEYS)
192
201
  const args = data['args']
193
202
  if (!Array.isArray(args) || args.length > MAX_PROCESS_ARGS)
194
203
  throw new ProtocolError('Process arguments must be an array of at most ' + MAX_PROCESS_ARGS)
@@ -216,7 +225,7 @@ export function parseProcessLaunch(value: unknown): ProcessLaunch {
216
225
  }
217
226
  if ('cwd' in data) {
218
227
  const cwd = object(data['cwd'])
219
- keys(cwd, ['grant_id', 'path'])
228
+ keys(cwd, PROCESS_CWD_KEYS)
220
229
  launch.cwd = { grant_id: id(cwd['grant_id']), path: parseFilePath(cwd['path']) }
221
230
  }
222
231
  if ('terminal' in data) launch.terminal = parseTerminalSize(data['terminal'])
@@ -241,9 +250,11 @@ export function parseProcessOutput(value: unknown): string {
241
250
  return data
242
251
  }
243
252
 
253
+ const PROCESS_EXIT_KEYS = fields('code', 'signal')
254
+
244
255
  export function parseProcessExit(value: unknown): ProcessExit {
245
256
  const data = object(value)
246
- keys(data, ['code', 'signal'])
257
+ keys(data, PROCESS_EXIT_KEYS)
247
258
  if ('code' in data && !('signal' in data))
248
259
  return { code: integer(data['code'], 'exit code', 0, 255) }
249
260
  if ('signal' in data && !('code' in data))
package/src/services.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ProtocolError } from './framing.ts'
2
- import { keys, number, object, string } from './values.ts'
2
+ import { fields, keys, number, object, string } from './values.ts'
3
3
  import { parseFilesystemRoots, type FilesystemRoot } from './files.ts'
4
4
  import { parseProcessExecutables, type ProcessExecutable } from './processes.ts'
5
5
 
@@ -53,9 +53,11 @@ export interface ShortcutModifiers {
53
53
  platform: boolean
54
54
  }
55
55
 
56
+ const SHORTCUT_MODIFIERS_KEYS = fields('control', 'alt', 'shift', 'platform')
57
+
56
58
  export function parseShortcutModifiers(value: unknown): ShortcutModifiers {
57
59
  const data = object(value)
58
- keys(data, ['control', 'alt', 'shift', 'platform'])
60
+ keys(data, SHORTCUT_MODIFIERS_KEYS)
59
61
  const flag = (key: keyof ShortcutModifiers) => {
60
62
  if (typeof data[key] !== 'boolean')
61
63
  throw new ProtocolError('Global shortcut modifiers must state each modifier explicitly')
@@ -103,13 +105,16 @@ export function parseNotificationDismissal(value: unknown): NotificationDismissa
103
105
 
104
106
  export type ClipboardText = { kind: 'empty' } | { kind: 'text'; text: string }
105
107
 
108
+ const CLIPBOARD_TEXT_EMPTY_KEYS = fields('kind')
109
+ const CLIPBOARD_TEXT_READ_KEYS = fields('kind', 'text')
110
+
106
111
  export function parseClipboardText(value: unknown): ClipboardText {
107
112
  const data = object(value)
108
113
  if (data['kind'] === 'empty') {
109
- keys(data, ['kind'])
114
+ keys(data, CLIPBOARD_TEXT_EMPTY_KEYS)
110
115
  return { kind: 'empty' }
111
116
  }
112
- keys(data, ['kind', 'text'])
117
+ keys(data, CLIPBOARD_TEXT_READ_KEYS)
113
118
  if (data['kind'] !== 'text') throw new ProtocolError('Unknown clipboard text result')
114
119
  return { kind: 'text', text: string(data['text'], 'clipboard text', MAX_CLIPBOARD_BYTES) }
115
120
  }
@@ -132,9 +137,11 @@ export interface DisplaySnapshot {
132
137
 
133
138
  const DISPLAY_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
134
139
 
140
+ const DISPLAY_BOUNDS_KEYS = fields('x', 'y', 'width', 'height')
141
+
135
142
  function displayBounds(value: unknown, name: string): DisplayBounds {
136
143
  const bounds = object(value)
137
- keys(bounds, ['x', 'y', 'width', 'height'])
144
+ keys(bounds, DISPLAY_BOUNDS_KEYS)
138
145
  return {
139
146
  x: number(bounds['x'], name + ' x', -1_048_576, 1_048_576),
140
147
  y: number(bounds['y'], name + ' y', -1_048_576, 1_048_576),
@@ -143,21 +150,23 @@ function displayBounds(value: unknown, name: string): DisplayBounds {
143
150
  }
144
151
  }
145
152
 
153
+ const SERVICE_POLICY_KEYS = fields(
154
+ 'window_display',
155
+ 'clipboard_read',
156
+ 'clipboard_write',
157
+ 'global_shortcuts',
158
+ 'notifications',
159
+ 'drag_drop',
160
+ 'filesystem_roots',
161
+ 'processes',
162
+ 'file_dialogs',
163
+ 'app_menu',
164
+ 'task_dialogs'
165
+ )
166
+
146
167
  export function parseServicePolicy(value: unknown): ServicePolicy {
147
168
  const data = object(value)
148
- keys(data, [
149
- 'window_display',
150
- 'clipboard_read',
151
- 'clipboard_write',
152
- 'global_shortcuts',
153
- 'notifications',
154
- 'drag_drop',
155
- 'filesystem_roots',
156
- 'processes',
157
- 'file_dialogs',
158
- 'app_menu',
159
- 'task_dialogs',
160
- ])
169
+ keys(data, SERVICE_POLICY_KEYS)
161
170
  const grant = (key: keyof ServicePolicy) => {
162
171
  if (typeof data[key] !== 'boolean')
163
172
  throw new ProtocolError('Native service policy must explicitly grant or deny each operation')
@@ -178,9 +187,11 @@ export function parseServicePolicy(value: unknown): ServicePolicy {
178
187
  }
179
188
  }
180
189
 
190
+ const DISPLAY_SNAPSHOT_KEYS = fields('bounds', 'scale_factor', 'visible_bounds', 'display_uuid')
191
+
181
192
  export function parseDisplaySnapshot(value: unknown): DisplaySnapshot {
182
193
  const data = object(value)
183
- keys(data, ['bounds', 'scale_factor', 'visible_bounds', 'display_uuid'])
194
+ keys(data, DISPLAY_SNAPSHOT_KEYS)
184
195
  const snapshot: DisplaySnapshot = {
185
196
  bounds: displayBounds(data['bounds'], 'display'),
186
197
  scale_factor: number(data['scale_factor'], 'display scale factor', 0.125, 16),
package/src/values.ts CHANGED
@@ -10,9 +10,15 @@ export function object(value: unknown): Record<string, unknown> {
10
10
  return value as Record<string, unknown>
11
11
  }
12
12
 
13
- export function keys(value: Record<string, unknown>, allowed: readonly string[]): void {
13
+ /** A message's accepted property names, hoisted to module scope so validation allocates nothing. */
14
+ export function fields(...names: readonly string[]): ReadonlySet<string> {
15
+ return new Set(names)
16
+ }
17
+
18
+ /** Reject own enumerable properties outside `allowed`; inherited keys are ignored. */
19
+ export function keys(value: Record<string, unknown>, allowed: ReadonlySet<string>): void {
14
20
  for (const key of Object.keys(value))
15
- if (!allowed.includes(key)) throw new ProtocolError('Unknown message property: ' + key)
21
+ if (!allowed.has(key)) throw new ProtocolError('Unknown message property: ' + key)
16
22
  }
17
23
 
18
24
  export function number(value: unknown, field: string, min: number, max: number): number {