ffl-python 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl

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.
ffl/bin/ffl.com CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: ffl/bin/ffl.com
3
- size: 51721850 bytes
4
- sha256: 0c614cf8b87a1e73fde86b04911a246dd3ca9420819cfe8a05e8edc03146737b
3
+ size: 48780381 bytes
4
+ sha256: 15cab56cc9f9a86fb5ae7f521a85667b91cb8bbfc9704c9105d1a79634e24396
ffl/events.py CHANGED
@@ -31,11 +31,17 @@ from urllib.error import URLError
31
31
  from urllib.request import Request, urlopen
32
32
 
33
33
 
34
+ # FFL posts one integrated /transfer/* event for every HTTP, WebRTC, and
35
+ # direct P2P transfer. Semantic listeners bind to those names, plus
36
+ # /share/available. Transport-specific events stay on the raw channel, so one
37
+ # transfer notifies each semantic listener once.
38
+ _HOOK_OK_BODY = b'{}'
34
39
  _SEMANTIC_EVENT_NAMES = {
35
- '/hook/server/endpoints/register': 'ready',
36
- '/hook/transfer/progress': 'progress',
37
- '/hook/transfer/transport': 'transport',
38
- '/hook/transfer/complete': 'completed',
40
+ '/share/available': 'ready',
41
+ '/transfer/create': 'started',
42
+ '/transfer/progress': 'progress',
43
+ '/transfer/complete': 'completed',
44
+ '/transfer/fail': 'failed',
39
45
  }
40
46
 
41
47
 
@@ -176,12 +182,15 @@ class FFLHookEventChannel:
176
182
  try:
177
183
  channel._publish(body)
178
184
  published = True
179
- self.send_response(204)
185
+ self.send_response(200)
186
+ self.send_header('Content-Type', 'application/json')
187
+ self.send_header('Content-Length', str(len(_HOOK_OK_BODY)))
188
+ self.end_headers()
189
+ self.wfile.write(_HOOK_OK_BODY)
180
190
  except Exception as error:
181
191
  channel._record_error(error)
182
192
  self.send_response(500)
183
-
184
- self.end_headers()
193
+ self.end_headers()
185
194
 
186
195
  if published and channel._forward_url is not None:
187
196
  channel._forward_async(body)
ffl/models.py CHANGED
@@ -34,6 +34,8 @@ class TransferMode(IntEnum):
34
34
  WEBRTC_P2P = 2
35
35
  HTTP_FALLBACK = 3
36
36
  HTTP_DIRECT = 4
37
+ P2P_TCP = 5
38
+ P2P_QUIC = 6
37
39
 
38
40
 
39
41
  class ShareSession:
ffl/parsing.py CHANGED
@@ -42,12 +42,12 @@ class FFLResultParser:
42
42
 
43
43
  @classmethod
44
44
  def _detect_transfer_mode(cls, output: str) -> TransferMode:
45
- if (
46
- 'P2P direct' in output
47
- or 'P2P TCP' in output
48
- or 'WebRTC P2P' in output
49
- ):
45
+ if 'P2P direct' in output or 'WebRTC P2P' in output:
50
46
  return TransferMode.WEBRTC_P2P
47
+ if 'P2P UDP/QUIC' in output:
48
+ return TransferMode.P2P_QUIC
49
+ if 'P2P TCP' in output:
50
+ return TransferMode.P2P_TCP
51
51
  if 'HTTP fallback' in output:
52
52
  return TransferMode.HTTP_FALLBACK
53
53
  if (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ffl-python
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: Python binding for FastFileLink, backed by the portable ffl.com APE
5
5
  Author: ffl-python contributors
6
6
  License-Expression: Apache-2.0
@@ -20,13 +20,24 @@ Dynamic: license-file
20
20
 
21
21
  # ffl-python
22
22
 
23
- Python binding for FastFileLink. The package bundles the portable `ffl.com` APE and
24
- runs it behind a Python API; callers do not need to locate or install a separate FFL
25
- binary.
23
+ `ffl-python` is the Python binding for the [FastFileLink](https://github.com/nuwainfo/ffl)
24
+ CLI (FFL), which turns a file, folder, or stream into a browser-ready HTTPS link so the
25
+ recipient can download it without installing anything. It prefers a direct QUIC/WebRTC P2P
26
+ connection and falls back to a relayed/tunneled HTTPS link, with optional end-to-end
27
+ encryption. See the FFL repository for the full protocol and CLI details.
26
28
 
27
- The low-level command grammar is generated by APEBind from `binding/ffl.apebind.yaml`.
28
- The public API in `src/ffl/client.py` is intentionally handwritten so FFL-specific
29
- library semantics stay explicit and reviewable.
29
+ The package bundles the portable `ffl.com` APE and runs it behind a Python API,
30
+ powered by [APEBind](https://github.com/nuwainfo/apebind),
31
+ so callers do not need to locate or install a separate FFL
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install ffl-python
37
+ ```
38
+
39
+ This installs the `ffl` package with the bundled `ffl.com` APE included -- no separate
40
+ FFL install or PATH setup required.
30
41
 
31
42
  ## Development
32
43
 
@@ -42,14 +53,6 @@ python -m venv .venv
42
53
  # or: ./scripts/build.sh # Linux/macOS
43
54
  ```
44
55
 
45
- The test directly imports `ffl`, shares a binary file, and downloads it through the
46
- bundled `ffl.com` APE. It requires working network access to FastFileLink. A wheel is a
47
- build artifact, not the source of truth.
48
-
49
- The integration suite covers ordinary, E2EE, relay, pickup-code, and public-key
50
- transfers; text, bytes, folders, multiple files, QR images, hooks, explicit ports, and
51
- session shutdown, and Basic Auth download.
52
-
53
56
  ## Share
54
57
 
55
58
  ```python
@@ -77,10 +80,6 @@ with ffl.share_text("hello", name="hello.txt") as session:
77
80
  print(session.link)
78
81
  ```
79
82
 
80
- Optional-value FFL flags use natural Python values. For example, `receipt=True` emits
81
- `--receipt` without a value, while `receipt="me@example.com"` emits the flag with the
82
- address.
83
-
84
83
  ### Stream a source without a temporary file
85
84
 
86
85
  `share_stream()` passes a binary file-like object directly to FFL stdin. It is useful
@@ -93,6 +92,25 @@ with open("backup.tar", "rb") as source:
93
92
  print(session.link)
94
93
  ```
95
94
 
95
+ ### Events
96
+
97
+ `session.on(name, listener)` follows FFL's integrated events:
98
+
99
+ | Semantic name | FFL event |
100
+ | --- | --- |
101
+ | `ready` | `/share/available` |
102
+ | `started` | `/transfer/create` |
103
+ | `progress` | `/transfer/progress` |
104
+ | `completed` | `/transfer/complete` |
105
+ | `failed` | `/transfer/fail` |
106
+
107
+ HTTP, WebRTC, and direct P2P also emit their own events, such as
108
+ `/download/complete`. Those stay on `session.on_raw()`. `completed` follows
109
+ `/transfer/complete`, so one transfer notifies that listener once. Each accepted
110
+ hook POST is answered with HTTP 200 and `{}`.
111
+
112
+ The bundled `ffl.com` emits these names.
113
+
96
114
  ## Download
97
115
 
98
116
  ```python
@@ -154,9 +172,6 @@ print(result.public_key_path)
154
172
  print(result.private_key_path)
155
173
  ```
156
174
 
157
- `keygen()` has a 60-second process timeout and verifies that the key paths reported by
158
- FFL exist.
159
-
160
175
  ## Version and raw access
161
176
 
162
177
  ```python
@@ -167,44 +182,37 @@ result = ffl.raw(["download", "--help"])
167
182
  `raw()` is the escape hatch for new FFL options or commands that the semantic API has
168
183
  not adopted yet.
169
184
 
170
- ## WSL2
171
-
172
- If an operation fails with `TLSError([0x6300])`, WSL may be routing the bundled
173
- `.com` APE through Windows interop. Run the following in WSL, then restart the WSL
174
- session:
175
-
176
- ```bash
177
- sudo sh -c 'echo -1 > /proc/sys/fs/binfmt_misc/WSLInterop'
178
- ```
179
-
180
- ## Updating the FFL binding
181
-
182
- Install APEBind from its source project. When adopting a new `ffl.com`, first inspect the
183
- CLI into a raw discovery file:
184
-
185
- ```bash
186
- ./scripts/inspect.sh /path/to/ffl.com
187
- ```
188
-
189
- This writes `binding/ffl.discovered.apebind.yaml` and automatically applies the hidden
190
- command seeds in `binding/ffl.commands.yaml`. Review the discovered-schema diff, then
191
- manually merge CLI changes into the canonical semantic contract
192
- `binding/ffl.apebind.yaml`. Automatic inspection never overwrites the semantic contract.
193
-
194
- After reviewing the semantic schema, regenerate the low-level binding:
195
-
196
- ```bash
197
- ./scripts/regenerate.sh /path/to/ffl.com
198
- # Windows: .\scripts\regenerate.ps1 D:\ffl.com
199
- ```
200
-
201
- The script invokes APEBind into an isolated temporary project and replaces only these
202
- machine-owned files:
203
-
204
- - `src/ffl/_generated.py`
205
- - `src/ffl/_runtime.py`
206
- - `src/ffl/bin/ffl.com`
207
- - `src/ffl/py.typed`
208
-
209
- It deliberately does not overwrite `client.py`, `models.py`, parsing logic, tests, or the
210
- semantic schema.
185
+ ## Compared to magic-wormhole
186
+
187
+ [magic-wormhole](https://github.com/magic-wormhole/magic-wormhole) is the other Python
188
+ tool commonly reached for to move a file between two machines. Both are ad hoc,
189
+ non-account-based transfers you can drive from Python, but they differ in shape:
190
+
191
+ - **Binding vs. native library.** `ffl-python` is a subprocess wrapper around the
192
+ separate `ffl.com` CLI binary -- WebRTC/QUIC, NAT traversal, and relay/tunnel fallback
193
+ all happen in that external process. `wormhole` is a native, in-process Python package
194
+ (Twisted-based); no external binary is involved. In practice, though, wormhole's own
195
+ *file*-transfer path is also driven through its CLI machinery rather than a stable
196
+ public library call -- `wormhole.create()` covers generic message exchange, not file
197
+ transfer directly.
198
+ - **Recipient experience.** An FFL share is an HTTPS link: the recipient opens it in a
199
+ browser and downloads, no install required. A wormhole transfer is a short code
200
+ (e.g. `7-crossbow-clockwork`) exchanged out-of-band; the recipient needs the
201
+ `wormhole` CLI installed to redeem it.
202
+ - **Transport.** FFL tries a direct WebRTC/QUIC P2P connection first, falls back to a
203
+ plain P2P TCP connection, and falls back again to a relayed/tunneled HTTPS link if no
204
+ P2P path is reachable at all. wormhole's own transit protocol does direct TCP with a
205
+ relay fallback, with the connection authenticated by a SPAKE2 PAKE key derived from the
206
+ code.
207
+ - **Security default.** wormhole is end-to-end encrypted on every transfer by
208
+ construction of the code exchange. FFL's end-to-end encryption is opt-in (`--e2ee`);
209
+ without it, data in transit is only as protected as the HTTPS connection to FFL's
210
+ relay/tunnel infrastructure.
211
+ - **Feature surface.** FFL adds application-layer conveniences wormhole doesn't have: a
212
+ secondary pickup-code/public-key recipient check layered on top of the link,
213
+ receipt-confirmation emails, a pluggable choice of tunnel backend when P2P isn't
214
+ reachable (built-in `default`, plus Cloudflare, ngrok, Localtunnel, Loophole, Dev
215
+ Tunnel, Bore, or a self-hosted sish tunnel via `--preferred-tunnel`, with custom
216
+ tunnels configurable in `~/.fastfilelink/tunnels.json`), and general SOCKS5/HTTP
217
+ proxy configuration (wormhole only knows how to route through Tor, via `--tor`).
218
+
@@ -0,0 +1,15 @@
1
+ ffl/__init__.py,sha256=GQ8-MHqcrG1mT0Jkqdo1ifZdMp9j7YVOvsk8QP6o7so,1887
2
+ ffl/_generated.py,sha256=y_o02NvGXOciWYmBzCjsh3Ro-ObtJQunWYAomPmh6ec,44072
3
+ ffl/_runtime.py,sha256=hb4lQYWY51iupkBIXZk5Ok1aV2PJv6KoEdJKmcjmhrU,34800
4
+ ffl/client.py,sha256=ZmpMNr0zDu9djxshdtkrkcwGTaNVZbdgxPrfw_1WuI8,10649
5
+ ffl/errors.py,sha256=RarGTbHUzMYJ8ifYcwbQh3guNHHH8sPKPuxXgceLvtY,965
6
+ ffl/events.py,sha256=WKX_2NaRrmVX9buxsR2JbT_m0deXJ6l54JJUN8p-i4A,9777
7
+ ffl/models.py,sha256=t9ty_CSdBXJB0nfqH33gKc0OY1jYq-Ga8mLsiI7FK0Q,8008
8
+ ffl/parsing.py,sha256=rc5BLmq5gybQCgczJHeCYyNrPo57WmecUI3OIHP8_Z0,4009
9
+ ffl/py.typed,sha256=qzxecjP9nBZND4fnAxWaIqIc3-9Z7bDos33dUet8d9g,46
10
+ ffl/bin/ffl.com,sha256=Fcq1bMn5qG-1rn9SGoVme5HLi7_JcEyRBdGnljTiQ5Y,48780381
11
+ ffl_python-0.1.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
12
+ ffl_python-0.1.5.dist-info/METADATA,sha256=OU5PAMc43CzaRdXarUCGbmIusOAiNsYqVL6HFhiA7Kg,7964
13
+ ffl_python-0.1.5.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
14
+ ffl_python-0.1.5.dist-info/top_level.txt,sha256=I3tRB8VSu6Bs5i_luGnIYh-dBWVtyqtLjpAfJqGtFMA,4
15
+ ffl_python-0.1.5.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- ffl/__init__.py,sha256=GQ8-MHqcrG1mT0Jkqdo1ifZdMp9j7YVOvsk8QP6o7so,1887
2
- ffl/_generated.py,sha256=y_o02NvGXOciWYmBzCjsh3Ro-ObtJQunWYAomPmh6ec,44072
3
- ffl/_runtime.py,sha256=hb4lQYWY51iupkBIXZk5Ok1aV2PJv6KoEdJKmcjmhrU,34800
4
- ffl/client.py,sha256=ZmpMNr0zDu9djxshdtkrkcwGTaNVZbdgxPrfw_1WuI8,10649
5
- ffl/errors.py,sha256=RarGTbHUzMYJ8ifYcwbQh3guNHHH8sPKPuxXgceLvtY,965
6
- ffl/events.py,sha256=V6NYG0Tfw8XR4YLF2tUqRWMhE6fRE-tnvENAoffeSV4,9263
7
- ffl/models.py,sha256=yqV2_HwgYJXS2FIzVxLycwU8_j0ORKzFXNlceonh52A,7975
8
- ffl/parsing.py,sha256=DPtgoMbbbKyeE0aqTY5iKpfRnUJBhpTnqmH3NVG6G8U,3930
9
- ffl/py.typed,sha256=qzxecjP9nBZND4fnAxWaIqIc3-9Z7bDos33dUet8d9g,46
10
- ffl/bin/ffl.com,sha256=DGFM-Lh6HnP96GsEkRokbdPKlCCBnP6KBejtwDFGc3s,51721850
11
- ffl_python-0.1.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
12
- ffl_python-0.1.4.dist-info/METADATA,sha256=hean35OifKffdfYtKxv_HC9JdfvQ9-WLiOKYs98-t5k,6583
13
- ffl_python-0.1.4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
14
- ffl_python-0.1.4.dist-info/top_level.txt,sha256=I3tRB8VSu6Bs5i_luGnIYh-dBWVtyqtLjpAfJqGtFMA,4
15
- ffl_python-0.1.4.dist-info/RECORD,,