@buoy-gg/tv-remote 7.0.31
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/LICENSE +58 -0
- package/README.md +89 -0
- package/lib/commonjs/index.js +56 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/store/tvEventStore.js +167 -0
- package/lib/commonjs/store/tvEventStore.js.map +1 -0
- package/lib/commonjs/sync/tvRemoteSyncAdapter.js +57 -0
- package/lib/commonjs/sync/tvRemoteSyncAdapter.js.map +1 -0
- package/lib/module/index.js +23 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/store/tvEventStore.js +157 -0
- package/lib/module/store/tvEventStore.js.map +1 -0
- package/lib/module/sync/tvRemoteSyncAdapter.js +53 -0
- package/lib/module/sync/tvRemoteSyncAdapter.js.map +1 -0
- package/lib/typescript/index.d.ts +14 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/store/tvEventStore.d.ts +66 -0
- package/lib/typescript/store/tvEventStore.d.ts.map +1 -0
- package/lib/typescript/sync/tvRemoteSyncAdapter.d.ts +34 -0
- package/lib/typescript/sync/tvRemoteSyncAdapter.d.ts.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
PROPRIETARY SOFTWARE LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present Buoy. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and its source code are proprietary and confidential.
|
|
6
|
+
|
|
7
|
+
NOTICE: This is NOT open source software. This software is licensed,
|
|
8
|
+
not sold, and is protected by copyright laws and international treaties.
|
|
9
|
+
|
|
10
|
+
TERMS AND CONDITIONS:
|
|
11
|
+
|
|
12
|
+
1. LICENSE GRANT
|
|
13
|
+
Subject to the terms of this Agreement and payment of applicable fees,
|
|
14
|
+
Buoy grants you a limited, non-exclusive, non-transferable license
|
|
15
|
+
to use the compiled software packages in your applications.
|
|
16
|
+
|
|
17
|
+
2. RESTRICTIONS
|
|
18
|
+
You may NOT:
|
|
19
|
+
- Copy, modify, or distribute the source code
|
|
20
|
+
- Reverse engineer, decompile, or disassemble the software
|
|
21
|
+
- Remove or alter any proprietary notices or labels
|
|
22
|
+
- Sublicense, rent, lease, or lend the software
|
|
23
|
+
- Use the software to create competing products
|
|
24
|
+
- Share access credentials with unauthorized parties
|
|
25
|
+
|
|
26
|
+
3. OWNERSHIP
|
|
27
|
+
React Buoy retains all right, title, and interest in the software,
|
|
28
|
+
including all intellectual property rights. This license does not
|
|
29
|
+
grant you any rights to trademarks or service marks.
|
|
30
|
+
|
|
31
|
+
4. TERMINATION
|
|
32
|
+
This license is effective until terminated. Your rights under this
|
|
33
|
+
license will terminate automatically without notice if you fail to
|
|
34
|
+
comply with any of its terms. Upon termination, you must cease all
|
|
35
|
+
use and destroy all copies of the software.
|
|
36
|
+
|
|
37
|
+
5. DISCLAIMER OF WARRANTIES
|
|
38
|
+
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
|
39
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
40
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
41
|
+
|
|
42
|
+
6. LIMITATION OF LIABILITY
|
|
43
|
+
IN NO EVENT SHALL BUOY BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
|
|
44
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN
|
|
45
|
+
CONNECTION WITH THIS LICENSE OR THE USE OF THE SOFTWARE.
|
|
46
|
+
|
|
47
|
+
7. GOVERNING LAW
|
|
48
|
+
This Agreement shall be governed by and construed in accordance with
|
|
49
|
+
the laws of the United States, without regard to its conflict of
|
|
50
|
+
law provisions.
|
|
51
|
+
|
|
52
|
+
For licensing inquiries and subscription information:
|
|
53
|
+
- Website: https://buoy.gg
|
|
54
|
+
- Email: AustinLovesWorking@gmail.com
|
|
55
|
+
|
|
56
|
+
Unauthorized reproduction or distribution of this software, or any
|
|
57
|
+
portion of it, may result in severe civil and criminal penalties,
|
|
58
|
+
and will be prosecuted to the maximum extent possible under the law.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @buoy-gg/tv-remote
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@buoy-gg/tv-remote) [](https://www.npmjs.com/package/@buoy-gg/tv-remote)
|
|
4
|
+
|
|
5
|
+
**Press the TV remote from Buoy Desktop — drive your Apple TV / Android TV app's D-pad, record press sequences as macros, and replay them across every connected device.**
|
|
6
|
+
|
|
7
|
+
Part of [Buoy](https://github.com/Buoy-gg/buoy) — devtools that live inside your React Native app. Install it and the TV Remote panel lights up in Buoy Desktop for that device.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @buoy-gg/core @buoy-gg/tv-remote
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { FloatingDevTools } from "@buoy-gg/core";
|
|
17
|
+
|
|
18
|
+
export default function App() {
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
{/* your app */}
|
|
22
|
+
<FloatingDevTools headless /> {/* TV apps run headless — no bubble */}
|
|
23
|
+
</>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## What this package does — and deliberately does not
|
|
29
|
+
|
|
30
|
+
This package **only observes**. It captures the remote events your app receives
|
|
31
|
+
(`up`/`down`/`left`/`right`/`select`/`menu`/`playPause`) and streams them to Buoy Desktop.
|
|
32
|
+
|
|
33
|
+
**Presses are injected from the host**, by Buoy Desktop shelling out to `adb shell input keyevent`
|
|
34
|
+
(Android TV) or `idb ui key` (Apple TV simulator). That is the only honest path:
|
|
35
|
+
|
|
36
|
+
- On tvOS, the two things an app *can* do in-process are post the internal `onHWKeyEvent`
|
|
37
|
+
notification (fires the JS event, but focus never moves) and `requestTVFocus` (jumps focus,
|
|
38
|
+
but fires no event and traverses nothing). Each is half of a press, and the missing half is
|
|
39
|
+
the half QA cares about.
|
|
40
|
+
- On Android, directional focus navigation for unconsumed D-pad keys happens in `ViewRootImpl`,
|
|
41
|
+
*above* the Activity — so an in-process `dispatchKeyEvent` would fire JS events **without
|
|
42
|
+
moving focus** exactly on the screens that are broken.
|
|
43
|
+
|
|
44
|
+
Host-side injection goes through `InputManager` / the simulator's HID layer — the same pipeline
|
|
45
|
+
as a physical remote, driving the real focus engine.
|
|
46
|
+
|
|
47
|
+
The package also renders **nothing**. On Android TV any focusable view in an overlay becomes a
|
|
48
|
+
D-pad stop in the host app's focus order, so a visible tool would alter the navigation it is
|
|
49
|
+
supposed to be testing.
|
|
50
|
+
|
|
51
|
+
## Support matrix
|
|
52
|
+
|
|
53
|
+
| Target | Drive-able? | Via |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| Android TV emulator | ✅ full fidelity | `adb -s <serial> shell input keyevent` |
|
|
56
|
+
| Android TV device | ✅ full fidelity | same, over `adb connect <ip>:5555` |
|
|
57
|
+
| Apple TV simulator | ✅ arrows / select / menu — ❌ play-pause | `idb ui key --udid <udid>` |
|
|
58
|
+
| Apple TV device | ❌ replay unsupported — **record only** | (future: an XCUITest `XCUIRemote` companion) |
|
|
59
|
+
|
|
60
|
+
Capture is pure JS and works on every target, including retail hardware — so the
|
|
61
|
+
**record-on-retail** workflow holds: press the physical remote on a rack device, get a macro,
|
|
62
|
+
replay it against emulators and simulators.
|
|
63
|
+
|
|
64
|
+
`idb` is not part of Xcode. Without it on `PATH` the Apple TV lane is disabled with an install
|
|
65
|
+
hint (`brew install idb-companion` + `pipx install fb-idb`).
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
Everything is driven from Buoy Desktop; these exports exist for custom integrations.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import {
|
|
73
|
+
arm, // start capturing
|
|
74
|
+
disarm, // stop capturing, hand the Menu key back to the platform
|
|
75
|
+
clear,
|
|
76
|
+
setMenuCapture, // tvOS: route Menu to JS (opt-in, always undone on disarm)
|
|
77
|
+
getEventsSince, // (seq) => events newer than seq
|
|
78
|
+
getState,
|
|
79
|
+
subscribe,
|
|
80
|
+
tvRemoteSyncAdapter, // auto-discovered by @buoy-gg/core
|
|
81
|
+
} from "@buoy-gg/tv-remote";
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
On non-TV platforms `getState().supported` is `false`, the native listener is never attached,
|
|
85
|
+
and the package costs one boolean.
|
|
86
|
+
|
|
87
|
+
## Docs
|
|
88
|
+
|
|
89
|
+
Full documentation: <https://buoy.gg/buoy/latest/docs/tools/tv-remote>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "arm", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _tvEventStore.arm;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "clear", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _tvEventStore.clear;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "disarm", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _tvEventStore.disarm;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(exports, "getEventsSince", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () {
|
|
27
|
+
return _tvEventStore.getEventsSince;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(exports, "getState", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function () {
|
|
33
|
+
return _tvEventStore.getState;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
Object.defineProperty(exports, "setMenuCapture", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () {
|
|
39
|
+
return _tvEventStore.setMenuCapture;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(exports, "subscribe", {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
get: function () {
|
|
45
|
+
return _tvEventStore.subscribe;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
Object.defineProperty(exports, "tvRemoteSyncAdapter", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
get: function () {
|
|
51
|
+
return _tvRemoteSyncAdapter.tvRemoteSyncAdapter;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
var _tvRemoteSyncAdapter = require("./sync/tvRemoteSyncAdapter");
|
|
55
|
+
var _tvEventStore = require("./store/tvEventStore");
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_tvRemoteSyncAdapter","require","_tvEventStore"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,IAAAA,oBAAA,GAAAC,OAAA;AAKA,IAAAC,aAAA,GAAAD,OAAA","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.arm = arm;
|
|
7
|
+
exports.clear = clear;
|
|
8
|
+
exports.disarm = disarm;
|
|
9
|
+
exports.getEventsSince = getEventsSince;
|
|
10
|
+
exports.getState = getState;
|
|
11
|
+
exports.setMenuCapture = setMenuCapture;
|
|
12
|
+
exports.subscribe = subscribe;
|
|
13
|
+
var ReactNative = _interopRequireWildcard(require("react-native"));
|
|
14
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
15
|
+
/**
|
|
16
|
+
* tvEventStore — the whole device-side half of the TV remote tool.
|
|
17
|
+
*
|
|
18
|
+
* CONTRACT: this store only ever OBSERVES. It never injects, synthesizes, or
|
|
19
|
+
* fakes a remote press. Presses come from the host (Buoy Desktop shells out to
|
|
20
|
+
* `adb shell input keyevent` / `idb ui key`), because those go through the real
|
|
21
|
+
* platform focus engine; anything we could fake in-process would move focus
|
|
22
|
+
* without firing events, or fire events without moving focus. See
|
|
23
|
+
* TV_REMOTE_DRIVER_PLAN.md §2.
|
|
24
|
+
*
|
|
25
|
+
* It is also deliberately UI-less. On Android TV any focusable view we rendered
|
|
26
|
+
* would become a D-pad stop in the host app's focus order, so the tool would
|
|
27
|
+
* alter the navigation it is supposed to be replaying.
|
|
28
|
+
*
|
|
29
|
+
* Safe to ship in non-TV apps: the native event emitter is never touched unless
|
|
30
|
+
* `Platform.isTV` is true, so the cost on a phone is one boolean.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** One captured remote event, as it goes over the wire to the dashboard. */
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Ring buffer depth. Replay polls `getEventsSince` every few dozen ms with a
|
|
37
|
+
* ~2s budget per step, so 50 is many times more than any echo needs; it exists
|
|
38
|
+
* so a human scrubbing the echo strip has some scrollback.
|
|
39
|
+
*/
|
|
40
|
+
const MAX_EVENTS = 50;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Continuous gestures are not presses and are never recorded — the macro format
|
|
44
|
+
* is discrete keys. Dropping them here keeps them out of the ring buffer so
|
|
45
|
+
* they can't push real presses out of it during a replay.
|
|
46
|
+
*/
|
|
47
|
+
const IGNORED_EVENT_TYPES = new Set(["pan", "swipeUp", "swipeDown", "swipeLeft", "swipeRight"]);
|
|
48
|
+
/**
|
|
49
|
+
* `TVEventHandler` / `TVEventControl` only exist on the react-native-tvos fork.
|
|
50
|
+
* A namespace read (rather than a named import) keeps this module importable on
|
|
51
|
+
* stock react-native, where those exports are absent.
|
|
52
|
+
*/
|
|
53
|
+
const RN = ReactNative;
|
|
54
|
+
const isTV = RN.Platform?.isTV === true;
|
|
55
|
+
let events = [];
|
|
56
|
+
let seq = 0;
|
|
57
|
+
let armed = false;
|
|
58
|
+
let menuCaptureArmed = false;
|
|
59
|
+
let subscription = null;
|
|
60
|
+
const listeners = new Set();
|
|
61
|
+
function emitChange() {
|
|
62
|
+
listeners.forEach(listener => {
|
|
63
|
+
listener();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function handleEvent(raw) {
|
|
67
|
+
// Disarming removes the subscription, but don't take that on trust: a native
|
|
68
|
+
// emitter that delivered one more event after `remove()` would land a stray
|
|
69
|
+
// press in the buffer, and a stray press is exactly what a replay would
|
|
70
|
+
// mistake for a step's echo.
|
|
71
|
+
if (!armed) return;
|
|
72
|
+
const event = raw;
|
|
73
|
+
const eventType = event?.eventType;
|
|
74
|
+
if (typeof eventType !== "string") return;
|
|
75
|
+
if (IGNORED_EVENT_TYPES.has(eventType)) return;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Dedupe by ignoring key DOWN, never key UP. tvOS emits keyAction 1 (up)
|
|
79
|
+
* ONLY — taps reach the gesture recognizer at StateEnded — so filtering to
|
|
80
|
+
* "down" would swallow every tvOS event. Android's down-emit is
|
|
81
|
+
* feature-flagged off by default, but dropping it here is the correct
|
|
82
|
+
* invariant either way: one press must produce exactly one echo.
|
|
83
|
+
*/
|
|
84
|
+
if (event?.eventKeyAction === 0) return;
|
|
85
|
+
seq += 1;
|
|
86
|
+
const next = {
|
|
87
|
+
seq,
|
|
88
|
+
t: Date.now(),
|
|
89
|
+
eventType,
|
|
90
|
+
eventKeyAction: event?.eventKeyAction
|
|
91
|
+
};
|
|
92
|
+
events = events.length >= MAX_EVENTS ? [...events.slice(1), next] : [...events, next];
|
|
93
|
+
emitChange();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Start pushing remote events into the buffer. Idempotent. */
|
|
97
|
+
function arm() {
|
|
98
|
+
if (armed) return;
|
|
99
|
+
armed = true;
|
|
100
|
+
if (isTV && RN.TVEventHandler && !subscription) {
|
|
101
|
+
subscription = RN.TVEventHandler.addListener(handleEvent);
|
|
102
|
+
}
|
|
103
|
+
emitChange();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Stop capturing and hand the Menu key back to the platform. Every teardown
|
|
108
|
+
* path funnels through here — leaving menu capture on would leave the app
|
|
109
|
+
* unable to navigate back, which is an App Store behavior change.
|
|
110
|
+
*/
|
|
111
|
+
function disarm() {
|
|
112
|
+
if (subscription) {
|
|
113
|
+
subscription.remove();
|
|
114
|
+
subscription = null;
|
|
115
|
+
}
|
|
116
|
+
if (menuCaptureArmed) setMenuCapture(false);
|
|
117
|
+
if (!armed) return;
|
|
118
|
+
armed = false;
|
|
119
|
+
emitChange();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* tvOS: route the Menu key to JS so it can be recorded, instead of letting it
|
|
124
|
+
* pop the navigation stack (and background the app at the root). This changes
|
|
125
|
+
* how the app under test behaves, so it is opt-in per recording session and is
|
|
126
|
+
* always undone on disarm.
|
|
127
|
+
*/
|
|
128
|
+
function setMenuCapture(on) {
|
|
129
|
+
if (!isTV || RN.Platform.OS !== "ios" || !RN.TVEventControl) return;
|
|
130
|
+
if (on === menuCaptureArmed) return;
|
|
131
|
+
if (on) {
|
|
132
|
+
RN.TVEventControl.enableTVMenuKey();
|
|
133
|
+
} else {
|
|
134
|
+
RN.TVEventControl.disableTVMenuKey();
|
|
135
|
+
}
|
|
136
|
+
menuCaptureArmed = on;
|
|
137
|
+
emitChange();
|
|
138
|
+
}
|
|
139
|
+
function clear() {
|
|
140
|
+
if (events.length === 0) return;
|
|
141
|
+
events = [];
|
|
142
|
+
emitChange();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Events strictly newer than `sinceSeq` — the replay echo cursor. */
|
|
146
|
+
function getEventsSince(sinceSeq) {
|
|
147
|
+
if (!Number.isFinite(sinceSeq)) return events.slice();
|
|
148
|
+
return events.filter(event => event.seq > sinceSeq);
|
|
149
|
+
}
|
|
150
|
+
function getState() {
|
|
151
|
+
return {
|
|
152
|
+
supported: isTV,
|
|
153
|
+
platform: RN.Platform?.OS ?? "unknown",
|
|
154
|
+
captureArmed: armed,
|
|
155
|
+
listening: subscription !== null,
|
|
156
|
+
menuCaptureArmed,
|
|
157
|
+
seq,
|
|
158
|
+
recent: events.slice()
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function subscribe(listener) {
|
|
162
|
+
listeners.add(listener);
|
|
163
|
+
return () => {
|
|
164
|
+
listeners.delete(listener);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=tvEventStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["ReactNative","_interopRequireWildcard","require","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","MAX_EVENTS","IGNORED_EVENT_TYPES","Set","RN","isTV","Platform","events","seq","armed","menuCaptureArmed","subscription","listeners","emitChange","forEach","listener","handleEvent","raw","event","eventType","eventKeyAction","next","Date","now","length","slice","arm","TVEventHandler","addListener","disarm","remove","setMenuCapture","on","OS","TVEventControl","enableTVMenuKey","disableTVMenuKey","clear","getEventsSince","sinceSeq","Number","isFinite","filter","getState","supported","platform","captureArmed","listening","recent","subscribe","add","delete"],"sourceRoot":"../../../src","sources":["store/tvEventStore.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.tvRemoteSyncAdapter = void 0;
|
|
7
|
+
var _tvEventStore = require("../store/tvEventStore");
|
|
8
|
+
/**
|
|
9
|
+
* tvRemoteSyncAdapter — streams captured TV remote events to Buoy Desktop via
|
|
10
|
+
* @buoy-gg/external-sync.
|
|
11
|
+
*
|
|
12
|
+
* Structurally matches external-sync's `ToolSyncAdapter` WITHOUT importing the
|
|
13
|
+
* package (same as every other tool's adapter). Auto-discovery in @buoy-gg/core
|
|
14
|
+
* picks it up by export name and announces the "tv-remote" capability.
|
|
15
|
+
*
|
|
16
|
+
* `getEventsSince` exists because snapshots are throttled 200ms trailing-edge
|
|
17
|
+
* per tool, which is too coarse to time a replay step's echo. Replay polls this
|
|
18
|
+
* awaited action instead and gets the real latency.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const tvRemoteSyncAdapter = exports.tvRemoteSyncAdapter = {
|
|
22
|
+
version: 1,
|
|
23
|
+
getSnapshot() {
|
|
24
|
+
return (0, _tvEventStore.getState)();
|
|
25
|
+
},
|
|
26
|
+
subscribe(onChange) {
|
|
27
|
+
return (0, _tvEventStore.subscribe)(onChange);
|
|
28
|
+
},
|
|
29
|
+
actions: {
|
|
30
|
+
arm() {
|
|
31
|
+
(0, _tvEventStore.arm)();
|
|
32
|
+
return (0, _tvEventStore.getState)();
|
|
33
|
+
},
|
|
34
|
+
disarm() {
|
|
35
|
+
(0, _tvEventStore.disarm)();
|
|
36
|
+
return (0, _tvEventStore.getState)();
|
|
37
|
+
},
|
|
38
|
+
/** Cursor-paged echo poll. `seq: 0` returns everything buffered. */
|
|
39
|
+
getEventsSince(params) {
|
|
40
|
+
const since = typeof params?.seq === "number" ? params.seq : 0;
|
|
41
|
+
const state = (0, _tvEventStore.getState)();
|
|
42
|
+
return {
|
|
43
|
+
seq: state.seq,
|
|
44
|
+
events: (0, _tvEventStore.getEventsSince)(since)
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
setMenuCapture(params) {
|
|
48
|
+
(0, _tvEventStore.setMenuCapture)(params?.on === true);
|
|
49
|
+
return (0, _tvEventStore.getState)();
|
|
50
|
+
},
|
|
51
|
+
clear() {
|
|
52
|
+
(0, _tvEventStore.clear)();
|
|
53
|
+
return (0, _tvEventStore.getState)();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=tvRemoteSyncAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_tvEventStore","require","tvRemoteSyncAdapter","exports","version","getSnapshot","getState","subscribe","onChange","actions","arm","disarm","getEventsSince","params","since","seq","state","events","setMenuCapture","on","clear"],"sourceRoot":"../../../src","sources":["sync/tvRemoteSyncAdapter.ts"],"mappings":";;;;;;AAaA,IAAAA,aAAA,GAAAC,OAAA;AAbA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAcO,MAAMC,mBAAmB,GAAAC,OAAA,CAAAD,mBAAA,GAAG;EACjCE,OAAO,EAAE,CAAC;EAEVC,WAAWA,CAAA,EAAkB;IAC3B,OAAO,IAAAC,sBAAQ,EAAC,CAAC;EACnB,CAAC;EAEDC,SAASA,CAACC,QAAoB,EAAc;IAC1C,OAAO,IAAAD,uBAAS,EAACC,QAAQ,CAAC;EAC5B,CAAC;EAEDC,OAAO,EAAE;IACPC,GAAGA,CAAA,EAAkB;MACnB,IAAAA,iBAAG,EAAC,CAAC;MACL,OAAO,IAAAJ,sBAAQ,EAAC,CAAC;IACnB,CAAC;IAEDK,MAAMA,CAAA,EAAkB;MACtB,IAAAA,oBAAM,EAAC,CAAC;MACR,OAAO,IAAAL,sBAAQ,EAAC,CAAC;IACnB,CAAC;IAED;IACAM,cAAcA,CAACC,MAAyB,EAGtC;MACA,MAAMC,KAAK,GAAG,OAAOD,MAAM,EAAEE,GAAG,KAAK,QAAQ,GAAGF,MAAM,CAACE,GAAG,GAAG,CAAC;MAC9D,MAAMC,KAAK,GAAG,IAAAV,sBAAQ,EAAC,CAAC;MACxB,OAAO;QAAES,GAAG,EAAEC,KAAK,CAACD,GAAG;QAAEE,MAAM,EAAE,IAAAL,4BAAc,EAACE,KAAK;MAAE,CAAC;IAC1D,CAAC;IAEDI,cAAcA,CAACL,MAAyB,EAAiB;MACvD,IAAAK,4BAAc,EAACL,MAAM,EAAEM,EAAE,KAAK,IAAI,CAAC;MACnC,OAAO,IAAAb,sBAAQ,EAAC,CAAC;IACnB,CAAC;IAEDc,KAAKA,CAAA,EAAkB;MACrB,IAAAA,mBAAK,EAAC,CAAC;MACP,OAAO,IAAAd,sBAAQ,EAAC,CAAC;IACnB;EACF;AACF,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @buoy-gg/tv-remote
|
|
5
|
+
*
|
|
6
|
+
* The device-side half of Buoy's TV remote driver: it CAPTURES the D-pad /
|
|
7
|
+
* select / menu / play-pause events the app actually receives and streams them
|
|
8
|
+
* to Buoy Desktop, which is where presses are injected from (adb / idb) and
|
|
9
|
+
* where macros are recorded and replayed.
|
|
10
|
+
*
|
|
11
|
+
* This package renders nothing and injects nothing. See the README for why.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// EXTERNAL SYNC (device → desktop dashboard)
|
|
16
|
+
// =============================================================================
|
|
17
|
+
export { tvRemoteSyncAdapter } from "./sync/tvRemoteSyncAdapter";
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// CAPTURE STORE (arm/disarm + types)
|
|
21
|
+
// =============================================================================
|
|
22
|
+
export { arm, disarm, clear, setMenuCapture, getEventsSince, getState, subscribe } from "./store/tvEventStore";
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["tvRemoteSyncAdapter","arm","disarm","clear","setMenuCapture","getEventsSince","getState","subscribe"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,SAASA,mBAAmB,QAAQ,4BAA4B;;AAEhE;AACA;AACA;AACA,SACEC,GAAG,EACHC,MAAM,EACNC,KAAK,EACLC,cAAc,EACdC,cAAc,EACdC,QAAQ,EACRC,SAAS,QACJ,sBAAsB","ignoreList":[]}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* tvEventStore — the whole device-side half of the TV remote tool.
|
|
5
|
+
*
|
|
6
|
+
* CONTRACT: this store only ever OBSERVES. It never injects, synthesizes, or
|
|
7
|
+
* fakes a remote press. Presses come from the host (Buoy Desktop shells out to
|
|
8
|
+
* `adb shell input keyevent` / `idb ui key`), because those go through the real
|
|
9
|
+
* platform focus engine; anything we could fake in-process would move focus
|
|
10
|
+
* without firing events, or fire events without moving focus. See
|
|
11
|
+
* TV_REMOTE_DRIVER_PLAN.md §2.
|
|
12
|
+
*
|
|
13
|
+
* It is also deliberately UI-less. On Android TV any focusable view we rendered
|
|
14
|
+
* would become a D-pad stop in the host app's focus order, so the tool would
|
|
15
|
+
* alter the navigation it is supposed to be replaying.
|
|
16
|
+
*
|
|
17
|
+
* Safe to ship in non-TV apps: the native event emitter is never touched unless
|
|
18
|
+
* `Platform.isTV` is true, so the cost on a phone is one boolean.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as ReactNative from "react-native";
|
|
22
|
+
|
|
23
|
+
/** One captured remote event, as it goes over the wire to the dashboard. */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Ring buffer depth. Replay polls `getEventsSince` every few dozen ms with a
|
|
27
|
+
* ~2s budget per step, so 50 is many times more than any echo needs; it exists
|
|
28
|
+
* so a human scrubbing the echo strip has some scrollback.
|
|
29
|
+
*/
|
|
30
|
+
const MAX_EVENTS = 50;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Continuous gestures are not presses and are never recorded — the macro format
|
|
34
|
+
* is discrete keys. Dropping them here keeps them out of the ring buffer so
|
|
35
|
+
* they can't push real presses out of it during a replay.
|
|
36
|
+
*/
|
|
37
|
+
const IGNORED_EVENT_TYPES = new Set(["pan", "swipeUp", "swipeDown", "swipeLeft", "swipeRight"]);
|
|
38
|
+
/**
|
|
39
|
+
* `TVEventHandler` / `TVEventControl` only exist on the react-native-tvos fork.
|
|
40
|
+
* A namespace read (rather than a named import) keeps this module importable on
|
|
41
|
+
* stock react-native, where those exports are absent.
|
|
42
|
+
*/
|
|
43
|
+
const RN = ReactNative;
|
|
44
|
+
const isTV = RN.Platform?.isTV === true;
|
|
45
|
+
let events = [];
|
|
46
|
+
let seq = 0;
|
|
47
|
+
let armed = false;
|
|
48
|
+
let menuCaptureArmed = false;
|
|
49
|
+
let subscription = null;
|
|
50
|
+
const listeners = new Set();
|
|
51
|
+
function emitChange() {
|
|
52
|
+
listeners.forEach(listener => {
|
|
53
|
+
listener();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function handleEvent(raw) {
|
|
57
|
+
// Disarming removes the subscription, but don't take that on trust: a native
|
|
58
|
+
// emitter that delivered one more event after `remove()` would land a stray
|
|
59
|
+
// press in the buffer, and a stray press is exactly what a replay would
|
|
60
|
+
// mistake for a step's echo.
|
|
61
|
+
if (!armed) return;
|
|
62
|
+
const event = raw;
|
|
63
|
+
const eventType = event?.eventType;
|
|
64
|
+
if (typeof eventType !== "string") return;
|
|
65
|
+
if (IGNORED_EVENT_TYPES.has(eventType)) return;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Dedupe by ignoring key DOWN, never key UP. tvOS emits keyAction 1 (up)
|
|
69
|
+
* ONLY — taps reach the gesture recognizer at StateEnded — so filtering to
|
|
70
|
+
* "down" would swallow every tvOS event. Android's down-emit is
|
|
71
|
+
* feature-flagged off by default, but dropping it here is the correct
|
|
72
|
+
* invariant either way: one press must produce exactly one echo.
|
|
73
|
+
*/
|
|
74
|
+
if (event?.eventKeyAction === 0) return;
|
|
75
|
+
seq += 1;
|
|
76
|
+
const next = {
|
|
77
|
+
seq,
|
|
78
|
+
t: Date.now(),
|
|
79
|
+
eventType,
|
|
80
|
+
eventKeyAction: event?.eventKeyAction
|
|
81
|
+
};
|
|
82
|
+
events = events.length >= MAX_EVENTS ? [...events.slice(1), next] : [...events, next];
|
|
83
|
+
emitChange();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Start pushing remote events into the buffer. Idempotent. */
|
|
87
|
+
export function arm() {
|
|
88
|
+
if (armed) return;
|
|
89
|
+
armed = true;
|
|
90
|
+
if (isTV && RN.TVEventHandler && !subscription) {
|
|
91
|
+
subscription = RN.TVEventHandler.addListener(handleEvent);
|
|
92
|
+
}
|
|
93
|
+
emitChange();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Stop capturing and hand the Menu key back to the platform. Every teardown
|
|
98
|
+
* path funnels through here — leaving menu capture on would leave the app
|
|
99
|
+
* unable to navigate back, which is an App Store behavior change.
|
|
100
|
+
*/
|
|
101
|
+
export function disarm() {
|
|
102
|
+
if (subscription) {
|
|
103
|
+
subscription.remove();
|
|
104
|
+
subscription = null;
|
|
105
|
+
}
|
|
106
|
+
if (menuCaptureArmed) setMenuCapture(false);
|
|
107
|
+
if (!armed) return;
|
|
108
|
+
armed = false;
|
|
109
|
+
emitChange();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* tvOS: route the Menu key to JS so it can be recorded, instead of letting it
|
|
114
|
+
* pop the navigation stack (and background the app at the root). This changes
|
|
115
|
+
* how the app under test behaves, so it is opt-in per recording session and is
|
|
116
|
+
* always undone on disarm.
|
|
117
|
+
*/
|
|
118
|
+
export function setMenuCapture(on) {
|
|
119
|
+
if (!isTV || RN.Platform.OS !== "ios" || !RN.TVEventControl) return;
|
|
120
|
+
if (on === menuCaptureArmed) return;
|
|
121
|
+
if (on) {
|
|
122
|
+
RN.TVEventControl.enableTVMenuKey();
|
|
123
|
+
} else {
|
|
124
|
+
RN.TVEventControl.disableTVMenuKey();
|
|
125
|
+
}
|
|
126
|
+
menuCaptureArmed = on;
|
|
127
|
+
emitChange();
|
|
128
|
+
}
|
|
129
|
+
export function clear() {
|
|
130
|
+
if (events.length === 0) return;
|
|
131
|
+
events = [];
|
|
132
|
+
emitChange();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Events strictly newer than `sinceSeq` — the replay echo cursor. */
|
|
136
|
+
export function getEventsSince(sinceSeq) {
|
|
137
|
+
if (!Number.isFinite(sinceSeq)) return events.slice();
|
|
138
|
+
return events.filter(event => event.seq > sinceSeq);
|
|
139
|
+
}
|
|
140
|
+
export function getState() {
|
|
141
|
+
return {
|
|
142
|
+
supported: isTV,
|
|
143
|
+
platform: RN.Platform?.OS ?? "unknown",
|
|
144
|
+
captureArmed: armed,
|
|
145
|
+
listening: subscription !== null,
|
|
146
|
+
menuCaptureArmed,
|
|
147
|
+
seq,
|
|
148
|
+
recent: events.slice()
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export function subscribe(listener) {
|
|
152
|
+
listeners.add(listener);
|
|
153
|
+
return () => {
|
|
154
|
+
listeners.delete(listener);
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=tvEventStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["ReactNative","MAX_EVENTS","IGNORED_EVENT_TYPES","Set","RN","isTV","Platform","events","seq","armed","menuCaptureArmed","subscription","listeners","emitChange","forEach","listener","handleEvent","raw","event","eventType","has","eventKeyAction","next","t","Date","now","length","slice","arm","TVEventHandler","addListener","disarm","remove","setMenuCapture","on","OS","TVEventControl","enableTVMenuKey","disableTVMenuKey","clear","getEventsSince","sinceSeq","Number","isFinite","filter","getState","supported","platform","captureArmed","listening","recent","subscribe","add","delete"],"sourceRoot":"../../../src","sources":["store/tvEventStore.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* tvRemoteSyncAdapter — streams captured TV remote events to Buoy Desktop via
|
|
5
|
+
* @buoy-gg/external-sync.
|
|
6
|
+
*
|
|
7
|
+
* Structurally matches external-sync's `ToolSyncAdapter` WITHOUT importing the
|
|
8
|
+
* package (same as every other tool's adapter). Auto-discovery in @buoy-gg/core
|
|
9
|
+
* picks it up by export name and announces the "tv-remote" capability.
|
|
10
|
+
*
|
|
11
|
+
* `getEventsSince` exists because snapshots are throttled 200ms trailing-edge
|
|
12
|
+
* per tool, which is too coarse to time a replay step's echo. Replay polls this
|
|
13
|
+
* awaited action instead and gets the real latency.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { arm, clear, disarm, getEventsSince, getState, setMenuCapture, subscribe } from "../store/tvEventStore";
|
|
17
|
+
export const tvRemoteSyncAdapter = {
|
|
18
|
+
version: 1,
|
|
19
|
+
getSnapshot() {
|
|
20
|
+
return getState();
|
|
21
|
+
},
|
|
22
|
+
subscribe(onChange) {
|
|
23
|
+
return subscribe(onChange);
|
|
24
|
+
},
|
|
25
|
+
actions: {
|
|
26
|
+
arm() {
|
|
27
|
+
arm();
|
|
28
|
+
return getState();
|
|
29
|
+
},
|
|
30
|
+
disarm() {
|
|
31
|
+
disarm();
|
|
32
|
+
return getState();
|
|
33
|
+
},
|
|
34
|
+
/** Cursor-paged echo poll. `seq: 0` returns everything buffered. */
|
|
35
|
+
getEventsSince(params) {
|
|
36
|
+
const since = typeof params?.seq === "number" ? params.seq : 0;
|
|
37
|
+
const state = getState();
|
|
38
|
+
return {
|
|
39
|
+
seq: state.seq,
|
|
40
|
+
events: getEventsSince(since)
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
setMenuCapture(params) {
|
|
44
|
+
setMenuCapture(params?.on === true);
|
|
45
|
+
return getState();
|
|
46
|
+
},
|
|
47
|
+
clear() {
|
|
48
|
+
clear();
|
|
49
|
+
return getState();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=tvRemoteSyncAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["arm","clear","disarm","getEventsSince","getState","setMenuCapture","subscribe","tvRemoteSyncAdapter","version","getSnapshot","onChange","actions","params","since","seq","state","events","on"],"sourceRoot":"../../../src","sources":["sync/tvRemoteSyncAdapter.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SACEA,GAAG,EACHC,KAAK,EACLC,MAAM,EACNC,cAAc,EACdC,QAAQ,EACRC,cAAc,EACdC,SAAS,QAGJ,uBAAuB;AAE9B,OAAO,MAAMC,mBAAmB,GAAG;EACjCC,OAAO,EAAE,CAAC;EAEVC,WAAWA,CAAA,EAAkB;IAC3B,OAAOL,QAAQ,CAAC,CAAC;EACnB,CAAC;EAEDE,SAASA,CAACI,QAAoB,EAAc;IAC1C,OAAOJ,SAAS,CAACI,QAAQ,CAAC;EAC5B,CAAC;EAEDC,OAAO,EAAE;IACPX,GAAGA,CAAA,EAAkB;MACnBA,GAAG,CAAC,CAAC;MACL,OAAOI,QAAQ,CAAC,CAAC;IACnB,CAAC;IAEDF,MAAMA,CAAA,EAAkB;MACtBA,MAAM,CAAC,CAAC;MACR,OAAOE,QAAQ,CAAC,CAAC;IACnB,CAAC;IAED;IACAD,cAAcA,CAACS,MAAyB,EAGtC;MACA,MAAMC,KAAK,GAAG,OAAOD,MAAM,EAAEE,GAAG,KAAK,QAAQ,GAAGF,MAAM,CAACE,GAAG,GAAG,CAAC;MAC9D,MAAMC,KAAK,GAAGX,QAAQ,CAAC,CAAC;MACxB,OAAO;QAAEU,GAAG,EAAEC,KAAK,CAACD,GAAG;QAAEE,MAAM,EAAEb,cAAc,CAACU,KAAK;MAAE,CAAC;IAC1D,CAAC;IAEDR,cAAcA,CAACO,MAAyB,EAAiB;MACvDP,cAAc,CAACO,MAAM,EAAEK,EAAE,KAAK,IAAI,CAAC;MACnC,OAAOb,QAAQ,CAAC,CAAC;IACnB,CAAC;IAEDH,KAAKA,CAAA,EAAkB;MACrBA,KAAK,CAAC,CAAC;MACP,OAAOG,QAAQ,CAAC,CAAC;IACnB;EACF;AACF,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @buoy-gg/tv-remote
|
|
3
|
+
*
|
|
4
|
+
* The device-side half of Buoy's TV remote driver: it CAPTURES the D-pad /
|
|
5
|
+
* select / menu / play-pause events the app actually receives and streams them
|
|
6
|
+
* to Buoy Desktop, which is where presses are injected from (adb / idb) and
|
|
7
|
+
* where macros are recorded and replayed.
|
|
8
|
+
*
|
|
9
|
+
* This package renders nothing and injects nothing. See the README for why.
|
|
10
|
+
*/
|
|
11
|
+
export { tvRemoteSyncAdapter } from "./sync/tvRemoteSyncAdapter";
|
|
12
|
+
export { arm, disarm, clear, setMenuCapture, getEventsSince, getState, subscribe, } from "./store/tvEventStore";
|
|
13
|
+
export type { TvRemoteEvent, TvRemoteState } from "./store/tvEventStore";
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAKjE,OAAO,EACL,GAAG,EACH,MAAM,EACN,KAAK,EACL,cAAc,EACd,cAAc,EACd,QAAQ,EACR,SAAS,GACV,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tvEventStore — the whole device-side half of the TV remote tool.
|
|
3
|
+
*
|
|
4
|
+
* CONTRACT: this store only ever OBSERVES. It never injects, synthesizes, or
|
|
5
|
+
* fakes a remote press. Presses come from the host (Buoy Desktop shells out to
|
|
6
|
+
* `adb shell input keyevent` / `idb ui key`), because those go through the real
|
|
7
|
+
* platform focus engine; anything we could fake in-process would move focus
|
|
8
|
+
* without firing events, or fire events without moving focus. See
|
|
9
|
+
* TV_REMOTE_DRIVER_PLAN.md §2.
|
|
10
|
+
*
|
|
11
|
+
* It is also deliberately UI-less. On Android TV any focusable view we rendered
|
|
12
|
+
* would become a D-pad stop in the host app's focus order, so the tool would
|
|
13
|
+
* alter the navigation it is supposed to be replaying.
|
|
14
|
+
*
|
|
15
|
+
* Safe to ship in non-TV apps: the native event emitter is never touched unless
|
|
16
|
+
* `Platform.isTV` is true, so the cost on a phone is one boolean.
|
|
17
|
+
*/
|
|
18
|
+
/** One captured remote event, as it goes over the wire to the dashboard. */
|
|
19
|
+
export interface TvRemoteEvent {
|
|
20
|
+
/** Monotonic counter — the cursor `getEventsSince` pages from. */
|
|
21
|
+
seq: number;
|
|
22
|
+
/** Capture time (ms epoch). */
|
|
23
|
+
t: number;
|
|
24
|
+
/** Raw RN event type: "up" | "down" | "left" | "right" | "select" | "playPause" | "menu" | "longSelect" | … */
|
|
25
|
+
eventType: string;
|
|
26
|
+
/** 0 = key down, 1 = key up. tvOS only ever emits 1. */
|
|
27
|
+
eventKeyAction?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface TvRemoteState {
|
|
30
|
+
/** False on phones/tablets/web — the tool renders as unavailable. */
|
|
31
|
+
supported: boolean;
|
|
32
|
+
platform: string;
|
|
33
|
+
captureArmed: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Whether a native listener is actually attached. Distinct from
|
|
36
|
+
* `captureArmed` on purpose: if the RN build has no TVEventHandler, arming
|
|
37
|
+
* would otherwise look successful while nothing was ever captured, and the
|
|
38
|
+
* desktop would report every replay step as swallowed input.
|
|
39
|
+
*/
|
|
40
|
+
listening: boolean;
|
|
41
|
+
/** tvOS only: Menu is being routed to JS instead of popping the stack. */
|
|
42
|
+
menuCaptureArmed: boolean;
|
|
43
|
+
seq: number;
|
|
44
|
+
recent: TvRemoteEvent[];
|
|
45
|
+
}
|
|
46
|
+
/** Start pushing remote events into the buffer. Idempotent. */
|
|
47
|
+
export declare function arm(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Stop capturing and hand the Menu key back to the platform. Every teardown
|
|
50
|
+
* path funnels through here — leaving menu capture on would leave the app
|
|
51
|
+
* unable to navigate back, which is an App Store behavior change.
|
|
52
|
+
*/
|
|
53
|
+
export declare function disarm(): void;
|
|
54
|
+
/**
|
|
55
|
+
* tvOS: route the Menu key to JS so it can be recorded, instead of letting it
|
|
56
|
+
* pop the navigation stack (and background the app at the root). This changes
|
|
57
|
+
* how the app under test behaves, so it is opt-in per recording session and is
|
|
58
|
+
* always undone on disarm.
|
|
59
|
+
*/
|
|
60
|
+
export declare function setMenuCapture(on: boolean): void;
|
|
61
|
+
export declare function clear(): void;
|
|
62
|
+
/** Events strictly newer than `sinceSeq` — the replay echo cursor. */
|
|
63
|
+
export declare function getEventsSince(sinceSeq: number): TvRemoteEvent[];
|
|
64
|
+
export declare function getState(): TvRemoteState;
|
|
65
|
+
export declare function subscribe(listener: () => void): () => void;
|
|
66
|
+
//# sourceMappingURL=tvEventStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tvEventStore.d.ts","sourceRoot":"","sources":["../../../src/store/tvEventStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,+BAA+B;IAC/B,CAAC,EAAE,MAAM,CAAC;IACV,+GAA+G;IAC/G,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB;;;;;OAKG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,0EAA0E;IAC1E,gBAAgB,EAAE,OAAO,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAwFD,+DAA+D;AAC/D,wBAAgB,GAAG,IAAI,IAAI,CAO1B;AAED;;;;GAIG;AACH,wBAAgB,MAAM,IAAI,IAAI,CAS7B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAUhD;AAED,wBAAgB,KAAK,IAAI,IAAI,CAI5B;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CAGhE;AAED,wBAAgB,QAAQ,IAAI,aAAa,CAUxC;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAK1D"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tvRemoteSyncAdapter — streams captured TV remote events to Buoy Desktop via
|
|
3
|
+
* @buoy-gg/external-sync.
|
|
4
|
+
*
|
|
5
|
+
* Structurally matches external-sync's `ToolSyncAdapter` WITHOUT importing the
|
|
6
|
+
* package (same as every other tool's adapter). Auto-discovery in @buoy-gg/core
|
|
7
|
+
* picks it up by export name and announces the "tv-remote" capability.
|
|
8
|
+
*
|
|
9
|
+
* `getEventsSince` exists because snapshots are throttled 200ms trailing-edge
|
|
10
|
+
* per tool, which is too coarse to time a replay step's echo. Replay polls this
|
|
11
|
+
* awaited action instead and gets the real latency.
|
|
12
|
+
*/
|
|
13
|
+
import { type TvRemoteEvent, type TvRemoteState } from "../store/tvEventStore";
|
|
14
|
+
export declare const tvRemoteSyncAdapter: {
|
|
15
|
+
version: number;
|
|
16
|
+
getSnapshot(): TvRemoteState;
|
|
17
|
+
subscribe(onChange: () => void): () => void;
|
|
18
|
+
actions: {
|
|
19
|
+
arm(): TvRemoteState;
|
|
20
|
+
disarm(): TvRemoteState;
|
|
21
|
+
/** Cursor-paged echo poll. `seq: 0` returns everything buffered. */
|
|
22
|
+
getEventsSince(params?: {
|
|
23
|
+
seq?: number;
|
|
24
|
+
}): {
|
|
25
|
+
seq: number;
|
|
26
|
+
events: TvRemoteEvent[];
|
|
27
|
+
};
|
|
28
|
+
setMenuCapture(params?: {
|
|
29
|
+
on?: boolean;
|
|
30
|
+
}): TvRemoteState;
|
|
31
|
+
clear(): TvRemoteState;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=tvRemoteSyncAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tvRemoteSyncAdapter.d.ts","sourceRoot":"","sources":["../../../src/sync/tvRemoteSyncAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAQL,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,mBAAmB;;mBAGf,aAAa;wBAIR,MAAM,IAAI,GAAG,MAAM,IAAI;;eAKlC,aAAa;kBAKV,aAAa;QAKvB,oEAAoE;gCAC5C;YAAE,GAAG,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG;YACzC,GAAG,EAAE,MAAM,CAAC;YACZ,MAAM,EAAE,aAAa,EAAE,CAAC;SACzB;gCAMuB;YAAE,EAAE,CAAC,EAAE,OAAO,CAAA;SAAE,GAAG,aAAa;iBAK/C,aAAa;;CAKzB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@buoy-gg/tv-remote",
|
|
3
|
+
"version": "7.0.31",
|
|
4
|
+
"description": "TV remote capture for React Native TV apps — records the D-pad/select/menu events the app actually receives, so Buoy Desktop can drive and replay real remote presses. Part of Buoy devtools.",
|
|
5
|
+
"main": "lib/commonjs/index.js",
|
|
6
|
+
"module": "lib/module/index.js",
|
|
7
|
+
"types": "lib/typescript/index.d.ts",
|
|
8
|
+
"react-native": "lib/module/index.js",
|
|
9
|
+
"source": "src/index.tsx",
|
|
10
|
+
"files": [
|
|
11
|
+
"lib"
|
|
12
|
+
],
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"react": "*",
|
|
16
|
+
"react-native": "*"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/react": "^19.1.0",
|
|
20
|
+
"@types/react-native": "^0.73.0",
|
|
21
|
+
"typescript": "~5.8.3"
|
|
22
|
+
},
|
|
23
|
+
"react-native-builder-bob": {
|
|
24
|
+
"source": "src",
|
|
25
|
+
"output": "lib",
|
|
26
|
+
"targets": [
|
|
27
|
+
"commonjs",
|
|
28
|
+
"module",
|
|
29
|
+
[
|
|
30
|
+
"typescript",
|
|
31
|
+
{
|
|
32
|
+
"project": "tsconfig.build.json"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/Buoy-gg/buoy.git",
|
|
40
|
+
"directory": "packages/tv-remote"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/LovesWorking/react-native-buoy/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://buoy.gg/buoy/latest/docs/tools/tv-remote",
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public",
|
|
48
|
+
"tag": "latest"
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"react-native",
|
|
52
|
+
"expo",
|
|
53
|
+
"devtools",
|
|
54
|
+
"debugging",
|
|
55
|
+
"react-native-devtools",
|
|
56
|
+
"tvos",
|
|
57
|
+
"android-tv",
|
|
58
|
+
"react-native-tvos",
|
|
59
|
+
"remote",
|
|
60
|
+
"d-pad",
|
|
61
|
+
"focus"
|
|
62
|
+
],
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "bob build",
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
66
|
+
"clean": "rimraf lib",
|
|
67
|
+
"test": "pnpm run typecheck",
|
|
68
|
+
"postinstall": "echo \"Run pnpm build to compile this package\""
|
|
69
|
+
}
|
|
70
|
+
}
|