{"_id":"@captello/ulc-webview-sdk","_rev":"12-f48a9039f6c57ecc3c43d17a13e5cda4","name":"@captello/ulc-webview-sdk","dist-tags":{"latest":"1.5.0"},"versions":{"0.1.0":{"name":"@captello/ulc-webview-sdk","version":"0.1.0","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"author":{"name":"Lead Liaison"},"license":"MIT","_id":"@captello/ulc-webview-sdk@0.1.0","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"dist":{"shasum":"ac24f25fb09307ee9cb620a5cae4b60c92d34808","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-0.1.0.tgz","fileCount":27,"integrity":"sha512-2QZn7xlpkBPHWw8HgstlN7y8eqAg1P+b6DKsXoSzI4r5lv906ukgX9nnSyohycoQT7q0tgFMeT8+yp+NzhHbSg==","signatures":[{"sig":"MEUCIQDvyn6mc8B5h9Hi0F94L8wF2zM5bWb41xfB+BIvnwxOawIgbxPpXxDtqU23smQqKxk4CF37IG8RjgsEwY3LXHZOfNY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":433187},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js","require":"./dist/react.cjs"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js","require":"./dist/promises.cjs"},"./package.json":"./package.json"},"gitHead":"46e0fc42a3c65c0445a1b2a9e07bdad065e1edd0","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","clean":"rm -rf dist","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ulc-webview-sdk_0.1.0_1782307098707_0.2606865411912649","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@captello/ulc-webview-sdk","version":"0.2.0","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"author":{"name":"Lead Liaison"},"license":"MIT","_id":"@captello/ulc-webview-sdk@0.2.0","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"dist":{"shasum":"d67b4c606ad2a708cc7455f498dd655466c065df","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-0.2.0.tgz","fileCount":17,"integrity":"sha512-Ok/HGZ5ztBbs3xVD5cfkTcbQCtfHtg1mGWJuU2PxMFk+GFU7sMb38Gd590R39SB9mOYMLhtNzWC69iMfOPtNrQ==","signatures":[{"sig":"MEQCIGsHuZo9IrVkinaFQasESOzi9xf1zkU1lJZpQ+mnEih1AiAQjO/xl6KeTYHmd04UvZQ00FNV5CAyqAuarZNU4yEDeA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":163829},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js"},"./package.json":"./package.json"},"gitHead":"6380d33575691b3f081002876b629a21f4171464","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","clean":"rm -rf dist","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ulc-webview-sdk_0.2.0_1782311939603_0.12689697692430646","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@captello/ulc-webview-sdk","version":"0.3.0","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"author":{"name":"Lead Liaison"},"license":"MIT","_id":"@captello/ulc-webview-sdk@0.3.0","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"dist":{"shasum":"7466aea172601e539fb54fc3ce1dab89a48df984","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-0.3.0.tgz","fileCount":17,"integrity":"sha512-kxvAA25i7Ecn7GkvbhnOMXVgpfwM94C+X8Wd8rd6EDMiLR1N9cnAZXppDcufbElGUhyjIGVtUygp2+zsTRaqVA==","signatures":[{"sig":"MEQCIFJzMrm/ja2UwV7pzzYez5BsNGEnL4af3FIsHofMRZIeAiAKibHSo8a4V2ndnCp9T90It1PYYUXD9CU3BQruu/25hw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":165519},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js"},"./package.json":"./package.json"},"gitHead":"7ad4e18077ccfa9b5192962eabce0693e208b071","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","clean":"rm -rf dist","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ulc-webview-sdk_0.3.0_1782378761038_0.7923652660049496","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@captello/ulc-webview-sdk","version":"0.4.0","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"author":{"name":"Lead Liaison"},"license":"MIT","_id":"@captello/ulc-webview-sdk@0.4.0","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"dist":{"shasum":"0ac9a7e90b442b3c61274753f5f564a5fa2189dc","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-0.4.0.tgz","fileCount":17,"integrity":"sha512-WjuOknYRRBV6f28xTqDly+Y5/z/QGJpo8WXIPwL3FI4i1DRSeRJ5xZlkvqcRI6yL4mLTr3Ubgcf6anaQMVxsAA==","signatures":[{"sig":"MEUCIGd3BqzSo6DFZ8Z/8g5bF4uBnGZFt1rpWtIV/S+abmg3AiEA0al0uqKmgg9ZyvQynianL7XARZADYWzY1dUpx7zYWx0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":160102},"type":"module","_from":"file:captello-ulc-webview-sdk-0.4.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","clean":"rm -rf dist","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"_resolved":"/private/var/folders/4h/856b97vj2v5c8x9w8g0bxcv80000gn/T/97cb951421c503a1c859b130d8f8d4fb/captello-ulc-webview-sdk-0.4.0.tgz","_integrity":"sha512-WjuOknYRRBV6f28xTqDly+Y5/z/QGJpo8WXIPwL3FI4i1DRSeRJ5xZlkvqcRI6yL4mLTr3Ubgcf6anaQMVxsAA==","_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ulc-webview-sdk_0.4.0_1782393141295_0.1322369555540528","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@captello/ulc-webview-sdk","version":"0.5.0","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"author":{"name":"Lead Liaison"},"license":"MIT","_id":"@captello/ulc-webview-sdk@0.5.0","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"dist":{"shasum":"ce067facc2a7568b3076b955001971f1cb156e5b","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-0.5.0.tgz","fileCount":17,"integrity":"sha512-ldUDve9qScN9SIrEE3Een9oJPwk2kThsF7wgvfbUoc7eueKqjKNX+nUcZKgz/dm/QRbzoq9sIXpiJv3LyxS3BQ==","signatures":[{"sig":"MEQCIB70FisZkATR7e+9J3EBmOp5hPsl5n3ZHFzNFyuH9K/5AiAodkvq096DeWprk4ZIOWUsYlHuIVvGG7mTAWAglO7EAg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":178597},"type":"module","_from":"file:captello-ulc-webview-sdk-0.5.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","clean":"rm -rf dist","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"_resolved":"/private/var/folders/4h/856b97vj2v5c8x9w8g0bxcv80000gn/T/464cc68674904ea07f9e7385e5ebd21c/captello-ulc-webview-sdk-0.5.0.tgz","_integrity":"sha512-ldUDve9qScN9SIrEE3Een9oJPwk2kThsF7wgvfbUoc7eueKqjKNX+nUcZKgz/dm/QRbzoq9sIXpiJv3LyxS3BQ==","_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ulc-webview-sdk_0.5.0_1782654444821_0.37597309613141316","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@captello/ulc-webview-sdk","version":"0.6.0","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"author":{"name":"Lead Liaison"},"license":"MIT","_id":"@captello/ulc-webview-sdk@0.6.0","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"dist":{"shasum":"2f5114ed2483830d04bd2c1b26cf6c3fa2aa8830","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-0.6.0.tgz","fileCount":17,"integrity":"sha512-rkT9WhvgmtHjJ6ixEbQ2CseSaD/RxffwR7KqohbbtkhEc7EE62b8n08WyEepfHdVWb+Avcd+uj2C3aVsQ0CX1g==","signatures":[{"sig":"MEQCIC1p9ULpZb3pNr38MB7GH28o9gud923cUjLV8jNTwIVHAiAycVK0Q0nGSx+aN52nDY03dGAl3X0qGGwHIA0U2GEJbg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":180111},"type":"module","_from":"file:captello-ulc-webview-sdk-0.6.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","clean":"rm -rf dist","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"_resolved":"/private/var/folders/4h/856b97vj2v5c8x9w8g0bxcv80000gn/T/aaac964a8d0ade5c8f61b3e8ef2f99e6/captello-ulc-webview-sdk-0.6.0.tgz","_integrity":"sha512-rkT9WhvgmtHjJ6ixEbQ2CseSaD/RxffwR7KqohbbtkhEc7EE62b8n08WyEepfHdVWb+Avcd+uj2C3aVsQ0CX1g==","_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ulc-webview-sdk_0.6.0_1782656807144_0.6688770655164116","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@captello/ulc-webview-sdk","version":"1.0.0","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"author":{"name":"Lead Liaison"},"license":"MIT","_id":"@captello/ulc-webview-sdk@1.0.0","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"dist":{"shasum":"563fad7ff23d6e4c7d17bd2f4f4f5b0cd5f0b396","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-1.0.0.tgz","fileCount":18,"integrity":"sha512-zg615ll8mpk5/FpbomMcfNCoXG7KAv+QJebmb4FblzdzppjJ79tQV5/qv7F888xq/nEgMdLa9QcPZqxwe3mlzg==","signatures":[{"sig":"MEQCIFTI9coCc1lWG5YZr9zZqO7JOyBPFWYiJvXz9DgP8AD7AiAIj+Hfs2l9LaIKXUFQQ7pfb/K6ykG635a0jMpYcCQwOg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":195494},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js"},"./package.json":"./package.json"},"gitHead":"57498c0fac5f2f27694b0d9a6f8d4cd3c843558c","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","clean":"rm -rf dist","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ulc-webview-sdk_1.0.0_1784456343591_0.5008308224830711","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@captello/ulc-webview-sdk","version":"1.1.0","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"author":{"name":"Lead Liaison"},"license":"MIT","_id":"@captello/ulc-webview-sdk@1.1.0","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"dist":{"shasum":"fb124c894c7e9937002b4d4c384ca07ee886b9e5","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-1.1.0.tgz","fileCount":18,"integrity":"sha512-D9j3hthj27YdIl/XEj7jIabvleLak66ATuVVW9BlehIXKbwXVas/EkSzzE+RHN8SHiuwDcI9tnvThxhvvkYS9A==","signatures":[{"sig":"MEQCHyjl2HjF28o0orGYa/YLB/5HMJkFaBRGnjFYIU79abQCIQC2RsHJMZ/sCU8wQ0aMPlPvqQLxRXzpU20rHQrPmBW2uA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":200898},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js"},"./package.json":"./package.json"},"gitHead":"b0bf2708991a90cd79ed284d9cdfc4122fb3ba0a","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","clean":"rm -rf dist","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ulc-webview-sdk_1.1.0_1785654487501_0.6998686983051445","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@captello/ulc-webview-sdk","version":"1.2.0","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"author":{"name":"Lead Liaison"},"license":"MIT","_id":"@captello/ulc-webview-sdk@1.2.0","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"dist":{"shasum":"3eb1e2d5cb4e219e1c3975afc434ae50f6035b88","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-1.2.0.tgz","fileCount":21,"integrity":"sha512-KnHn05NQ/lBy2QtQC6yVqAWVWffOaoVnI0JdVoZl1ioWB/W1093xP+wdBgDKqAfjjrGJVlxP0Bz2YD2i9lL5pQ==","signatures":[{"sig":"MEYCIQC7FLqqXJQTIDRMmo42wDw4Y2zIr9OIWY58RWZclndocQIhAPieT55igwR4Icf8leAj18J/Pe5ygc4Ym7bmr9YQZWFG","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":325479},"type":"module","_from":"file:captello-ulc-webview-sdk-1.2.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./app":{"types":"./dist/app-host.d.ts","import":"./dist/app-host.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsup --watch","e2e":"pnpm build && playwright test","test":"vitest run","build":"tsup","clean":"rm -rf dist dist-iife","e2e:ui":"pnpm build && playwright test --ui","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"_resolved":"/private/var/folders/4h/856b97vj2v5c8x9w8g0bxcv80000gn/T/0fe75fbb1e74cd320f7f9e42f8d84230/captello-ulc-webview-sdk-1.2.0.tgz","_integrity":"sha512-KnHn05NQ/lBy2QtQC6yVqAWVWffOaoVnI0JdVoZl1ioWB/W1093xP+wdBgDKqAfjjrGJVlxP0Bz2YD2i9lL5pQ==","_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"app":["./dist/app-host.d.ts"],"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@playwright/test":"1.62.1","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ulc-webview-sdk_1.2.0_1788717184413_0.8605318106043511","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@captello/ulc-webview-sdk","version":"1.3.0","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"author":{"name":"Lead Liaison"},"license":"MIT","_id":"@captello/ulc-webview-sdk@1.3.0","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"dist":{"shasum":"73a114e56813893d95238642b669bda9960520f1","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-1.3.0.tgz","fileCount":27,"integrity":"sha512-uBwgG3eR9PCiYCaPDYH7un9196ufIP3L8orcjZmq+ax3iK+3PIOavAQ4KU9WYaGNmP5+z+o6L49Ckb/5Btl5cA==","signatures":[{"sig":"MEUCIDba9BHNUiUtQHpDgJTtJhNKVBcaq30A2CJbZVCNMCzSAiEAtwwlwhntr3o/SSAj8fZY+TpiB0ifFERAQz2UAe7SCjM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":359659},"type":"module","_from":"file:captello-ulc-webview-sdk-1.3.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js"},"./mobile-host":{"types":"./dist/mobile-host.d.ts","import":"./dist/mobile-host.js"},"./embedded-app":{"types":"./dist/embedded-app.d.ts","import":"./dist/embedded-app.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsup --watch","e2e":"pnpm build && playwright test","test":"vitest run","build":"tsup","clean":"rm -rf dist dist-iife","e2e:ui":"pnpm build && playwright test --ui","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"_resolved":"/private/var/folders/4h/856b97vj2v5c8x9w8g0bxcv80000gn/T/4623cf1a905cf5d4a455f7a1c1ee2860/captello-ulc-webview-sdk-1.3.0.tgz","_integrity":"sha512-uBwgG3eR9PCiYCaPDYH7un9196ufIP3L8orcjZmq+ax3iK+3PIOavAQ4KU9WYaGNmP5+z+o6L49Ckb/5Btl5cA==","_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"],"mobile-host":["./dist/mobile-host.d.ts"],"embedded-app":["./dist/embedded-app.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@playwright/test":"1.62.1","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ulc-webview-sdk_1.3.0_1788718928644_0.634742625846034","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"name":"@captello/ulc-webview-sdk","version":"1.4.0","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"author":{"name":"Lead Liaison"},"license":"MIT","_id":"@captello/ulc-webview-sdk@1.4.0","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"dist":{"shasum":"f59a2026d0b20bd95adc28b32845e439ecfa468d","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-1.4.0.tgz","fileCount":27,"integrity":"sha512-CVIT6cNZ1Bvha0uCLMrazIImZi9T8A5R+5U14nxGdThEwYqMOiEY7XJf+8MLUw8HVrnsOMLyEuK4nUUc6ej+Qw==","signatures":[{"sig":"MEUCIGfHp+qdSVZ+nQS12SdugTm7X0hThyrumq5gfhoPC1QlAiEAhx0VJzgI56hy9PrFq3teOQBr0UIdDTkeeFpoG6i4ez4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDuAm+Q2UCv0ogNuhybb5eKZ347QeaTEWHRyV7iYv3M4wIhAK3wVpPW6Rbu/Z+zpfSR+IBs5vN9rt55f4a1zez90oT0","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":377295},"type":"module","_from":"file:captello-ulc-webview-sdk-1.4.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js"},"./mobile-host":{"types":"./dist/mobile-host.d.ts","import":"./dist/mobile-host.js"},"./embedded-app":{"types":"./dist/embedded-app.d.ts","import":"./dist/embedded-app.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsup --watch","e2e":"pnpm build && playwright test","test":"vitest run","build":"tsup","clean":"rm -rf dist dist-iife","e2e:ui":"pnpm build && playwright test --ui","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"_resolved":"/private/var/folders/4h/856b97vj2v5c8x9w8g0bxcv80000gn/T/20b4c0d43dd8802e8a27ee6c13fc9249/captello-ulc-webview-sdk-1.4.0.tgz","_integrity":"sha512-CVIT6cNZ1Bvha0uCLMrazIImZi9T8A5R+5U14nxGdThEwYqMOiEY7XJf+8MLUw8HVrnsOMLyEuK4nUUc6ej+Qw==","_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"],"mobile-host":["./dist/mobile-host.d.ts"],"embedded-app":["./dist/embedded-app.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@playwright/test":"1.62.1","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ulc-webview-sdk_1.4.0_1790518112197_0.6339912778630667","host":"s3://npm-registry-packages-npm-production"}},"1.5.0":{"_id":"@captello/ulc-webview-sdk@1.5.0","dist":{"shasum":"43151502a82360b866f0fc72742e846824b85c4d","tarball":"https://registry.npmjs.org/@captello/ulc-webview-sdk/-/ulc-webview-sdk-1.5.0.tgz","fileCount":27,"integrity":"sha512-8S8VRu7laM8VwUlh/II+Df2KkDS2ciR768MPkbuqFyAAiPE7G7NAJdHJDnIqbNRkw8AeQLcx2bftYes3s2NDeA==","signatures":[{"sig":"MEYCIQC3KWKHHBO0Z7yVsdnTw6LGgNLfvN96OS/Nawwwi9pXkAIhAK8JQCg2RQDMDG+QrXPj+iLrCBYkZzwqLZrYZLGajzaF","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBexgVAKbFO9tYc8/PXW7lMZUO0MMiZQ0Cp5UIMDUQFFAiEAoyn4zSZFOp8dh3QZJBdv+45JLo23NAuxuv3Ui2wQDWw="}],"unpackedSize":382418},"name":"@captello/ulc-webview-sdk","type":"module","_from":"file:captello-ulc-webview-sdk-1.5.0.tgz","types":"./dist/index.d.ts","author":{"name":"Lead Liaison"},"module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./promises":{"types":"./dist/promises.d.ts","import":"./dist/promises.js"},"./mobile-host":{"types":"./dist/mobile-host.d.ts","import":"./dist/mobile-host.js"},"./embedded-app":{"types":"./dist/embedded-app.d.ts","import":"./dist/embedded-app.js"},"./package.json":"./package.json"},"license":"MIT","scripts":{"dev":"tsup --watch","e2e":"pnpm build && playwright test","test":"vitest run","build":"tsup","clean":"rm -rf dist dist-iife","e2e:ui":"pnpm build && playwright test --ui","typecheck":"tsc --noEmit","test:watch":"vitest","typecheck:test":"tsc -p tsconfig.test.json"},"version":"1.5.0","_npmUser":{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"},"keywords":["captello","webview","iframe","postmessage","embed","sdk"],"_resolved":"/private/var/folders/4h/856b97vj2v5c8x9w8g0bxcv80000gn/T/005fb9f113687d6cc72d611818cb87d3/captello-ulc-webview-sdk-1.5.0.tgz","_integrity":"sha512-8S8VRu7laM8VwUlh/II+Df2KkDS2ciR768MPkbuqFyAAiPE7G7NAJdHJDnIqbNRkw8AeQLcx2bftYes3s2NDeA==","_npmVersion":"11.7.0","description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","directories":{},"maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"react":["./dist/react.d.ts"],"promises":["./dist/promises.d.ts"],"mobile-host":["./dist/mobile-host.d.ts"],"embedded-app":["./dist/embedded-app.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.3.5","jsdom":"25.0.1","react":"19.2.2","vitest":"2.1.9","react-dom":"19.2.2","typescript":"5.9.3","@types/react":"19.2.2","@playwright/test":"1.62.1","@testing-library/react":"16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ulc-webview-sdk_1.5.0_1790608165672_0.07614880792348755"}}},"time":{"created":"2026-06-24T13:18:18.527Z","modified":"2026-09-28T15:09:25.941Z","0.1.0":"2026-06-24T13:18:18.866Z","0.2.0":"2026-06-24T14:38:59.741Z","0.3.0":"2026-06-25T09:12:41.170Z","0.4.0":"2026-06-25T13:12:21.446Z","0.5.0":"2026-06-28T13:47:24.964Z","0.6.0":"2026-06-28T14:26:47.283Z","1.0.0":"2026-07-19T10:19:03.771Z","1.1.0":"2026-08-02T07:08:07.689Z","1.2.0":"2026-09-06T17:53:04.560Z","1.3.0":"2026-09-06T18:22:08.835Z","1.4.0":"2026-09-27T14:08:32.538Z","1.5.0":"2026-09-28T15:09:25.768Z"},"author":{"name":"Lead Liaison"},"license":"MIT","keywords":["captello","webview","iframe","postmessage","embed","sdk"],"description":"Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.","maintainers":[{"name":"mohammed.leadliaison","email":"mohammed.abdelgawad@leadliaison.com"}],"readme":"# @captello/ulc-webview-sdk\n\nTyped SDK for embedding the **Captello capture webview** in a host application\n(React, plain JS, or any framework). It encodes the exact `postMessage` contract\nthe webview speaks, so integrators don't have to reverse-engineer message strings.\n\nIt provides:\n\n1. **The message protocol** — enums and discriminated-union types for every\n   message the webview emits and accepts.\n2. **`CaptelloWebview`** — a host-side client that wraps an `<iframe>` and handles\n   the send/receive wire details (JSON-string encoding, origin/source filtering).\n3. **`buildEmbedUrl`** — a typed builder for the webview's query-string contract.\n4. **Typed submission data** — `VisibleSubmissionDataItem` and friends type a\n   submission's `visible_submissions_data` as a discriminated union you narrow on\n   `element_type` to render however you like.\n5. **Promise helpers** (`@captello/ulc-webview-sdk/promises`) — one-shot `await`-style\n   utilities like `submitForm(iframe)` and `waitForFormLoad(iframe)`.\n6. **React adapter** (`@captello/ulc-webview-sdk/react`) — a `useCaptelloWebview` hook that\n   manages the client lifecycle and returns an iframe ref plus typed senders.\n7. **The mobile-app channel** — the SDK's second, separate protocol, for when the\n   **Captello mobile app** is the host and embeds an application:\n    - `@captello/ulc-webview-sdk/mobile-host` — `MobileHostClient`, used by the _embedded\n      application_ (the meeting platform, Connexions) to reach the mobile app for an auth\n      token, the badge scanner, and navigation.\n    - `@captello/ulc-webview-sdk/embedded-app` — `EmbeddedAppClient`, used by the _mobile\n      app_ to serve those requests through the iframe it hosts.\n\nThe core package is framework-agnostic with no runtime dependencies; React is an\noptional peer dependency used only by the `/react` entry point.\n\n## Install\n\n```bash\npnpm add @captello/ulc-webview-sdk\n```\n\n### No bundler? Use the hosted script\n\nThe webview deployment also serves the SDK as a plain script — a single IIFE\nbundle exposing everything (core + promise helpers, minus the React adapter)\non a `CaptelloSdk` global. Because it deploys with the webview, the hosted\nfile always matches the protocol of the webview at the same origin.\n\n```html\n<script src=\"https://capture.captello.com/sdk/v1/captello-sdk.js\"></script>\n<script>\n    const { buildEmbedUrl, CaptelloWebview, FormMode } = CaptelloSdk;\n\n    const src = buildEmbedUrl(\"https://capture.captello.com\", {\n        formId: 1234,\n        mode: FormMode.Submit,\n    });\n    // ... point an iframe at `src` and wire up `new CaptelloWebview(iframe, ...)`\n    // exactly as in the quick start below.\n</script>\n```\n\nUse the same origin you embed (`capture.captello.com`, `capture-demo.…`, etc.).\nThe `v1` path segment only changes on breaking protocol changes. Teams with a\nbundler should prefer the npm package for types and tree-shaking.\n\n## Quick start\n\n```ts\nimport {\n    CaptelloWebview,\n    buildEmbedUrl,\n    FormMode,\n    Language,\n    LauncherType,\n    OutboundMessageType,\n} from \"@captello/ulc-webview-sdk\";\n\n// 1. Build the embed URL. Pass just the capture origin — the SDK appends the\n//    capture path for you (passing the full \".../capture/submission\" URL also works).\nconst src = buildEmbedUrl(\"https://capture.captello.com\", {\n    formId: 1234,\n    mode: FormMode.Submit,\n    launcher: LauncherType.EventGenWeb,\n    language: Language.English,\n});\n\n// 2. Point an iframe at it.\nconst iframe = document.createElement(\"iframe\");\niframe.src = src;\ndocument.body.appendChild(iframe);\n\n// 3. Wire up the client (scope messages to the webview's origin).\nconst webview = new CaptelloWebview(iframe, {\n    targetOrigin: new URL(src).origin,\n});\n\nwebview.on(OutboundMessageType.FormLoadComplete, () => {\n    console.log(\"form is ready\");\n});\n\nwebview.on(OutboundMessageType.SubmissionBody, (msg) => {\n    // Embedded forms hand the submission to the host instead of submitting directly.\n    persist(msg.data);\n});\n\nwebview.on(OutboundMessageType.FormErrorMessage, (msg) => {\n    showToast(msg.data); // already translated & display-ready\n});\n\n// 4. Drive the form programmatically.\nsubmitButton.onclick = () => webview.submit();\n\n// 5. Tear down when the iframe is removed.\nwebview.destroy();\n```\n\n### Submit and await the result\n\nFor the common \"click submit, then act on the outcome\" flow, use `submitAndWait()`\ninstead of wiring `submit()` to separate listeners. It resolves with the submission\nbody on `submission_body`, rejects with a `SubmissionError` on `form_error_message`,\nand rejects with a `SubmissionTimeoutError` if neither arrives in time — cleaning up\nits listeners in every case.\n\n```ts\nimport { SubmissionError } from \"@captello/ulc-webview-sdk\";\n\ntry {\n    const body = await webview.submitAndWait(); // default 60s timeout\n    await persistSubmission(body);\n    closeDialog();\n} catch (err) {\n    if (err instanceof SubmissionError) {\n        showToast(err.message); // translated, display-ready\n    } else {\n        // SubmissionTimeoutError or a send failure\n    }\n}\n```\n\nA refused submit — an invalid email, an empty required field, a blocking form-fill action —\nreaches `submitAndWait()` as a `SubmissionError` as soon as the form refuses it. Whether the\nform **also** shows the error as a toast is yours to choose, per call:\n\n```ts\n// You show the message yourself, so the form doesn't show it too.\nawait webview.submitAndWait({ showErrorInForm: false });\n\nawait webview.submitAndWait({ showErrorInForm: true, timeoutMs: 30_000 });\n```\n\nLeft out, the webview decides by launcher: Event Gen hosts get no toast, the rest do.\n`submitAndWait(30_000)` still works as the timeout on its own.\n\n### Read the data without validating\n\n`getSubmissionData()` returns the form's current values as a `SubmissionBody` **without\nvalidating them** — required fields can be empty. Nothing is checked, prompted, or saved,\nand the form stays as it is, so the user can keep editing and submit later. Use it to save\nprogress, or to read what has been entered so far.\n\n```ts\nconst body = await webview.getSubmissionData(); // default 60s timeout\nawait saveProgress(body.data);\n```\n\nThe body is built the way `submission_body` is on submit: pending files are uploaded first\nand arrive as URLs. It is delivered only to this call, as a `submission_data_result`, so an\n`on(SubmissionBody, …)` handler that persists final submissions does not fire. It rejects\nwith a `SubmissionError` when the webview can't build the body (e.g. a file failed to\nupload), and with a `SubmissionTimeoutError` when nothing answers — which is also what a\nwebview build older than this message does.\n\n### The transcribe button — host-side processing\n\nHosts that can transcribe scanned badges/cards can put a **Transcribe** button in\nthe form's badge element: enable it with `showTranscribeButton: true` in the embed\nURL, and answer the requests with `onTranscribeScannerRequest`. The button renders\ninside the badge/barcode element and only while the submission has no email (once\nan email lands — from the badge lookup or from a transcription — it disappears). When pressed, the form sends a\n`transcribe_scanner_request` carrying the scan's `element_id`, its `image_urls`, and\nthe `draft_submission_token` the webview was loaded with; the handler's resolved\nfields are sent back and filled into the form (empty values are skipped, so they\nnever blank already-filled inputs). Only enable the button when a handler is\nregistered — otherwise it times out with an error for the user.\n\n```ts\nconst src = buildEmbedUrl(\"https://capture.captello.com\", {\n    formId: 1234,\n    mode: FormMode.Submit,\n    showTranscribeButton: true, // show_transcribe_button=1\n});\n\nwebview.onTranscribeScannerRequest(async ({ image_urls, draft_submission_token }) => {\n    const fields = await transcribeService(image_urls, draft_submission_token);\n    // fields: [{ llFieldId: 16, llFieldIdentifier: \"FirstName\", value: \"Patrick\" }, ...]\n    return { fields, submissionType: \"ocr_transcription\" };\n});\n```\n\nA thrown `Error`'s message is shown to the user by the form, so throw display-ready\nmessages. The wire shapes are exported as `TranscribeScannerRequestMessage`,\n`TranscribeScannerResultMessage`, `TranscribeScannerResultData`, and\n`TranscribedScannerField`. Host processing is a convention, not a one-off: future\noperations get their own `*_request` / `*_result` pairs shaped like this one. In\nReact, pass the handler as the `onTranscribeScannerRequest` option/prop instead.\n\n#### Answering later, from callback-style code\n\nThe handler's second argument is a **reply handle** — call it whenever the work\nfinishes. Use it when the transcription can't hand back a promise: a callback-style\nAJAX wrapper, an event bus, a method that returns `void`. Nothing else changes, and the\nhost never builds or posts a message itself:\n\n```ts\nwebview.onTranscribeScannerRequest((request, reply) => {\n    ll_ajax_manager.send_request(\n        \"DraftedSubmissions\",\n        \"transcribeDraftScannerElement\",\n        { draft_submission_token: request.draft_submission_token, image_urls: request.image_urls },\n        (response) =>\n            response.success\n                ? reply.resolve({ fields: response.fields, submissionType: \"ocr_transcription\" })\n                : reply.reject(response.error),\n        () => reply.reject(\"Transcription failed.\"),\n    );\n});\n```\n\nBecause `reply` is just an object with `resolve`/`reject`, you can hand it straight to\nwhatever does the work and keep that code free of any SDK reference:\n\n```ts\nwebview.onTranscribeScannerRequest((request, reply) => myTranscriber(request, reply));\n```\n\nA handler that returns nothing is taken to own the reply, so the request stays open\nuntil `reply` answers it — returning the fields and answering through `reply` are both\nfirst-class, and the **first answer wins** (a second is ignored, with a dev warning).\n`reply.answered` tells you whether one already landed. `reply.reject`'s text is shown\nto the user verbatim, so pass display-ready messages.\n\nIf the reply happens somewhere with no access to the handler's scope — a global bus, a\ndifferent module holding only the id — the client also exposes the same two answers\ndirectly, taking the request or its bare `request_id`:\n\n```ts\nwebview.resolveTranscribeScannerRequest(request, { fields, submissionType: \"ocr_transcription\" });\nwebview.rejectTranscribeScannerRequest(request.request_id, \"Transcription failed.\");\n```\n\nEither way the result posts immediately, never sitting in the ready-queue, so a client\nthat attached after the form loaded still replies. Answering after `destroy()`, or once\nthe iframe is detached, is a silent no-op rather than a throw: by then nobody is\nlistening, and the form times its own button out.\n\n## React — `@captello/ulc-webview-sdk/react`\n\nThe React adapter is the smoothest way to integrate, in two flavors:\n\n- **`<CaptelloForm>`** — a turnkey component. Drop it in with an `embedUrl` and message\n  callbacks; it renders the `<iframe>`, shows your loading / error overlays, and forwards\n  the senders on a `ref`. The shortest path.\n- **`useCaptelloWebview`** — the underlying hook, for when you'd rather own the markup.\n\nBoth own one `CaptelloWebview` for the iframe's lifetime: they build the embed URL, create\nthe client when the iframe mounts, wire outbound messages to typed callbacks, track\nreadiness, and destroy the client on unmount.\n\n`react` is an optional peer dependency (React 18+).\n\n### `<CaptelloForm>` — the turnkey component\n\n```tsx\nimport { useRef } from \"react\";\nimport { FormMode, LauncherType, ActionButtonPosition, type SubmissionBody } from \"@captello/ulc-webview-sdk\";\nimport { CaptelloForm, type CaptelloFormHandle } from \"@captello/ulc-webview-sdk/react\";\n\nfunction UlcForm({\n    eventWebAccessToken,\n    onSubmitted,\n}: {\n    eventWebAccessToken: string;\n    onSubmitted: (body: SubmissionBody) => void;\n}) {\n    const form = useRef<CaptelloFormHandle>(null);\n\n    return (\n        <CaptelloForm\n            ref={form}\n            style={{ height: 600 }} // an iframe has no intrinsic height — size the form here\n            embedUrl={{\n                baseUrl: \"https://capture.captello.com\",\n                eventWebAccessToken,\n                actionButtonPosition: ActionButtonPosition.Hidden, // b=2\n                mode: FormMode.Submit,\n                launcher: LauncherType.EventGenWeb,\n            }}\n            defaultFormValues={{\n                info: [\n                    { ll_field_unique_identifier: \"FirstName\", value: \"Ada\" },\n                    { ll_field_unique_identifier: \"Email\", value: \"ada@example.com\" },\n                ],\n            }}\n            onSubmissionBody={onSubmitted}\n            loading={<Spinner />}\n            error={(message) => <ErrorBanner>{message}</ErrorBanner>}\n        >\n            {({ isReady }) => (\n                <button disabled={!isReady} onClick={() => form.current?.submit()}>\n                    Submit\n                </button>\n            )}\n        </CaptelloForm>\n    );\n}\n```\n\nProps are the hook options (the required `embedUrl`, `defaultFormValues`, every message\ncallback, and the client options) plus a few rendering conveniences:\n\n- **`className` / `style` / `id`** — applied to the wrapper element. Size the form here; the\n  iframe fills it.\n- **`iframeProps`** — attributes spread onto the `<iframe>` (`title`, `allow`, `sandbox`,\n  `name`, …). Defaults: `title=\"Captello form\"`, `allow=\"camera; microphone; geolocation\"`.\n  `src` is ignored — the URL comes from `embedUrl`.\n- **`loading`** — a node shown, centered over the iframe, until `form_load_complete`. The\n  iframe stays mounted underneath so it keeps loading.\n- **`error`** — a node (or `(message) => node`) shown when the form reports\n  `form_error_message`; the function form receives the translated, display-ready text.\n- **`children`** — inline controls rendered after the form. A function receives the live api\n  (status + senders), so a submit button needs no separate `ref`.\n- **`ref`** — a `CaptelloFormHandle`: the senders (`submit`, `reset`, `updateDraft`,\n  `triggerValidation`, `prefill`, `submitAndWait`, `getSubmissionData`) plus `status` / `isReady`, `getIframe()`,\n  and `getClient()`. Use it to drive the form from a parent without lifting state.\n\n### `useCaptelloWebview` — the hook\n\nPrefer to own the markup? The hook returns `iframeProps` to spread, an `isReady` flag, and\nstable senders — so a typical form is just the hook plus an `<iframe>`.\n\n```tsx\nimport { FormMode, LauncherType, ActionButtonPosition, type SubmissionBody } from \"@captello/ulc-webview-sdk\";\nimport { useCaptelloWebview } from \"@captello/ulc-webview-sdk/react\";\n\nfunction UlcForm({\n    eventWebAccessToken,\n    onSubmitted,\n}: {\n    eventWebAccessToken: string;\n    onSubmitted: (body: SubmissionBody) => void;\n}) {\n    const { iframeProps, isReady, submit, prefill } = useCaptelloWebview({\n        embedUrl: {\n            baseUrl: \"https://capture.captello.com\",\n            eventWebAccessToken,\n            actionButtonPosition: ActionButtonPosition.Hidden, // b=2\n            mode: FormMode.Submit,\n            launcher: LauncherType.EventGenWeb,\n        },\n        onSubmissionBody: onSubmitted,\n        onFormErrorMessage: showToast,\n    });\n\n    return (\n        <>\n            {!isReady && <Spinner />}\n            <iframe {...iframeProps} title=\"UlcForm\" allow=\"camera; microphone\" />\n            <button onClick={submit}>Submit</button>\n        </>\n    );\n}\n```\n\nWhat the hook (and the component built on it) handle for you:\n\n- **URL + origin.** `embedUrl: { baseUrl, ...EmbedUrlOptions }` is required; the hook builds\n  the URL, derives `targetOrigin`, and returns it as `iframeProps.src` — no separate\n  `buildEmbedUrl` / manual `src` wiring to keep in sync. (Need to own both the URL and the\n  origin? Use the `CaptelloWebview` class directly.)\n- **Readiness.** `isReady` and `status` (`\"loading\" | \"ready\" | \"error\"`) — no manual\n  `useState` + `onFormLoadComplete` for a spinner.\n- **Prefill timing.** Sends made before the form loads are queued and flushed on\n  `form_load_complete`, so you can `prefill(...)` as soon as you have data — no\n  gating on readiness, and no silently-dropped messages.\n- **Default values.** `defaultFormValues: { submission?, info? }` populates the form as soon\n  as it's ready, so seeding a known email or a previous submission needs no `ref`, no effect,\n  and no readiness check. See below.\n- **No memoization.** Callbacks are read fresh via a ref, so inline arrow functions\n  won't re-subscribe or re-create the client. The client is recreated only when the\n  `embedUrl`-derived origin / `hostWindow` / `matchSource` / `queueUntilReady` change.\n- **Stable senders** (`submit`, `reset`, `updateDraft`, `triggerValidation`, `prefill`,\n  `submitAndWait`, `getSubmissionData`) — safe in deps or passed to children. `getClient()` returns the live\n  client for escape hatches.\n\n### `defaultFormValues` — populate the form on load\n\nBoth `<CaptelloForm>` and `useCaptelloWebview` take a `defaultFormValues` option that seeds\nthe form the moment it reports `form_load_complete`:\n\n```tsx\n<CaptelloForm\n    embedUrl={{ baseUrl: \"https://capture.captello.com\", eventWebAccessToken }}\n    defaultFormValues={{\n        // matched by ll_field_unique_identifier\n        info: [{ ll_field_unique_identifier: \"Email\", value: user.email }],\n        // and/or keyed by element id, same shape prefill() takes\n        submission: { data: { \"1042\": \"Acme Inc.\" } },\n    }}\n/>\n```\n\nIt's the declarative form of `prefill(...)` — same wire message, same shapes — so you don't\nneed a `ref`, an effect, or a readiness check just to seed a form. Semantics:\n\n- **Sent first.** It goes out ahead of everything else, so an explicit `prefill(...)` you\n  make later overwrites it.\n- **Read once, at mount.** These are _defaults_, not controlled values: changing the prop\n  afterwards does **not** re-populate the form. Call `prefill(...)` for that.\n- **No memoization needed.** An inline object literal won't re-fire it or re-create the client.\n- **Omit or leave empty** (`{}`) and no message is sent at all.\n\n`submission` accepts a partial, and a whole `SubmissionBody` handed to you by\n`onSubmissionBody` also fits — handy for re-opening a captured lead.\n\n### Callback props\n\n`useCaptelloWebview` accepts one optional callback per outbound message. Each receives the\nmessage's **payload**, not the `{ type, … }` envelope — the callback name already tells you\nthe type, so there's nothing to discriminate on:\n\n| Callback                      | Receives                    |\n| ----------------------------- | --------------------------- |\n| `onFormLoadComplete`          | — (no payload)              |\n| `onFormErrorMessage`          | `string` (translated text)  |\n| `onSubmissionBody`            | `SubmissionBody`            |\n| `onFormSubmitSuccess`         | `\"create\" \\| \"update\"`      |\n| `onConnexionsProfileRedirect` | — (no payload)              |\n| `onConnexionsDownloadVcard`   | — (no payload)              |\n| `onAnyMessage`                | the whole `OutboundMessage` |\n\n`onAnyMessage` is the exception: it fires for every type (after the specific handler), so it\ngets the full message including `type`. So does the low-level `CaptelloWebview.on(type, …)`,\nwhich is unchanged — envelopes there, payloads here.\n\n`embedUrl` (required), `defaultFormValues`, and the client options (`hostWindow`,\n`matchSource`, `queueUntilReady`) go in the same object.\n\n### Without the adapter\n\nIf you don't want the hook, create a `CaptelloWebview` yourself in an effect against an\niframe ref, subscribe with `.on(...)`, and call `.destroy()` on unmount. Hold the client\nin a ref (or context) so sibling components can drive it without querying the DOM.\n\n## The message contract\n\n**Wire format:** every message is a JSON **string** with a `type` discriminator.\nThe SDK handles this for you — `CaptelloWebview` `JSON.stringify`s outgoing messages\n(the webview parses inbound data with `JSON.parse`, so a raw object would be silently\nignored) and parses + validates incoming ones. Direction is named from the webview's\npoint of view.\n\n### Outbound — webview → host (you listen)\n\n| `type`                        | Constant                                        | Payload                                         | Meaning                                            |\n| ----------------------------- | ----------------------------------------------- | ----------------------------------------------- | -------------------------------------------------- |\n| `form_load_complete`          | `OutboundMessageType.FormLoadComplete`          | —                                               | Form finished loading/rendering.                   |\n| `form_error_message`          | `OutboundMessageType.FormErrorMessage`          | `data: string`                                  | Translated, display-ready error message.           |\n| `submission_body`             | `OutboundMessageType.SubmissionBody`            | `data: SubmissionBody`                          | Full submission for the host to persist.           |\n| `form_submit_success`         | `OutboundMessageType.FormSubmitSuccess`         | —                                               | Submission succeeded (kiosk / quick-capture).      |\n| `connexions_profile_redirect` | `OutboundMessageType.ConnexionsProfileRedirect` | —                                               | Host should perform the profile redirect.          |\n| `connexions_download_vcard`   | `OutboundMessageType.ConnexionsDownloadVcard`   | —                                               | Host should trigger the vCard download.            |\n| `submission_data_result`      | `OutboundMessageType.SubmissionDataResult`      | `request_id`, `data?: SubmissionBody`, `error?` | Answer to `getSubmissionData()` — handled for you. |\n\n### Inbound — host → webview (you send)\n\n| `type`                    | Method                                    | Notes                                                                    |\n| ------------------------- | ----------------------------------------- | ------------------------------------------------------------------------ |\n| `submit_form`             | `webview.submit()`, `submitAndWait()`     | Submit as if the user pressed the button. Optional `show_error_in_form`. |\n| `reset_form`              | `webview.reset()`                         | Clear all entered values.                                                |\n| `update_draft`            | `webview.updateDraft()`                   | Switch to draft-update mode.                                             |\n| `trigger_validation`      | `webview.triggerValidation(target)`       | `target`: `\"email\" \\| \"invitation_code\" \\| \"all\"`.                       |\n| `form_prefill`            | `webview.prefill({ submission?, info? })` | Submission body, transcription items, or both.                           |\n| `submission_data_request` | `webview.getSubmissionData()`             | Current values, unvalidated; answered with `submission_data_result`.     |\n\n### Prefill (`form_prefill`)\n\nA single `prefill({ submission?, info? })` carries either or both payloads:\n\n- `submission` — a `SubmissionPrefill` (a partial `{ data?, ... }` you assemble). Its `data`\n  accepts either shape you might be holding, and the webview normalizes whichever it gets:\n    - a `SubmissionPrefillDataItem[]` — the array returned by the submissions API, so a\n      `submission.data` fetched from there passes through as-is;\n    - a `DraftSubmissionData` — values flat-keyed by element / sub-element id. This is the shape\n      a received `SubmissionBody` carries and the shape a draft is stored in, so an\n      `onSubmissionBody` payload round-trips directly.\n- `info` — a list of `PrefillInfoItem`. The webview matches each item by\n  `ll_field_unique_identifier` (e.g. `\"FirstName\"`, `\"Email\"`); `ll_field_id` is optional\n  metadata (number or string) and `value` may be a string or boolean.\n\nBoth ride the same wire `data_type` (`ulc_submission_and_info`); pass just the key(s) you\nhave.\n\n## Rendering a submission\n\nA `submission_body` payload includes `visible_submissions_data` — one entry per filled,\nvisible element, typed as `VisibleSubmissionDataItem[]` and discriminated by\n`element_type`. Narrow on `element_type` and `element_value` is precisely typed (string,\nstring array, name/address objects, order quantities, etc.), so you render it exactly\nhow your UI needs — no flattening helper to fight:\n\n```ts\nimport { FormElementType, OutboundMessageType } from \"@captello/ulc-webview-sdk\";\n\nwebview.on(OutboundMessageType.SubmissionBody, (msg) => {\n    for (const item of msg.data.visible_submissions_data ?? []) {\n        switch (item.element_type) {\n            case FormElementType.email:\n                addRow(item.element_title, item.element_value); // element_value: string\n                break;\n            case FormElementType.checkbox:\n                // element_value: string[] | OrderCheckboxSubmissionData\n                addRow(item.element_title, renderChoices(item.element_value));\n                break;\n            case FormElementType.simple_name:\n                // element_value: NameSubmissionValue ({ FirstName?, LastName? })\n                addRow(\n                    item.element_title,\n                    [item.element_value.FirstName, item.element_value.LastName].filter(Boolean).join(\" \"),\n                );\n                break;\n            case FormElementType.address:\n                // element_value: AddressSubmissionValue ({ StreetAddress?, City?, State?, Zipcode?, Country?, … })\n                addRow(\n                    item.element_title,\n                    [item.element_value.StreetAddress, item.element_value.City].filter(Boolean).join(\", \"),\n                );\n                break;\n            // …other element types\n        }\n    }\n});\n```\n\nBecause `visible_submissions_data` is already typed, there's no parse/validate step in\nthe SDK — read it straight off the message. The `submission_body` data is `unknown`-safe\nat the boundary (`SubmissionBody.data` is `Record<string, unknown>`), so cast or validate\nto taste if you consume untrusted sources.\n\n## `buildEmbedUrl(baseUrl, options)`\n\nMaps friendly option names onto the webview's short query keys. Existing params on\n`baseUrl` are preserved; options override matching keys.\n\n**You only pass the capture origin** (e.g. `https://capture.captello.com`) — the SDK owns\nthe capture path. The origin, the origin with a trailing slash, and the full\n`…/capture/submission` URL all normalize to the same result, so there's nothing to get\nwrong. An existing `…/capture/activation` route is preserved rather than rewritten.\n\n```ts\nbuildEmbedUrl(\"https://capture.captello.com\", { formId: 1234 });\nbuildEmbedUrl(\"https://capture.captello.com/\", { formId: 1234 });\nbuildEmbedUrl(\"https://capture.captello.com/capture/submission\", { formId: 1234 });\n// → all produce \"https://capture.captello.com/capture/submission?f=1234\"\n```\n\n| Option                      | Query key   | Notes                                                                             |\n| --------------------------- | ----------- | --------------------------------------------------------------------------------- |\n| `formId`                    | `f`         |                                                                                   |\n| `submissionToken`           | `s`         |                                                                                   |\n| `stationId`                 | `st`        |                                                                                   |\n| `mode`                      | `m`         | `FormMode` enum.                                                                  |\n| `eventWebAccessToken`       | `e`         |                                                                                   |\n| `activationId`              | `a`         |                                                                                   |\n| `language`                  | `l`         |                                                                                   |\n| `actionButtonPosition`      | `b`         | `ActionButtonPosition` enum: `Fixed` (`\"0\"`), `Bottom` (`\"1\"`), `Hidden` (`\"2\"`). |\n| `formType`                  | `form_type` | `\"template\" \\| \"device\"`.                                                         |\n| `launcher`                  | `launcher`  | `LauncherType` enum.                                                              |\n| `submissionType`            | `t`         | `\"normal\" \\| \"drafted\"`.                                                          |\n| `submitButtonBottomPadding` | `sbbp`      |                                                                                   |\n| `useIn`                     | `useIn`     | `\"outbound\" \\| \"inbound\" \\| \"notes\"`.                                             |\n| `platform`                  | `platform`  | `\"web\" \\| \"mobile\"`.                                                              |\n| `hideEmail`                 | `he`        | Boolean → `\"1\"` when true, omitted when false.                                    |\n| `connexionsEmbedMode`       | `cem`       | Boolean → `\"1\"`.                                                                  |\n| `emro`                      | `emro`      | Boolean → `\"1\"`. Edit mode read-only.                                             |\n| `extraParams`               | (verbatim)  | Appended as-is; `undefined`/`null` skipped.                                       |\n\n## `CaptelloWebview` API\n\n```ts\nnew CaptelloWebview(iframe, {\n  targetOrigin?: string;      // recommend the webview origin; defaults to \"*\"\n  hostWindow?: Window;        // defaults to global window\n  matchSource?: boolean;      // default true: only accept messages from this iframe\n  queueUntilReady?: boolean;  // default true: buffer sends until form_load_complete\n  autoDestroy?: boolean;      // default true: destroy() when the iframe leaves the DOM\n});\n```\n\nEvery `on*` method returns a subscription handle — an object with `unsubscribe()`,\nwhich is also directly callable:\n\n```ts\nconst sub = webview.on(OutboundMessageType.SubmissionBody, save);\nsub.unsubscribe(); // preferred\nsub(); // equivalent, for the older off() style\n```\n\n- `isReady` — `true` once the form has reported `form_load_complete`.\n- `on(type, listener) => unsubscribe` — subscribe to one outbound type.\n- `once(type, listener) => unsubscribe` — fire at most once.\n- `onAny(listener) => unsubscribe` — every outbound message.\n- `submit()`, `reset()`, `updateDraft()`, `triggerValidation(target)` — inbound helpers.\n- `submitAndWait(options?)` — submit and await `submission_body` / `form_error_message` (see above).\n  `options` is `{ timeoutMs?, showErrorInForm? }`, or just the timeout in ms.\n- `getSubmissionData(timeoutMs?)` — the current values as a `SubmissionBody`, without validation (see above).\n- `prefill({ submission?, info? })` — pre-fill from a submission body, transcription items, or both.\n- `onTranscribeScannerRequest(handler) => unsubscribe` — answer transcribe requests. The\n  handler gets `(request, reply)`: return the fields, or call `reply.resolve(...)` /\n  `reply.reject(...)` when callback-style work finishes (see\n  [above](#the-transcribe-button--host-side-processing)).\n- `resolveTranscribeScannerRequest(request, data)` / `rejectTranscribeScannerRequest(request, error)`\n  — answer from outside the handler's scope. Takes the request or its bare `request_id`.\n- `send(message)` — low-level escape hatch for any `InboundMessage`.\n- `destroy()` — detach the listener and drop subscriptions (idempotent; also runs itself when the iframe is removed).\n\n**The frame argument** (`FrameLike`) is the `<iframe>` element, or anything exposing a\n`contentWindow`. It must already be mounted — the caller owns that ordering. See\n[The client needs a mounted iframe](#the-client-needs-a-mounted-iframe).\n\n**Automatic teardown.** With `autoDestroy` (default `true`), the client calls\n`destroy()` on itself once the iframe leaves the document — including when an ancestor\nis removed — so a torn-down panel can't leak the `message` listener. Set it to `false`\nto manage the lifetime yourself.\n\n**`targetOrigin` accepts a full URL**, not just a bare origin: pass the embed URL or your\nwebview base URL and the SDK reduces it with `new URL(value).origin`. This is the usual\nreason a hand-rolled origin check gets commented out — a base URL never equals\n`event.origin`.\n\n**Send queueing.** With `queueUntilReady` (default `true`), any send before the webview\nreports `form_load_complete` is buffered and flushed, in order, on load — so calling\n`prefill(...)` right after mount won't be silently dropped. A client that attaches\n_after_ the form already loaded won't observe `form_load_complete`; either create the\nclient with the iframe, or pass `queueUntilReady: false` to send immediately. The\none-shot `/promises` helpers set `queueUntilReady: false` automatically.\n\n### Security note\n\nAlways set `targetOrigin` to the webview's origin in production. With the default\n`\"*\"`, the client accepts messages from any origin and posts without an origin check —\nacceptable only for trusted/local development. Derive it from the URL you built with\n`new URL(src).origin`.\n\n## Promise helpers — `@captello/ulc-webview-sdk/promises`\n\nA separate entry point with one-shot, `await`-style helpers for imperative flows.\nWhere `CaptelloWebview` is a long-lived client you subscribe to, these take an iframe\ndirectly, create a short-lived client internally, wait for the relevant message, and\ntear it down — convenient when you just want to \"submit and get the body\" without\nmanaging a client instance.\n\n```ts\nimport { submitForm, waitForFormLoad, waitForMessage, SubmissionError } from \"@captello/ulc-webview-sdk/promises\";\nimport { OutboundMessageType } from \"@captello/ulc-webview-sdk\";\n\nawait waitForFormLoad(iframe, { targetOrigin }); // resolves on form_load_complete\n\ntry {\n    const body = await submitForm(iframe, { targetOrigin }); // submit + await result\n    await persist(body);\n} catch (err) {\n    if (err instanceof SubmissionError) showToast(err.message);\n}\n\n// generic: await the next message of any outbound type\nconst msg = await waitForMessage(iframe, OutboundMessageType.SubmissionBody, { targetOrigin });\n```\n\n| Helper                               | Resolves / rejects                                                                                         |\n| ------------------------------------ | ---------------------------------------------------------------------------------------------------------- |\n| `submitForm(frame, opts?)`           | resolves `SubmissionBody`; rejects `SubmissionError` (on `form_error_message`) or `SubmissionTimeoutError` |\n| `waitForFormLoad(frame, opts?)`      | resolves `void` on `form_load_complete`; rejects `MessageTimeoutError`                                     |\n| `waitForMessage(frame, type, opts?)` | resolves the typed message; rejects `MessageTimeoutError`                                                  |\n\n`opts` is `{ targetOrigin?, hostWindow?, matchSource?, timeoutMs? }` (`timeoutMs`\ndefaults to 60_000; `0`/`Infinity` waits indefinitely). All helpers remove their\ninternal listener before settling, including on timeout.\n\n> Note: these catch a _future_ message. If the form may already have loaded before you\n> call `waitForFormLoad` (e.g. you attach late), create a long-lived `CaptelloWebview`\n> before the iframe navigates instead.\n\n## The mobile-app channel — `/mobile-host` and `/embedded-app`\n\nThe SDK covers two embedding directions, and they are different protocols. Keep them apart:\n\n| Who embeds whom                             | Host                | Embedded              | Client to use                                                             | Wire                            |\n| ------------------------------------------- | ------------------- | --------------------- | ------------------------------------------------------------------------- | ------------------------------- |\n| **Your page embeds the capture webview**    | your page           | Captello capture form | `CaptelloWebview` (everything above this section)                         | JSON strings, `snake_case`      |\n| **The Captello mobile app embeds your app** | Captello mobile app | your application      | `MobileHostClient` (in your app), `EmbeddedAppClient` (in the mobile app) | plain objects, `SCREAMING_CASE` |\n\n### In the embedded application — `MobileHostClient`\n\n```ts\nimport { MobileHostClient, ScannerError } from \"@captello/ulc-webview-sdk/mobile-host\";\n\nconst mobileHost = new MobileHostClient(); // listens on window, posts to window.parent\n\nconst token = await mobileHost.requestAuthToken(); // exchange it for a session, then:\nmobileHost.notifyReady(); // the mobile app hides its spinner\n\ntry {\n    const people = await mobileHost.openScanner(); // resolves when the scanner closes\n    const [first] = people; // undefined if the user cancelled\n    if (first) fillForm(first.fields, first.badgeId);\n} catch (e) {\n    if (e instanceof ScannerError) showToast(e.message);\n}\n```\n\n`openScanner()` resolves with every person the scanner captured, in scan order — several\nfor a group scan. Each `ScannedPerson` is `{ badgeId, fields }`, where `fields` is a\n`PrefillInfoItem[]` keyed by `ll_field_unique_identifier`, so it can be fed straight into a\ncapture form's `prefill({ info })`. A badge with no lookup data still arrives with its\n`badgeId` and empty `fields`. `send(request)` posts any other `MobileHostRequest`;\n`on(MobileHostResponseType.X, listener)` subscribes to any response. `targetOrigin`\ndefaults to `\"*\"` here: the mobile app's webview origin differs per platform and an\napplication only uses this channel when it is running inside the app.\n\n### In the mobile app — `EmbeddedAppClient`\n\n```ts\nimport { EmbeddedAppClient, MobileHostRequestType } from \"@captello/ulc-webview-sdk/embedded-app\";\n\nconst embedded = new EmbeddedAppClient(iframe); // target origin read from iframe.src\n\nembedded.onRequest(MobileHostRequestType.RequestAuthToken, async () => {\n    embedded.sendAuthToken(await mintMagicToken());\n});\nembedded.onRequest(MobileHostRequestType.OpenScanner, async () => {\n    for (const person of await runScanner()) embedded.sendScannerResult(person);\n    embedded.sendScannerClosed();\n});\nembedded.onRequest(MobileHostRequestType.NavigateBack, () => modal.dismiss());\n\n// when the iframe goes away:\nembedded.destroy();\n```\n\nRequests are only accepted from the bound iframe's window (`matchSource`) and, once a\ntarget origin is known, from that origin. Responses can carry an auth token, so with no\n`targetOrigin` option and no parsable `iframe.src` the client refuses to send rather than\nfall back to `\"*\"`.\n\n### Wire reference\n\n| Embedded app → mobile app (`MobileHostRequestType`)                             | `MobileHostClient`     | Mobile app → embedded app (`MobileHostResponseType`)                             | `EmbeddedAppClient`                                                         |\n| ------------------------------------------------------------------------------- | ---------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |\n| `APP_READY`                                                                     | `notifyReady()`        | —                                                                                | —                                                                           |\n| `ERROR { message }`                                                             | `notifyError(message)` | —                                                                                | —                                                                           |\n| `NAVIGATE_BACK`                                                                 | `navigateBack()`       | —                                                                                | —                                                                           |\n| `REQUEST_AUTH_TOKEN`                                                            | `requestAuthToken()`   | `AUTH_TOKEN { token }`                                                           | `sendAuthToken(token)`                                                      |\n| `OPEN_ULC_FORM_SCANNER`                                                         | `openScanner()`        | `ULC_FORM_SCANNER_RESULT { badgeId, result }` ×N, then `ULC_FORM_SCANNER_CLOSED` | `sendScannerResult(person)`, `sendScannerError(msg)`, `sendScannerClosed()` |\n| `OPEN_URL`, `COPY_TEXT`, `SHARE_URL`, `SAVE_VCARD`, `ADD_TO_WALLET`, `SYNC_APP` | `send({ type, ... })`  | `COPIED_*`, `SAVE_VCARD_*`, `ADD_TO_WALLET_*` acks                               | `send({ type })`                                                            |\n\n## Migrating an existing integration\n\nHost apps that integrated before this SDK typically hand-rolled the same three pieces:\ntheir own copy of the message-type strings, a manual `URLSearchParams` builder with the\nshort keys, and ad-hoc `window.addEventListener(\"message\")` / `iframe.contentWindow.postMessage`\ncalls. Replace them as follows.\n\n**1. Message-type constants → SDK enums.** Delete local copies (e.g. `UlcFormActionTypeSent`,\n`UlcFormActionTypeReceived`, `UlcFormDataType`) and import `InboundMessageType` /\n`OutboundMessageType` (the `form_prefill` `data_type` is set for you by `prefill(...)`).\n\n**2. Manual URL building → `buildEmbedUrl`.**\n\n```ts\n// before\nconst src = `${base}/capture/submission?e=${token}&b=2&m=submit&launcher=event_gen_web` + (code ? `&l=${code}` : \"\");\n\n// after — pass the origin; the SDK appends the capture path\nconst src = buildEmbedUrl(base, {\n    eventWebAccessToken: token,\n    actionButtonPosition: ActionButtonPosition.Hidden,\n    mode: FormMode.Submit,\n    launcher: LauncherType.EventGenWeb,\n    language: code as Language | undefined,\n});\n```\n\n**3. Manual listeners → `client.on(...)`.** Replace the `messageListener` +\n`safeJsonParse(e.data)` + `if (type === ...)` chain with typed subscriptions; the SDK\nparses, validates origin/source, and cleans up on `destroy()`.\n\n**4. Hand-rolled submit promise → `submitAndWait()`.** A common pattern is a custom\npromise that posts `submit_form`, listens for `submission_body`/`form_error_message`,\ndedupes listeners, and times out. That whole helper collapses to:\n\n```ts\nconst body = await client.submitAndWait(); // throws SubmissionError on form_error_message\n```\n\n**5. `document.querySelector(\"#ulcForm\").contentWindow.postMessage(...)` → client methods.**\nHold the `CaptelloWebview` instance in a ref/context and call `submit()` / `reset()` /\n`prefill()` instead of re-querying the DOM and stringifying messages by hand.\n\n### Worked example: swapping in a global `message` listener\n\nThe common pre-SDK shape is one listener registered at page setup that JSON-parses every\nevent and dispatches on `type`. It swaps in wholesale — with one structural change: the\nclient is created where the iframe is created, rather than once at page setup, since it\nneeds a mounted iframe. In exchange it tears itself down with that iframe, so there is no\nteardown to remember.\n\n```js\n// before — one global listener, manual parse, if/else chain\nconst messageListener = (event) => {\n    let dataParsed = {};\n    try {\n        dataParsed = JSON.parse(event.data);\n    } catch (e) {\n        return;\n    }\n    if (dataParsed.type === \"form_error_message\") {\n        show_error_message(dataParsed.data);\n        return;\n    }\n    if (dataParsed.type === \"transcribe_scanner_request\") {\n        ll_form_submits_manager.transcribe_draft_scanner_element(dataParsed);\n        return;\n    }\n    if (dataParsed.type === \"submission_body\") {\n        saveSubmission(dataParsed.data);\n    }\n};\nwindow.addEventListener(\"message\", messageListener);\n```\n\n```js\n// after — created alongside the iframe; dies with it\nconst webview = new CaptelloSdk.CaptelloWebview(panelIframe, {\n    targetOrigin: CAPTURE_PORTAL_WEB_VIEW_BASE, // a full URL is fine — reduced to its origin\n});\n\nwebview.on(CaptelloSdk.OutboundMessageType.FormErrorMessage, (msg) => show_error_message(msg.data));\n\nwebview.on(CaptelloSdk.OutboundMessageType.TranscribeScannerRequest, (request) =>\n    ll_form_submits_manager.transcribe_draft_scanner_element(request),\n);\n\nwebview.on(CaptelloSdk.OutboundMessageType.SubmissionBody, (msg) => saveSubmission(msg.data));\n```\n\nWhat you get by dropping the hand-rolled listener: the origin check is enforced (the\none usually commented out because the constant is a base URL, not a bare origin — the\nSDK reduces it for you), non-Captello and malformed `postMessage` traffic from other\nscripts on the page is filtered out instead of hitting your `try/catch`, unknown `type`\nvalues are ignored, and `webview.destroy()` removes everything in one call.\n\nWhere the old code posted a reply by hand, use the client so it is stringified and\norigin-targeted for you — `transcribe_draft_scanner_element` ends with:\n\n```js\nwebview.resolveTranscribeScannerRequest(request, { fields, submissionType: \"ocr_transcription\" });\n// …or, on failure:\nwebview.rejectTranscribeScannerRequest(request, \"Transcription failed.\");\n```\n\n### `attach()` — no iframe reference needed\n\nIf your integration is a global `message` listener rather than something that owns the\niframe element, `attach()` is the whole thing. Call it once, register handlers, done —\nnothing to query from the DOM, no ordering to get right relative to when the iframe is\ncreated, and no teardown to remember:\n\n```js\nconst webview = CaptelloSdk.attach({ targetOrigin: CAPTURE_PORTAL_WEB_VIEW_BASE });\n\nwebview.on(CaptelloSdk.OutboundMessageType.FormErrorMessage, (m) => showError(m.data));\nwebview.on(CaptelloSdk.OutboundMessageType.SubmissionBody, (m) => save(m.data));\nwebview.onTranscribeScannerRequest((request, reply) => transcribe(request, reply));\n```\n\n**How it finds the form.** The first inbound message that clears the origin check _and_\nparses as a well-formed Captello message identifies the webview. That sender becomes the\ntarget for sends and the window every later message is source-matched against — so after\nthe first message this is exactly as strict as passing the iframe. With `targetOrigin`\nset to the webview's origin, only the webview can ever be adopted; under the default\n`\"*\"` that gate is absent, which is one more reason to set it.\n\n**Teardown is still automatic.** A removed iframe's window reports `closed`, and `closed`\nis readable cross-origin, so the client notices its form going away and destroys itself.\n(Verified in Chromium against a genuinely cross-origin iframe.) Pass `autoDestroy: false`\nto own the lifetime yourself.\n\n**The one trade-off.** Until the form speaks once there is no window to post to, so an\nunprompted `submit()` or `prefill()` before then has nowhere to go. With the default\n`queueUntilReady` those sends are buffered and flushed on `form_load_complete`, which\ncovers the normal case. If you drive the form unprompted from page load _and_ can hold\nthe element, prefer the explicit form below.\n\n### Or pass the iframe directly\n\nConstruct the client once the iframe is in the DOM — the caller owns that ordering. The\nclient source-matches every inbound message against the iframe's `contentWindow` from\nthe very first one, and posts straight to it.\n\nIf your old listener was registered at page setup, before the panel's iframe existed,\nmove client creation to the moment you create the iframe:\n\n```js\n// when the panel opens\nvar panelIframe = document.createElement(\"iframe\");\npanelIframe.src = captureUrl;\npanel.appendChild(panelIframe);\n\nvar webview = new CaptelloSdk.CaptelloWebview(panelIframe, { targetOrigin: BASE });\nwebview.on(CaptelloSdk.OutboundMessageType.FormErrorMessage, showError);\n// ...\n```\n\n**You do not need a matching `destroy()`.** The client watches for its iframe leaving\nthe document and tears itself down when it does — removing the `message` listener and\ndropping every subscription. An ancestor being removed counts, which is the usual case:\nemptying a panel or modal takes the iframe with it, and the client goes too. Calling\n`destroy()` yourself still works and is idempotent, so belt-and-braces teardown is fine.\n\nPass `autoDestroy: false` to own the lifetime entirely — e.g. if you deliberately detach\nand re-insert the same iframe element and want the client to survive it. Detection uses\n`MutationObserver`, so with a `{ contentWindow }` stand-in or in a non-DOM environment\nit is simply inert.\n\nIf you'd rather keep your own listener for now, `parseOutboundMessage(event.data)` is\nexported on its own: it replaces the `try { JSON.parse } catch` guard and returns a\ntyped message (or `null` for anything that isn't ours), so you can adopt the protocol\ntypes without restructuring anything.\n\n**Prefill notes for migrators.** `prefill({ info })` items are matched by\n`ll_field_unique_identifier`; `ll_field_id` is optional (number or string) and `value`\nmay be a string or boolean — so existing payloads with numeric ids and boolean values\ntype-check as-is. `prefill({ submission })` accepts a loose `SubmissionPrefill`, so a\npartial `{ email?, data?, ... }` you assemble type-checks as-is, and `data` may be either a\n`SubmissionPrefillDataItem[]` (the submissions API array) or a flat `DraftSubmissionData`\nrecord (the shape a received `SubmissionBody` carries) — a previously-received body can be\npassed back directly.\n\n**Out of scope.** If your app is _itself_ embedded inside the webview shell and talks to\n_its_ parent via `window.parent.postMessage` (e.g. relaying `email`/`clientId`, or custom\n`NAVIGATE_BACK` / scanner messages), that is a separate channel from the capture-form\ncontract — keep that code; this SDK only models host ↔ capture-webview messaging.\n\n## Testing\n\nTwo layers, deliberately split:\n\n- **`pnpm test`** — vitest unit tests against a fake window. Fast, covers the client's\n  logic, queueing, and every branch of the message protocol.\n- **`pnpm e2e`** — Playwright browser tests that load the **built IIFE bundle** into a real\n  page and talk to a real iframe on a **different origin** (`localhost` and `127.0.0.1` on\n  one server). This is the only layer that can exercise genuine `postMessage` semantics:\n  origin filtering, `event.source` identity, and a removed iframe's `closed` flag — jsdom\n  reports `closed` as `undefined`, so `attach()`'s teardown is unprovable without a browser.\n  The webview here is a stub, so this proves the client's behaviour, not that both sides\n  agree. `e2e/mobile-app-channel.spec.ts` does the same for the mobile-app channel: a stub\n  mobile app on one origin (`EmbeddedAppClient`) embeds an application on the other\n  (`MobileHostClient`), so both shipped clients are proven against each other.\n- **`e2e/sdk-integration.spec.ts` in the app repo** (`pnpm exec playwright test e2e/sdk-integration.spec.ts`)\n  — the SDK against the **real webview**, cross-origin. This is the layer that catches\n  protocol drift between the two, because neither side is a stub: it waits for a real\n  `form_load_complete`, prefills and asserts the values land in real form inputs, and runs\n  `submitAndWait()` through to a real `submission_body` (and, separately, to a real\n  validation failure surfacing as `SubmissionError`).\n\n## Development\n\n```bash\npnpm install   # from the repo root (pnpm workspace) or this package\npnpm build     # bundle ESM + .d.ts into dist/\npnpm typecheck\n```\n\n## License\n\nMIT © Lead Liaison, LLC. See [LICENSE](./LICENSE).\n","readmeFilename":"README.md"}