mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-24 01:42:55 +08:00
feat(editor): allow embedding any iframes (#12895)
fix BS-3606 #### PR Dependency Tree * **PR #12892** * **PR #12895** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced support for embedding generic iframes with customizable dimensions and permissions, while ensuring only secure, non-AFFiNE URLs are allowed. * Enhanced embedding options by prioritizing custom embed blocks over iframe blocks for a richer embedding experience across toolbars and link actions. * Added URL validation to support secure and flexible embedding configurations. * **Bug Fixes** * Improved iframe embedding reliability by removing restrictive HTTP headers and certain Content Security Policy directives that could block iframe usage in the Electron app. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -11,6 +11,8 @@ import {
|
||||
EmbedCardLightVerticalIcon,
|
||||
} from '@blocksuite/affine-components/icons';
|
||||
import { ColorScheme } from '@blocksuite/affine-model';
|
||||
import { EmbedOptionProvider } from '@blocksuite/affine-shared/services';
|
||||
import type { BlockStdScope } from '@blocksuite/std';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
type EmbedCardIcons = {
|
||||
@@ -40,3 +42,8 @@ export function getEmbedCardIcons(theme: ColorScheme): EmbedCardIcons {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function canEmbedAsEmbedBlock(std: BlockStdScope, url: string) {
|
||||
const options = std.get(EmbedOptionProvider).getEmbedBlockOptions(url);
|
||||
return options?.viewType === 'embed';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { EmbedIframeConfigExtension } from '@blocksuite/affine-shared/services';
|
||||
|
||||
const GENERIC_DEFAULT_WIDTH_IN_SURFACE = 800;
|
||||
const GENERIC_DEFAULT_HEIGHT_IN_SURFACE = 600;
|
||||
const GENERIC_DEFAULT_WIDTH_PERCENT = 100;
|
||||
const GENERIC_DEFAULT_HEIGHT_IN_NOTE = 400;
|
||||
|
||||
/**
|
||||
* AFFiNE domains that should be excluded from generic embedding
|
||||
* These are based on the centralized cloud constants and known AFFiNE domains
|
||||
*/
|
||||
const AFFINE_DOMAINS = [
|
||||
'affine.pro', // Main AFFiNE domain
|
||||
'app.affine.pro', // Stable cloud domain
|
||||
'insider.affine.pro', // Beta/internal cloud domain
|
||||
'affine.fail', // Canary cloud domain
|
||||
'toeverything.app', // Safety measure for potential future use
|
||||
];
|
||||
|
||||
/**
|
||||
* Validates if a URL is suitable for generic iframe embedding
|
||||
* Allows HTTPS URLs but excludes AFFiNE domains
|
||||
* @param url The URL to validate
|
||||
* @returns Boolean indicating if the URL can be generically embedded
|
||||
*/
|
||||
function isValidGenericEmbedUrl(url: string): boolean {
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
// Only allow HTTPS for security
|
||||
if (parsedUrl.protocol !== 'https:') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exclude AFFiNE domains
|
||||
const hostname = parsedUrl.hostname.toLowerCase();
|
||||
if (
|
||||
AFFINE_DOMAINS.some(
|
||||
domain => hostname === domain || hostname.endsWith(`.${domain}`)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
// Invalid URL
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const genericConfig = {
|
||||
name: 'generic',
|
||||
match: (url: string) => isValidGenericEmbedUrl(url),
|
||||
buildOEmbedUrl: (url: string) => {
|
||||
if (!isValidGenericEmbedUrl(url)) {
|
||||
return undefined;
|
||||
}
|
||||
return url;
|
||||
},
|
||||
useOEmbedUrlDirectly: true,
|
||||
options: {
|
||||
widthInSurface: GENERIC_DEFAULT_WIDTH_IN_SURFACE,
|
||||
heightInSurface: GENERIC_DEFAULT_HEIGHT_IN_SURFACE,
|
||||
widthPercent: GENERIC_DEFAULT_WIDTH_PERCENT,
|
||||
heightInNote: GENERIC_DEFAULT_HEIGHT_IN_NOTE,
|
||||
allowFullscreen: true,
|
||||
style: 'border: none; border-radius: 8px;',
|
||||
allow: 'clipboard-read; clipboard-write; picture-in-picture;',
|
||||
referrerpolicy: 'no-referrer-when-downgrade',
|
||||
},
|
||||
};
|
||||
|
||||
export const GenericEmbedConfig = EmbedIframeConfigExtension(genericConfig);
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ExcalidrawEmbedConfig } from './excalidraw';
|
||||
import { GenericEmbedConfig } from './generic';
|
||||
import { GoogleDocsEmbedConfig } from './google-docs';
|
||||
import { GoogleDriveEmbedConfig } from './google-drive';
|
||||
import { MiroEmbedConfig } from './miro';
|
||||
@@ -10,4 +11,5 @@ export const EmbedIframeConfigExtensions = [
|
||||
MiroEmbedConfig,
|
||||
ExcalidrawEmbedConfig,
|
||||
GoogleDocsEmbedConfig,
|
||||
GenericEmbedConfig,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user