mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-22 16:57:00 +08:00
feat: improve electron sandbox (#14156)
This commit is contained in:
@@ -82,7 +82,8 @@ export class EmbedFigmaBlockComponent extends EmbedBlockComponent<EmbedFigmaMode
|
||||
<div class="affine-embed-figma-iframe-container">
|
||||
<iframe
|
||||
src=${`https://www.figma.com/embed?embed_host=blocksuite&url=${url}`}
|
||||
allowfullscreen
|
||||
sandbox="allow-same-origin allow-scripts allow-presentation"
|
||||
allow="fullscreen"
|
||||
loading="lazy"
|
||||
credentialless
|
||||
></iframe>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { EmbedIframeConfigExtension } from '@blocksuite/affine-shared/services';
|
||||
|
||||
import {
|
||||
type EmbedIframeUrlValidationOptions,
|
||||
validateEmbedIframeUrl,
|
||||
} from '../../utils';
|
||||
|
||||
const BILIBILI_DEFAULT_WIDTH_IN_SURFACE = 800;
|
||||
const BILIBILI_DEFAULT_HEIGHT_IN_SURFACE = 450;
|
||||
const BILIBILI_DEFAULT_HEIGHT_IN_NOTE = 450;
|
||||
const BILIBILI_DEFAULT_WIDTH_PERCENT = 100;
|
||||
|
||||
const bilibiliValidationOptions: EmbedIframeUrlValidationOptions = {
|
||||
protocols: ['https:'],
|
||||
hostnames: ['player.bilibili.com', 'www.bilibili.com', 'bilibili.com'],
|
||||
};
|
||||
|
||||
const biliPlayerValidationOptions: EmbedIframeUrlValidationOptions = {
|
||||
protocols: ['https:'],
|
||||
hostnames: ['player.bilibili.com'],
|
||||
};
|
||||
|
||||
const AV_REGEX = /av([0-9]+)/i;
|
||||
const BV_REGEX = /(BV[0-9A-Za-z]{10})/;
|
||||
|
||||
const extractAvid = (url: string) => {
|
||||
const match = url.match(AV_REGEX);
|
||||
return match ? match[1] : undefined;
|
||||
};
|
||||
|
||||
const extractBvid = (url: string) => {
|
||||
const match = url.match(BV_REGEX);
|
||||
return match ? match[1] : undefined;
|
||||
};
|
||||
|
||||
const buildBiliPlayerEmbedUrl = (url: string) => {
|
||||
// If the user pasted the embed URL directly, keep it
|
||||
if (validateEmbedIframeUrl(url, biliPlayerValidationOptions)) {
|
||||
return url;
|
||||
}
|
||||
const avid = extractAvid(url);
|
||||
if (avid) {
|
||||
const params = new URLSearchParams({
|
||||
aid: avid,
|
||||
autoplay: '0',
|
||||
});
|
||||
return `https://player.bilibili.com/player.html?${params.toString()}`;
|
||||
}
|
||||
const bvid = extractBvid(url);
|
||||
if (bvid) {
|
||||
const params = new URLSearchParams({
|
||||
bvid,
|
||||
autoplay: '0',
|
||||
});
|
||||
return `https://player.bilibili.com/player.html?${params.toString()}`;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const bilibiliConfig = {
|
||||
name: 'bilibili',
|
||||
match: (url: string) =>
|
||||
validateEmbedIframeUrl(url, bilibiliValidationOptions) &&
|
||||
(!!extractAvid(url) || !!extractBvid(url)),
|
||||
buildOEmbedUrl: buildBiliPlayerEmbedUrl,
|
||||
useOEmbedUrlDirectly: true,
|
||||
options: {
|
||||
widthInSurface: BILIBILI_DEFAULT_WIDTH_IN_SURFACE,
|
||||
heightInSurface: BILIBILI_DEFAULT_HEIGHT_IN_SURFACE,
|
||||
heightInNote: BILIBILI_DEFAULT_HEIGHT_IN_NOTE,
|
||||
widthPercent: BILIBILI_DEFAULT_WIDTH_PERCENT,
|
||||
allow: 'clipboard-write; encrypted-media; picture-in-picture',
|
||||
sandbox: 'allow-same-origin allow-scripts',
|
||||
style: 'border: none; border-radius: 8px;',
|
||||
allowFullscreen: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const BilibiliEmbedConfig = EmbedIframeConfigExtension(bilibiliConfig);
|
||||
@@ -67,8 +67,9 @@ const genericConfig = {
|
||||
heightInNote: GENERIC_DEFAULT_HEIGHT_IN_NOTE,
|
||||
allowFullscreen: true,
|
||||
style: 'border: none; border-radius: 8px;',
|
||||
allow: 'clipboard-read; clipboard-write; picture-in-picture;',
|
||||
allow: '',
|
||||
referrerpolicy: 'no-referrer-when-downgrade',
|
||||
sandbox: 'allow-scripts',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BilibiliEmbedConfig } from './bilibili';
|
||||
import { ExcalidrawEmbedConfig } from './excalidraw';
|
||||
import { GenericEmbedConfig } from './generic';
|
||||
import { GoogleDocsEmbedConfig } from './google-docs';
|
||||
@@ -11,5 +12,6 @@ export const EmbedIframeConfigExtensions = [
|
||||
MiroEmbedConfig,
|
||||
ExcalidrawEmbedConfig,
|
||||
GoogleDocsEmbedConfig,
|
||||
BilibiliEmbedConfig,
|
||||
GenericEmbedConfig,
|
||||
];
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
type ReadonlySignal,
|
||||
signal,
|
||||
} from '@preact/signals-core';
|
||||
import { html } from 'lit';
|
||||
import { html, nothing } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
import { type ClassInfo, classMap } from 'lit/directives/class-map.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
@@ -45,6 +45,10 @@ import { safeGetIframeSrc } from './utils.js';
|
||||
|
||||
export type EmbedIframeStatus = 'idle' | 'loading' | 'success' | 'error';
|
||||
|
||||
const TRUSTED_SANDBOX =
|
||||
'allow-same-origin allow-scripts allow-forms allow-presentation';
|
||||
const UNTRUSTED_SANDBOX = 'allow-scripts';
|
||||
|
||||
export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIframeBlockModel> {
|
||||
selectedStyle$: ReadonlySignal<ClassInfo> | null = computed<ClassInfo>(
|
||||
() => ({
|
||||
@@ -89,6 +93,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
});
|
||||
|
||||
protected iframeOptions: IframeOptions | undefined = undefined;
|
||||
private currentConfigName: string | undefined;
|
||||
|
||||
get embedIframeService() {
|
||||
return this.std.get(EmbedIframeService);
|
||||
@@ -279,6 +284,10 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
const config = this.embedIframeService?.getConfig(url);
|
||||
if (config) {
|
||||
this.iframeOptions = config.options;
|
||||
this.currentConfigName = config.name;
|
||||
} else {
|
||||
this.iframeOptions = undefined;
|
||||
this.currentConfigName = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -328,26 +337,46 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
referrerpolicy,
|
||||
scrolling,
|
||||
allowFullscreen,
|
||||
sandbox,
|
||||
} = this.iframeOptions ?? {};
|
||||
const width = `${widthPercent}%`;
|
||||
// if the block is in the surface, use 100% as the height
|
||||
// otherwise, use the heightInNote
|
||||
const height = this.inSurface ? '100%' : heightInNote;
|
||||
return html`
|
||||
<iframe
|
||||
const sandboxValue =
|
||||
sandbox ??
|
||||
(this.currentConfigName === 'generic'
|
||||
? UNTRUSTED_SANDBOX
|
||||
: TRUSTED_SANDBOX);
|
||||
const sourceHost = this._getSourceHost();
|
||||
return html`<iframe
|
||||
width=${width ?? DEFAULT_IFRAME_WIDTH}
|
||||
height=${height ?? DEFAULT_IFRAME_HEIGHT}
|
||||
?allowfullscreen=${allowFullscreen}
|
||||
loading="lazy"
|
||||
frameborder="0"
|
||||
credentialless
|
||||
sandbox=${sandboxValue}
|
||||
src=${ifDefined(iframeUrl)}
|
||||
allow=${ifDefined(allow)}
|
||||
referrerpolicy=${ifDefined(referrerpolicy)}
|
||||
scrolling=${ifDefined(scrolling)}
|
||||
style=${ifDefined(style)}
|
||||
></iframe>
|
||||
`;
|
||||
${sourceHost
|
||||
? html`<div class="affine-embed-iframe-source">${sourceHost}</div>`
|
||||
: nothing}`;
|
||||
};
|
||||
|
||||
private readonly _getSourceHost = () => {
|
||||
const url = this.model.props.url ?? this.model.props.iframeUrl;
|
||||
if (!url) return null;
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return parsed.hostname;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _renderContent = () => {
|
||||
|
||||
@@ -23,6 +23,19 @@ export const embedIframeBlockStyles = css`
|
||||
height: 100%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.affine-embed-iframe-source {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
bottom: 8px;
|
||||
padding: 2px 6px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.affine-embed-iframe-block-overlay.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,8 @@ export class EmbedLoomBlockComponent extends EmbedBlockComponent<
|
||||
<iframe
|
||||
src=${`https://www.loom.com/embed/${videoId}?hide_title=true`}
|
||||
frameborder="0"
|
||||
allow="fullscreen; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allow="fullscreen; autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"
|
||||
sandbox="allow-scripts allow-same-origin allow-presentation"
|
||||
loading="lazy"
|
||||
credentialless
|
||||
></iframe>
|
||||
|
||||
@@ -148,8 +148,8 @@ export class EmbedYoutubeBlockComponent extends EmbedBlockComponent<
|
||||
type="text/html"
|
||||
src=${`https://www.youtube.com/embed/${videoId}`}
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
allow="fullscreen; autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"
|
||||
sandbox="allow-scripts allow-same-origin allow-presentation"
|
||||
loading="lazy"
|
||||
credentialless
|
||||
></iframe>
|
||||
|
||||
Reference in New Issue
Block a user