├── tests ├── 404.html ├── platform │ ├── navigator │ │ ├── api.js │ │ └── navigator.spec.ts │ ├── script │ │ ├── set-get-attr.js │ │ ├── jsonp-a.js │ │ ├── jsonp-b.js │ │ ├── jsonp-c.js │ │ ├── async-1.js │ │ ├── async-2.js │ │ ├── defer-1.js │ │ ├── defer-2.js │ │ └── source-mapping-url.js │ ├── window │ │ ├── text.txt │ │ └── data.json │ ├── iframe │ │ ├── onload.js │ │ ├── external.js │ │ ├── location2.js │ │ ├── no-global-share.js │ │ ├── external-js.html │ │ ├── location2.html │ │ ├── no-global-share.html │ │ ├── global-var.html │ │ ├── content.html │ │ ├── src-change1.html │ │ ├── call-fn-on-parent.js │ │ ├── no-deadlock.html │ │ ├── current-script.js │ │ ├── no-deadlock.js │ │ ├── src-change2.html │ │ ├── post-message.html │ │ ├── fn-on-window.js │ │ ├── location1.html │ │ ├── parent-window.js │ │ └── cross-origin.html │ ├── image │ │ ├── dot.gif │ │ └── image.spec.ts │ ├── audio │ │ ├── submit.mp3 │ │ ├── audio-scripts.js │ │ └── audio.spec.ts │ ├── history │ │ └── iframe-history.html │ ├── document-prod │ │ └── current-script-src.js │ ├── document │ │ └── current-script-src.js │ ├── svg │ │ └── svg.spec.ts │ ├── screen │ │ └── screen.spec.ts │ ├── intersection-observer │ │ └── intersection-observer.spec.ts │ ├── video │ │ └── video.spec.__ts │ ├── element-style │ │ └── element-style.spec.ts │ ├── canvas │ │ └── canvas.spec.ts │ ├── style │ │ └── style.spec.ts │ ├── mutation-observer │ │ └── mutation-observer.spec.ts │ ├── resize-observer │ │ └── resize-observer.spec.ts │ ├── anchor │ │ └── anchor.spec.ts │ ├── storage │ │ └── storage.spec.ts │ ├── element-class │ │ └── element-class.spec.ts │ └── node │ │ └── node.spec.ts ├── react-app │ ├── src │ │ ├── react-app-env.d.ts │ │ ├── index.tsx │ │ ├── index.css │ │ ├── App.tsx │ │ ├── App.css │ │ └── logo.svg │ ├── public │ │ └── index.html │ ├── tsconfig.json │ └── package.json ├── nextjs │ ├── next.config.js │ ├── README.md │ ├── pages │ │ ├── _app.js │ │ └── index.js │ ├── playwright.nextjs.ts │ ├── package.json │ └── nextjs.spec.ts ├── integrations │ ├── intercom │ │ └── submit.3abafccd.mp3 │ ├── adobe-launch │ │ ├── adobe-launch.spec.ts │ │ └── standard.html │ ├── gtm │ │ └── gtm.spec.ts │ ├── facebook-pixel │ │ └── facebook-pixel.spec.ts │ ├── event-forwarding │ │ └── event-forwarding.spec.ts │ ├── config │ │ └── config.spec.ts │ ├── mermaid │ │ ├── standard.html │ │ └── index.html │ ├── hubspot │ │ ├── forms-standard.html │ │ ├── forms.html │ │ ├── 20632911.js │ │ └── scripts-20632911.js │ ├── wistia │ │ ├── standard.html │ │ └── index.html │ └── twitter │ │ └── standard.html ├── unit │ ├── snippet.spec.ts │ └── integration.spec.ts └── benchmarks │ ├── run.cjs │ ├── benchmark.js │ └── index.html ├── docs ├── site │ ├── .npmrc │ ├── public │ │ ├── 196x196.png │ │ ├── 256x256.png │ │ ├── 32x32.png │ │ ├── favicon.ico │ │ ├── partytown-logo.png │ │ └── partytown-media.png │ ├── tsconfig.json │ ├── src │ │ ├── config.ts │ │ ├── components │ │ │ ├── Header │ │ │ │ ├── SkipToContent.astro │ │ │ │ └── SidebarToggle.tsx │ │ │ ├── RightSidebar │ │ │ │ ├── RightSidebar.astro │ │ │ │ ├── MoreMenu.astro │ │ │ │ └── TableOfContents.tsx │ │ │ ├── Footer │ │ │ │ └── Footer.astro │ │ │ ├── HeadCommon.astro │ │ │ ├── HeadSEO.astro │ │ │ └── PageContent │ │ │ │ └── PageContent.astro │ │ └── styles │ │ │ └── code.css │ ├── astro.config.mjs │ ├── README.md │ └── package.json ├── integrations.md ├── sandboxing.md ├── nuxt.md ├── debugging.md ├── getting-started.md ├── google-tag-manager.md ├── partytown-scripts.md ├── _table-of-contents.md ├── html.md ├── facebook-pixel.md ├── browser-support.md ├── astro.md ├── react.md ├── nextjs.md ├── forwarding-events.md └── remix.md ├── .gitattributes ├── src ├── lib │ ├── build-modules │ │ ├── version.ts │ │ ├── web-worker-blob.ts │ │ ├── web-worker-url.ts │ │ ├── sync-create-messenger.ts │ │ └── sync-send-message-to-main.ts │ ├── main │ │ └── snippet-entry.ts │ ├── web-worker │ │ ├── worker-performance.ts │ │ ├── worker-location.ts │ │ ├── media │ │ │ ├── bridge.ts │ │ │ ├── index.ts │ │ │ ├── time-ranges.ts │ │ │ ├── audio.ts │ │ │ ├── utils.ts │ │ │ ├── canvas │ │ │ │ ├── index.ts │ │ │ │ └── context-webgl.ts │ │ │ ├── media-element.ts │ │ │ └── audio-track.ts │ │ ├── worker-element.ts │ │ ├── worker-node-list.ts │ │ ├── worker-forwarded-trigger.ts │ │ ├── worker-state.ts │ │ ├── worker-navigator.ts │ │ ├── worker-css-style-declaration.ts │ │ ├── worker-constructors.ts │ │ ├── worker-environment.ts │ │ ├── worker-media.ts │ │ ├── worker-anchor.ts │ │ ├── worker-image.ts │ │ ├── worker-instance.ts │ │ ├── worker-script.ts │ │ ├── init-web-worker.ts │ │ ├── worker-src-element.ts │ │ └── worker-storage.ts │ ├── sandbox │ │ ├── main-globals.ts │ │ ├── main-constants.ts │ │ ├── main-forward-trigger.ts │ │ ├── on-messenge-from-worker.ts │ │ ├── index.ts │ │ └── main-register-window.ts │ ├── service-worker │ │ ├── index.ts │ │ ├── sync-send-message-to-main-sw.ts │ │ └── sync-create-messenger-sw.ts │ └── atomics │ │ ├── sync-send-message-to-main-atomics.ts │ │ └── sync-create-messenger-atomics.ts ├── react │ ├── index.ts │ ├── package.json │ ├── api.md │ └── api-extractor.json ├── services │ ├── index.ts │ ├── package.json │ ├── google-tag-manager.ts │ ├── facebook-pixel.ts │ ├── freshpaint.ts │ ├── api.md │ └── api-extractor.json ├── utils │ ├── index.ts │ ├── package.json │ ├── api.md │ └── api-extractor.json ├── integration │ ├── package.json │ ├── index.ts │ ├── snippet.ts │ ├── api-extractor.json │ └── api.md └── modules.d.ts ├── .prettierignore ├── playwright.atomics.config.ts ├── scripts ├── serve.cjs ├── rollup.config.js ├── build-api.ts ├── build-utils.ts ├── build-services.ts ├── build-react.ts ├── copy-site.cjs ├── build-media-implementations.ts ├── build-main-snippet.ts ├── build-integration.ts ├── build-atomics.ts └── build-web-worker.ts ├── .gitignore ├── playwright.config.ts ├── tsconfig.json ├── vercel.json ├── LICENSE ├── bin └── partytown.cjs └── DEVELOPER.md /tests/404.html: -------------------------------------------------------------------------------- 1 | 404 2 | -------------------------------------------------------------------------------- /tests/platform/navigator/api.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/site/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /tests/platform/script/set-get-attr.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/platform/window/text.txt: -------------------------------------------------------------------------------- 1 | text -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | tests/** linguist-vendored 2 | -------------------------------------------------------------------------------- /tests/platform/window/data.json: -------------------------------------------------------------------------------- 1 | { "mph": 88 } 2 | -------------------------------------------------------------------------------- /tests/platform/iframe/onload.js: -------------------------------------------------------------------------------- 1 | (function () {})(); 2 | -------------------------------------------------------------------------------- /tests/platform/script/jsonp-a.js: -------------------------------------------------------------------------------- 1 | jsonpA({ mph: 88 }); 2 | -------------------------------------------------------------------------------- /tests/platform/script/jsonp-b.js: -------------------------------------------------------------------------------- 1 | jsonpB({ mph: 99 }); 2 | -------------------------------------------------------------------------------- /tests/platform/script/jsonp-c.js: -------------------------------------------------------------------------------- 1 | jsonpC({ mph: 77 }); 2 | -------------------------------------------------------------------------------- /src/lib/build-modules/version.ts: -------------------------------------------------------------------------------- 1 | export const VERSION = '_VERSION_'; 2 | -------------------------------------------------------------------------------- /src/react/index.ts: -------------------------------------------------------------------------------- 1 | export { Partytown, PartytownProps } from './snippet'; 2 | -------------------------------------------------------------------------------- /tests/react-app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tests/nextjs/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | }; 4 | -------------------------------------------------------------------------------- /src/lib/build-modules/web-worker-blob.ts: -------------------------------------------------------------------------------- 1 | const WEB_WORKER_BLOB = ''; 2 | export default WEB_WORKER_BLOB; 3 | -------------------------------------------------------------------------------- /src/lib/build-modules/web-worker-url.ts: -------------------------------------------------------------------------------- 1 | const WEB_WORKER_URL = ''; 2 | export default WEB_WORKER_URL; 3 | -------------------------------------------------------------------------------- /docs/site/public/196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/partytown/main/docs/site/public/196x196.png -------------------------------------------------------------------------------- /docs/site/public/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/partytown/main/docs/site/public/256x256.png -------------------------------------------------------------------------------- /docs/site/public/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/partytown/main/docs/site/public/32x32.png -------------------------------------------------------------------------------- /docs/site/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/partytown/main/docs/site/public/favicon.ico -------------------------------------------------------------------------------- /tests/platform/image/dot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/partytown/main/tests/platform/image/dot.gif -------------------------------------------------------------------------------- /tests/nextjs/README.md: -------------------------------------------------------------------------------- 1 | ```bash 2 | npm run dev 3 | ``` 4 | 5 | [http://localhost:3000](http://localhost:3000) 6 | -------------------------------------------------------------------------------- /tests/platform/audio/submit.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/partytown/main/tests/platform/audio/submit.mp3 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | /lib 3 | /node_modules 4 | /react 5 | /tsc 6 | api.md 7 | tests/platform/script/source-mapping-url.js -------------------------------------------------------------------------------- /docs/site/public/partytown-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/partytown/main/docs/site/public/partytown-logo.png -------------------------------------------------------------------------------- /docs/site/public/partytown-media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/partytown/main/docs/site/public/partytown-media.png -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './facebook-pixel'; 2 | export * from './freshpaint'; 3 | export * from './google-tag-manager'; 4 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { copyLibFiles, libDirPath } from './copy-lib-files'; 2 | export type { CopyLibFilesOptions } from './copy-lib-files'; 3 | -------------------------------------------------------------------------------- /tests/integrations/intercom/submit.3abafccd.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/partytown/main/tests/integrations/intercom/submit.3abafccd.mp3 -------------------------------------------------------------------------------- /tests/nextjs/pages/_app.js: -------------------------------------------------------------------------------- 1 | function MyApp({ Component, pageProps }) { 2 | return ; 3 | } 4 | 5 | export default MyApp; 6 | -------------------------------------------------------------------------------- /tests/platform/script/async-1.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const elm = document.getElementById('testAsync'); 3 | elm.textContent += ' a1'; 4 | })(); 5 | -------------------------------------------------------------------------------- /tests/platform/script/async-2.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const elm = document.getElementById('testAsync'); 3 | elm.textContent += ' a2'; 4 | })(); 5 | -------------------------------------------------------------------------------- /tests/platform/script/defer-1.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const elm = document.getElementById('testAsync'); 3 | elm.textContent += ' d1'; 4 | })(); 5 | -------------------------------------------------------------------------------- /src/lib/main/snippet-entry.ts: -------------------------------------------------------------------------------- 1 | import { snippet } from './snippet'; 2 | 3 | snippet(window as any, document, navigator, top!, top!.crossOriginIsolated); 4 | -------------------------------------------------------------------------------- /src/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@builder.io/partytown-react", 3 | "main": "index.cjs", 4 | "module": "index.mjs", 5 | "types": "index.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@builder.io/partytown-utils", 3 | "main": "index.cjs", 4 | "module": "index.mjs", 5 | "types": "index.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /src/services/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@builder.io/partytown-services", 3 | "main": "index.cjs", 4 | "module": "index.mjs", 5 | "types": "index.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /tests/platform/iframe/external.js: -------------------------------------------------------------------------------- 1 | window.someWindowVar = { 2 | year: 1985, 3 | }; 4 | 5 | document.getElementById('output').textContent = window.someWindowVar.year; 6 | -------------------------------------------------------------------------------- /src/integration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@builder.io/partytown-integration", 3 | "main": "index.cjs", 4 | "module": "index.mjs", 5 | "types": "index.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /docs/site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "esnext", 5 | "jsx": "preserve" 6 | }, 7 | "moduleResolution": "node" 8 | } 9 | -------------------------------------------------------------------------------- /tests/platform/history/iframe-history.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/platform/script/defer-2.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const elm = document.getElementById('testAsync'); 3 | elm.textContent += ' d2'; 4 | setTimeout(() => { 5 | elm.className = 'testAsync'; 6 | }, 50); 7 | })(); 8 | -------------------------------------------------------------------------------- /docs/site/src/config.ts: -------------------------------------------------------------------------------- 1 | export const SITE = { 2 | title: 'Partytown', 3 | description: 'Run scripts from a web worker.', 4 | defaultLanguage: 'en_US', 5 | }; 6 | 7 | export const GITHUB_EDIT_URL = `https://github.com/BuilderIO/partytown/edit/main/docs`; 8 | -------------------------------------------------------------------------------- /tests/platform/iframe/location2.js: -------------------------------------------------------------------------------- 1 | document.getElementById('output').textContent = location.pathname; 2 | parent.document.getElementById('testLocation').textContent = location.pathname; 3 | parent.document.getElementById('testLocation').className = 'testLocation'; 4 | -------------------------------------------------------------------------------- /tests/platform/iframe/no-global-share.js: -------------------------------------------------------------------------------- 1 | window.noGlobalShare = window.noGlobalShare || { 2 | value: Math.random(), 3 | }; 4 | 5 | (function () { 6 | const elm = document.getElementById('testWWGlobalValue'); 7 | elm.textContent = window.noGlobalShare.value; 8 | })(); 9 | -------------------------------------------------------------------------------- /tests/platform/iframe/external-js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/react-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /src/modules.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@sandbox' { 2 | const str: string; 3 | export default str; 4 | } 5 | 6 | declare module '@sandbox-debug' { 7 | const str: string; 8 | export default str; 9 | } 10 | 11 | declare module '@snippet' { 12 | const str: string; 13 | export default str; 14 | } 15 | -------------------------------------------------------------------------------- /src/services/google-tag-manager.ts: -------------------------------------------------------------------------------- 1 | import type { PartytownForwardProperty } from '../lib/types'; 2 | 3 | /** 4 | * Forwards Google Tag Manager main window calls to Partytown's worker thread. 5 | * 6 | * @public 7 | */ 8 | export const googleTagManagerForward: PartytownForwardProperty[] = ['dataLayer.push']; 9 | -------------------------------------------------------------------------------- /src/lib/web-worker/worker-performance.ts: -------------------------------------------------------------------------------- 1 | import { WorkerInstance } from './worker-instance'; 2 | 3 | export class Performance extends WorkerInstance { 4 | now() { 5 | // use the web worker's performance.now() 6 | // no need to go to main for this 7 | return performance.now(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/build-modules/sync-create-messenger.ts: -------------------------------------------------------------------------------- 1 | import type { Messenger } from '../types'; 2 | 3 | const syncCreateMessenger: Messenger = async (receiveMessage) => { 4 | // dynamically replaced at build-time with 5 | receiveMessage; 6 | return null as any; 7 | }; 8 | 9 | export default syncCreateMessenger; 10 | -------------------------------------------------------------------------------- /tests/platform/iframe/location2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/platform/script/source-mapping-url.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see LICENSE.txt */ 2 | (function () { 3 | const elm = document.getElementById('testSourceMappingURL'); 4 | elm.textContent = 'sourceMappingURL'; 5 | elm.className = 'testSourceMappingURL'; 6 | })(); 7 | //# sourceMappingURL=source-mapping-url.js.map -------------------------------------------------------------------------------- /tests/platform/iframe/no-global-share.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/web-worker/worker-location.ts: -------------------------------------------------------------------------------- 1 | import { logWorker } from '../log'; 2 | 3 | export class Location extends URL { 4 | assign() { 5 | logWorker(`location.assign(), noop`); 6 | } 7 | reload() { 8 | logWorker(`location.reload(), noop`); 9 | } 10 | replace() { 11 | logWorker(`location.replace(), noop`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/platform/iframe/global-var.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /playwright.atomics.config.ts: -------------------------------------------------------------------------------- 1 | import config from './playwright.config'; 2 | 3 | config.use.launchOptions = { 4 | args: ['--enable-features=SharedArrayBuffer'], 5 | }; 6 | config.use.contextOptions.recordVideo = undefined; 7 | config.webServer.command = 'npm run serve.atomics.test'; 8 | config.webServer.port = 4003; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /tests/platform/document-prod/current-script-src.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const elm = document.getElementById('testCurrentScriptSrc'); 3 | elm.textContent = document.currentScript.dataset.currentScript; 4 | 5 | const loc = document.getElementById('testCurrentScriptSrcLocation'); 6 | loc.textContent = new URL(document.currentScript.src).pathname; 7 | })(); 8 | -------------------------------------------------------------------------------- /tests/nextjs/playwright.nextjs.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm start', 6 | port: 3000, 7 | timeout: 120 * 1000, 8 | reuseExistingServer: !process.env.CI, 9 | }, 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /src/lib/build-modules/sync-send-message-to-main.ts: -------------------------------------------------------------------------------- 1 | import type { MainAccessResponse } from '../types'; 2 | 3 | export default function syncSendMessageToMainServiceWorker( 4 | webWorkerCtx: any, 5 | accessReq: any 6 | ): MainAccessResponse { 7 | // dynamically replaced at build-time with 8 | webWorkerCtx; 9 | accessReq; 10 | return null as any; 11 | } 12 | -------------------------------------------------------------------------------- /src/services/facebook-pixel.ts: -------------------------------------------------------------------------------- 1 | import type { PartytownForwardProperty } from '../lib/types'; 2 | 3 | /** 4 | * Forwards Facebool Pixels main window calls to Partytown's worker thread. 5 | * 6 | * https://developers.facebook.com/docs/facebook-pixel/get-started 7 | * 8 | * @public 9 | */ 10 | export const facebookPixelForward: PartytownForwardProperty[] = ['fbq']; 11 | -------------------------------------------------------------------------------- /scripts/serve.cjs: -------------------------------------------------------------------------------- 1 | const { createServer } = require('./server.cjs'); 2 | 3 | const port = parseInt(process.argv[2], 10); 4 | const enableAtomics = process.argv.includes('--atomics'); 5 | 6 | (async () => { 7 | const server = await createServer(port, enableAtomics); 8 | console.log(`Serving${server.enableAtomics ? ` (atomics)` : ``}: ${server.address}tests/`); 9 | })(); 10 | -------------------------------------------------------------------------------- /src/lib/sandbox/main-globals.ts: -------------------------------------------------------------------------------- 1 | import type { MainWindow, PartytownConfig } from '../types'; 2 | import { debug } from '../utils'; 3 | 4 | export const mainWindow: MainWindow = window.parent; 5 | export const doc = document; 6 | 7 | export const config: PartytownConfig = mainWindow.partytown || {}; 8 | export const libPath = (config.lib || '/~partytown/') + (debug ? 'debug/' : ''); 9 | -------------------------------------------------------------------------------- /tests/platform/iframe/content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 |
12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/platform/document/current-script-src.js: -------------------------------------------------------------------------------- 1 | const elm = document.getElementById('testCurrentScriptSrc'); 2 | elm.textContent = document.currentScript.dataset.currentScript; 3 | 4 | const loc = document.getElementById('testCurrentScriptSrcLocation'); 5 | 6 | const currentScript = document.currentScript; 7 | const currentUrl = new URL(currentScript.src); 8 | loc.textContent = currentUrl.pathname; 9 | -------------------------------------------------------------------------------- /tests/platform/iframe/src-change1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /scripts/rollup.config.js: -------------------------------------------------------------------------------- 1 | // build scripts are generated after tsc runs 2 | import { runBuild } from '../tsc/scripts/index.js'; 3 | import { join } from 'path'; 4 | 5 | export default function (cmdArgs) { 6 | const rootDir = join(__dirname, '..'); 7 | const isDev = !!cmdArgs.configDev; 8 | const generateApi = !!cmdArgs.configApi; 9 | return runBuild(rootDir, isDev, generateApi); 10 | } 11 | -------------------------------------------------------------------------------- /tests/platform/audio/audio-scripts.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const audio = new Audio('./submit.mp3?testNewAudio'); 3 | document.getElementById('btnNewAudio').addEventListener('click', () => { 4 | audio.play().then(() => { 5 | const elm = document.getElementById('testNewAudio'); 6 | elm.textContent = 'played'; 7 | elm.className = 'testNewAudio'; 8 | }); 9 | }); 10 | })(); 11 | -------------------------------------------------------------------------------- /src/lib/web-worker/media/bridge.ts: -------------------------------------------------------------------------------- 1 | import type { MediaSelf } from '../../types'; 2 | 3 | export const [ 4 | getter, 5 | setter, 6 | callMethod, 7 | constructGlobal, 8 | definePrototypePropertyDescriptor, 9 | randomId, 10 | WorkerProxy, 11 | WorkerEventTargetProxy, 12 | WinIdKey, 13 | InstanceIdKey, 14 | ApplyPathKey, 15 | ] = self.ptm!; 16 | 17 | declare const self: MediaSelf; 18 | -------------------------------------------------------------------------------- /tests/platform/iframe/call-fn-on-parent.js: -------------------------------------------------------------------------------- 1 | const parentWindow = window.parent; 2 | const rtn = parentWindow.sum1985(30); 3 | 4 | const valElm = document.createElement('span'); 5 | valElm.textContent = rtn; 6 | document.body.appendChild(valElm); 7 | 8 | const elm = parentWindow.document.getElementById('testCallWindowParentFn'); 9 | elm.textContent = rtn; 10 | elm.className = 'testCallWindowParentFn'; 11 | -------------------------------------------------------------------------------- /tests/platform/iframe/no-deadlock.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/services/freshpaint.ts: -------------------------------------------------------------------------------- 1 | import type { PartytownForwardProperty } from '../lib/types'; 2 | 3 | /** 4 | * Forwards Freshpaint.io main window calls to Partytown's worker thread. 5 | * 6 | * https://www.freshpaint.io/ 7 | * 8 | * @public 9 | */ 10 | export const freshpaintForward: PartytownForwardProperty[] = [ 11 | 'freshpaint.addPageviewProperties', 12 | 'freshpaint.identify', 13 | 'freshpaint.track', 14 | ]; 15 | -------------------------------------------------------------------------------- /tests/platform/iframe/current-script.js: -------------------------------------------------------------------------------- 1 | const elm = window.parent.document.getElementById('testCurrentScript'); 2 | elm.className = 'testCurrentScript'; 3 | 4 | const currentScript = document.currentScript; 5 | const currentUrl = new URL(currentScript.src); 6 | elm.textContent = currentUrl.pathname; 7 | 8 | const div = document.createElement('div'); 9 | div.textContent = elm.textContent; 10 | document.body.appendChild(div); 11 | -------------------------------------------------------------------------------- /tests/platform/iframe/no-deadlock.js: -------------------------------------------------------------------------------- 1 | function fn(x) { 2 | const win = window; 3 | const winParent = win.parent; 4 | const winParentDocument = winParent.document; 5 | 6 | const iframe = winParentDocument.getElementById('deadlock-iframe'); 7 | 8 | const iframeWin = iframe.contentWindow; 9 | 10 | const result = iframeWin.val + x; 11 | 12 | return result; 13 | } 14 | 15 | window.fn = fn; 16 | window.val = 88; 17 | -------------------------------------------------------------------------------- /tests/platform/iframe/src-change2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/react-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /docs/site/astro.config.mjs: -------------------------------------------------------------------------------- 1 | // Full Astro Configuration API Documentation: 2 | // https://docs.astro.build/reference/configuration-reference 3 | 4 | // @ts-check 5 | export default /** @type {import('astro').AstroUserConfig} */ ({ 6 | renderers: [ 7 | // Enable the Preact renderer to support Preact JSX components. 8 | '@astrojs/renderer-preact', 9 | ], 10 | buildOptions: { 11 | site: 'https://partytown.builder.io', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /tests/platform/iframe/post-message.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | iframe origin: 11 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/react-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React App 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | .next/ 3 | .DS_Store 4 | /dist/ 5 | /docs/site/dist/ 6 | /docs/site/public/~partytown/ 7 | /docs/site/src/table-of-contents.ts 8 | /docs/site/src/pages/ 9 | node_modules/ 10 | /integration/ 11 | /lib/ 12 | /react/ 13 | /tests/nextjs/public/ 14 | /tests/benchmarks/screenshots/ 15 | /tests/posts/ 16 | /tests/react-app/public/~partytown/ 17 | /tests/videos/ 18 | /services/ 19 | /tsc/ 20 | /utils/ 21 | /index.cjs 22 | /index.mjs 23 | /index.d.ts -------------------------------------------------------------------------------- /tests/integrations/adobe-launch/adobe-launch.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('adobe-launch', async ({ page }) => { 4 | await page.goto('/tests/integrations/adobe-launch/'); 5 | 6 | const btnTrack = page.locator('#track'); 7 | await btnTrack.click(); 8 | 9 | await page.waitForSelector('.completed'); 10 | 11 | const result = page.locator('#result'); 12 | await expect(result).toHaveText('tracked'); 13 | }); 14 | -------------------------------------------------------------------------------- /src/lib/web-worker/media/index.ts: -------------------------------------------------------------------------------- 1 | import { createAudioConstructor } from './audio'; 2 | import './media-element'; 3 | import { createMediaSourceConstructor } from './media-source'; 4 | import './canvas/index'; 5 | import type { InitLazyMediaConstructors } from '../../types'; 6 | 7 | const MediaCstrs: InitLazyMediaConstructors = { 8 | Audio: createAudioConstructor, 9 | MediaSource: createMediaSourceConstructor, 10 | }; 11 | 12 | (self as any).ptm = MediaCstrs; 13 | -------------------------------------------------------------------------------- /docs/site/README.md: -------------------------------------------------------------------------------- 1 | # Party Docs Site 2 | 3 | | Command | Action | 4 | | :---------------- | :------------------------------------------- | 5 | | `npm install` | Installs dependencies | 6 | | `npm run dev` | Starts local dev server at `localhost:3000` | 7 | | `npm run build` | Build your production site to `./dist/` | 8 | | `npm run preview` | Preview your build locally, before deploying | 9 | -------------------------------------------------------------------------------- /tests/platform/iframe/fn-on-window.js: -------------------------------------------------------------------------------- 1 | const parentWindow = window.parent; 2 | parentWindow.sum1955 = (val) => 1955 + val; 3 | 4 | const fn = parentWindow.sum1955; 5 | const rtn = fn(30); 6 | 7 | const output = document.createElement('span'); 8 | output.id = 'output'; 9 | output.textContent = rtn; 10 | document.body.appendChild(output); 11 | 12 | const elm = parentWindow.document.getElementById('testSetCallWindowParentFn'); 13 | elm.textContent = output.textContent; 14 | -------------------------------------------------------------------------------- /tests/integrations/gtm/gtm.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('gtm', async ({ page }) => { 4 | await page.goto('/tests/integrations/gtm/'); 5 | 6 | await page.waitForSelector('.completed'); 7 | 8 | const buttonDataLayerPush = page.locator('#buttonDataLayerPush'); 9 | await buttonDataLayerPush.click(); 10 | 11 | const testDataLayer = page.locator('#testDataLayer'); 12 | await expect(testDataLayer).toHaveText('pushed'); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-app", 3 | "private": true, 4 | "scripts": { 5 | "dev": "npm run partytown && next dev", 6 | "build": "npm run partytown && next build", 7 | "start": "next start", 8 | "partytown": "../../bin/partytown.cjs copylib public/~partytown" 9 | }, 10 | "dependencies": { 11 | "@builder.io/partytown": "0.2.3", 12 | "react": "^17.0.2", 13 | "react-dom": "^17.0.2", 14 | "next": "12.0.10" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/integrations/facebook-pixel/facebook-pixel.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('facebook-pixel', async ({ page }) => { 4 | await page.goto('/tests/integrations/facebook-pixel/'); 5 | 6 | await page.waitForSelector('.completed'); 7 | 8 | const buttonSendEvent = page.locator('#buttonSendEvent'); 9 | await buttonSendEvent.click(); 10 | 11 | const testFbq = page.locator('#testFbq'); 12 | await expect(testFbq).toHaveText('called'); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/platform/iframe/location1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 |
12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/platform/svg/svg.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('svg', async ({ page }) => { 4 | await page.goto('/tests/platform/svg/'); 5 | 6 | await page.waitForSelector('.completed'); 7 | 8 | const testCreateNS = page.locator('#testCreateNS'); 9 | await expect(testCreateNS).toHaveText('svg 1'); 10 | 11 | const testNamespaceURI = page.locator('#testNamespaceURI'); 12 | await expect(testNamespaceURI).toHaveText('http://www.w3.org/2000/svg'); 13 | }); 14 | -------------------------------------------------------------------------------- /src/lib/sandbox/main-constants.ts: -------------------------------------------------------------------------------- 1 | import type { MainWindow, MainWindowContext, WinId } from '../types'; 2 | 3 | export const InstanceIdKey = /*#__PURE__*/ Symbol(); 4 | export const CreatedKey = /*#__PURE__*/ Symbol(); 5 | export const instances = /*#__PURE__*/ new Map(); 6 | export const mainRefs = /*#__PURE__*/ new Map(); 7 | export const winCtxs: { [winId: WinId]: MainWindowContext | undefined } = {}; 8 | export const windowIds = /*#__PURE__*/ new WeakMap(); 9 | -------------------------------------------------------------------------------- /src/lib/web-worker/media/time-ranges.ts: -------------------------------------------------------------------------------- 1 | import { callMethod, getter, WorkerProxy } from './bridge'; 2 | import { defineCstr } from './utils'; 3 | 4 | export class TimeRanges extends WorkerProxy { 5 | start(...args: any[]) { 6 | return callMethod(this, ['start'], args); 7 | } 8 | 9 | end(...args: any[]) { 10 | return callMethod(this, ['end'], args); 11 | } 12 | 13 | get length() { 14 | return getter(this, ['length']); 15 | } 16 | } 17 | 18 | defineCstr('TimeRanges', TimeRanges); 19 | -------------------------------------------------------------------------------- /src/lib/web-worker/media/audio.ts: -------------------------------------------------------------------------------- 1 | import type { InitLazyMediaConstructor, WebWorkerEnvironment } from '../../types'; 2 | import { defineCstrName } from './utils'; 3 | 4 | export const createAudioConstructor: InitLazyMediaConstructor = (env: WebWorkerEnvironment) => 5 | defineCstrName( 6 | 'HTMLAudioElement', 7 | class { 8 | constructor(src?: string) { 9 | const audio = env.$document$.createElement('audio'); 10 | audio.src = src!; 11 | return audio; 12 | } 13 | } 14 | ); 15 | -------------------------------------------------------------------------------- /tests/platform/screen/screen.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('screen', async ({ page }) => { 4 | await page.goto('/tests/platform/screen/'); 5 | 6 | await page.waitForSelector('.completed'); 7 | 8 | const testWidthHeight = page.locator('#testWidthHeight'); 9 | const dimensions = (await testWidthHeight.textContent()).split('x'); 10 | const w = parseInt(dimensions[0], 10); 11 | const h = parseInt(dimensions[0], 10); 12 | expect(w > 9).toBe(true); 13 | expect(h > 9).toBe(true); 14 | }); 15 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | use: { 5 | viewport: { 6 | width: 520, 7 | height: 600, 8 | }, 9 | contextOptions: { 10 | recordVideo: { 11 | dir: 'tests/videos/', 12 | }, 13 | }, 14 | }, 15 | webServer: { 16 | command: 'npm run serve.test', 17 | port: 4001, 18 | timeout: 120 * 1000, 19 | reuseExistingServer: !process.env.CI, 20 | }, 21 | }; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /tests/platform/navigator/navigator.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('navigator', async ({ page }) => { 4 | await page.goto('/tests/platform/navigator/'); 5 | 6 | const testUserAgent = await page.waitForSelector('.testUserAgent'); 7 | const ua = await testUserAgent.textContent(); 8 | expect(ua.startsWith('Mozilla/5.0')).toBe(true); 9 | 10 | await page.waitForSelector('.testSendBeacon'); 11 | const testSendBeacon = page.locator('#testSendBeacon'); 12 | await expect(testSendBeacon).toContainText('true'); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/nextjs/nextjs.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('nextjs', async ({ page }) => { 4 | await page.goto('/tests/'); 5 | 6 | await page.waitForSelector('.completed'); 7 | 8 | const h1 = page.locator('h1'); 9 | await expect(h1).toHaveText('Next.js with 🎉'); 10 | 11 | const outputNextScript = page.locator('#output-next-script'); 12 | await expect(outputNextScript).toHaveText('passed'); 13 | 14 | const outputScript = page.locator('#output-script'); 15 | await expect(outputScript).toHaveText('passed'); 16 | }); 17 | -------------------------------------------------------------------------------- /src/react/api.md: -------------------------------------------------------------------------------- 1 | ## API Report File for "@builder.io/partytown-react" 2 | 3 | > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). 4 | 5 | ```ts 6 | 7 | // @public 8 | export const Partytown: (props?: PartytownProps) => any; 9 | 10 | // Warning: (ae-forgotten-export) The symbol "PartytownConfig" needs to be exported by the entry point index.d.ts 11 | // 12 | // @public 13 | export interface PartytownProps extends PartytownConfig { 14 | } 15 | 16 | // (No @packageDocumentation comment for this package) 17 | 18 | ``` 19 | -------------------------------------------------------------------------------- /src/lib/service-worker/index.ts: -------------------------------------------------------------------------------- 1 | import { onFetchServiceWorkerRequest, receiveMessageFromSandboxToServiceWorker } from './fetch'; 2 | 3 | (self as any as ServiceWorkerGlobalScope).oninstall = () => 4 | (self as any as ServiceWorkerGlobalScope).skipWaiting(); 5 | 6 | (self as any as ServiceWorkerGlobalScope).onactivate = () => 7 | (self as any as ServiceWorkerGlobalScope).clients.claim(); 8 | 9 | (self as any as ServiceWorkerGlobalScope).onmessage = receiveMessageFromSandboxToServiceWorker; 10 | 11 | (self as any as ServiceWorkerGlobalScope).onfetch = onFetchServiceWorkerRequest; 12 | -------------------------------------------------------------------------------- /src/lib/web-worker/worker-element.ts: -------------------------------------------------------------------------------- 1 | import type { Node } from './worker-node'; 2 | import { NamespaceKey, NodeNameKey } from './worker-constants'; 3 | 4 | export const ElementDescriptorMap: PropertyDescriptorMap & ThisType = { 5 | localName: { 6 | get() { 7 | return this[NodeNameKey]!.toLowerCase(); 8 | }, 9 | }, 10 | namespaceURI: { 11 | get() { 12 | return this[NamespaceKey] || 'http://www.w3.org/1999/xhtml'; 13 | }, 14 | }, 15 | nodeType: { 16 | value: 1, 17 | }, 18 | tagName: { 19 | get() { 20 | return this[NodeNameKey]; 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /docs/site/src/components/Header/SkipToContent.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | -------------------------------------------------------------------------------- /src/utils/api.md: -------------------------------------------------------------------------------- 1 | ## API Report File for "@builder.io/partytown-utils" 2 | 3 | > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). 4 | 5 | ```ts 6 | 7 | // @public 8 | export function copyLibFiles(destDir: string, opts?: CopyLibFilesOptions): Promise<{ 9 | src: string; 10 | dest: string; 11 | }>; 12 | 13 | // @public (undocumented) 14 | export interface CopyLibFilesOptions { 15 | debugDir?: boolean; 16 | } 17 | 18 | // @public 19 | export function libDirPath(): string; 20 | 21 | // (No @packageDocumentation comment for this package) 22 | 23 | ``` 24 | -------------------------------------------------------------------------------- /tests/platform/iframe/parent-window.js: -------------------------------------------------------------------------------- 1 | const win = window; 2 | const winTop = win.top; 3 | const winTopTestScriptWindowParent = winTop.testScriptWindowParent; 4 | const topYear = winTopTestScriptWindowParent.year; 5 | 6 | const parentYear = window.parent.testScriptWindowParent.year; 7 | const total = topYear + parentYear; 8 | 9 | const div = document.createElement('div'); 10 | div.textContent = total; 11 | document.body.appendChild(div); 12 | 13 | window.top.document.getElementById('testScriptWindowParent').textContent = total; 14 | window.parent.document 15 | .getElementById('testScriptWindowParent') 16 | .classList.add('testScriptWindowParent'); 17 | -------------------------------------------------------------------------------- /src/lib/service-worker/sync-send-message-to-main-sw.ts: -------------------------------------------------------------------------------- 1 | import type { MainAccessRequest, MainAccessResponse, WebWorkerContext } from '../types'; 2 | import { partytownLibUrl } from '../web-worker/worker-constants'; 3 | 4 | const syncSendMessageToMainServiceWorker = ( 5 | webWorkerCtx: WebWorkerContext, 6 | accessReq: MainAccessRequest 7 | ): MainAccessResponse => { 8 | const xhr = new XMLHttpRequest(); 9 | xhr.open('POST', partytownLibUrl('proxytown'), false); 10 | xhr.send(JSON.stringify(accessReq)); 11 | // look ma, i'm synchronous (•‿•) 12 | return JSON.parse(xhr.responseText); 13 | }; 14 | 15 | export default syncSendMessageToMainServiceWorker; 16 | -------------------------------------------------------------------------------- /tests/platform/intersection-observer/intersection-observer.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('intersection-observer', async ({ page }) => { 4 | await page.goto('/tests/platform/intersection-observer/'); 5 | 6 | const testIntersectionObserver = page.locator('#testIntersectionObserver'); 7 | await expect(testIntersectionObserver).toHaveText(''); 8 | 9 | const linkScrollDown = page.locator('#scrolldown'); 10 | await linkScrollDown.click(); 11 | 12 | await page.waitForSelector('.testIntersectionObserver'); 13 | 14 | await expect(testIntersectionObserver).toHaveText('intersecting #testIntersectionObserver'); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/react-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/integration/index.ts: -------------------------------------------------------------------------------- 1 | import PartytownSnippet from '@snippet'; 2 | import { createSnippet } from './snippet'; 3 | import type { PartytownConfig } from '../lib/types'; 4 | 5 | /** 6 | * Function that returns the Partytown snippet as a string, which can be 7 | * used as the innerHTML of the inlined Partytown script in the head. 8 | * 9 | * @public 10 | */ 11 | export const partytownSnippet = (config?: PartytownConfig) => 12 | createSnippet(config, PartytownSnippet); 13 | 14 | export { SCRIPT_TYPE } from '../lib/utils'; 15 | 16 | export type { 17 | PartytownConfig, 18 | PartytownForwardProperty, 19 | ApplyHook, 20 | GetHook, 21 | SetHook, 22 | } from '../lib/types'; 23 | -------------------------------------------------------------------------------- /tests/platform/video/video.spec.__ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('video', async ({ page, context }) => { 4 | // TODO: figure out why playwright crashes 5 | 6 | await page.goto('/tests/platform/video/', { waitUntil: 'networkidle' }); 7 | 8 | const btnPlay = page.locator('#btnPlay'); 9 | await btnPlay.click(); 10 | await page.waitForSelector('.testPlay'); 11 | const testPlay = page.locator('#testPlay'); 12 | await expect(testPlay).toHaveText('played'); 13 | const testBufferedLength = page.locator('#testBufferedLength'); 14 | const bufLen = await testBufferedLength.textContent(); 15 | expect(!isNaN(bufLen as any)).toBe(true); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/react-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | import { Partytown } from '@builder.io/partytown/react'; 5 | 6 | function App() { 7 | return ( 8 |
9 | 10 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/integrations/event-forwarding/event-forwarding.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('integration event forwarding', async ({ page }) => { 4 | await page.goto('/tests/integrations/event-forwarding/'); 5 | await page.waitForSelector('.completed'); 6 | 7 | const testFn = page.locator('#testFn'); 8 | await expect(testFn).toHaveText('fnReady'); 9 | 10 | const testArray = page.locator('#testArray'); 11 | await expect(testArray).toHaveText('arrayReady'); 12 | 13 | const buttonForwardEvent = page.locator('#buttonForwardEvent'); 14 | await buttonForwardEvent.click(); 15 | await expect(testFn).toHaveText('click1'); 16 | await buttonForwardEvent.click(); 17 | await expect(testFn).toHaveText('click2'); 18 | 19 | const buttonArrayPush = page.locator('#buttonArrayPush'); 20 | await buttonArrayPush.click(); 21 | await expect(testArray).toHaveText(JSON.stringify({ mph: 88 })); 22 | await buttonArrayPush.click(); 23 | await expect(testArray).toHaveText(JSON.stringify({ mph: 89 })); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@builder.io/partytown": "file:../..", 7 | "@types/react": "^17.0.38", 8 | "@types/react-dom": "^17.0.11", 9 | "react": "^17.0.2", 10 | "react-dom": "^17.0.2", 11 | "react-scripts": "5.0.0", 12 | "typescript": "^4.5.4" 13 | }, 14 | "scripts": { 15 | "start": "npm run partytown && react-scripts start", 16 | "build": "npm run partytown && react-scripts build", 17 | "test": "react-scripts test", 18 | "partytown": "../../bin/partytown.cjs copylib public/~partytown" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/web-worker/worker-state.ts: -------------------------------------------------------------------------------- 1 | import { InstanceStateKey, webWorkerRefIdsByRef, webWorkerRefsByRefId } from './worker-constants'; 2 | import { randomId } from '../utils'; 3 | import type { RefHandler } from '../types'; 4 | import type { WorkerInstance } from './worker-instance'; 5 | 6 | export const hasInstanceStateValue = (instance: WorkerInstance, stateKey: string | number) => 7 | stateKey in instance[InstanceStateKey]; 8 | 9 | export const getInstanceStateValue = ( 10 | instance: WorkerInstance, 11 | stateKey: string | number 12 | ): T => instance[InstanceStateKey][stateKey]; 13 | 14 | export const setInstanceStateValue = ( 15 | instance: WorkerInstance, 16 | stateKey: string | number, 17 | stateValue: any 18 | ) => (instance[InstanceStateKey][stateKey] = stateValue); 19 | 20 | export const setWorkerRef = (ref: RefHandler, refId?: number) => { 21 | if (!(refId = webWorkerRefIdsByRef.get(ref))) { 22 | webWorkerRefIdsByRef.set(ref, (refId = randomId())); 23 | webWorkerRefsByRefId[refId] = ref; 24 | } 25 | return refId; 26 | }; 27 | -------------------------------------------------------------------------------- /docs/site/src/components/Footer/Footer.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { path } = Astro.props; 3 | --- 4 | 5 | 21 | 22 | 41 | -------------------------------------------------------------------------------- /tests/platform/canvas/canvas.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('canvas', async ({ page }) => { 4 | await page.goto('/tests/platform/canvas/'); 5 | 6 | await page.waitForSelector('.completed'); 7 | 8 | const testGetContext2dCanvas = page.locator('#testGetContext2dCanvas'); 9 | await expect(testGetContext2dCanvas).toHaveText('CANVAS'); 10 | 11 | const testMeasureText = page.locator('#testMeasureText'); 12 | await expect(testMeasureText).not.toHaveText(''); 13 | 14 | const testFillStyle = page.locator('#testFillStyle'); 15 | await expect(testFillStyle).toHaveText('blue'); 16 | 17 | const testCreateRadialGradient = page.locator('#testCreateRadialGradient'); 18 | await expect(testCreateRadialGradient).toHaveText('CanvasGradient'); 19 | 20 | const testGetContextWebGLCanvas = page.locator('#testGetContextWebGLCanvas'); 21 | await expect(testGetContextWebGLCanvas).toHaveText('CANVAS'); 22 | 23 | const testWebGLRenderInfo = page.locator('#testWebGLRenderInfo'); 24 | await expect(testWebGLRenderInfo).not.toHaveText(''); 25 | }); 26 | -------------------------------------------------------------------------------- /docs/site/src/components/RightSidebar/MoreMenu.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { githubEditUrl } = Astro.props; 3 | --- 4 | 5 | 29 | 30 | 38 | -------------------------------------------------------------------------------- /src/lib/atomics/sync-send-message-to-main-atomics.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MainAccessRequest, 3 | MainAccessResponse, 4 | WebWorkerContext, 5 | WorkerMessageType, 6 | } from '../types'; 7 | 8 | const syncSendMessageToMainAtomics = ( 9 | webWorkerCtx: WebWorkerContext, 10 | accessReq: MainAccessRequest 11 | ): MainAccessResponse => { 12 | const sharedDataBuffer = webWorkerCtx.$sharedDataBuffer$!; 13 | const sharedData = new Int32Array(sharedDataBuffer); 14 | 15 | // Reset length before call 16 | Atomics.store(sharedData, 0, 0); 17 | 18 | // Asyncronously call main 19 | webWorkerCtx.$postMessage$([WorkerMessageType.ForwardWorkerAccessRequest, accessReq]); 20 | 21 | // Syncronously wait for response 22 | Atomics.wait(sharedData, 0, 0); 23 | 24 | let dataLength = Atomics.load(sharedData, 0); 25 | let accessRespStr = ''; 26 | let i = 0; 27 | 28 | for (; i < dataLength; i++) { 29 | accessRespStr += String.fromCharCode(sharedData[i + 1]); 30 | } 31 | 32 | return JSON.parse(accessRespStr) as MainAccessResponse; 33 | }; 34 | 35 | export default syncSendMessageToMainAtomics; 36 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": [ 3 | { 4 | "source": "/(.*)", 5 | "headers": [ 6 | { 7 | "key": "Cross-Origin-Embedder-Policy", 8 | "value": "credentialless" 9 | }, 10 | { 11 | "key": "Cross-Origin-Opener-Policy", 12 | "value": "same-origin" 13 | } 14 | ] 15 | }, 16 | { 17 | "source": "/(.*)", 18 | "has": [ 19 | { 20 | "type": "query", 21 | "key": "coep", 22 | "value": "require-corp" 23 | } 24 | ], 25 | "headers": [ 26 | { 27 | "key": "Cross-Origin-Embedder-Policy", 28 | "value": "require-corp" 29 | } 30 | ] 31 | }, 32 | { 33 | "source": "/(.*)", 34 | "has": [ 35 | { 36 | "type": "query", 37 | "key": "coep", 38 | "value": "false" 39 | } 40 | ], 41 | "headers": [ 42 | { 43 | "key": "Cross-Origin-Embedder-Policy", 44 | "value": "unsafe-none" 45 | } 46 | ] 47 | } 48 | ], 49 | "public": true 50 | } 51 | -------------------------------------------------------------------------------- /src/lib/web-worker/worker-navigator.ts: -------------------------------------------------------------------------------- 1 | import type { WebWorkerEnvironment } from '../types'; 2 | import { debug } from '../utils'; 3 | import { logWorker } from '../log'; 4 | import { resolveUrl } from './worker-exec'; 5 | import { webWorkerCtx } from './worker-constants'; 6 | 7 | export const createNavigator = (env: WebWorkerEnvironment) => { 8 | const navigator = self.navigator as any; 9 | 10 | navigator.sendBeacon = (url: string, body?: any) => { 11 | if (debug && webWorkerCtx.$config$.logSendBeaconRequests) { 12 | try { 13 | logWorker( 14 | `sendBeacon: ${resolveUrl(env, url, true)}${ 15 | body ? ', data: ' + JSON.stringify(body) : '' 16 | }` 17 | ); 18 | } catch (e) { 19 | console.error(e); 20 | } 21 | } 22 | try { 23 | fetch(resolveUrl(env, url, true), { 24 | method: 'POST', 25 | body, 26 | mode: 'no-cors', 27 | keepalive: true, 28 | }); 29 | return true; 30 | } catch (e) { 31 | console.error(e); 32 | return false; 33 | } 34 | }; 35 | 36 | return navigator; 37 | }; 38 | -------------------------------------------------------------------------------- /docs/sandboxing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sandboxing 3 | --- 4 | 5 | Third-party scripts are often a black-box with large amounts of code. What's buried within the obfuscated code is difficult to tell. It's minified for good reason, but regardless it becomes very difficult to understand what third-party scripts are executing on _your_ site and _your_ user's devices. 6 | 7 | Partytown on the other hand, is able to isolate and sandbox third-party scripts within a web worker, and allow, or deny, access to main thread APIs. This includes cookies, localStorage, or the entire document. Because the code is executed within the worker, and their access to the main thread _must_ go through the proxy, Partytown is able to give developers control over what the scripts that can execute. 8 | 9 | Essentially, Partytown lets you: 10 | 11 | - Isolate third-party scripts within a sandbox ([web worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)). 12 | - Configure which browser APIs specific scripts can, and cannot, execute. 13 | - Option to log API calls and passed in arguments in order to give better insight as to what the scripts are doing. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Builder.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/lib/web-worker/worker-css-style-declaration.ts: -------------------------------------------------------------------------------- 1 | import type { ApplyPath } from '../types'; 2 | import { cachedDimensions } from './worker-constants'; 3 | import { logDimensionCacheClearStyle } from '../log'; 4 | import { setter } from './worker-proxy'; 5 | import { WorkerInstance } from './worker-instance'; 6 | 7 | export class CSSStyleDeclaration extends WorkerInstance { 8 | constructor(winId: number, instanceId: number, applyPath: ApplyPath, styles: any) { 9 | super(winId, instanceId, applyPath); 10 | 11 | Object.assign(this, styles); 12 | 13 | return new Proxy(this, { 14 | get(target, propName) { 15 | return (target as any)[propName]; 16 | }, 17 | set(target, propName, propValue) { 18 | setter(target, [propName], propValue); 19 | logDimensionCacheClearStyle(target, propName); 20 | cachedDimensions.clear(); 21 | return true; 22 | }, 23 | }); 24 | } 25 | 26 | getPropertyValue(propName: string) { 27 | return (this as any)[propName]; 28 | } 29 | 30 | setProperty(propName: string, propValue: any) { 31 | (this as any)[propName] = propValue; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/platform/style/style.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('style', async ({ page }) => { 4 | await page.goto('/tests/platform/style/'); 5 | 6 | await page.waitForSelector('.completed'); 7 | 8 | const testSheetType = page.locator('#testSheetType'); 9 | await expect(testSheetType).toHaveText('text/css'); 10 | 11 | const testCssRulesLength = page.locator('#testCssRulesLength'); 12 | await expect(testCssRulesLength).toHaveText('1'); 13 | 14 | const testOwnerNode = page.locator('#testOwnerNode'); 15 | await expect(testOwnerNode).toHaveText('STYLE'); 16 | 17 | const testCssRules0 = page.locator('#testCssRules0'); 18 | await expect(testCssRules0).toHaveText('#testCssRules0 { color: red; }'); 19 | 20 | const testCssRulesItem0 = page.locator('#testCssRulesItem0'); 21 | await expect(testCssRulesItem0).toHaveText('#testCssRulesItem0'); 22 | 23 | const testInsertRule = page.locator('#testInsertRule'); 24 | await expect(testInsertRule).toHaveText('#testInsertRule'); 25 | 26 | const testDeleteRule = page.locator('#testDeleteRule'); 27 | await expect(testDeleteRule).toHaveText('rgb(0, 128, 0) 2'); 28 | }); 29 | -------------------------------------------------------------------------------- /src/lib/web-worker/worker-constructors.ts: -------------------------------------------------------------------------------- 1 | import { htmlMedia, lazyLoadMedia } from './worker-media'; 2 | import type { Node } from './worker-node'; 3 | import { nodeConstructors, webWorkerInstances } from './worker-constants'; 4 | 5 | export const getOrCreateNodeInstance = ( 6 | winId: number, 7 | instanceId: number, 8 | nodeName: string, 9 | namespace?: string, 10 | instance?: Node 11 | ) => { 12 | instance = webWorkerInstances.get(instanceId); 13 | if (!instance) { 14 | instance = createNodeInstance(winId, instanceId, nodeName, namespace); 15 | webWorkerInstances.set(instanceId, instance); 16 | } 17 | return instance; 18 | }; 19 | 20 | export const createNodeInstance = ( 21 | winId: number, 22 | instanceId: number, 23 | nodeName: string, 24 | namespace?: string 25 | ) => { 26 | if (htmlMedia.includes(nodeName)) { 27 | lazyLoadMedia(); 28 | } 29 | 30 | const NodeCstr: typeof Node = nodeConstructors[nodeName] 31 | ? nodeConstructors[nodeName] 32 | : nodeName.includes('-') 33 | ? nodeConstructors.UNKNOWN 34 | : (self as any).HTMLElement; 35 | 36 | return new NodeCstr(winId, instanceId, [], nodeName, namespace); 37 | }; 38 | -------------------------------------------------------------------------------- /src/react/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "bundledPackages": ["react"], 4 | "compiler": { 5 | "tsconfigFilePath": "/tsconfig.json" 6 | }, 7 | "mainEntryPointFilePath": "/tsc/src/react/index.d.ts", 8 | "apiReport": { 9 | "enabled": true, 10 | "reportFileName": "api.md", 11 | "reportFolder": "/src/react/", 12 | "reportTempFolder": "/.cache/api-extractor/react/" 13 | }, 14 | "dtsRollup": { 15 | "enabled": true, 16 | "untrimmedFilePath": "/react/index.d.ts" 17 | }, 18 | "docModel": { 19 | "enabled": false 20 | }, 21 | "tsdocMetadata": { 22 | "enabled": false 23 | }, 24 | "messages": { 25 | "compilerMessageReporting": { 26 | "default": { 27 | "logLevel": "none" 28 | } 29 | }, 30 | "extractorMessageReporting": { 31 | "default": { 32 | "logLevel": "error" 33 | } 34 | }, 35 | "tsdocMessageReporting": { 36 | "default": { 37 | "logLevel": "error" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/utils/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "bundledPackages": ["react"], 4 | "compiler": { 5 | "tsconfigFilePath": "/tsconfig.json" 6 | }, 7 | "mainEntryPointFilePath": "/tsc/src/utils/index.d.ts", 8 | "apiReport": { 9 | "enabled": true, 10 | "reportFileName": "api.md", 11 | "reportFolder": "/src/utils/", 12 | "reportTempFolder": "/.cache/api-extractor/utils/" 13 | }, 14 | "dtsRollup": { 15 | "enabled": true, 16 | "untrimmedFilePath": "/utils/index.d.ts" 17 | }, 18 | "docModel": { 19 | "enabled": false 20 | }, 21 | "tsdocMetadata": { 22 | "enabled": false 23 | }, 24 | "messages": { 25 | "compilerMessageReporting": { 26 | "default": { 27 | "logLevel": "none" 28 | } 29 | }, 30 | "extractorMessageReporting": { 31 | "default": { 32 | "logLevel": "error" 33 | } 34 | }, 35 | "tsdocMessageReporting": { 36 | "default": { 37 | "logLevel": "error" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/services/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "bundledPackages": ["partytown"], 4 | "compiler": { 5 | "tsconfigFilePath": "/tsconfig.json" 6 | }, 7 | "mainEntryPointFilePath": "/tsc/src/services/index.d.ts", 8 | "apiReport": { 9 | "enabled": true, 10 | "reportFileName": "api.md", 11 | "reportFolder": "/src/services/", 12 | "reportTempFolder": "/.cache/api-extractor/services/" 13 | }, 14 | "dtsRollup": { 15 | "enabled": true, 16 | "untrimmedFilePath": "/services/index.d.ts" 17 | }, 18 | "docModel": { 19 | "enabled": false 20 | }, 21 | "tsdocMetadata": { 22 | "enabled": false 23 | }, 24 | "messages": { 25 | "compilerMessageReporting": { 26 | "default": { 27 | "logLevel": "none" 28 | } 29 | }, 30 | "extractorMessageReporting": { 31 | "default": { 32 | "logLevel": "error" 33 | } 34 | }, 35 | "tsdocMessageReporting": { 36 | "default": { 37 | "logLevel": "error" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/integration/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "bundledPackages": ["partytown"], 4 | "compiler": { 5 | "tsconfigFilePath": "/tsconfig.json" 6 | }, 7 | "mainEntryPointFilePath": "/tsc/src/integration/index.d.ts", 8 | "apiReport": { 9 | "enabled": true, 10 | "reportFileName": "api.md", 11 | "reportFolder": "/src/integration/", 12 | "reportTempFolder": "/.cache/api-extractor/integration/" 13 | }, 14 | "dtsRollup": { 15 | "enabled": true, 16 | "untrimmedFilePath": "/integration/index.d.ts" 17 | }, 18 | "docModel": { 19 | "enabled": false 20 | }, 21 | "tsdocMetadata": { 22 | "enabled": false 23 | }, 24 | "messages": { 25 | "compilerMessageReporting": { 26 | "default": { 27 | "logLevel": "none" 28 | } 29 | }, 30 | "extractorMessageReporting": { 31 | "default": { 32 | "logLevel": "error" 33 | } 34 | }, 35 | "tsdocMessageReporting": { 36 | "default": { 37 | "logLevel": "error" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/nuxt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Nuxt 3 | --- 4 | 5 | There is a first-class [Nuxt integration for partytown](https://github.com/nuxt-community/partytown-module). 6 | 7 | ## Install 8 | 9 | Add `@nuxtjs/partytown` dependency to your project. 10 | 11 | ```bash 12 | yarn add @nuxtjs/partytown # or npm install @nuxtjs/partytown 13 | ``` 14 | 15 | ## Configure 16 | 17 | Add `@nuxtjs/partytown` to the `modules` section of `nuxt.config.ts`. Use the `partytown` property for the [configuration](/configuration). 18 | 19 | ```js 20 | import { defineNuxtConfig } from 'nuxt3'; 21 | 22 | export default defineNuxtConfig({ 23 | modules: ['@nuxtjs/partytown'], 24 | partytown: { 25 | /* any partytown-specific configuration */ 26 | }, 27 | }); 28 | ``` 29 | 30 | ## Partytown Script 31 | 32 | Add `type: 'text/partytown'` [attribute](/partytown-scripts) to any scripts you want to be handled by partytown. 33 | 34 | ```html 35 |