Skip to content

Commit 6081bcf

Browse files
committed
PR review
1 parent ce90ef9 commit 6081bcf

12 files changed

Lines changed: 58 additions & 132 deletions

File tree

e2e/qr-scanner.spec.ts

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { test, expect } from '@playwright/test';
2-
import { execSync } from 'child_process';
32
import * as fs from 'fs';
43
import * as path from 'path';
54
import {
@@ -31,28 +30,6 @@ test.describe('QR Scanner', () => {
3130
}
3231
});
3332

34-
// Extract a compact share string from a bundle's README.txt using the CLI
35-
function getCompactShare(bundleDir: string): string {
36-
const readmePath = path.join(bundleDir, 'README.txt');
37-
const content = fs.readFileSync(readmePath, 'utf8');
38-
39-
// Parse the PEM share via the CLI to get the compact format
40-
// We can use the share content directly - extract the PEM block
41-
const pemMatch = content.match(
42-
/-----BEGIN REMEMORY SHARE-----([\s\S]*?)-----END REMEMORY SHARE-----/
43-
);
44-
if (!pemMatch) throw new Error('No PEM share found in README.txt');
45-
46-
// Use the binary to convert - run rememory doc compact-share with the share file
47-
// Actually, let's just extract the share data from the output directory
48-
const sharesDir = path.join(projectDir, 'output', 'shares');
49-
const shareFiles = fs.readdirSync(sharesDir);
50-
51-
// We need the compact format. Let's get it via page.evaluate after WASM loads.
52-
// For now, return the full PEM content and we'll convert in-browser.
53-
return pemMatch[0];
54-
}
55-
5633
test('scan button is visible when BarcodeDetector is available', async ({ page }) => {
5734
const bundleDir = extractBundle(bundlesDir, 'Alice');
5835

@@ -176,17 +153,10 @@ test.describe('QR Scanner', () => {
176153
const compactShare = await page.evaluate((pem: string) => {
177154
const result = (window as any).rememoryParseShare(pem);
178155
if (result.error || !result.share) return '';
179-
const share = result.share;
180-
const b64url = share.dataB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
181-
const data = Uint8Array.from(atob(share.dataB64), (c: string) => c.charCodeAt(0));
182-
return crypto.subtle.digest('SHA-256', data).then((hash: ArrayBuffer) => {
183-
const arr = new Uint8Array(hash);
184-
const check = Array.from(arr.slice(0, 2)).map(b => b.toString(16).padStart(2, '0')).join('');
185-
return `RM1:${share.index}:${share.total}:${share.threshold}:${b64url}:${check}`;
186-
});
156+
return result.share.compact;
187157
}, bobPemShare);
188158

189-
expect(compactShare).toMatch(/^RM1:\d+:\d+:\d+:[A-Za-z0-9_-]+:[0-9a-f]{4}$/);
159+
expect(compactShare).toMatch(/^RM\d+:\d+:\d+:\d+:[A-Za-z0-9_-]+:[0-9a-f]{4}$/);
190160

191161
// Verify the compact share parses correctly
192162
const parseResult = await page.evaluate((compact: string) => {
@@ -256,18 +226,11 @@ test.describe('QR Scanner', () => {
256226
const recovery = new RecoveryPage(page, aliceDir);
257227
await recovery.open();
258228

259-
// Build compact share from PEM via in-browser conversion
229+
// Convert PEM share to compact format via WASM
260230
const compactShare = await page.evaluate((pem: string) => {
261231
const result = (window as any).rememoryParseShare(pem);
262232
if (result.error || !result.share) return '';
263-
const share = result.share;
264-
const b64url = share.dataB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
265-
const data = Uint8Array.from(atob(share.dataB64), (c: string) => c.charCodeAt(0));
266-
return crypto.subtle.digest('SHA-256', data).then((hash: ArrayBuffer) => {
267-
const arr = new Uint8Array(hash);
268-
const check = Array.from(arr.slice(0, 2)).map(b => b.toString(16).padStart(2, '0')).join('');
269-
return `RM1:${share.index}:${share.total}:${share.threshold}:${b64url}:${check}`;
270-
});
233+
return result.share.compact;
271234
}, pemMatch[0]);
272235

273236
await page.evaluate((compact: string) => {

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
filippo.io/age v1.3.1
77
github.com/go-pdf/fpdf v0.9.0
88
github.com/hashicorp/vault v1.21.2
9+
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
910
github.com/spf13/cobra v1.10.2
1011
golang.org/x/text v0.33.0
1112
gopkg.in/yaml.v3 v3.0.1
@@ -16,7 +17,6 @@ require (
1617
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
1718
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1819
github.com/russross/blackfriday/v2 v2.1.0 // indirect
19-
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
2020
github.com/spf13/pflag v1.0.9 // indirect
2121
go.yaml.in/yaml/v3 v3.0.4 // indirect
2222
golang.org/x/crypto v0.46.0 // indirect

internal/bundle/bundle.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,15 @@ func GenerateAll(p *project.Project, cfg Config) error {
5858
var otherFriendsInfo []html.FriendInfo
5959
if !p.Anonymous {
6060
otherFriends = make([]project.Friend, 0, len(p.Friends)-1)
61+
otherFriendsInfo = make([]html.FriendInfo, 0, len(p.Friends)-1)
6162
for j, f := range p.Friends {
6263
if j != i {
6364
otherFriends = append(otherFriends, f)
64-
}
65-
}
66-
67-
// Convert to FriendInfo for HTML personalization
68-
otherFriendsInfo = make([]html.FriendInfo, len(otherFriends))
69-
for j, f := range otherFriends {
70-
otherFriendsInfo[j] = html.FriendInfo{
71-
Name: f.Name,
72-
Contact: f.Contact,
65+
otherFriendsInfo = append(otherFriendsInfo, html.FriendInfo{
66+
Name: f.Name,
67+
Contact: f.Contact,
68+
ShareIndex: j + 1, // 1-based share index
69+
})
7370
}
7471
}
7572
}

internal/html/assets/recover.html

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,6 @@ <h2><span class="step-number">3</span> <span data-i18n="step3_title">Recover Fil
175175
scan_btn: "Scan QR code",
176176
scan_title: "Scan a QR code",
177177
scan_hint: "Point your camera at a QR code from a friend's PDF",
178-
scan_unsupported: "QR scanning is not supported in this browser. You can type the short code below the QR code instead.",
179178
scan_camera_error: "Could not access the camera",
180179
// Error titles
181180
error_title: "Something went wrong",
@@ -268,7 +267,6 @@ <h2><span class="step-number">3</span> <span data-i18n="step3_title">Recover Fil
268267
scan_btn: "Escanear QR",
269268
scan_title: "Escanear un código QR",
270269
scan_hint: "Apunta tu cámara al código QR del PDF de tu amigo",
271-
scan_unsupported: "Tu navegador no admite escanear QR. Puedes escribir el código corto debajo del QR.",
272270
scan_camera_error: "No se pudo acceder a la cámara",
273271
error_title: "Algo salió mal",
274272
error_wasm_title: "Error al cargar la herramienta",
@@ -359,7 +357,6 @@ <h2><span class="step-number">3</span> <span data-i18n="step3_title">Recover Fil
359357
scan_btn: "QR-Code scannen",
360358
scan_title: "QR-Code scannen",
361359
scan_hint: "Richte deine Kamera auf den QR-Code aus dem PDF deines Freundes",
362-
scan_unsupported: "QR-Scanning wird in diesem Browser nicht unterstützt. Du kannst den kurzen Code unter dem QR-Code stattdessen eintippen.",
363360
scan_camera_error: "Kein Zugriff auf die Kamera",
364361
error_title: "Etwas ist schiefgelaufen",
365362
error_wasm_title: "Wiederherstellungstool konnte nicht geladen werden",
@@ -450,7 +447,6 @@ <h2><span class="step-number">3</span> <span data-i18n="step3_title">Recover Fil
450447
scan_btn: "Scanner QR",
451448
scan_title: "Scanner un code QR",
452449
scan_hint: "Dirigez votre caméra vers le QR code du PDF de votre ami",
453-
scan_unsupported: "Le scan QR n'est pas supporté dans ce navigateur. Vous pouvez taper le code court sous le QR code.",
454450
scan_camera_error: "Impossible d'accéder à la caméra",
455451
error_title: "Une erreur s'est produite",
456452
error_wasm_title: "Échec du chargement de l'outil",
@@ -540,7 +536,6 @@ <h2><span class="step-number">3</span> <span data-i18n="step3_title">Recover Fil
540536
scan_btn: "Skeniraj QR kodo",
541537
scan_title: "Skeniraj QR kodo",
542538
scan_hint: "Usmerite kamero na QR kodo s prijateljevega PDF-ja",
543-
scan_unsupported: "Skeniranje QR kode ni podprto v tem brskalniku. Namesto tega lahko vnesete kratko kodo pod QR kodo.",
544539
scan_camera_error: "Ni mogoče dostopati do kamere",
545540
// Error titles
546541
error_title: "Nekaj je šlo narobe",

internal/html/assets/src/app.ts

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,6 @@ declare const t: TranslationFunction;
260260
state.total = share.total;
261261
state.shares.push(share);
262262

263-
// Now that we know the holder's index, assign share indices to contact items
264-
assignContactIndices(share.index);
265-
266263
updateSharesUI();
267264
updateContactList();
268265
}
@@ -271,23 +268,6 @@ declare const t: TranslationFunction;
271268
checkRecoverReady();
272269
}
273270

274-
// Assign share indices to contact list items so we can match compact shares (which lack holder names)
275-
function assignContactIndices(holderIndex: number): void {
276-
if (!personalization?.otherFriends || !elements.contactList) return;
277-
278-
const items = elements.contactList.querySelectorAll('.contact-item');
279-
// otherFriends are in project order, skipping the holder.
280-
// Share indices are 1-based: project friend[0] = share 1, friend[1] = share 2, etc.
281-
let friendIdx = 0;
282-
for (let shareIndex = 1; shareIndex <= state.total; shareIndex++) {
283-
if (shareIndex === holderIndex) continue;
284-
if (friendIdx < items.length) {
285-
(items[friendIdx] as HTMLElement).dataset.shareIndex = String(shareIndex);
286-
friendIdx++;
287-
}
288-
}
289-
}
290-
291271
// ============================================
292272
// URL Fragment Share Loading
293273
// ============================================
@@ -332,6 +312,9 @@ declare const t: TranslationFunction;
332312
const item = document.createElement('div');
333313
item.className = 'contact-item';
334314
item.dataset.name = friend.name;
315+
if (friend.shareIndex) {
316+
item.dataset.shareIndex = String(friend.shareIndex);
317+
}
335318

336319
const contactInfo = friend.contact ? escapeHtml(friend.contact) : '';
337320

internal/html/assets/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface ParsedShare {
1111
total: number;
1212
holder?: string;
1313
dataB64: string;
14+
compact?: string; // Compact-encoded string (e.g. RM1:2:5:3:BASE64:CHECK)
1415
isHolder?: boolean; // True if this is the current user's share
1516
}
1617

@@ -90,6 +91,7 @@ export interface ExtractResult {
9091
export interface FriendInfo {
9192
name: string;
9293
contact?: string;
94+
shareIndex: number; // 1-based share index for this friend
9395
}
9496

9597
export interface FriendInput {

internal/html/recover.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import (
1010

1111
// FriendInfo holds friend contact information for the UI.
1212
type FriendInfo struct {
13-
Name string `json:"name"`
14-
Contact string `json:"contact,omitempty"`
13+
Name string `json:"name"`
14+
Contact string `json:"contact,omitempty"`
15+
ShareIndex int `json:"shareIndex"` // 1-based share index for this friend
1516
}
1617

1718
// PersonalizationData holds the data to personalize recover.html for a specific friend.

internal/pdf/readme.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package pdf
33
import (
44
"bytes"
55
"fmt"
6+
"net/url"
67
"strings"
78
"time"
89

@@ -50,7 +51,7 @@ func (d ReadmeData) QRContent() string {
5051
if recoveryURL == "" {
5152
recoveryURL = core.DefaultRecoveryURL
5253
}
53-
return recoveryURL + "#share=" + compact
54+
return recoveryURL + "#share=" + url.QueryEscape(compact)
5455
}
5556

5657
// GenerateReadme creates the README.pdf content.

internal/pdf/readme_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package pdf
33
import (
44
"bytes"
55
"image/png"
6+
"net/url"
67
"testing"
78
"time"
89

@@ -60,7 +61,7 @@ func TestQRContent(t *testing.T) {
6061

6162
// Without RecoveryURL set: defaults to production URL
6263
content := data.QRContent()
63-
expected := core.DefaultRecoveryURL + "#share=" + data.Share.CompactEncode()
64+
expected := core.DefaultRecoveryURL + "#share=" + url.QueryEscape(data.Share.CompactEncode())
6465
if content != expected {
6566
t.Errorf("QRContent without URL: got %q, want %q", content, expected)
6667
}
@@ -71,7 +72,7 @@ func TestQRContentWithRecoveryURL(t *testing.T) {
7172
data.RecoveryURL = "https://example.com/recover.html"
7273

7374
content := data.QRContent()
74-
expected := "https://example.com/recover.html#share=" + data.Share.CompactEncode()
75+
expected := "https://example.com/recover.html#share=" + url.QueryEscape(data.Share.CompactEncode())
7576
if content != expected {
7677
t.Errorf("QRContent with URL: got %q, want %q", content, expected)
7778
}
@@ -120,7 +121,7 @@ func TestQRCodeContentMatchesCompact(t *testing.T) {
120121

121122
qrContent := data.QRContent()
122123
compact := share.CompactEncode()
123-
expected := core.DefaultRecoveryURL + "#share=" + compact
124+
expected := core.DefaultRecoveryURL + "#share=" + url.QueryEscape(compact)
124125

125126
if qrContent != expected {
126127
t.Errorf("QR content doesn't match expected URL:\n got: %q\n want: %q", qrContent, expected)

internal/wasm/create.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -224,18 +224,15 @@ func createBundles(config CreateBundlesConfig) ([]BundleOutput, error) {
224224
var otherFriendsInfo []html.FriendInfo
225225
if !config.Anonymous {
226226
otherFriends = make([]project.Friend, 0, n-1)
227+
otherFriendsInfo = make([]html.FriendInfo, 0, n-1)
227228
for j, f := range projectFriends {
228229
if j != i {
229230
otherFriends = append(otherFriends, f)
230-
}
231-
}
232-
233-
// Convert to FriendInfo for HTML personalization
234-
otherFriendsInfo = make([]html.FriendInfo, len(otherFriends))
235-
for j, f := range otherFriends {
236-
otherFriendsInfo[j] = html.FriendInfo{
237-
Name: f.Name,
238-
Contact: f.Contact,
231+
otherFriendsInfo = append(otherFriendsInfo, html.FriendInfo{
232+
Name: f.Name,
233+
Contact: f.Contact,
234+
ShareIndex: j + 1, // 1-based share index
235+
})
239236
}
240237
}
241238
}

0 commit comments

Comments
 (0)