624 lines
30 KiB
HTML
624 lines
30 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Notion Electron Reverse Engineering: Clipboard — REA</title>
|
||
<meta
|
||
name="description"
|
||
content="Follow Notion's clipboard API through Electron's preload, IPC wrappers and main process, with short code excerpts and a look at its rich clipboard format."
|
||
/>
|
||
<!-- website:sharing:start -->
|
||
<!-- prettier-ignore -->
|
||
<link rel="canonical" href="https://rea.tools/showcase/notion/" />
|
||
<!-- website:languages:start -->
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="en" href="https://rea.tools/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="zh-CN" href="https://rea.tools/zh-CN/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="zh-TW" href="https://rea.tools/zh-TW/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="ja" href="https://rea.tools/ja/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="ko" href="https://rea.tools/ko/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="tr" href="https://rea.tools/tr/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="ru" href="https://rea.tools/ru/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="vi" href="https://rea.tools/vi/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="th" href="https://rea.tools/th/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="de" href="https://rea.tools/de/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="es" href="https://rea.tools/es/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="fr" href="https://rea.tools/fr/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="uk" href="https://rea.tools/uk/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="pl" href="https://rea.tools/pl/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="pt-BR" href="https://rea.tools/pt-BR/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="ar" href="https://rea.tools/ar/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="fa" href="https://rea.tools/fa/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="id" href="https://rea.tools/id/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="it" href="https://rea.tools/it/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<link rel="alternate" hreflang="x-default" href="https://rea.tools/showcase/notion/" />
|
||
<!-- website:languages:end -->
|
||
<!-- prettier-ignore -->
|
||
<meta property="og:type" content="website" />
|
||
<!-- prettier-ignore -->
|
||
<meta property="og:site_name" content="REA" />
|
||
<!-- prettier-ignore -->
|
||
<meta property="og:title" content="Notion Electron Reverse Engineering: Clipboard — REA" />
|
||
<!-- prettier-ignore -->
|
||
<meta property="og:description" content="Follow Notion's clipboard API through Electron's preload, IPC wrappers and main process, with short code excerpts and a look at its rich clipboard format." />
|
||
<!-- prettier-ignore -->
|
||
<meta property="og:url" content="https://rea.tools/showcase/notion/" />
|
||
<!-- prettier-ignore -->
|
||
<meta property="og:image" content="https://rea.tools/assets/social-card.png" />
|
||
<!-- prettier-ignore -->
|
||
<meta property="og:image:type" content="image/png" />
|
||
<!-- prettier-ignore -->
|
||
<meta property="og:image:width" content="1200" />
|
||
<!-- prettier-ignore -->
|
||
<meta property="og:image:height" content="630" />
|
||
<!-- prettier-ignore -->
|
||
<meta
|
||
property="og:image:alt"
|
||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||
/>
|
||
<!-- prettier-ignore -->
|
||
<meta name="twitter:card" content="summary_large_image" />
|
||
<!-- prettier-ignore -->
|
||
<meta name="twitter:title" content="Notion Electron Reverse Engineering: Clipboard — REA" />
|
||
<!-- prettier-ignore -->
|
||
<meta name="twitter:description" content="Follow Notion's clipboard API through Electron's preload, IPC wrappers and main process, with short code excerpts and a look at its rich clipboard format." />
|
||
<!-- prettier-ignore -->
|
||
<meta name="twitter:image" content="https://rea.tools/assets/social-card.png" />
|
||
<!-- prettier-ignore -->
|
||
<meta
|
||
name="twitter:image:alt"
|
||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||
/>
|
||
<!-- website:sharing:end -->
|
||
<meta name="theme-color" content="#ffffff" />
|
||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||
<script src="../../assets/main.js" defer></script>
|
||
</head>
|
||
<body id="top">
|
||
<a class="skip-link" href="#main">Skip to content</a>
|
||
<!-- website:header:start -->
|
||
<!-- prettier-ignore -->
|
||
<header class="site-header">
|
||
<div class="container header-inner">
|
||
<a class="brand" href="../../" aria-label="REA home"
|
||
><span class="brand-name">REA</span
|
||
><span class="brand-caption" data-i18n-skip>Reverse Engineer Anything</span></a
|
||
>
|
||
<nav class="navigation" aria-label="Main navigation">
|
||
<a href="../../showcase/" aria-current="page">Showcases</a>
|
||
<a href="../../blog/">Blog</a>
|
||
<a href="../../get-started/" class="nav-start">Get started</a>
|
||
<a href="../../guides/">Guides</a>
|
||
<a href="../../faq/">FAQ</a>
|
||
</nav>
|
||
<div class="header-social">
|
||
<details class="language-picker" data-i18n-skip><summary><span aria-hidden="true">文 / A</span> <span>English</span></summary><nav aria-label="English"><a href="../../showcase/notion/" lang="en" dir="ltr" data-language="en" aria-current="true">English</a><a href="../../zh-CN/showcase/notion/" lang="zh-CN" dir="ltr" data-language="zh-CN">简体中文</a><a href="../../zh-TW/showcase/notion/" lang="zh-TW" dir="ltr" data-language="zh-TW">繁體中文</a><a href="../../ja/showcase/notion/" lang="ja" dir="ltr" data-language="ja">日本語</a><a href="../../ko/showcase/notion/" lang="ko" dir="ltr" data-language="ko">한국어</a><a href="../../tr/showcase/notion/" lang="tr" dir="ltr" data-language="tr">Türkçe</a><a href="../../ru/showcase/notion/" lang="ru" dir="ltr" data-language="ru">Русский</a><a href="../../vi/showcase/notion/" lang="vi" dir="ltr" data-language="vi">Tiếng Việt</a><a href="../../th/showcase/notion/" lang="th" dir="ltr" data-language="th">ไทย</a><a href="../../de/showcase/notion/" lang="de" dir="ltr" data-language="de">Deutsch</a><a href="../../es/showcase/notion/" lang="es" dir="ltr" data-language="es">Español</a><a href="../../fr/showcase/notion/" lang="fr" dir="ltr" data-language="fr">Français</a><a href="../../uk/showcase/notion/" lang="uk" dir="ltr" data-language="uk">Українська</a><a href="../../pl/showcase/notion/" lang="pl" dir="ltr" data-language="pl">Polski</a><a href="../../pt-BR/showcase/notion/" lang="pt-BR" dir="ltr" data-language="pt-BR">Português (Brasil)</a><a href="../../ar/showcase/notion/" lang="ar" dir="rtl" data-language="ar">العربية</a><a href="../../fa/showcase/notion/" lang="fa" dir="rtl" data-language="fa">فارسی</a><a href="../../id/showcase/notion/" lang="id" dir="ltr" data-language="id">Bahasa Indonesia</a><a href="../../it/showcase/notion/" lang="it" dir="ltr" data-language="it">Italiano</a></nav></details>
|
||
<a
|
||
class="nav-github"
|
||
href="https://github.com/morluto/rea"
|
||
aria-label="REA on GitHub"
|
||
title="REA on GitHub"
|
||
>
|
||
<svg
|
||
width="22"
|
||
height="22"
|
||
viewBox="0 0 16 16"
|
||
aria-hidden="true"
|
||
focusable="false"
|
||
>
|
||
<path
|
||
fill="currentColor"
|
||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.65 7.65 0 0 1 2-.27c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"
|
||
/>
|
||
</svg>
|
||
<span class="nav-github-count" hidden aria-hidden="true"></span>
|
||
</a>
|
||
<a
|
||
class="nav-discord"
|
||
href="https://discord.gg/GkcryMnJDM"
|
||
aria-label="Join REA on Discord"
|
||
title="Join REA on Discord"
|
||
>
|
||
<svg
|
||
width="22"
|
||
height="22"
|
||
viewBox="0 0 24 24"
|
||
aria-hidden="true"
|
||
focusable="false"
|
||
>
|
||
<path
|
||
fill="currentColor"
|
||
d="M20.317 4.3698a19.7913 19.7913 0 0 0-4.8851-1.5152.0741.0741 0 0 0-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 0 0-.0785-.037 19.7363 19.7363 0 0 0-4.8852 1.515.0699.0699 0 0 0-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 0 0 .0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 0 0 .0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 0 0-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 0 1-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 0 1 .0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 0 1 .0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 0 1-.0066.1276 12.2986 12.2986 0 0 1-1.873.8914.0766.0766 0 0 0-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 0 0 .0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 0 0 .0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 0 0-.0312-.0286ZM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189Zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"
|
||
/>
|
||
</svg>
|
||
</a>
|
||
<a
|
||
class="nav-x"
|
||
href="https://x.com/reatools"
|
||
aria-label="Follow REA on X"
|
||
title="Follow REA on X"
|
||
>
|
||
<svg
|
||
width="18"
|
||
height="18"
|
||
viewBox="0 0 24 24"
|
||
aria-hidden="true"
|
||
focusable="false"
|
||
>
|
||
<path
|
||
fill="currentColor"
|
||
d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"
|
||
/>
|
||
</svg>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<!-- website:header:end -->
|
||
<main id="main" class="container">
|
||
<div class="page-heading">
|
||
<p class="breadcrumb">
|
||
<a href="../../">Home</a> / <a href="../">Showcases</a> / Notion
|
||
</p>
|
||
<p class="eyebrow">Case study · Electron</p>
|
||
<h1>How Notion copies<br />text and HTML.</h1>
|
||
<p class="lede">
|
||
Trace a clipboard call from Notion's page API, through the preload and
|
||
main process, to Electron's clipboard writer.
|
||
</p>
|
||
<p class="page-meta">Notion Desktop 7.6.1 · JavaScript · REA</p>
|
||
</div>
|
||
|
||
<figure class="workflow-figure">
|
||
<p class="figure-title">The bridge inside Notion's desktop package</p>
|
||
<ol class="flow-steps notion-bridge-flow">
|
||
<li>
|
||
<span class="flow-label">01 · Page-facing API</span>
|
||
<span class="flow-node"><code>clipboard.write(data)</code></span>
|
||
<span class="flow-note"
|
||
>The preload exposes a callable method.</span
|
||
>
|
||
</li>
|
||
<li>
|
||
<span class="flow-label">02 · Preload wrapper</span>
|
||
<span class="flow-node"><code>ipcRenderer.invoke</code></span>
|
||
<span class="flow-note"
|
||
>The wrapper sends the channel and arguments.</span
|
||
>
|
||
</li>
|
||
<li>
|
||
<span class="flow-label">03 · Main-process handler</span>
|
||
<span class="flow-node"><code>ipcMain.handle</code></span>
|
||
<span class="flow-note"
|
||
>The receiver checks which page sent it.</span
|
||
>
|
||
</li>
|
||
<li>
|
||
<span class="flow-label">04 · System clipboard</span>
|
||
<span class="flow-node"><code>clipboard.write(data)</code></span>
|
||
<span class="flow-note"
|
||
>Electron writes the supplied text and HTML.</span
|
||
>
|
||
</li>
|
||
</ol>
|
||
<p class="flow-result">
|
||
One channel joins the two sides:
|
||
<code class="notion-channel">notion:clipboard:write</code>
|
||
</p>
|
||
<figcaption>
|
||
The packaged code uses the same channel in the preload and main
|
||
process. Resolving the wrappers reveals the Electron calls behind
|
||
those names.
|
||
</figcaption>
|
||
</figure>
|
||
|
||
<div class="article-layout">
|
||
<nav class="contents" aria-label="On this page">
|
||
<p>In this case study</p>
|
||
<ul>
|
||
<li><a href="#clipboard">Follow the clipboard bridge</a></li>
|
||
<li><a href="#rich-clipboard">Keeping a block's structure</a></li>
|
||
<li><a href="#continue">More examples and sources</a></li>
|
||
</ul>
|
||
</nav>
|
||
<article class="article" aria-label="Notion Electron case study">
|
||
<section class="article-section" id="clipboard">
|
||
<h2>Where does the copy operation go?</h2>
|
||
<p>
|
||
The page sees a method called <code>clipboard.write</code>. Its
|
||
implementation in the preload is a wrapper. The useful question is
|
||
what that wrapper sends, and which code receives it.
|
||
</p>
|
||
<div class="agent-terminal">
|
||
<div class="terminal-header">
|
||
<span class="terminal-title"
|
||
><span class="terminal-dots" aria-hidden="true"
|
||
><i></i><i></i><i></i></span
|
||
>Your coding agent</span
|
||
><button
|
||
class="copy-button"
|
||
type="button"
|
||
data-copy="notion-prompt"
|
||
aria-label="Copy Notion analysis prompt"
|
||
>
|
||
Copy
|
||
</button>
|
||
</div>
|
||
<p class="terminal-prompt">
|
||
<span class="prompt-symbol" aria-hidden="true">›</span
|
||
><span id="notion-prompt"
|
||
>Use REA to trace how Notion copies text and HTML to the
|
||
system clipboard. Show the code at each step.</span
|
||
><span class="terminal-cursor" aria-hidden="true"></span>
|
||
</p>
|
||
</div>
|
||
<p class="caption">
|
||
An example prompt with Notion's local application package
|
||
available to your coding agent.
|
||
</p>
|
||
|
||
<h3 class="small-heading">REA finds where to start reading</h3>
|
||
<p>
|
||
Analyzing the tab preload with REA locates the API exposed to the
|
||
page and the call that sends an IPC request. Both findings come
|
||
with source ranges in the minified bundle:
|
||
</p>
|
||
<figure class="workflow-figure">
|
||
<p class="figure-title">REA findings → two places to open</p>
|
||
<div class="rea-query-pair">
|
||
<div class="code-block">
|
||
<div class="code-header"><span>Exposed page API</span></div>
|
||
<pre><code>api_key: "__electronApi"
|
||
api_status: "dynamic"
|
||
source: preload.js:1:22764–22828</code></pre>
|
||
</div>
|
||
<div class="code-block">
|
||
<div class="code-header"><span>Renderer IPC call</span></div>
|
||
<pre><code>operation: "invoke"
|
||
channel_expression: "e"
|
||
source: preload.js:1:6486–6522</code></pre>
|
||
</div>
|
||
</div>
|
||
<figcaption>
|
||
Selected fields from a fresh
|
||
<code>analyze_javascript_application</code> result on Notion's
|
||
tab preload, recorded with REA 4.1.0. Positions use the original
|
||
bundle's line and zero-based column.
|
||
</figcaption>
|
||
</figure>
|
||
<p>
|
||
The agent opens those locations, finds the clipboard member in the
|
||
assembled API, and reads <code>invokerInMain</code>. Its call site
|
||
supplies
|
||
<code class="notion-channel">notion:clipboard:write</code>. That
|
||
name gives the agent something precise to look for in the main
|
||
bundle. The four excerpts below show the route it connects.
|
||
</p>
|
||
|
||
<h3 class="small-heading">Four short pieces of code</h3>
|
||
<p class="caption">
|
||
Simplified from the packaged JavaScript, with readable variable
|
||
names. Unrelated members are omitted.
|
||
</p>
|
||
<ol class="rea-queries">
|
||
<li>
|
||
<h4>Start at the API exposed to the page</h4>
|
||
<div class="code-block">
|
||
<div class="code-header">
|
||
<span
|
||
>tab_browser_view/<span translate="no">preload.js</span> ·
|
||
API member</span
|
||
><button
|
||
class="copy-button"
|
||
type="button"
|
||
data-copy="notion-preload-source"
|
||
aria-label="Copy Notion preload excerpt"
|
||
>
|
||
Copy
|
||
</button>
|
||
</div>
|
||
<pre><code id="notion-preload-source">const clipboardApi = {
|
||
write: invokerInMain(<span class="notion-channel">"notion:clipboard:write"</span>)
|
||
};
|
||
|
||
// Included in the API exposed through contextBridge:
|
||
// window.__electronApi.clipboard</code></pre>
|
||
</div>
|
||
<p>
|
||
This member supplies the channel name. The implementation of
|
||
<code>invokerInMain</code> tells us how the request leaves the
|
||
page's process.
|
||
</p>
|
||
</li>
|
||
<li>
|
||
<h4>Resolve the preload's wrapper</h4>
|
||
<div class="code-block">
|
||
<div class="code-header">
|
||
<span
|
||
>tab_browser_view/<span translate="no">preload.js</span> ·
|
||
IPC helper</span
|
||
><button
|
||
class="copy-button"
|
||
type="button"
|
||
data-copy="notion-invoke-source"
|
||
aria-label="Copy Notion invoke wrapper excerpt"
|
||
>
|
||
Copy
|
||
</button>
|
||
</div>
|
||
<pre><code id="notion-invoke-source">function invokerInMain(channel) {
|
||
return (...args) => invokeInMainAndReturnResult(channel, ...args);
|
||
}
|
||
|
||
function invokeInMainAndReturnResult(channel, ...args) {
|
||
return ipcRenderer.invoke(channel, ...args);
|
||
}</code></pre>
|
||
</div>
|
||
<p>
|
||
The returned function forwards the arguments through
|
||
Electron's request-and-response IPC. We can now look for
|
||
<code class="notion-channel">notion:clipboard:write</code>
|
||
on the main-process side.
|
||
</p>
|
||
</li>
|
||
<li>
|
||
<h4>Check how the receiver is registered</h4>
|
||
<div class="code-block">
|
||
<div class="code-header">
|
||
<span
|
||
>main/<span translate="no">index.js</span> · handler
|
||
helper</span
|
||
><button
|
||
class="copy-button"
|
||
type="button"
|
||
data-copy="notion-handler-source"
|
||
aria-label="Copy Notion handler wrapper excerpt"
|
||
>
|
||
Copy
|
||
</button>
|
||
</div>
|
||
<pre><code id="notion-handler-source">const handleRequestFromRenderer = {
|
||
addListener(channel, handler) {
|
||
ipcMain.handle(channel, handler);
|
||
}
|
||
};</code></pre>
|
||
</div>
|
||
<p>
|
||
The main process uses another wrapper. Its
|
||
<code>addListener</code> method registers an
|
||
<code>ipcMain.handle</code> receiver for that channel.
|
||
</p>
|
||
</li>
|
||
<li>
|
||
<h4>Find the write and its sender check</h4>
|
||
<div class="code-block">
|
||
<div class="code-header">
|
||
<span
|
||
>main/<span translate="no">index.js</span> · clipboard
|
||
receiver</span
|
||
><button
|
||
class="copy-button"
|
||
type="button"
|
||
data-copy="notion-write-source"
|
||
aria-label="Copy Notion clipboard receiver excerpt"
|
||
>
|
||
Copy
|
||
</button>
|
||
</div>
|
||
<pre><code id="notion-write-source">handleRequestFromRenderer.addListener(
|
||
<span class="notion-channel">"notion:clipboard:write"</span>,
|
||
(event, data) =>
|
||
isNotionWebContents(event.sender)
|
||
? clipboard.write(data)
|
||
: undefined
|
||
);</code></pre>
|
||
</div>
|
||
<p>
|
||
This is the destination. After checking the sender, the
|
||
handler passes the supplied data to Electron's clipboard API.
|
||
The channel and the two resolved wrappers connect the exposed
|
||
method to this call.
|
||
</p>
|
||
</li>
|
||
</ol>
|
||
</section>
|
||
|
||
<section class="article-section" id="rich-clipboard">
|
||
<h2>How does a copied block keep its structure?</h2>
|
||
<p>
|
||
Notion's web editor sends text and HTML through
|
||
<code>electronApi.clipboard.write</code>. It keeps the block data
|
||
locally and puts an ID in the copied HTML to connect them.
|
||
</p>
|
||
<figure class="workflow-figure">
|
||
<p class="figure-title">HTML links to saved block data</p>
|
||
<div class="notion-clipboard-grid">
|
||
<div class="code-block">
|
||
<div class="code-header">
|
||
<span>Sent to the system clipboard</span>
|
||
</div>
|
||
<pre><code>text: "Project notes"
|
||
html: "<p>Project notes</p>"
|
||
+ "<!-- notionvc: <span class="notion-channel"><copy-id></span> -->"</code></pre>
|
||
</div>
|
||
<div class="code-block">
|
||
<div class="code-header">
|
||
<span>Saved locally by Notion</span>
|
||
</div>
|
||
<pre><code>id: <span class="notion-channel"><copy-id></span>
|
||
mimeTypes:
|
||
text/_notion-blocks-v3-production
|
||
→ structured block data</code></pre>
|
||
</div>
|
||
</div>
|
||
<ol
|
||
class="notion-mini-flow"
|
||
aria-label="Restoring rich clipboard data on paste"
|
||
>
|
||
<li>Read the HTML ID</li>
|
||
<li>Find the same local ID</li>
|
||
<li>Restore Notion block data</li>
|
||
</ol>
|
||
<figcaption>
|
||
Illustrative text and ID. The recorded module test restores the
|
||
block data when IDs match; a stale entry returns only text and
|
||
HTML.
|
||
</figcaption>
|
||
</figure>
|
||
<p>
|
||
On paste, Notion looks for the HTML ID and checks it against the
|
||
saved copy. Matching IDs restore the block data.
|
||
</p>
|
||
<p class="caption">
|
||
Clipboard format recorded from web assets cached on July 13, 2026.
|
||
</p>
|
||
</section>
|
||
|
||
<section class="article-section" id="continue">
|
||
<h2>More examples and sources</h2>
|
||
<details class="evidence-details" id="markdown">
|
||
<summary>Another example: Markdown tables</summary>
|
||
<p>
|
||
Notion parses Markdown into block data. This example shows how a
|
||
table becomes header and data rows.
|
||
</p>
|
||
<figure class="workflow-figure">
|
||
<p class="figure-title">
|
||
A table through Notion's Markdown parser
|
||
</p>
|
||
<div class="notion-table-example">
|
||
<div class="code-block">
|
||
<div class="code-header"><span>Markdown input</span></div>
|
||
<pre><code>| A | B |
|
||
| - | - |
|
||
| 1 | 2 |</code></pre>
|
||
</div>
|
||
<div class="code-block">
|
||
<div class="code-header">
|
||
<span>Structured parser output · excerpt</span>
|
||
</div>
|
||
<pre><code>table · headerRow: true
|
||
table_row
|
||
cells: ["A"], ["B"]
|
||
table_row
|
||
cells: ["1"], ["2"]</code></pre>
|
||
</div>
|
||
</div>
|
||
<figcaption>
|
||
Recorded output from an extracted Markdown parser. The table
|
||
becomes rows and cells.
|
||
</figcaption>
|
||
</figure>
|
||
</details>
|
||
<details class="evidence-details">
|
||
<summary>Another path: the offline SQLite connection</summary>
|
||
<p>
|
||
Notion's renderer requests <code>getSqliteMeta</code> through
|
||
IPC. The main process creates a message channel and gives one
|
||
port to the renderer and the other to a SQLite utility process.
|
||
Subsequent statement batches and results travel over that port.
|
||
</p>
|
||
<p>
|
||
Following this handoff explains where the database work runs,
|
||
and why tracing only the initial IPC request misses the later
|
||
conversation.
|
||
</p>
|
||
</details>
|
||
<details class="evidence-details">
|
||
<summary>Sources and how to read the excerpts</summary>
|
||
<p>
|
||
The clipboard bridge comes from Notion Desktop 7.6.1's
|
||
<code>app.asar</code>: the tab preload and
|
||
<code>main/index.js</code>. The excerpts expand minified
|
||
variable names and omit unrelated code; the sender-check helper
|
||
is shown with its underlying
|
||
<code>isNotionWebContents</code> call.
|
||
</p>
|
||
<p>
|
||
The virtual clipboard and Markdown examples come from web assets
|
||
cached on July 13, 2026, including modules <code>747001</code>,
|
||
<code>58169</code>, <code>736810</code> and <code>422457</code>.
|
||
Those assets have their own version. Saved probes executed
|
||
extracted modules and small harnesses; their outputs describe
|
||
those probes rather than a captured desktop UI session.
|
||
</p>
|
||
<p>
|
||
The REA result shown here is a focused analysis of the saved tab
|
||
preload, recorded with the published REA 4.1.0 package. It
|
||
locates the API and invoke operation. The clipboard member and
|
||
matching receiver are connected by reading their wrapper
|
||
implementations in the package.
|
||
</p>
|
||
<p>
|
||
The Markdown example uses parser module <code>422457</code>.
|
||
Saved module tests show that both page and chat filters preserve
|
||
this table. The paste/import, structured Markdown and AI/chat
|
||
parsers have separate entry points and intermediate formats.
|
||
</p>
|
||
</details>
|
||
<ul class="reading-list">
|
||
<li>
|
||
<a href="../../guides/javascript/"
|
||
>JavaScript application guide</a
|
||
><span
|
||
>Follow a small Electron application's export feature.</span
|
||
>
|
||
</li>
|
||
<li>
|
||
<a href="../dx-ball/">DX-Ball reconstruction</a
|
||
><span
|
||
>Follow native instructions to a checked C function.</span
|
||
>
|
||
</li>
|
||
</ul>
|
||
<div class="actions">
|
||
<a class="button" href="../../get-started/"
|
||
>Analyze your own app</a
|
||
>
|
||
</div>
|
||
</section>
|
||
</article>
|
||
</div>
|
||
</main>
|
||
<!-- website:footer:start -->
|
||
<!-- prettier-ignore -->
|
||
<footer class="site-footer">
|
||
<div class="container footer-inner">
|
||
<p>REA · Reverse Engineer Anything</p>
|
||
<div class="footer-links">
|
||
<a href="https://github.com/morluto/rea">Source</a
|
||
><a href="https://github.com/morluto/rea/blob/main/LICENSE">MIT license</a
|
||
><a href="https://discord.gg/GkcryMnJDM">Join the community</a
|
||
><a href="https://x.com/reatools">Follow us on X</a>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
<p class="sr-only" id="copy-status" role="status" aria-live="polite"></p>
|
||
<a class="back-to-top" href="#top" aria-label="Back to top">
|
||
<span aria-hidden="true">↑</span> Top
|
||
</a>
|
||
<!-- website:footer:end -->
|
||
</body>
|
||
</html>
|