website/docs/help/dev/spec/web_app/index.html

17 lines
45 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

<!doctype html>
<html lang="en" dir="ltr" class="docs-wrapper docs-doc-page docs-version-current plugin-docs plugin-id-default docs-doc-id-dev/spec/web_app" data-has-hydrated="false">
<head>
<meta charset="UTF-8">
<meta name="generator" content="Docusaurus v2.4.3">
<title data-rh="true">Web app | Joplin</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="https://joplinapp.org/help/dev/spec/web_app"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="Web app | Joplin"><meta data-rh="true" name="description" content=":::note This document explains how the Joplin Web App works from a technical perspective. ::: The Joplin Web App is a version of the mobile application that targets web using react-native-web. See ..."><meta data-rh="true" property="og:description" content=":::note This document explains how the Joplin Web App works from a technical perspective. ::: The Joplin Web App is a version of the mobile application that targets web using react-native-web. See ..."><link data-rh="true" rel="icon" href="https://joplinapp.org/favicon.ico"><link data-rh="true" rel="canonical" href="https://joplinapp.org/help/dev/spec/web_app"><link data-rh="true" rel="alternate" href="https://joplinapp.org/help/dev/spec/web_app" hreflang="en"><link data-rh="true" rel="alternate" href="https://joplinapp.org/fr/help/dev/spec/web_app" hreflang="fr"><link data-rh="true" rel="alternate" href="https://joplinapp.org/help/dev/spec/web_app" hreflang="x-default"><link rel="alternate" type="application/rss+xml" href="/news/rss.xml" title="Joplin RSS Feed">
<link rel="alternate" type="application/atom+xml" href="/news/atom.xml" title="Joplin Atom Feed"><link rel="stylesheet" href="/assets/css/styles.3f0207fa.css">
<link rel="preload" href="/assets/js/runtime~main.1fb4f966.js" as="script">
<link rel="preload" href="/assets/js/main.aa9faae4.js" as="script">
</head>
<body class="navigation-with-keyboard">
<script>!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){var t=null;try{t=new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}return t}()||function(){var t=null;try{t=localStorage.getItem("theme")}catch(t){}return t}();t(null!==e?e:"light")}()</script><div id="__docusaurus">
<div role="region" aria-label="Skip to main content"><a class="skipToContent_fXgn" href="#__docusaurus_skipToContent_fallback">Skip to main content</a></div><nav aria-label="Main" class="navbar navbar--fixed-top"><div class="navbar__inner"><div class="navbar__items"><button aria-label="Toggle navigation bar" aria-expanded="false" class="navbar__toggle clean-btn" type="button"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a href="https://joplinapp.org" target="_self" rel="noopener noreferrer" class="navbar__brand"><div class="navbar__logo"><img src="/images/logo-text-blue.svg" alt="Joplin" class="themedImage_ToTc themedImage--light_HNdA"><img src="/images/logo-text-blue.svg" alt="Joplin" class="themedImage_ToTc themedImage--dark_i4oU"></div><b class="navbar__title text--truncate"></b></a></div><div class="navbar__items navbar__items--right"><a class="navbar__item navbar__link" href="/news">News</a><a aria-current="page" class="navbar__item navbar__link navbar__link--active" href="/help/">Help</a><a href="https://discourse.joplinapp.org" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">Forum</a><a href="https://joplinapp.org/plans" target="_self" rel="noopener noreferrer" class="navbar__item navbar__link navbar-custom-buttons plans-button">Joplin Cloud</a><a href="https://joplinapp.org/donate" target="_self" rel="noopener noreferrer" class="navbar__item navbar__link navbar-custom-buttons sponsor-button">♡ Support us</a><div class="navbar__item dropdown dropdown--hoverable dropdown--right"><a href="#" aria-haspopup="true" aria-expanded="false" role="button" class="navbar__link"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" class="iconLanguage_nlXk"><path fill="currentColor" d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"></path></svg>English</a><ul class="dropdown__menu"><li><a href="/help/dev/spec/web_app" target="_self" rel="noopener noreferrer" class="dropdown__link dropdown__link--active" lang="en">English</a></li><li><a href="/fr/help/dev/spec/web_app" target="_self" rel="noopener noreferrer" class="dropdown__link" lang="fr">Français</a></li></ul></div><div class="toggle_vylO colorModeToggle_DEke"><button class="clean-btn toggleButton_gllP toggleButtonDisabled_aARS" type="button" disabled="" title="Switch between dark and light mode (currently light mode)" aria-label="Switch between dark and light mode (currently light mode)" aria-live="polite"><svg viewBox="0 0 24 24" width="24" height="24" class="lightToggleIcon_pyhR"><path fill="currentColor" d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" class="darkToggleIcon_wfgR"><path fill="currentColor" d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"></path></svg></button></div><div class="searchBox_ZlJk"><div class="navbar__search"><span aria-label="expand searchbar" role="button" class="search-icon" tabindex="0"></span><input type="search" id="search_input_react" placeholder="Loading..." aria-label="Search" class="navbar__search-input search-bar" disabled=""></div></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div id="__docusaurus_skipToContent_fallback" class="main-wrapper mainWrapper_z2l0 docsWrapper_BCFX"><button aria-label="Scroll back to top" class="clean-btn theme-back-to-top-button backToTopButton_sjWU" type="button"></button><div class="docPage__5DB"><aside class="theme-doc-sidebar-container docSidebarContainer_b6E3"><div class="sidebarViewport_Xe31"><div class="sidebar_njMd"><nav aria-label="Docs sidebar" class="menu thin-scrollbar menu_SIkG"><ul class="theme-doc-sidebar-menu menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/help/">What is Joplin?</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/help/install">Installation</a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist" aria-expanded="false" href="/help/apps/">Using Joplin</a><button aria-label="Toggle the collapsible sidebar category &#x27;Using Joplin&#x27;" type="button" class="clean-btn menu__caret"></button></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist" aria-expanded="false" href="/help/api/">Programmatic API</a><button aria-label="Toggle the collapsible sidebar category &#x27;Programmatic API&#x27;" type="button" class="clean-btn menu__caret"></button></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--active" aria-expanded="true" href="/help/dev/">Development</a><button aria-label="Toggle the collapsible sidebar category &#x27;Development&#x27;" type="button" class="clean-btn menu__caret"></button></div><ul style="display:block;overflow:visible;height:auto" class="menu__list"><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-2 menu__list-item"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret menu__link--active" aria-expanded="true" tabindex="0" href="/help/dev/spec/architecture">Technical specifications</a></div><ul style="display:block;overflow:visible;height:auto" class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/architecture">Joplin architecture</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/clipper_auth">Clipper authorisation mechanism</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/default_plugins">How to add a new default plugin</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/desktop_styling">Desktop application styling</a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-3 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist" aria-expanded="false" tabindex="0" href="/help/dev/spec/e2ee/">Encryption</a><button aria-label="Toggle the collapsible sidebar category &#x27;Encryption&#x27;" type="button" class="clean-btn menu__caret"></button></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/history">Note history</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/interop_with_frontmatter">Markdown with Front Matter Exporter/Importer</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/note_viewer_isolation">Note viewer isolation</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/plugins">Plugin system architecture</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/read_only">Read-only</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/search_sorting">Search Engine</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_debug">Debugging Server project with vscode</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_delta_sync">Joplin Server delta sync</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_file_url_format">Structure of file URLs in Joplin Server</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_items">Joplin Server items</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_sharing">Joplin Server sharing feature</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_sharing_e2ee">Sharing a notebook with E2EE enabled</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_user_status">Joplin Server user status</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/sync">Joplin synchronisation</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/sync_lock">Synchronisation locks</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/sync_scroll">Synchronous Scroll</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/voice_typing">Voice typing</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link menu__link--active" aria-current="page" tabindex="0" href="/help/dev/spec/web_app">Web app</a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-2 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" aria-expanded="false" tabindex="0" href="/help/dev/gsoc/gsoc2020/">Google Summer of Code</a></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/BUILD">Building the applications</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/DEPLOY">Deploying Joplin apps and scripts</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/build_troubleshooting">Build troubleshooting</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/coding_style">Coding style</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/localisation">Localisation</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spellcheck">Spellchecking using CSpell</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/technical_spec">Guide to writing a technical spec</a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" aria-expanded="false" href="/help/about/changelog/android">About</a></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/help/faq">FAQ</a></li></ul></nav></div></div></aside><main class="docMainContainer_gTbr"><div class="container padding-top--md padding-bottom--lg"><div class="row"><div class="col docItemCol_VOVn"><div class="docItemContainer_Djhp"><article><div class="tocCollapsible_ETCw theme-doc-toc-mobile tocMobile_ITEo"><button type="button" class="clean-btn tocCollapsibleButton_TO0P">On this page</button></div><div class="theme-doc-markdown markdown"><h1>Web app</h1><div class="donate-links"><p><a href="https://www.paypal.com/donate/?business=E8JMYD2LQ8MMA&amp;no_recurring=0&amp;item_name=I+rely+on+donations+to+maintain+and+improve+the+Joplin+open+source+project.+Thank+you+for+your+help+-+it+makes+a+difference%21&amp;currency_code=EUR" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-PayPal-green.svg" alt="Donate using PayPal" class="img_ev3q"></a> <a href="https://github.com/sponsors/laurent22/" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/GitHub-Badge.svg" alt="Sponsor on GitHub" class="img_ev3q"></a> <a href="https://www.patreon.com/joplin" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Patreon-Badge.svg" alt="Become a patron" class="img_ev3q"></a> <a href="https://joplinapp.org/donate/#donations" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-IBAN.svg" alt="Donate using IBAN" class="img_ev3q"></a></p></div><div class="theme-admonition theme-admonition-note alert alert--secondary admonition_LlT9"><div class="admonitionHeading_tbUL"><span class="admonitionIcon_kALy"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_S0QG"><p>This document explains how the Joplin Web App works from a technical perspective.</p></div></div><p>The Joplin Web App is a version of the mobile application that targets web using <a href="https://www.npmjs.com/package/react-native-web" target="_blank" rel="noopener noreferrer"><code>react-native-web</code></a>. See <a href="/help/dev/BUILD#web">BUILD.md</a> for information about building the web app.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="file-system">File system<a href="#file-system" class="hash-link" aria-label="Direct link to File system" title="Direct link to File system"></a></h2><p>On web, <code>shim.fsDriver</code> wraps <a href="https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system" target="_blank" rel="noopener noreferrer">the Origin Private File System (OPFS)</a>. As of July 2024, some major browsers (Safari) only provide the synchronous versions of certain operations (e.g. <code>createSyncAccessHandle</code>). These synchronous operations can only be accessed in a web <code>Worker</code>.</p><p>In addition to files stored persistently through the OPFS API, the web <code>fsDriver</code> also supports non-persistent virtual files and access to local user-specified directories.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="virtual-files">Virtual files<a href="#virtual-files" class="hash-link" aria-label="Direct link to Virtual files" title="Direct link to Virtual files"></a></h3><p>In some cases, it doesn&#x27;t make sense to write temporary files to persistent storage. To support this, <code>fsDriver.web</code> supports creating read-only virtual files with <code>createReadOnlyVirtualFile</code>. These files are stored in local memory and can be accessed using most <code>fsDriver</code> methods.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="local-file-access">Local file access<a href="#local-file-access" class="hash-link" aria-label="Direct link to Local file access" title="Direct link to Local file access"></a></h3><p>In some browsers, it&#x27;s possible to mount local directories using <code>fsDriver.mountExternalDirectory</code>. This method takes a handle returned by <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker" target="_blank" rel="noopener noreferrer"><code>showDirectoryPicker</code></a>, which, as of 2024, has <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker#browser_compatibility" target="_blank" rel="noopener noreferrer">limited browser support</a>.</p><p>To persist access to external directories, file system handles are stored in <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/indexedDB" target="_blank" rel="noopener noreferrer">an <code>indexedDB</code> database</a>, which supports storing and loading file system handles.</p><p>External files are mounted as subdirectories of the virtual <code>/external/</code> directory.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="database-and-cross-origin-isolation">Database and cross-origin isolation<a href="#database-and-cross-origin-isolation" class="hash-link" aria-label="Direct link to Database and cross-origin isolation" title="Direct link to Database and cross-origin isolation"></a></h2><p>The web app uses <a href="https://github.com/sqlite/sqlite-wasm" target="_blank" rel="noopener noreferrer"><code>@sqlite.org/sqlite-wasm</code></a> for database access.</p><p>To function properly (likely due to use of <code>SharedArrayBuffer</code>), <code>@sqlite.org/sqlite-wasm</code> requires <a href="https://web.dev/articles/coop-coep" target="_blank" rel="noopener noreferrer">cross origin isolation to be enabled</a>. This is done adding certain HTTP headers to responses from the server.</p><p>As of July 2024, the official deployment of the Web App is hosted on GitHub pages, <a href="https://github.com/orgs/community/discussions/13309" target="_blank" rel="noopener noreferrer">which doesn&#x27;t support serving with these headers</a>. The Web App works around this by providing these headers in a <code>ServiceWorker</code>.</p><p>The Web App&#x27;s <code>ServiceWorker</code> is a heavily-modified fork of <a href="https://github.com/gzuidhof/coi-serviceworker" target="_blank" rel="noopener noreferrer">the coi-serviceworker project</a>, which enables cross-origin isolation.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="single-instance-lock">Single-instance lock<a href="#single-instance-lock" class="hash-link" aria-label="Direct link to Single-instance lock" title="Direct link to Single-instance lock"></a></h2><p>To prevent data corruption due to out-of-sync state between tabs, the Web App currently only allows one copy of the app to be open at a time.</p><p>This single-instance lock is enforced using a <code>ServiceWorker</code>. If a user attempts to load the Web App in a new tab, the <code>ServiceWorker</code>,</p><ol><li>determines whether the request is for the Web App (see <code>handleRedirects</code> in <code>serviceWorker.ts</code>),</li><li>if so, intercepts the request,</li><li>checks for an already-running copy of the app, and</li><li>if a copy of the app is already running, returns an error page.</li></ol><p>If the <code>ServiceWorker</code> fails to register (and cross-origin isolation is enabled by the server), it may be possible to bypass the <code>ServiceWorker</code>&#x27;s single-instance lock. As such, a secondary single-instance lock is also present. This lock attempts to communicate with other open Web Apps using a <code>BroadcastChannel</code>:</p><ul><li>This might succeed even if the original <code>ServiceWorker</code> failed to register.</li><li>This check might incorrectly report that only one app is open if other apps are in a different tab and haven&#x27;t been used recently.</li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="offline-support">Offline support<a href="#offline-support" class="hash-link" aria-label="Direct link to Offline support" title="Direct link to Offline support"></a></h2><p>The <code>ServiceWorker</code> intercepts and caches responses to requests for certain file types <strong>from the same domain as the web client</strong>. In the future, when equivalent requests fail, the <code>ServiceWorker</code> responds with a cached response.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="webviews">WebViews<a href="#webviews" class="hash-link" aria-label="Direct link to WebViews" title="Direct link to WebViews"></a></h2><p>On all platforms, WebViews that load a local file should use <code>ExtendedWebView</code>. This component uses a different WebView implementation on different platforms:</p><ul><li>Android and iOS use <code>react-native-webview</code>,</li><li>Jest uses <code>JSDOM</code>-based mock, and</li><li>Web uses a sandboxed <code>iframe</code>.<ul><li>As of July 2024, <code>react-native-webview</code> does not support web.</li></ul></li></ul><p>This section summarizes how <code>iframe</code>-based WebViews work on web.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="ipc">IPC<a href="#ipc" class="hash-link" aria-label="Direct link to IPC" title="Direct link to IPC"></a></h3><p>High-level communication can be done with <code>ExtendedWebView</code> using an <code>RNToWebViewMessenger</code> paired with a <code>WebViewToRNMessenger</code>. These <code>RemoteMessenger</code>s expose methods on JavaScript objects to content inside or outside a <code>WebView</code>.</p><p>Although <code>RNToWebViewMessenger</code> and <code>WebViewToRNMessenger</code> wrap <code>.postMessage</code> and <code>.onmessage</code> with a higher-level API, it&#x27;s still possible to use the lower-level messaging API.</p><h4 class="anchor anchorWithStickyNavbar_LWe7" id="postmessage"><code>.postMessage</code><a href="#postmessage" class="hash-link" aria-label="Direct link to postmessage" title="Direct link to postmessage"></a></h4><p>For compatibility with <code>react-native-webview</code>, <code>ExtendedWebView</code> exposes a <code>ReactNativeWebView</code> global object to content running within the <code>WebView</code>.</p><p>For example, the following syntax can be used to send some <code>message</code> to the <code>ExtendedWebView</code>&#x27;s <code>onMessage</code> handler.</p><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token maybe-class-name">ReactNativeWebView</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">postMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Calls onMessage</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h4 class="anchor anchorWithStickyNavbar_LWe7" id="windowonmessage"><code>window.onmessage</code><a href="#windowonmessage" class="hash-link" aria-label="Direct link to windowonmessage" title="Direct link to windowonmessage"></a></h4><p>Message sent to the WebView using <code>webviewRef.postMessage</code> are received by the global <code>onmessage</code> event. For example,</p><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// ...within some component</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> webViewRef </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">useRef</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">WebViewControl</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">ExtendedWebView</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">webviewInstanceId</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">&#x27;</span><span class="token tag attr-value" style="color:#e3116c">test-webview</span><span class="token tag attr-value punctuation" style="color:#393A34">&#x27;</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">html</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript string" style="color:#e3116c">&#x27;some html here&#x27;</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">injectedJavaScript</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:#e3116c">`</span><span class="token tag script language-javascript template-string string" style="color:#e3116c"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag script language-javascript template-string string" style="color:#e3116c"> window.addEventListener(&#x27;message&#x27;, event =&gt; {</span><br></span><span class="token-line" style="color:#393A34"><span class="token tag script language-javascript template-string string" style="color:#e3116c"> if (event.origin === &#x27;react-native&#x27;) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token tag script language-javascript template-string string" style="color:#e3116c"> const messageData = event.data;</span><br></span><span class="token-line" style="color:#393A34"><span class="token tag script language-javascript template-string string" style="color:#e3116c"> // ...use event.data...</span><br></span><span class="token-line" style="color:#393A34"><span class="token tag script language-javascript template-string string" style="color:#e3116c"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token tag script language-javascript template-string string" style="color:#e3116c"> });</span><br></span><span class="token-line" style="color:#393A34"><span class="token tag script language-javascript template-string string" style="color:#e3116c"> </span><span class="token tag script language-javascript template-string template-punctuation string" style="color:#e3116c">`</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">ref</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">webViewRef</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onLoadEnd</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"> webViewRef</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">current</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript method function property-access" style="color:#d73a49">postMessage</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript string" style="color:#e3116c">&#x27;test&#x27;</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h2 class="anchor anchorWithStickyNavbar_LWe7" id="note-viewer">Note viewer<a href="#note-viewer" class="hash-link" aria-label="Direct link to Note viewer" title="Direct link to Note viewer"></a></h2><p>As on Android and iOS, the <code>NoteBodyViewer</code> uses an <code>ExtendedWebView</code> to render and display notes. However, because the Web App uses a virtual file system, extra processing is needed to transfer resources to the WebView.</p><p>For resources, this process might look like this:</p><p>A similar process loads plugin assets (e.g. CSS and fonts used by rendered math).</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="incompatible-libraries">Incompatible libraries<a href="#incompatible-libraries" class="hash-link" aria-label="Direct link to Incompatible libraries" title="Direct link to Incompatible libraries"></a></h2><p>Some libraries are incompatible with <code>react-native-web</code>. There are two or more ways to handle this:</p><ol><li>Use the library only in an Android and iOS-only file. For example, if the library is used in <code>shareImage.ts</code>, create <code>shareImage.web.ts</code> with a web-only implementation. On web, the <code>.web.ts</code> extension is preferred to the <code>.ts</code> extension. On other platforms, this is not the case. As such, on web <code>shareImage.web.ts</code> will be imported, while on other platforms <code>shareImage.ts</code> will be.</li><li>Replace the incompatible library with an empty mock (see <code>web/webpack.config.js</code>). This can be useful if the library is <code>imported</code> by a code that is known to be unreachable on web.</li></ol></div><footer class="theme-doc-footer docusaurus-mt-lg"><div class="theme-doc-footer-edit-meta-row row"><div class="col"><a href="https://github.com/laurent22/joplin/tree/dev/readme/dev/spec/web_app.md" target="_blank" rel="noreferrer noopener" class="theme-edit-this-page"><svg fill="currentColor" height="20" width="20" viewBox="0 0 40 40" class="iconEdit_Z9Sw" aria-hidden="true"><g><path d="m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"></path></g></svg>Edit this page</a></div><div class="col lastUpdated_vwxv"></div></div></footer></article><nav class="pagination-nav docusaurus-mt-lg" aria-label="Docs pages"><a class="pagination-nav__link pagination-nav__link--prev" href="/help/dev/spec/voice_typing"><div class="pagination-nav__sublabel">Previous</div><div class="pagination-nav__label">Voice typing</div></a><a class="pagination-nav__link pagination-nav__link--next" href="/help/dev/gsoc/gsoc2020/"><div class="pagination-nav__sublabel">Next</div><div class="pagination-nav__label">Google Summer of Code 2020</div></a></nav></div></div><div class="col col--3"><div class="tableOfContents_bqdL thin-scrollbar theme-doc-toc-desktop"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#file-system" class="table-of-contents__link toc-highlight">File system</a><ul><li><a href="#virtual-files" class="table-of-contents__link toc-highlight">Virtual files</a></li><li><a href="#local-file-access" class="table-of-contents__link toc-highlight">Local file access</a></li></ul></li><li><a href="#database-and-cross-origin-isolation" class="table-of-contents__link toc-highlight">Database and cross-origin isolation</a></li><li><a href="#single-instance-lock" class="table-of-contents__link toc-highlight">Single-instance lock</a></li><li><a href="#offline-support" class="table-of-contents__link toc-highlight">Offline support</a></li><li><a href="#webviews" class="table-of-contents__link toc-highlight">WebViews</a><ul><li><a href="#ipc" class="table-of-contents__link toc-highlight">IPC</a></li></ul></li><li><a href="#note-viewer" class="table-of-contents__link toc-highlight">Note viewer</a></li><li><a href="#incompatible-libraries" class="table-of-contents__link toc-highlight">Incompatible libraries</a></li></ul></div></div></div></div></main></div></div><footer class="footer footer--dark"><div class="container container-fluid"><div class="row footer__links"><div class="col footer__col"><div class="footer__title">Community</div><ul class="footer__items clean-list"><li class="footer__item"><a href="https://twitter.com/joplinapp" target="_blank" rel="noopener noreferrer" class="footer__link-item">Twitter<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://www.patreon.com/joplin" target="_blank" rel="noopener noreferrer" class="footer__link-item">Patreon<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://www.linkedin.com/company/joplin" target="_blank" rel="noopener noreferrer" class="footer__link-item">LinkedIn<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://discord.gg/VSj7AFHvpq" target="_blank" rel="noopener noreferrer" class="footer__link-item">Discord<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://mastodon.social/@joplinapp" target="_blank" rel="noopener noreferrer" class="footer__link-item">Mastodon<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://sopuli.xyz/c/joplinapp" target="_blank" rel="noopener noreferrer" class="footer__link-item">Lemmy<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://github.com/laurent22/joplin/" target="_blank" rel="noopener noreferrer" class="footer__link-item">GitHub<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li></ul></div><div class="col footer__col"><div class="footer__title">Legal</div><ul class="footer__items clean-list"><li class="footer__item"><a href="https://joplinapp.org/privacy" target="_blank" rel="noopener noreferrer" class="footer__link-item">Privacy Policy</a></li></ul></div></div><div class="footer__bottom text--center"><div class="footer__copyright">Copyright © 2016-2024 Laurent Cozic</div></div></div></footer></div>
<script src="/assets/js/runtime~main.1fb4f966.js"></script>
<script src="/assets/js/main.aa9faae4.js"></script>
</body>
</html>