mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 19:27:15 +01:00
workout poke moves to tcp/8774: 8773 is book-guard's desktop wrapper, whose window the poke server had been answering since 2026-10-09 (phone app rebuilt, selftest default, contract doc)
Some checks failed
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (flutter, flutter-analyze, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint,flutter-analyze) (push) Waiting to run
Tests / test (push) Waiting to run
Tests / lock-popup-safety (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (0) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (1) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (2) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (3) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (4) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (5) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (6) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (7) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (8) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (9) (push) Waiting to run
Tests / flutter-coverage (push) Blocked by required conditions
Tests / web-coverage (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
release-apk / release (APK) (push) Has been cancelled
Some checks failed
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (flutter, flutter-analyze, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint,flutter-analyze) (push) Waiting to run
Tests / test (push) Waiting to run
Tests / lock-popup-safety (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (0) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (1) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (2) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (3) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (4) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (5) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (6) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (7) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (8) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (9) (push) Waiting to run
Tests / flutter-coverage (push) Blocked by required conditions
Tests / web-coverage (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
release-apk / release (APK) (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KrBEzjPRdx5oyp8NtTLW5F
This commit is contained in:
parent
e1f1c668a8
commit
b37b7559d8
@ -8,9 +8,10 @@ session that arrives both ways is credited once.
|
|||||||
|
|
||||||
## Transport
|
## Transport
|
||||||
|
|
||||||
- `POST http://<pc-host>:8773/v1/workout`, `Content-Type: application/json`.
|
- `POST http://<pc-host>:8774/v1/workout`, `Content-Type: application/json`.
|
||||||
|
(8773 until 2026-10-10: that is book-guard's desktop wrapper's port.)
|
||||||
- `<pc-host>` is a phone setting, default `192.168.1.43` (static on the PC).
|
- `<pc-host>` is a phone setting, default `192.168.1.43` (static on the PC).
|
||||||
LAN only: the PC firewall accepts tcp/8773 from `192.168.1.0/24` and nothing
|
LAN only: the PC firewall accepts tcp/8774 from `192.168.1.0/24` and nothing
|
||||||
else. No WireGuard. Off the home LAN the poke fails fast and the regular
|
else. No WireGuard. Off the home LAN the poke fails fast and the regular
|
||||||
sync is the only path.
|
sync is the only path.
|
||||||
- Header `X-Poke-Signature: <hex HMAC-SHA256(key, raw body bytes)>`.
|
- Header `X-Poke-Signature: <hex HMAC-SHA256(key, raw body bytes)>`.
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"""The resident LAN listener that credits a phone workout within a second.
|
"""The resident LAN listener that credits a phone workout within a second.
|
||||||
|
|
||||||
``workout-poke.service`` runs ``python3 -m screen_locker.poke_server``: a
|
``workout-poke.service`` runs ``python3 -m screen_locker.poke_server``: a
|
||||||
stdlib ``ThreadingHTTPServer`` on ``0.0.0.0:8773`` (the firewall admits only
|
stdlib ``ThreadingHTTPServer`` on ``0.0.0.0:8774`` (the firewall admits only
|
||||||
192.168.1.0/24) answering ``POST /v1/workout`` per
|
192.168.1.0/24) answering ``POST /v1/workout`` per
|
||||||
``docs/DOCS-workout-poke-contract.md``. Every import -- the locker, the
|
``docs/DOCS-workout-poke-contract.md``. Every import -- the locker, the
|
||||||
earned-time registry, gatelock -- is paid once at startup, so a request costs
|
earned-time registry, gatelock -- is paid once at startup, so a request costs
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
What ``workout-poke.service`` runs: load the shared key, warm the imports and
|
What ``workout-poke.service`` runs: load the shared key, warm the imports and
|
||||||
the budget sum, start the Firebase session stream (off-LAN credit,
|
the budget sum, start the Firebase session stream (off-LAN credit,
|
||||||
:mod:`screen_locker._session_stream`) on a daemon thread, then serve ``POST
|
:mod:`screen_locker._session_stream`) on a daemon thread, then serve ``POST
|
||||||
/v1/workout`` on port 8773 for good. The request handling lives in
|
/v1/workout`` on port 8774 for good. The request handling lives in
|
||||||
:mod:`screen_locker._poke_server`; the contract is
|
:mod:`screen_locker._poke_server`; the contract is
|
||||||
``docs/DOCS-workout-poke-contract.md``.
|
``docs/DOCS-workout-poke-contract.md``.
|
||||||
|
|
||||||
@ -29,7 +29,8 @@ __all__ = ["DEFAULT_KEY_FILE", "DEFAULT_PORT", "load_key", "main"]
|
|||||||
|
|
||||||
_logger: Final = logging.getLogger(__name__)
|
_logger: Final = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_PORT: Final = 8773
|
DEFAULT_PORT: Final = 8774
|
||||||
|
"""Not 8773: that is book-guard's desktop wrapper (loopback, older)."""
|
||||||
DEFAULT_KEY_FILE: Final = Path.home() / ".config" / "workout_poke" / "key"
|
DEFAULT_KEY_FILE: Final = Path.home() / ".config" / "workout_poke" / "key"
|
||||||
_KEYGEN_HINT: Final = "run scripts/workout_poke_keygen.sh"
|
_KEYGEN_HINT: Final = "run scripts/workout_poke_keygen.sh"
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ def load_key(path: Path) -> bytes:
|
|||||||
|
|
||||||
def _parse_args(argv: list[str] | None) -> argparse.Namespace:
|
def _parse_args(argv: list[str] | None) -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||||
# "" binds every interface; the firewall admits tcp/8773 from the LAN only.
|
# "" binds every interface; the firewall admits tcp/8774 from the LAN only.
|
||||||
parser.add_argument("--bind", default="", help="address (default: all)")
|
parser.add_argument("--bind", default="", help="address (default: all)")
|
||||||
parser.add_argument("--port", type=int, default=DEFAULT_PORT)
|
parser.add_argument("--port", type=int, default=DEFAULT_PORT)
|
||||||
parser.add_argument("--key-file", type=Path, default=DEFAULT_KEY_FILE)
|
parser.add_argument("--key-file", type=Path, default=DEFAULT_KEY_FILE)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Drive the running workout poke listener from this PC, signed with the real key.
|
"""Drive the running workout poke listener from this PC, signed with the real key.
|
||||||
|
|
||||||
Each case POSTs to ``http://127.0.0.1:8773/v1/workout`` exactly as the phone
|
Each case POSTs to ``http://127.0.0.1:8774/v1/workout`` exactly as the phone
|
||||||
would (``docs/DOCS-workout-poke-contract.md``) and prints the status and reply:
|
would (``docs/DOCS-workout-poke-contract.md``) and prints the status and reply:
|
||||||
|
|
||||||
* ``sandbox`` -- a signed sandbox request: 200, ok, credited=false;
|
* ``sandbox`` -- a signed sandbox request: 200, ok, credited=false;
|
||||||
@ -109,7 +109,7 @@ def main() -> int:
|
|||||||
"case", choices=["sandbox", "badsig", "replay", "stale", "duplicate", "latency"]
|
"case", choices=["sandbox", "badsig", "replay", "stale", "duplicate", "latency"]
|
||||||
)
|
)
|
||||||
parser.add_argument("--count", type=int, default=20)
|
parser.add_argument("--count", type=int, default=20)
|
||||||
parser.add_argument("--port", type=int, default=8773)
|
parser.add_argument("--port", type=int, default=8774)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
key = bytes.fromhex(KEY_FILE.read_text(encoding="ascii").strip())
|
key = bytes.fromhex(KEY_FILE.read_text(encoding="ascii").strip())
|
||||||
if args.case == "sandbox":
|
if args.case == "sandbox":
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import 'package:workout_app/services/pc_poke_wire.dart';
|
|||||||
/// guard per service. A refused connection surfaces as a normal sync failure
|
/// guard per service. A refused connection surfaces as a normal sync failure
|
||||||
/// in the UI, which is the honest answer: the sandbox is offline by design.
|
/// in the UI, which is the honest answer: the sandbox is offline by design.
|
||||||
///
|
///
|
||||||
/// The single exception is `http://<pokeHost>:8773/v1/workout`, so a sandbox
|
/// The single exception is `http://<pokeHost>:8774/v1/workout`, so a sandbox
|
||||||
/// workout can time the PC round trip. The PC verifies the signed
|
/// workout can time the PC round trip. The PC verifies the signed
|
||||||
/// `"sandbox": true` and never writes or credits it. Firebase and GitHub are
|
/// `"sandbox": true` and never writes or credits it. Firebase and GitHub are
|
||||||
/// https on 443 and can never match.
|
/// https on 443 and can never match.
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import 'package:workout_app/services/workout_sync_service.dart';
|
|||||||
|
|
||||||
export 'package:workout_app/services/pc_poke_result.dart';
|
export 'package:workout_app/services/pc_poke_result.dart';
|
||||||
|
|
||||||
/// Signs and POSTs one session to `http://<pc-host>:8773/v1/workout`.
|
/// Signs and POSTs one session to `http://<pc-host>:8774/v1/workout`.
|
||||||
///
|
///
|
||||||
/// Runs beside [WorkoutSyncService.push], never instead of it: the PC dedups
|
/// Runs beside [WorkoutSyncService.push], never instead of it: the PC dedups
|
||||||
/// by record id, so whichever path lands first credits and the other is a
|
/// by record id, so whichever path lands first credits and the other is a
|
||||||
|
|||||||
@ -11,8 +11,9 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
/// TCP port the PC's poke listener binds (8771 belongs to another service).
|
/// TCP port the PC's poke listener binds (8771 and 8773 -- book-guard's
|
||||||
const int kPcPokePort = 8773;
|
/// desktop wrapper -- belong to other services).
|
||||||
|
const int kPcPokePort = 8774;
|
||||||
|
|
||||||
/// Request path on the PC.
|
/// Request path on the PC.
|
||||||
const String kPcPokePath = '/v1/workout';
|
const String kPcPokePath = '/v1/workout';
|
||||||
|
|||||||
@ -36,7 +36,7 @@ void main() {
|
|||||||
expect(SandboxHttpOverrides.allows(poke), isFalse);
|
expect(SandboxHttpOverrides.allows(poke), isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('allows exactly http + configured host + 8773 + /v1/workout', () {
|
test('allows exactly http + configured host + 8774 + /v1/workout', () {
|
||||||
SandboxHttpOverrides.pokeHost = host;
|
SandboxHttpOverrides.pokeHost = host;
|
||||||
expect(SandboxHttpOverrides.allows(poke), isTrue);
|
expect(SandboxHttpOverrides.allows(poke), isTrue);
|
||||||
for (final other in [
|
for (final other in [
|
||||||
@ -87,12 +87,12 @@ void main() {
|
|||||||
} on SocketException catch (error) {
|
} on SocketException catch (error) {
|
||||||
outcome = error;
|
outcome = error;
|
||||||
}
|
}
|
||||||
// No listener on this host's 8773 is fine -- what matters is that a
|
// No listener on this host's poke port is fine -- what matters is that a
|
||||||
// refusal, if any, did not come from the sandbox.
|
// refusal, if any, did not come from the sandbox.
|
||||||
if (outcome is SocketException) {
|
if (outcome is SocketException) {
|
||||||
expect(outcome.message, isNot(contains('sandbox')));
|
expect(outcome.message, isNot(contains('sandbox')));
|
||||||
}
|
}
|
||||||
expect(printed.first, contains('allowing the PC poke to $host:8773'));
|
expect(printed.first, contains('allowing the PC poke to $host:$kPcPokePort'));
|
||||||
client.close(force: true);
|
client.close(force: true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Credit a phone workout the second it is poked over the LAN (tcp/8773)
|
Description=Credit a phone workout the second it is poked over the LAN (tcp/8774)
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
# Resident stdlib HTTP server (screen_locker/_poke_server.py): the phone POSTs
|
# Resident stdlib HTTP server (screen_locker/_poke_server.py): the phone POSTs
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user