Text Monitor Package
The Text Monitor package captures typing behavior on text fields and editors: keystroke timing, pauses, paste events, and deletions. Output is a per-session composition classification (Imported, Single Drafted, Substituted, Quoted, Original). This page covers the standalone @isnotai/text-monitor package for sites that do not run the NotAI pixel. If the pixel is already installed, enable text monitoring there instead (see the pixel install guide).
Overview
The package ships in two shapes that share the same capture engine:
- npm package with programmatic control. Use this when editors mount and unmount dynamically, or when you want to attach and detach at specific lifecycle points.
- CDN
/auto.jsscript for a declarative drop-in install. Use this when a single script tag is enough and you do not need runtime control.
Both shapes produce the same output and use the same configuration keys. Pick whichever fits your stack.
When to Use This
Use the standalone package if:
- Your site does not run the NotAI pixel.
- You want text monitoring on a specific subset of pages or fields.
- You need programmatic control over attach and detach timing (for example, editors that mount and unmount during a single-page-app navigation).
Use the pixel-bundled path instead if:
- You are already running the NotAI pixel.
- You want a one-line script install with no runtime code.
- You want RCE auto-detection.
Do not load both the pixel and the standalone package on the same page.
Install
Install from npm:
npm install @isnotai/text-monitor
yarn add @isnotai/text-monitor
pnpm add @isnotai/text-monitor
Or use the CDN /auto.js entry (see Declarative Install below).
Programmatic Usage
Create a monitor with your integration ID and region, then attach it to any supported text field. Detach on cleanup.
import { createMonitor } from '@isnotai/text-monitor';
const monitor = createMonitor({
integrationId: 'a7f3c2e1',
region: 'us'
});
// Attach to a specific field
monitor.attach(document.getElementById('essay'));
// Later, when the field unmounts
monitor.detach(document.getElementById('essay'));
Attach is idempotent. Detaching a field that was never attached is a no-op.
Options
| Option | Type | Required | Description |
|---|---|---|---|
integrationId |
string | Yes | 8-character hex, from your dashboard. See Integration ID. |
region |
string | Yes | 'us' or 'eu'. Must match the region on your account. See Regions. |
extraSelectors |
string[] | No | CSS selectors for additional custom editors beyond the standard set. |
Declarative Install (auto.js)
For a single-script install, drop the /auto.js script tag into your page. It reads its configuration from a data-config attribute and attaches to supported editors on load.
<script
src="https://cdn.isnotai.com/text-monitor/auto.js"
data-config='{"integrationId":"a7f3c2e1","region":"us"}'
async></script>
For custom editors not covered by the default selectors, pass extraSelectors:
<script
src="https://cdn.isnotai.com/text-monitor/auto.js"
data-config='{"integrationId":"a7f3c2e1","region":"us","extraSelectors":[".my-editor"]}'
async></script>
Runtime attach and detach are not available through /auto.js. If you need programmatic control, use the npm package.
Self-hosted Script
Some customers need the text monitor to be served from their own origin so it survives aggressive adblocker lists that target well-known third-party domains. For those cases we issue a pre-packaged bundle with your integration ID, region, and reporting endpoints baked in, which you host on a subdomain of your own site.
The setup is self-service from your dashboard:
- In your dashboard, open Settings → Self-hosted Script. You will see the CNAME targets your package needs.
- Create the CNAME records on your own subdomain (for example,
verify.example.com). The dashboard verifies propagation once the records are live. - Download your packaged bundle. It is built with your integration ID, region, and CNAMEd reporting hosts baked in.
- Host the file on your own CDN. Your install tag becomes a single script with no
data-*attributes:
<script src="//your-cdn.example.com/text-monitor.js" async></script>
The same packaged bundle works for programmatic control: import its createMonitor export from the local copy instead of the public npm package.
When you need to change configuration, return to the dashboard and download a fresh package. There is no runtime config for the self-hosted build; everything lives in the file you host.
Supported Editors
The package includes adapters for the following editor surfaces. Adapter selection is automatic: attach to any container and the package dispatches to the right adapter.
<textarea><input type="text">- Any element with
contenteditable="true" - Quill editor instances
- TinyMCE editor instances
If you use a different editor, pass its root container selector via extraSelectors. The package falls back to universal content diffing for anything that is not one of the specialized adapters above.
Integration ID
Every NotAI deployment is identified by an 8-character hexadecimal integration ID (for example a7f3c2e1). Copy yours from Integrations in your NotAI dashboard.
The integration ID is public by design: it identifies which account receives the session data. It does not authenticate the sender, and a leaked integration ID does not expose account data.
Regions
NotAI operates two data regions: US and EU. Pick the region that matches your account.
- US accounts: set
region: 'us'(the default). - EU accounts: set
region: 'eu'. Your site must also allow the EU reporting hosts in its Content Security Policy (see below).
A session sent to the wrong region will be rejected.
React Usage
Attach the monitor inside a useEffect hook. Detach in the cleanup function so navigation or conditional rendering does not leave a dangling attachment.
import { useEffect, useRef } from 'react';
import { createMonitor } from '@isnotai/text-monitor';
const monitor = createMonitor({
integrationId: 'a7f3c2e1',
region: 'us'
});
function EssayForm() {
const ref = useRef(null);
useEffect(() => {
if (!ref.current) return;
monitor.attach(ref.current);
return () => monitor.detach(ref.current);
}, []);
return <textarea ref={ref} />;
}
Create the monitor once at module scope, not on every render.
Vue Usage
Attach in onMounted, detach in onUnmounted.
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { createMonitor } from '@isnotai/text-monitor';
const monitor = createMonitor({
integrationId: 'a7f3c2e1',
region: 'us'
});
const textareaRef = ref(null);
onMounted(() => monitor.attach(textareaRef.value));
onUnmounted(() => monitor.detach(textareaRef.value));
</script>
<template>
<textarea ref="textareaRef"></textarea>
</template>
Content Security Policy
Allow the CDN script host and the reporting hosts that match your region. Pick one regional block. Never combine.
US
script-src 'self' https://cdn.isnotai.com;
connect-src 'self' https://chl.isnot.ai wss://chl.isnot.ai;
EU
script-src 'self' https://cdn.isnotai.com;
connect-src 'self' https://chl-eu.isnot.ai wss://chl-eu.isnot.ai;
If you already have the CDN host in script-src for other assets, only the region-specific connect-src line is new.
Troubleshooting
Monitor is not capturing
- Confirm
integrationIdandregionare correct for your account. - Ensure your page's origin is on the authorized origins list for your integration. Add origins from Settings → Authorized Origins in your dashboard. Requests from origins outside the list are rejected.
- Verify your Content Security Policy allows the hosts for your region (see above).
- Check the element you passed to
attach()is one of the supported editor surfaces.
CSP violations in the browser console
- Confirm you are using the CSP block for your account's region, not the other one.
- If the
/auto.jsscript is blocked, addhttps://cdn.isnotai.comtoscript-src. - If the reporting connection is blocked, add the regional host to
connect-src.
Memory leaks in single-page apps
- Call
detach()in every cleanup path where the attached element is removed from the DOM. - Create the monitor once at module scope. Do not call
createMonitorinside render paths.