@desklink/host 0.5.3 → 0.5.6

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 (76) hide show
  1. package/README.md +33 -19
  2. package/dist/bridge.js +3 -3
  3. package/dist/bridge.js.map +1 -1
  4. package/dist/engineProcess.d.ts +5 -0
  5. package/dist/engineProcess.d.ts.map +1 -1
  6. package/dist/engineProcess.js +6 -3
  7. package/dist/engineProcess.js.map +1 -1
  8. package/dist/protocol.d.ts +39 -0
  9. package/dist/protocol.d.ts.map +1 -1
  10. package/dist/protocol.js.map +1 -1
  11. package/docs/BOUNDARY.md +4 -2
  12. package/docs/PROTOCOL.md +70 -7
  13. package/engine/Cargo.lock +6 -8
  14. package/engine/Cargo.toml +13 -1
  15. package/engine/src/audio.rs +398 -0
  16. package/engine/src/clipboard.rs +0 -34
  17. package/engine/src/convert.rs +0 -170
  18. package/engine/src/cursor.rs +0 -23
  19. package/engine/src/input.rs +0 -65
  20. package/engine/src/input_mac.rs +0 -11
  21. package/engine/src/keeper.rs +0 -137
  22. package/engine/src/keymap.rs +0 -107
  23. package/engine/src/keymap_mac.rs +0 -25
  24. package/engine/src/mac.rs +1 -28
  25. package/engine/src/main.rs +22 -0
  26. package/engine/src/peer.rs +297 -0
  27. package/engine/src/portal.rs +0 -18
  28. package/engine/src/protocol.rs +22 -162
  29. package/engine/src/session.rs +176 -3
  30. package/engine/src/win.rs +1 -0
  31. package/engine/src/x11.rs +0 -13
  32. package/engine/vendor/rtc-ice/Cargo.toml +137 -0
  33. package/engine/vendor/rtc-ice/DESKLINK-PATCH.md +111 -0
  34. package/engine/vendor/rtc-ice/LICENSE-APACHE +201 -0
  35. package/engine/vendor/rtc-ice/LICENSE-MIT +21 -0
  36. package/engine/vendor/rtc-ice/README.md +30 -0
  37. package/engine/vendor/rtc-ice/examples/ping_pong.rs +376 -0
  38. package/engine/vendor/rtc-ice/src/agent/agent_config.rs +126 -0
  39. package/engine/vendor/rtc-ice/src/agent/agent_proto.rs +172 -0
  40. package/engine/vendor/rtc-ice/src/agent/agent_selector.rs +647 -0
  41. package/engine/vendor/rtc-ice/src/agent/agent_stats.rs +292 -0
  42. package/engine/vendor/rtc-ice/src/agent/agent_test.rs +3594 -0
  43. package/engine/vendor/rtc-ice/src/agent/mod.rs +1753 -0
  44. package/engine/vendor/rtc-ice/src/attributes/control/control_test.rs +168 -0
  45. package/engine/vendor/rtc-ice/src/attributes/control/mod.rs +142 -0
  46. package/engine/vendor/rtc-ice/src/attributes/mod.rs +6 -0
  47. package/engine/vendor/rtc-ice/src/attributes/priority/mod.rs +37 -0
  48. package/engine/vendor/rtc-ice/src/attributes/priority/priority_test.rs +39 -0
  49. package/engine/vendor/rtc-ice/src/attributes/use_candidate/mod.rs +33 -0
  50. package/engine/vendor/rtc-ice/src/attributes/use_candidate/use_candidate_test.rs +19 -0
  51. package/engine/vendor/rtc-ice/src/candidate/candidate_host.rs +53 -0
  52. package/engine/vendor/rtc-ice/src/candidate/candidate_pair.rs +215 -0
  53. package/engine/vendor/rtc-ice/src/candidate/candidate_pair_test.rs +201 -0
  54. package/engine/vendor/rtc-ice/src/candidate/candidate_peer_reflexive.rs +49 -0
  55. package/engine/vendor/rtc-ice/src/candidate/candidate_relay.rs +53 -0
  56. package/engine/vendor/rtc-ice/src/candidate/candidate_relay_test.rs +114 -0
  57. package/engine/vendor/rtc-ice/src/candidate/candidate_server_reflexive.rs +53 -0
  58. package/engine/vendor/rtc-ice/src/candidate/candidate_server_reflexive_test.rs +91 -0
  59. package/engine/vendor/rtc-ice/src/candidate/candidate_test.rs +540 -0
  60. package/engine/vendor/rtc-ice/src/candidate/mod.rs +679 -0
  61. package/engine/vendor/rtc-ice/src/dl_trace.rs +49 -0
  62. package/engine/vendor/rtc-ice/src/lib.rs +88 -0
  63. package/engine/vendor/rtc-ice/src/mdns/mdns_test.rs +150 -0
  64. package/engine/vendor/rtc-ice/src/mdns/mod.rs +69 -0
  65. package/engine/vendor/rtc-ice/src/network_type/mod.rs +162 -0
  66. package/engine/vendor/rtc-ice/src/network_type/network_type_test.rs +95 -0
  67. package/engine/vendor/rtc-ice/src/rand/mod.rs +91 -0
  68. package/engine/vendor/rtc-ice/src/rand/rand_test.rs +115 -0
  69. package/engine/vendor/rtc-ice/src/state/mod.rs +106 -0
  70. package/engine/vendor/rtc-ice/src/state/state_test.rs +46 -0
  71. package/engine/vendor/rtc-ice/src/stats/mod.rs +166 -0
  72. package/engine/vendor/rtc-ice/src/tcp_type/mod.rs +47 -0
  73. package/engine/vendor/rtc-ice/src/tcp_type/tcp_type_test.rs +18 -0
  74. package/engine/vendor/rtc-ice/src/url/mod.rs +266 -0
  75. package/engine/vendor/rtc-ice/src/url/url_test.rs +142 -0
  76. package/package.json +8 -3
@@ -300,68 +300,3 @@ impl Drop for InputDevices {
300
300
  }
301
301
  }
302
302
  }
303
-
304
- #[cfg(test)]
305
- mod tests {
306
- use super::*;
307
-
308
- #[test]
309
- fn button_numbers_map_to_the_expected_pointer_buttons() {
310
- assert_eq!(Button::from_number(1), Button::Left);
311
- assert_eq!(Button::from_number(2), Button::Middle);
312
- assert_eq!(Button::from_number(3), Button::Right);
313
- assert_eq!(Button::from_number(9), Button::Left);
314
- }
315
-
316
- #[test]
317
- fn held_state_releases_exactly_what_this_session_pressed() {
318
- let mut held = HeldState::default();
319
- held.key(29, true); // left ctrl
320
- held.key(46, true); // c
321
- held.button(Button::Left, true);
322
- // A repeat press is the same press, not a second one, so the plan still
323
- // releases it exactly once.
324
- held.key(29, true);
325
-
326
- let (buttons, keys) = held.release_plan();
327
- assert_eq!(buttons, vec![Button::Left]);
328
- assert_eq!(keys, vec![29, 46]);
329
- assert!(
330
- held.release_plan().1.is_empty(),
331
- "releasing twice releases nothing"
332
- );
333
- }
334
-
335
- #[test]
336
- fn an_explicit_release_leaves_only_what_is_still_pressed() {
337
- let mut held = HeldState::default();
338
- held.key(29, true);
339
- held.key(46, true);
340
- held.key(46, false);
341
- let (buttons, keys) = held.release_plan();
342
- assert!(buttons.is_empty());
343
- assert_eq!(keys, vec![29]);
344
- }
345
-
346
- /// Proving the backend is really available here is the point: if this host
347
- /// cannot create the devices, the engine must say so rather than offer a
348
- /// control surface that does nothing.
349
- #[test]
350
- fn this_host_can_create_and_destroy_the_virtual_devices() {
351
- // This touches the real desktop when uinput is writable. Ordinary
352
- // suites must never create devices on the owner's active session.
353
- if std::env::var("DESKLINK_TEST_UINPUT").as_deref() != Ok("1") {
354
- return;
355
- }
356
- match InputDevices::create(2560, 1440) {
357
- Ok(_devices) => {}
358
- Err(error) => {
359
- let unavailable = probe().unwrap_err();
360
- assert!(
361
- error.to_string().contains(&unavailable.reason),
362
- "a creation failure must be the reported availability problem"
363
- );
364
- }
365
- }
366
- }
367
- }
@@ -299,14 +299,3 @@ impl InputDevices {
299
299
  }
300
300
  }
301
301
  }
302
-
303
- #[cfg(test)]
304
- mod tests {
305
- use super::*;
306
-
307
- #[test]
308
- fn command_key_uses_quartz_command_flag() {
309
- assert_eq!(modifier_flag(keycode::LEFT_META), 0x100000);
310
- assert_eq!(modifier_flag(0), 0);
311
- }
312
- }
@@ -483,140 +483,3 @@ fn fatal(out: &mut impl Write, error: &str) -> Result<()> {
483
483
  write_line(out, &serde_json::json!({ "error": error }).to_string())?;
484
484
  Err(anyhow!("{error}"))
485
485
  }
486
-
487
- #[cfg(test)]
488
- mod tests {
489
- use super::*;
490
-
491
- #[test]
492
- fn content_fills_the_screen_and_helpers_are_parked() {
493
- assert_eq!(kiosk_geometry(1240, 760, 1280, 800), (0, 0, 1280, 800));
494
- // Exactly at the limit is content; one pixel under is not.
495
- assert_eq!(kiosk_geometry(120, 120, 1280, 800), (0, 0, 1280, 800));
496
- assert_eq!(
497
- kiosk_geometry(119, 800, 1280, 800),
498
- (i32::from(PARKED_XY), i32::from(PARKED_XY), 119, 800)
499
- );
500
- assert_eq!(
501
- kiosk_geometry(1000, 24, 1280, 800),
502
- (i32::from(PARKED_XY), i32::from(PARKED_XY), 1000, 24)
503
- );
504
- }
505
-
506
- #[test]
507
- fn wm_class_reads_the_class_not_the_instance() {
508
- assert_eq!(
509
- decode_wm_class(b"google-chrome\0Google-chrome\0"),
510
- Some(String::from("Google-chrome"))
511
- );
512
- assert_eq!(decode_wm_class(b"instance-only\0"), None);
513
- assert_eq!(decode_wm_class(b""), None);
514
- }
515
-
516
- #[test]
517
- fn titles_come_from_either_name_property_and_blank_is_none() {
518
- assert_eq!(
519
- decode_title("Pricing — Acme Store"),
520
- Some(String::from("Pricing — Acme Store"))
521
- );
522
- assert_eq!(
523
- decode_title("PPF probe - Google Chrome"),
524
- Some(String::from("PPF probe - Google Chrome"))
525
- );
526
- assert_eq!(decode_title(" "), None);
527
- assert_eq!(decode_title(""), None);
528
- assert_eq!(
529
- decode_title(&latin1(b"Gr\xf6\xdfe")),
530
- Some(String::from("Gr\u{00f6}\u{00df}e"))
531
- );
532
- }
533
-
534
- #[test]
535
- fn the_report_line_is_exactly_the_promised_shape() {
536
- let line = window_line(&[KeeperWindow {
537
- id: 0x0040_0001,
538
- title: Some(String::from("PPF probe - Google Chrome")),
539
- class: Some(String::from("Google-chrome")),
540
- pid: Some(1003571),
541
- width: 1280,
542
- height: 800,
543
- }]);
544
- let parsed: serde_json::Value = serde_json::from_str(&line).unwrap();
545
- assert_eq!(
546
- parsed,
547
- serde_json::json!({
548
- "windows": [{
549
- "id": 0x40_0001_u64,
550
- "title": "PPF probe - Google Chrome",
551
- "class": "Google-chrome",
552
- "pid": 1003571,
553
- "width": 1280,
554
- "height": 800,
555
- }]
556
- })
557
- );
558
- assert_eq!(window_line(&[]), r#"{"windows":[]}"#);
559
- }
560
-
561
- #[test]
562
- fn missing_fields_are_null_not_absent() {
563
- let line = window_line(&[KeeperWindow {
564
- id: 7,
565
- title: None,
566
- class: None,
567
- pid: None,
568
- width: 100,
569
- height: 100,
570
- }]);
571
- let parsed: serde_json::Value = serde_json::from_str(&line).unwrap();
572
- let window = &parsed["windows"][0];
573
- assert_eq!(window["title"], serde_json::Value::Null);
574
- assert_eq!(window["class"], serde_json::Value::Null);
575
- assert_eq!(window["pid"], serde_json::Value::Null);
576
- assert!(window.get("width").is_some());
577
- }
578
-
579
- #[test]
580
- fn an_unopenable_display_is_refused_in_one_error_line() {
581
- let mut out = Vec::new();
582
- assert!(keep(Some("not a display"), &mut out).is_err());
583
- let line: serde_json::Value =
584
- serde_json::from_str(&String::from_utf8(out).unwrap()).unwrap();
585
- assert!(line["error"]
586
- .as_str()
587
- .is_some_and(|error| !error.is_empty()));
588
- }
589
-
590
- #[test]
591
- fn a_refused_keeper_says_why_in_its_error_line() {
592
- let mut out = Vec::new();
593
- let error = fatal(&mut out, "another window manager").unwrap_err();
594
- assert_eq!(error.to_string(), "another window manager");
595
- assert_eq!(
596
- String::from_utf8(out).unwrap(),
597
- "{\"error\":\"another window manager\"}\n"
598
- );
599
- }
600
-
601
- #[test]
602
- fn the_display_flag_has_one_spelling() {
603
- let flag = |args: &[&str]| {
604
- display_argument(
605
- &args
606
- .iter()
607
- .map(|arg| String::from(*arg))
608
- .collect::<Vec<_>>(),
609
- )
610
- };
611
- assert_eq!(flag(&["--display", ":42"]), Some(String::from(":42")));
612
- assert_eq!(
613
- flag(&["--display", ":42", "--x"]),
614
- Some(String::from(":42"))
615
- );
616
- assert_eq!(flag(&[]), None);
617
- assert_eq!(flag(&["--display"]), None);
618
- // The undocumented `--display=:42` spelling is not a display: `keep`
619
- // refuses it rather than guessing what was meant.
620
- assert_eq!(flag(&["--display=:42"]), None);
621
- }
622
- }
@@ -254,110 +254,3 @@ impl Layout {
254
254
  (plan, unreachable)
255
255
  }
256
256
  }
257
-
258
- #[cfg(test)]
259
- mod tests {
260
- use super::*;
261
-
262
- #[test]
263
- fn a_layout_that_comes_from_the_environment_is_the_one_compiled() {
264
- let names = LayoutNames::from_lookup(|key| match key {
265
- "XKB_DEFAULT_LAYOUT" => Some(String::from("de")),
266
- _ => None,
267
- });
268
- assert_eq!(names.identity(), "de");
269
-
270
- let layout = Layout::from_names(&names).expect("the German keymap should compile");
271
- let (plan, unreachable) = layout.plan_text("z");
272
- assert!(
273
- unreachable.is_empty(),
274
- "z should be reachable: {unreachable:?}"
275
- );
276
- // QWERTZ puts z where a US layout puts y, so the compiled keymap is the
277
- // environment's, not the library default.
278
- assert_eq!(plan[0][0].code, 21, "z must sit on the y key position");
279
- }
280
-
281
- #[test]
282
- fn a_session_with_no_layout_variable_reports_the_library_default() {
283
- assert_eq!(LayoutNames::from_lookup(|_| None).identity(), "us");
284
- }
285
-
286
- #[test]
287
- fn a_character_is_planned_as_a_real_key_event_on_the_compiled_layout() {
288
- let layout = Layout::from_names(&LayoutNames::from_lookup(|_| None))
289
- .expect("a keymap should compile");
290
- let (plan, unreachable) = layout.plan_text("aA.\n");
291
- assert!(
292
- unreachable.is_empty(),
293
- "ASCII should be reachable: {unreachable:?}"
294
- );
295
- assert_eq!(plan.len(), 4);
296
- assert!(!plan[0][0].shift, "lower-case a needs no shift");
297
- assert!(
298
- plan[1][0].shift,
299
- "upper-case A needs shift on any normal layout"
300
- );
301
- assert_eq!(plan[0][0].code, plan[1][0].code, "both letters share a key");
302
- // The plan is evdev, but the real injector expects Moonlight/VK codes.
303
- // Passing Linux N (49) straight through instead presses VK_1.
304
- let native: Vec<_> = plan
305
- .iter()
306
- .flatten()
307
- .map(|stroke| crate::input::native_keycode(stroke.code).unwrap())
308
- .collect();
309
- assert_eq!(native, [0x41, 0x41, 0xBE, 0x0D]);
310
- assert_eq!(
311
- crate::input::native_keycode(plan[1][0].modifiers()[0]).unwrap(),
312
- 0x10
313
- );
314
- assert_eq!(
315
- crate::input::native_keycode(modifier_key("Control").unwrap()).unwrap(),
316
- 0x11
317
- );
318
- }
319
-
320
- #[test]
321
- fn named_keys_and_modifiers_resolve_to_linux_key_codes() {
322
- assert_eq!(named_key("Enter").unwrap().code, keycode::ENTER);
323
- assert_eq!(named_key("ArrowLeft").unwrap().code, keycode::LEFT);
324
- for (name, code) in [
325
- ("Delete", keycode::DELETE), ("Home", keycode::HOME), ("End", keycode::END),
326
- ("PageUp", keycode::PAGE_UP), ("PageDown", keycode::PAGE_DOWN),
327
- ("F1", keycode::F1), ("F2", keycode::F2), ("F3", keycode::F3),
328
- ("F4", keycode::F4), ("F5", keycode::F5), ("F6", keycode::F6),
329
- ("F7", keycode::F7), ("F8", keycode::F8), ("F9", keycode::F9),
330
- ("F10", keycode::F10), ("F11", keycode::F11), ("F12", keycode::F12),
331
- ] {
332
- assert_eq!(named_key(name).unwrap().code, code);
333
- assert!(crate::input::native_keycode(code).is_ok(), "{name} must reach inputtino");
334
- }
335
- assert_eq!(modifier_key("Control"), Some(keycode::LEFT_CTRL));
336
- assert_eq!(modifier_key("Alt"), Some(keycode::LEFT_ALT));
337
- assert_eq!(modifier_key("Super"), Some(keycode::LEFT_META));
338
- assert!(named_key("Frobnicate").is_none());
339
- assert!(modifier_key("Hyper").is_none());
340
- }
341
-
342
- #[test]
343
- fn text_this_layout_cannot_produce_is_reported_not_invented() {
344
- let layout = Layout::from_environment().unwrap();
345
- let (_, unreachable) = layout.plan_text("\u{1F600}");
346
- assert_eq!(unreachable, "\u{1F600}");
347
- }
348
-
349
- #[test]
350
- fn a_symbol_only_reachable_beyond_four_levels_is_refused() {
351
- let names = LayoutNames::from_lookup(|key| match key {
352
- "XKB_DEFAULT_LAYOUT" => Some(String::from("de")),
353
- "XKB_DEFAULT_VARIANT" => Some(String::from("neo")),
354
- _ => None,
355
- });
356
- let layout = Layout::from_names(&names).expect("the Neo keymap should compile");
357
- assert!(
358
- layout.keystroke_for_char('\u{a3}').is_none(),
359
- "the pound sign sits on a level this engine cannot press"
360
- );
361
- assert!(layout.keystroke_for_char('6').is_some());
362
- }
363
- }
@@ -182,28 +182,3 @@ fn us_ansi_key(character: char) -> Option<(i16, bool)> {
182
182
  _ => return None,
183
183
  })
184
184
  }
185
-
186
- #[cfg(test)]
187
- mod tests {
188
- use super::*;
189
-
190
- #[test]
191
- fn maps_common_ascii_to_ansi_virtual_keys() {
192
- let layout = Layout::from_environment().unwrap();
193
- let lower = layout.keystroke_for_char('a').unwrap();
194
- let upper = layout.keystroke_for_char('A').unwrap();
195
- assert_eq!(lower.code, 0);
196
- assert_eq!(upper.code, lower.code);
197
- assert!(upper.shift);
198
- assert_eq!(layout.keystroke_for_char('!').unwrap().code, 18);
199
- assert!(layout.keystroke_for_char('\u{1f600}').is_none());
200
- }
201
-
202
- #[test]
203
- fn resolves_mac_named_keys_and_modifiers() {
204
- assert_eq!(named_key("Return").unwrap().code, 36);
205
- assert_eq!(named_key("ArrowLeft").unwrap().code, 123);
206
- assert_eq!(modifier_key("Command"), Some(55));
207
- assert!(named_key("Frobnicate").is_none());
208
- }
209
- }
package/engine/src/mac.rs CHANGED
@@ -184,6 +184,7 @@ fn capabilities() -> Value {
184
184
  "formats": ["bgra"],
185
185
  "cursor": "embedded",
186
186
  "cursor_modes": ["embedded"],
187
+ // ScreenCaptureKit audio is not wired up; macOS is out of scope.
187
188
  "audio": false,
188
189
  "displays": displays(),
189
190
  "grant": if capture_granted { "granted" } else { "missing-screen-recording" },
@@ -717,31 +718,3 @@ pub fn run() -> i32 {
717
718
  }
718
719
  0
719
720
  }
720
-
721
- #[cfg(test)]
722
- mod tests {
723
- use super::*;
724
-
725
- #[cfg(desklink_vpx)]
726
- #[test]
727
- fn session_capabilities_are_non_prompting_and_expose_mac_features() {
728
- let value = crate::session::capabilities();
729
- assert_eq!(value["protocol"], crate::protocol::PROTOCOL_VERSION);
730
- assert_eq!(value["platform"], "macos");
731
- assert_eq!(value["capture"]["mechanism"], "screencapturekit");
732
- assert_eq!(value["encode"]["codecs"][0], "vp9", "VP9 is always offered");
733
- // Every Mac this engine runs on has a hardware H.264 encoder.
734
- assert_eq!(value["encode"]["codecs"][1], "h264");
735
- assert_eq!(value["encode"]["h264"]["encoder"], "videotoolbox");
736
- assert!(value["capture"]["displays"].is_array());
737
- assert!(value["clipboard"]["read"].as_bool().unwrap());
738
- assert!(matches!(
739
- value["capture"]["grant"].as_str(),
740
- Some("granted" | "missing-screen-recording")
741
- ));
742
- assert!(matches!(
743
- value["input"]["grant"].as_str(),
744
- Some("granted" | "missing-accessibility")
745
- ));
746
- }
747
- }
@@ -6,6 +6,8 @@
6
6
  //! process, owns its stdin/stdout, and carries the SDP/ICE the engine produces
7
7
  //! over whatever authenticated channel it already has.
8
8
 
9
+ #[cfg(target_os = "linux")]
10
+ mod audio;
9
11
  #[cfg(target_os = "linux")]
10
12
  mod capture;
11
13
  #[cfg(target_os = "macos")]
@@ -574,6 +576,15 @@ fn render_event(notice: session::Notice) -> Option<String> {
574
576
  "session.frame.changed",
575
577
  serde_json::json!({ "sessionId": session_id, "seq": seq, "damage": damage, "timestamp_us": timestamp_us }),
576
578
  ),
579
+ session::SessionEvent::Audio {
580
+ seq,
581
+ timestamp_us,
582
+ bytes,
583
+ gap_micros,
584
+ } => (
585
+ "session.audio.changed",
586
+ serde_json::json!({ "sessionId": session_id, "seq": seq, "timestamp_us": timestamp_us, "bytes": bytes, "gap_ms": gap_micros / 1000 }),
587
+ ),
577
588
  session::SessionEvent::Input { input } => (
578
589
  "session.input",
579
590
  serde_json::json!({ "sessionId": session_id, "input": input }),
@@ -658,6 +669,7 @@ async fn dispatch(
658
669
  loopback_tcp: params.loopback_tcp,
659
670
  agent_indicator: params.agent_indicator,
660
671
  local_frames: params.local_frames,
672
+ audio: params.audio,
661
673
  ice_servers: params
662
674
  .ice_servers
663
675
  .into_iter()
@@ -684,6 +696,7 @@ async fn dispatch(
684
696
  "geometry": session.geometry(),
685
697
  "cursor": session.cursor_info(),
686
698
  "agentIndicator": session.agent_indicator(),
699
+ "audio": session.audio_info(),
687
700
  });
688
701
  *current = Some(session);
689
702
  Ok(result)
@@ -787,6 +800,15 @@ async fn dispatch(
787
800
  .frame(params.since, &params.path, params.region)
788
801
  .map_err(|error| ErrorBody::new(error.code, error.message))
789
802
  }
803
+ "session.audio" => {
804
+ let session = require_session(current)?;
805
+ let params: protocol::AudioParams = serde_json::from_value(request.params.clone())
806
+ .map_err(|error| ErrorBody::new("malformed", error.to_string()))?;
807
+ check_session(session, &params.session_id, None)?;
808
+ session
809
+ .audio(&params.path, params.max_bytes)
810
+ .map_err(|error| ErrorBody::new(error.code, error.message))
811
+ }
790
812
  "session.feed" => {
791
813
  let session = require_session(current)?;
792
814
  let params: protocol::FeedParams = serde_json::from_value(request.params.clone())