Skip to content

Commit 4e1fbd4

Browse files
committed
Make P2P file transfer size limit configurable and default to 16 MiB
1 parent de4b277 commit 4e1fbd4

7 files changed

Lines changed: 62 additions & 12 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ To allow peers to rejoin (room stays active until TTL expires), edit [`config/in
9696
DESTROY_ROOM_ON_PEER_LEAVE = false # Allow rejoin via browser history
9797
```
9898

99+
### P2P file transfer size limit
100+
101+
The file transfer gate is configurable via environment variable (bytes):
102+
103+
```bash
104+
NULLROOM_FILE_TRANSFER_SIZE_LIMIT_BYTES=16777216
105+
```
106+
107+
Default is `16 MiB` (`16777216` bytes). The same limit is enforced server-side and reflected client-side in the room UI.
108+
99109
## Key routes
100110

101111
- `GET /` → room landing page

app/channels/rooms_channel.rb

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ def subscribed
2828

2929
# Send initialization message with initiator status
3030
# First person (count=1) is initiator, second person (count=2) is not
31-
transmit({ type: "init", initiator: count == 1, connection_id: @connection_id, file_sharing: true })
31+
transmit({
32+
type: "init",
33+
initiator: count == 1,
34+
connection_id: @connection_id,
35+
file_sharing: true,
36+
file_size_limit: Nullroom::Config::FILE_TRANSFER_SIZE_LIMIT_BYTES
37+
})
3238

3339
# If we're the second person, notify first person that we're ready
3440
if count == 2
@@ -85,7 +91,10 @@ def initiate_file_transfer(data)
8591
transmit({ type: "file_transfer_authorized" })
8692
else
8793
# Soft rejection — only the requesting sender receives this
88-
transmit({ type: "file_transfer_error", error: "Beta limit exceeded: Files must be under 24 MB." })
94+
transmit({
95+
type: "file_transfer_error",
96+
error: "Beta limit exceeded: Files must be under #{file_limit_label}."
97+
})
8998
end
9099
end
91100

@@ -94,11 +103,17 @@ def initiate_file_transfer(data)
94103
# Stubbed gate for the Beta phase.
95104
# The structure is ready for Blind Token (JWT) enforcement in the Pro launch.
96105
def authorized_for_file_transfer?(metadata)
97-
# 1. Enforce the Beta size limit (24 MiB)
98-
return false if metadata.to_h["file_size"].to_i > 25_165_824
106+
# 1. Enforce the Beta size limit.
107+
return false if metadata.to_h["file_size"].to_i > Nullroom::Config::FILE_TRANSFER_SIZE_LIMIT_BYTES
99108

100109
# 2. TODO: Implement BlindSignatureService.verify?(token, sig) for Pro launch.
101110
# For now, all P2P file transfers are permitted during the Beta phase.
102111
true
103112
end
113+
114+
def file_limit_label
115+
bytes = Nullroom::Config::FILE_TRANSFER_SIZE_LIMIT_BYTES
116+
mebibytes = bytes / 1024.0 / 1024.0
117+
mebibytes.round == mebibytes ? "#{mebibytes.to_i} MB" : "#{mebibytes.round(1)} MB"
118+
end
104119
end

app/javascript/controllers/room_controller.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ export default class extends Controller {
4444
connectionId: null, // Store our connection ID
4545
// File transfer
4646
fileSharing: false,
47-
pendingFile: null
47+
pendingFile: null,
48+
fileSizeLimit: FILE_SIZE_LIMIT
4849
}
4950
this.sender = null
5051
this.receiver = null
@@ -174,6 +175,7 @@ export default class extends Controller {
174175
// Store our connection ID and initialize peer
175176
this.state.connectionId = data.connection_id
176177
this.state.fileSharing = data.file_sharing === true
178+
this.state.fileSizeLimit = Number(data.file_size_limit) > 0 ? Number(data.file_size_limit) : FILE_SIZE_LIMIT
177179
console.log("[Room] Got init message, initiator:", data.initiator, "connection_id:", data.connection_id, "file_sharing:", this.state.fileSharing)
178180
this.initializePeer(data.initiator)
179181
} else if (data.type === "peer_ready") {
@@ -469,8 +471,8 @@ export default class extends Controller {
469471
*/
470472
_requestFileTransfer(file) {
471473
if (this.state.roomTerminated || !this.state.p2p) return
472-
if (file.size > FILE_SIZE_LIMIT) {
473-
this.showError("Files must be under 24 MB.")
474+
if (file.size > this.state.fileSizeLimit) {
475+
this.showError(`Files must be under ${this._fileSizeLimitLabel()}.`)
474476
return
475477
}
476478
// Store the file and ask the server gate
@@ -493,6 +495,7 @@ export default class extends Controller {
493495
(name, percent) => this.updateFileProgress(name, percent),
494496
(msg) => this.showError(msg)
495497
)
498+
this.sender.setFileSizeLimit(this.state.fileSizeLimit)
496499

497500
this.receiver = new FileTransferReceiver(
498501
decryptFn,
@@ -581,6 +584,11 @@ export default class extends Controller {
581584
return `${(bytes / 1_048_576).toFixed(1)} MB`
582585
}
583586

587+
_fileSizeLimitLabel() {
588+
const mebibytes = this.state.fileSizeLimit / (1024 * 1024)
589+
return Number.isInteger(mebibytes) ? `${mebibytes} MB` : `${mebibytes.toFixed(1)} MB`
590+
}
591+
584592
// ── Utilities ─────────────────────────────────────────────────────────────
585593

586594
// Escape user-provided text before injecting into the DOM.

app/javascript/modules/file_transfer.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ const CHUNK_SIZE = 65_536 // 64 KB per spec
1212
const MAX_BUFFER = 16_777_216 // 16 MB — pause sending above this (backpressure)
1313

1414
/** Maximum file size allowed in the Beta phase. Mirrors the server-side gate. */
15-
export const FILE_SIZE_LIMIT = 25_165_824 // 24 MiB (24 × 1024 × 1024)
15+
export const FILE_SIZE_LIMIT = 16_777_216 // 16 MiB (16 × 1024 × 1024)
16+
17+
function fileSizeLimitLabel(bytes) {
18+
const mebibytes = bytes / (1024 * 1024)
19+
return Number.isInteger(mebibytes) ? `${mebibytes} MB` : `${mebibytes.toFixed(1)} MB`
20+
}
1621

1722
// ─────────────────────────────────────────────────────────────────────────────
1823
// FileTransferSender
@@ -37,9 +42,17 @@ export class FileTransferSender {
3742
this.encryptFn = encryptFn
3843
this.onProgress = onProgress
3944
this.onError = onError
45+
this.fileSizeLimit = FILE_SIZE_LIMIT
4046
this._sending = false
4147
}
4248

49+
setFileSizeLimit(bytes) {
50+
const parsed = Number(bytes)
51+
if (Number.isFinite(parsed) && parsed > 0) {
52+
this.fileSizeLimit = parsed
53+
}
54+
}
55+
4356
/**
4457
* Validate and stream a File object over the file data channel.
4558
* Caller should already have received server authorisation before calling this.
@@ -52,8 +65,8 @@ export class FileTransferSender {
5265
}
5366

5467
// Client-side size guard (mirrors server gate — instant UX feedback)
55-
if (file.size > FILE_SIZE_LIMIT) {
56-
this.onError("Files must be under 24 MB.")
68+
if (file.size > this.fileSizeLimit) {
69+
this.onError(`Files must be under ${fileSizeLimitLabel(this.fileSizeLimit)}.`)
5770
return
5871
}
5972

app/views/rooms/show.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
<%= render "shared/icon", name: "paperclip", css: "w-4 h-4 shrink-0" %>
116116
<span class="text-xs font-mono">
117117
Drop a file or click to select
118-
<span class="text-white/30">(max 24 MB)</span>
118+
<span class="text-white/30">(max 16 MB)</span>
119119
</span>
120120
<%# Invisible native file picker layered over the styled div %>
121121
<input

config/initializers/nullroom.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,9 @@ module Config
77
# When true: room is destroyed immediately when any peer leaves (no rejoin possible via browser history)
88
# When false: room remains active until TTL expires (peers can reconnect via back/history)
99
DESTROY_ROOM_ON_PEER_LEAVE = false
10+
11+
# Maximum P2P file transfer size in bytes.
12+
# Override with NULLROOM_FILE_TRANSFER_SIZE_LIMIT_BYTES in environment.
13+
FILE_TRANSFER_SIZE_LIMIT_BYTES = ENV.fetch("NULLROOM_FILE_TRANSFER_SIZE_LIMIT_BYTES", 16 * 1024 * 1024).to_i
1014
end
1115
end

test/channels/rooms_channel_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def del(*keys)
242242

243243
response = transmissions.last.deep_symbolize_keys
244244
assert_equal "file_transfer_error", response[:type]
245-
assert_includes response[:error], "24 MB"
245+
assert_includes response[:error], "16 MB"
246246
end
247247
end
248248

0 commit comments

Comments
 (0)