Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@
"test:e2e:errors": "node run-e2e-with-error-capture.js"
},
"dependencies": {
"@synonymdev/pubky": "latest",
"@synonymdev/pubky": "^0.5.4",
"@types/dompurify": "^3.0.5",
"dom-anchor-text-position": "^5.0.0",
"dom-anchor-text-quote": "^4.0.2",
"dompurify": "^3.3.1",
"pubky-app-specs": "^0.4.0",
"qrcode": "^1.5.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-image-crop": "^10.1.8"
},
"devDependencies": {
"@playwright/test": "^1.40.0",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
"@types/chrome": "^0.0.268",
Expand All @@ -47,7 +50,6 @@
"tailwindcss": "^3.4.4",
"typescript": "^5.5.3",
"vite": "^5.3.3",
"vitest": "^1.3.1",
"@playwright/test": "^1.40.0"
"vitest": "^1.3.1"
}
}
22 changes: 19 additions & 3 deletions src/content/AnnotationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
} from '../utils/validation';
import { ANNOTATION_CONSTANTS, MESSAGE_TYPES, TIMING_CONSTANTS, UI_CONSTANTS } from '../utils/constants';
import { isHTMLButtonElement } from '../utils/type-guards';
import DOMPurify from 'dompurify';

/**
* Annotation data structure
Expand Down Expand Up @@ -378,7 +379,7 @@
cursor: not-allowed;
}
`;
document.head.appendChild(style);

Check failure on line 382 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:382:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:66:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:59:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 382 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:382:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:66:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:59:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 382 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:382:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:66:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:59:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 382 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:382:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:66:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:59:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 382 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:382:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:66:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:59:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 382 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:382:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:66:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:59:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 382 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:382:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:66:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:59:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 382 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:382:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:66:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:59:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 382 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:382:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:66:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:59:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 382 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:382:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:66:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:59:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.
}

private handleTextSelection(event: MouseEvent) {
Expand Down Expand Up @@ -460,12 +461,14 @@

const button = document.createElement('button');
button.className = 'pubky-annotation-button';
button.innerHTML = `
// Use DOMPurify to sanitize HTML (defense-in-depth, even though this is static)
const buttonHtml = `
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
</svg>
Add Annotation
`;
button.innerHTML = DOMPurify.sanitize(buttonHtml);

// Position button to the right and slightly below the selection
// Account for button width (approximately 140px) and add some padding
Expand Down Expand Up @@ -572,7 +575,8 @@
modal.className = 'pubky-annotation-modal';
modal.onclick = (e) => e.stopPropagation();

modal.innerHTML = `
// Sanitize modal HTML with DOMPurify
const modalHtml = `
<h3>Add Annotation</h3>
<div class="selected-text">"${this.escapeHtml(this.currentSelection.text)}"</div>
<textarea placeholder="Add your comment..." autofocus maxlength="${VALIDATION_LIMITS.COMMENT_MAX_LENGTH}"></textarea>
Expand All @@ -585,6 +589,7 @@
<button class="submit-btn">Post Annotation</button>
</div>
`;
modal.innerHTML = DOMPurify.sanitize(modalHtml);

const textarea = modal.querySelector('textarea')!;
const cancelBtn = modal.querySelector('.cancel-btn')!;
Expand Down Expand Up @@ -920,18 +925,29 @@
private handleHighlightClick(annotation: Annotation) {
logger.info('ContentScript', 'Highlight clicked', { id: annotation.id });

// Highlight the clicked annotation on the page
document.querySelectorAll(`.${this.activeHighlightClass}`).forEach((el) => {
el.classList.remove(this.activeHighlightClass);
});

const highlight = document.querySelector(`[data-annotation-id="${annotation.id}"]`);
if (highlight) {
highlight.classList.add(this.activeHighlightClass);
// Scroll the highlight into view
highlight.scrollIntoView({ behavior: 'smooth', block: 'center' });
}

// Send message to background to open sidepanel
// Must be synchronous to preserve user gesture context
chrome.runtime.sendMessage({
type: 'SHOW_ANNOTATION',
type: 'OPEN_SIDE_PANEL_FOR_ANNOTATION',
annotationId: annotation.id,
Comment on lines 942 to 944

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use handled message when clicking highlights

The highlight click handler now emits OPEN_SIDE_PANEL_FOR_ANNOTATION, but the defined message set still uses MESSAGE_TYPES.SHOW_ANNOTATION, and the background listener in src/background/background.ts only reacts to SHOW_ANNOTATION to scroll/open the side panel. There are no handlers for the new string (repo search), so clicking a highlight no longer triggers the side panel behavior. Stick to the existing message type or wire up a matching listener to avoid breaking annotation navigation.

Useful? React with 👍 / 👎.

}, () => {
if (chrome.runtime.lastError) {
logger.warn('ContentScript', 'Failed to send open sidepanel message', new Error(chrome.runtime.lastError.message));
} else {
logger.info('ContentScript', 'Sidepanel open requested', { annotationId: annotation.id });
}
});
}

Expand Down
4 changes: 3 additions & 1 deletion src/content/DrawingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { contentLogger as logger } from './logger';
import { compressCanvas, getRecommendedQuality, formatBytes } from '../utils/image-compression';
import { DRAWING_CONSTANTS, DRAWING_UI_CONSTANTS, MESSAGE_TYPES, UI_CONSTANTS } from '../utils/constants';
import { isHTMLInputElement } from '../utils/type-guards';
import DOMPurify from 'dompurify';

export class DrawingManager {
private canvas: HTMLCanvasElement | null = null;
Expand Down Expand Up @@ -202,7 +203,7 @@ export class DrawingManager {
backdrop-filter: blur(10px);
`;

this.toolbar.innerHTML = `
const toolbarHtml = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<div>
<div style="font-weight: 600; margin-bottom: 2px;">Graphiti Drawing</div>
Expand Down Expand Up @@ -284,6 +285,7 @@ export class DrawingManager {
">Save</button>
</div>
`;
this.toolbar.innerHTML = DOMPurify.sanitize(toolbarHtml);

document.body.appendChild(this.toolbar);

Expand Down
5 changes: 4 additions & 1 deletion src/content/PubkyURLHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { contentLogger as logger } from './logger';
import DOMPurify from 'dompurify';

export class PubkyURLHandler {
constructor() {
Expand Down Expand Up @@ -165,10 +166,12 @@ export class PubkyURLHandler {

const normalizedUrl = url.replace(/^pubky:(?!\/\/)/, 'pubky://');
button.setAttribute('data-pubky-url', normalizedUrl);
button.innerHTML = `
// Sanitize button HTML with DOMPurify (synchronous import)
const buttonHtml = `
<span class="pubky-link-icon">🔗</span>
<span>${url.length > 30 ? url.substring(0, 30) + '...' : url}</span>
`;
button.innerHTML = DOMPurify.sanitize(buttonHtml);
fragments.push(button);

lastIndex = matchIndex + url.length;
Expand Down
13 changes: 10 additions & 3 deletions src/offscreen/offscreen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ class OffscreenHandler {
try {
console.log('[Graphiti Offscreen] Initializing Pubky client...');

const { Client } = await import('@synonymdev/pubky');
this.client = new Client();
const { getPubkyClientAsync } = await import('../utils/pubky-client-factory');
this.client = await getPubkyClientAsync();
this.isInitialized = true;

console.log('[Graphiti Offscreen] Pubky client initialized successfully');
Expand Down Expand Up @@ -287,7 +287,14 @@ class OffscreenHandler {
const allAnnotations = await annotationStorage.getAllAnnotations();
for (const url in allAnnotations) {
for (const annotation of allAnnotations[url]) {
if (!annotation.postUri && annotation.author) {
// Sync if: no postUri AND (has author OR we can assign current session as author)
if (!annotation.postUri) {
// If annotation was created while logged out, assign current session as author
if (!annotation.author || annotation.author === '') {
annotation.author = session.pubky;
await annotationStorage.saveAnnotation(annotation);
}

const result = await this.syncAnnotation({
url: annotation.url,
selectedText: annotation.selectedText,
Expand Down
13 changes: 7 additions & 6 deletions src/profile/profile-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { imageHandler } from '../utils/image-handler';
import DOMPurify from 'dompurify';

// Inline logger for profile renderer
class ProfileRendererLogger {
Expand Down Expand Up @@ -76,9 +77,9 @@ class ProfileRenderer {

private async initializePubky() {
try {
const { Client } = await import('@synonymdev/pubky');
this.pubky = new Client();
rendererLogger.info('Pubky Client initialized');
const { getPubkyClientAsync } = await import('../utils/pubky-client-factory');
this.pubky = await getPubkyClientAsync();
rendererLogger.info('Pubky Client initialized via singleton');
} catch (error) {
rendererLogger.error('Failed to initialize Pubky Client', error);
throw new Error('Failed to initialize Pubky client');
Expand Down Expand Up @@ -255,16 +256,16 @@ class ProfileRenderer {
}
}

private showContent(html: string, title: string) {
private async showContent(html: string, title: string) {
// Update document title
document.title = title;

// Hide loading and error
this.loadingEl.classList.add('hidden');
this.errorEl.classList.add('hidden');

// Show content
this.contentEl.innerHTML = html;
// Sanitize HTML from homeserver before displaying (critical security fix)
this.contentEl.innerHTML = DOMPurify.sanitize(html);
this.contentEl.classList.remove('hidden');
}

Expand Down
46 changes: 25 additions & 21 deletions src/utils/auth-sdk.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { logger } from './logger';
import { storage, Session } from './storage';
import { profileManager } from './profile-manager';
import { getPubkyClientAsync } from './pubky-client-factory';

/**
* Pubky authentication using official @synonymdev/pubky SDK
Expand All @@ -25,7 +26,21 @@ class AuthManagerSDK {
private currentAuthRequest: AuthRequest | null = null;

private constructor() {
this.initializePubky();
// Client will be initialized lazily via ensureClient()
}

/**
* Validate capabilities format before use
*/
private validateCapabilities(capabilities: string): string {
if (!capabilities || typeof capabilities !== 'string') {
throw new Error('Capabilities must be a non-empty string');
}
// Validate format - must start with /pub/
if (!capabilities.startsWith('/pub/')) {
throw new Error('Capabilities must start with /pub/');
}
return capabilities;
}

static getInstance(): AuthManagerSDK {
Expand All @@ -36,28 +51,14 @@ class AuthManagerSDK {
}

/**
* Initialize Pubky client
*/
private async initializePubky() {
try {
// Dynamic import to handle the SDK
const { Client } = await import('@synonymdev/pubky');
this.client = new Client();
logger.info('AuthSDK', 'Pubky Client initialized');
} catch (error) {
logger.error('AuthSDK', 'Failed to initialize Pubky Client', error as Error);
throw error;
}
}

/**
* Ensure Client is initialized
* Ensure Client is initialized using singleton factory
*/
private async ensureClient(): Promise<Client> {
if (!this.client) {
await this.initializePubky();
this.client = await getPubkyClientAsync();
logger.info('AuthSDK', 'Pubky Client initialized via singleton');
}
return this.client!;
return this.client;
}

/**
Expand All @@ -69,8 +70,11 @@ class AuthManagerSDK {

const client = await this.ensureClient();

// Create auth request with capabilities
this.currentAuthRequest = client.authRequest(RELAY_URL, REQUIRED_CAPABILITIES);
// Validate capabilities before creating auth request
const validatedCapabilities = this.validateCapabilities(REQUIRED_CAPABILITIES);

// Create auth request with validated capabilities
this.currentAuthRequest = client.authRequest(RELAY_URL, validatedCapabilities);

// Get authorization URL (pubkyauth:// URL)
const authUrl = this.currentAuthRequest.url();
Expand Down
4 changes: 2 additions & 2 deletions src/utils/image-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export class ImageHandler {

private async ensureClient(): Promise<any> {
if (!this.client) {
const { Client } = await import('@synonymdev/pubky');
this.client = new Client();
const { getPubkyClientAsync } = await import('./pubky-client-factory');
this.client = await getPubkyClientAsync();
}
return this.client;
}
Expand Down
6 changes: 3 additions & 3 deletions src/utils/profile-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ export class ProfileManager {
}

/**
* Initialize the Pubky client
* Initialize the Pubky client using singleton factory
*/
private async ensureClient(): Promise<any> {
if (!this.client) {
const { Client } = await import('@synonymdev/pubky');
this.client = new Client();
const { getPubkyClientAsync } = await import('./pubky-client-factory');
this.client = await getPubkyClientAsync();
}
return this.client;
}
Expand Down
Loading
Loading