{"_id":"@callcorpacd/platform-bridge","_rev":"14-58466622dc94627e151328772462a7ed","name":"@callcorpacd/platform-bridge","dist-tags":{"latest":"1.9.1"},"versions":{"1.0.0":{"name":"@callcorpacd/platform-bridge","version":"1.0.0","keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","_id":"@callcorpacd/platform-bridge@1.0.0","maintainers":[{"name":"david.peterson","email":"david.peterson@callcorp.com"}],"dist":{"shasum":"6b907fc98a12d1a68c1a659cd48dd20079fa6d5e","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.0.0.tgz","fileCount":10,"integrity":"sha512-a5U+DmfBDHTn8b6DKK/av8tMQyHZ+wLwzSYp/fIleQpGnadqDbjJJ5a7y1niay/uki58uP0R0VTy699al1vNUw==","signatures":[{"sig":"MEUCIC1EOiFfiQ0SidiZGMdp4cD1yiEw3aaR/gqfkkSdiqyaAiEApR4BybnGIJ8RQamLmUBWiEnjEoAiVKHKdnIWxHCPYWU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":41737},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./host":"./src/host.js","./client":"./src/client.js","./protocol":"./src/protocol.js"},"gitHead":"156554387b465c14869342234541e951cf20972f","_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"_npmVersion":"11.6.2","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/platform-bridge_1.0.0_1784915257848_0.39668861992684135","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@callcorpacd/platform-bridge","version":"1.1.0","keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","_id":"@callcorpacd/platform-bridge@1.1.0","maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"dist":{"shasum":"9003c0b863386e86d34d766e632dab52470343dc","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.1.0.tgz","fileCount":13,"integrity":"sha512-Az0KedvvXCq6Q8Q8UOVROROGa/hWQKBCxaoHRCfDWlnriJU6EDt8kPo6eueLgOEhM2QbpeJgCJ6dChYjXVEVug==","signatures":[{"sig":"MEUCICW6zy4E8SL/LktPzBOp0Tk5z+wEV02z6jYIRjDSEXtjAiEA2NujyyfwxXhsl4vJ7t9zvmLIwiKje5sm5UpvstG+Bn4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":108288},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./host":"./src/host.js","./client":"./src/client.js","./protocol":"./src/protocol.js"},"gitHead":"76fac4014303aa13930f8b5b112d21589fb76c05","_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"_npmVersion":"11.6.2","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/platform-bridge_1.1.0_1785429778770_0.6014717112836414","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@callcorpacd/platform-bridge","version":"1.2.0","keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","_id":"@callcorpacd/platform-bridge@1.2.0","maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"dist":{"shasum":"2942270655b3c3909e00d610d2845525353b0cef","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.2.0.tgz","fileCount":13,"integrity":"sha512-ea1b2rx/oEvtGfhn88WYW6MQ5fZ6AUJgJmdu7WxAMs85evtO9EBth6sekFwRsyCrxVOH5BUCy40NVYeFtRUIdA==","signatures":[{"sig":"MEQCIARDg1nhz6JKxMqazCxS1RBLfPFpuZh+VIOzA/w2RPPTAiBq1jTm8fEnNHy01xqsyzK5d7RSSYKNozxKWiwdyF5ZTg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":118158},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./host":"./src/host.js","./client":"./src/client.js","./protocol":"./src/protocol.js"},"gitHead":"e626f7282fb09ef87c9c92d74c46b782eece3046","_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"_npmVersion":"11.6.2","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/platform-bridge_1.2.0_1785433817246_0.15817016570184972","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@callcorpacd/platform-bridge","version":"1.3.0","keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","_id":"@callcorpacd/platform-bridge@1.3.0","maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"dist":{"shasum":"ffc797f367d1f17d004c9141eef72b2fb59ecba5","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.3.0.tgz","fileCount":13,"integrity":"sha512-r5GWfJiEfeT8hfGhy2jk/Y2YUnYsYXvvMVQVfGrHXllXGALA6tpmqW6APmItOWJ9WLIG86tuxPD1V9rJzH7zGw==","signatures":[{"sig":"MEUCIQDxQ06PRF0yQNzyyRE/AJbNcUqhqtAvo3M9qXQK9oPjPAIgFscZOj01Exr3k3GAePEDF3vdEtvZoJPjOSbw+adUMho=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":122392},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./host":"./src/host.js","./client":"./src/client.js","./protocol":"./src/protocol.js"},"gitHead":"e626f7282fb09ef87c9c92d74c46b782eece3046","_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"_npmVersion":"11.6.2","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/platform-bridge_1.3.0_1785435672226_0.39062688498420406","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"name":"@callcorpacd/platform-bridge","version":"1.4.0","keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","_id":"@callcorpacd/platform-bridge@1.4.0","maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"dist":{"shasum":"bc51970fa4a330bdf965f31fa5d212201497307a","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.4.0.tgz","fileCount":13,"integrity":"sha512-QS9JGVhuLzTN4mJuET9d2bijMGHSmJK9MmstHa/Q6gipfo0j+hisWXqmmCA5XzdFl0kIqcNCHOJNMpndSxH6CA==","signatures":[{"sig":"MEUCIQCZ41d3nLgr9oli535ADykaAaTZRj6Jr14l0nz3psdnlgIgFhGFi6U5e7IiY3BJ9L0cWfE2Gqpe2bJs5O4KnoAZjfk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":122184},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./host":"./src/host.js","./client":"./src/client.js","./protocol":"./src/protocol.js"},"gitHead":"81c3e18c6fd2ce25f56aae84e2634642ec062120","_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"_npmVersion":"11.6.2","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/platform-bridge_1.4.0_1785444020212_0.9088322573064662","host":"s3://npm-registry-packages-npm-production"}},"1.4.1":{"name":"@callcorpacd/platform-bridge","version":"1.4.1","keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","_id":"@callcorpacd/platform-bridge@1.4.1","maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"dist":{"shasum":"e5b3f820c4213bc52cf1cd07dc06419da556e32b","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.4.1.tgz","fileCount":13,"integrity":"sha512-xbNGyeUUEgq12x0Tfp8d8dL15b5zW5WBgqCfz+WoLfyE308Z1mrGpj+OStihULdb/SINw+eQSLTsMhlh65eZkw==","signatures":[{"sig":"MEUCIHq1hFhkAHOk7jE9jflq9oCJqFt9SIk7QZrAyOmangQhAiEAr7dK4pJDIjmSm0sro93Q7ie21LRVAhzCkdpzQseaA8E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":123630},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./host":"./src/host.js","./client":"./src/client.js","./protocol":"./src/protocol.js"},"gitHead":"f52843fa1a5978839a731f4b45324f2458ff1aba","_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"_npmVersion":"11.6.2","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/platform-bridge_1.4.1_1785871957051_0.14149632634383003","host":"s3://npm-registry-packages-npm-production"}},"1.5.0":{"name":"@callcorpacd/platform-bridge","version":"1.5.0","keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","_id":"@callcorpacd/platform-bridge@1.5.0","maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"dist":{"shasum":"65a754337d62d5b1dc658b6ebdb00d8b680400d9","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.5.0.tgz","fileCount":13,"integrity":"sha512-tUjLJVFmeijP8cHgF7VyZU5zMk5RcK7j2ZLqxXGjkQonwgDemS1H4ESiX0Clx6e05EVK2eFiqHzEk9T+1PQNRg==","signatures":[{"sig":"MEUCIGOwGEs/IjNc9RugEB5efXhDhBhSd3iRgdhFV+nrBPmJAiEA74Dfl7nM+u1IynuU7xzmtPWlOnOcptSNMwmm2tU1eSc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":129695},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./host":"./src/host.js","./client":"./src/client.js","./protocol":"./src/protocol.js"},"gitHead":"16d631be429b5847c3a7f36c6dbde675a667c537","_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"_npmVersion":"11.6.2","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/platform-bridge_1.5.0_1785949789108_0.9755345018942589","host":"s3://npm-registry-packages-npm-production"}},"1.6.0":{"name":"@callcorpacd/platform-bridge","version":"1.6.0","keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","_id":"@callcorpacd/platform-bridge@1.6.0","maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"dist":{"shasum":"47c7c99fef22a306b0a57a949de07d71346d586b","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.6.0.tgz","fileCount":13,"integrity":"sha512-XsStwNlyOOwrJ+UmPvZ+Tm33ITgg+zwHu8K/xZg0ayX0/N/GKv6GOGPewk3KSA87ko+z9KpPWH2WXDrAUM+N0Q==","signatures":[{"sig":"MEUCIGyK3CbDiVUbhJYBeNL2SfN+lxnCmM1bqq+kSdbdMVILAiEA33HGUevOHPgtGqFEx3qOlNO6sXj/3PGCnOoD+tzUWVU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":135741},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./host":"./src/host.js","./client":"./src/client.js","./protocol":"./src/protocol.js"},"gitHead":"c0d720de041b05580272dbb86a713f2c53d2332d","_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"_npmVersion":"11.6.2","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/platform-bridge_1.6.0_1786031343094_0.8212615330094029","host":"s3://npm-registry-packages-npm-production"}},"1.7.0":{"name":"@callcorpacd/platform-bridge","version":"1.7.0","keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","_id":"@callcorpacd/platform-bridge@1.7.0","maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"dist":{"shasum":"4d6fb800cff3e8af813cc762a031e48e6db71bef","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.7.0.tgz","fileCount":16,"integrity":"sha512-0SRSB115Wp99Am4LNITrpHTqx8z4iJBJDO2bDzbRnxeA9KUVxS6DPUq7zzj75ZCZ3vLP9G+ZryVTxkaE7J1QhA==","signatures":[{"sig":"MEQCIF0OPYtNgUNH+I3AN00ACtW+k7muuVzNrDs+3wcZe2rBAiBC4AFu+76NhbhTxeCVIiMVfMpLZWSZY6tbWNOFc9aiTg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":224320},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./host":"./src/host.js","./client":"./src/client.js","./protocol":"./src/protocol.js"},"gitHead":"c5f3d174aa76be3b8e6393cc2e6dc7fb7f3c61cf","_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"_npmVersion":"11.6.2","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/platform-bridge_1.7.0_1786038293899_0.01158540568563815","host":"s3://npm-registry-packages-npm-production"}},"1.8.0":{"name":"@callcorpacd/platform-bridge","version":"1.8.0","keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","_id":"@callcorpacd/platform-bridge@1.8.0","maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"dist":{"shasum":"6ec8b0be10ee08662a66e5683361925f53e84810","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.8.0.tgz","fileCount":17,"integrity":"sha512-fRS0fs9N2stIRcN+BpXuIdljC0A+LQeN+XMhMphPZl6Ju12PWwma3egjQUrSieORktJJIkB9unmyWOkDSVgKxA==","signatures":[{"sig":"MEYCIQCQafTAu5tf1xIAMQLGctGR3CiX8Ugx0tAfpFV/r9rbOwIhAJPAs0aHk0dbcp1DjqCOQjuj3Ro9CiwkHQtJqGXSNMdp","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":242450},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./host":"./src/host.js","./client":"./src/client.js","./protocol":"./src/protocol.js"},"gitHead":"7024469ea363330baf22108a16e5bc7c7ea435ee","_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"_npmVersion":"11.6.2","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/platform-bridge_1.8.0_1786047964147_0.04602816982956326","host":"s3://npm-registry-packages-npm-production"}},"1.9.0":{"name":"@callcorpacd/platform-bridge","version":"1.9.0","keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","_id":"@callcorpacd/platform-bridge@1.9.0","maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"dist":{"shasum":"ce25413a27737906dd0af4aac4af60243dea5dc3","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.9.0.tgz","fileCount":17,"integrity":"sha512-cRvehkcLefAONeeo9pe7scsImH6Ks8U5JRdAa2mCFWSTygmJRxuODuUmokGMdxuEz+nLU9fNtyqiKe9ELGYrFQ==","signatures":[{"sig":"MEYCIQDmzDftDOWV8pgyMdi5PXt/R00ZQUeWu02Pil2ffnirJwIhAMbbCGyuds6N2Jkd4+Tyco1ZfgPhwWQmw1pizpyAYTXa","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":245084},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./host":"./src/host.js","./client":"./src/client.js","./protocol":"./src/protocol.js"},"gitHead":"655f0505683882d5c273e54a045e8cc204e99ba2","_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"_npmVersion":"11.6.2","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/platform-bridge_1.9.0_1786137818840_0.5883180138005637","host":"s3://npm-registry-packages-npm-production"}},"1.9.1":{"name":"@callcorpacd/platform-bridge","version":"1.9.1","description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","type":"module","main":"src/index.js","module":"src/index.js","exports":{".":"./src/index.js","./client":"./src/client.js","./host":"./src/host.js","./protocol":"./src/protocol.js"},"keywords":["callcorp","platform-bridge","iframe","sandbox"],"license":"UNLICENSED","publishConfig":{"access":"public"},"gitHead":"77df618091a83bad92aeb5485caf4ffb8abf2078","_id":"@callcorpacd/platform-bridge@1.9.1","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-QVw169U1dL8UF9gdTpQCYeUsmeM/aeIp+q3+jTkIovDlhu96z+FaXdbYcTo5YP8ma7XePUhu4JFu3k++WshufA==","shasum":"8428dd3fab6c4d65aaba236c3bc8fcb7d001189f","tarball":"https://registry.npmjs.org/@callcorpacd/platform-bridge/-/platform-bridge-1.9.1.tgz","fileCount":17,"unpackedSize":245553,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCdjhO2aAwrSfJfSv4wHKeFPPPcW99YSijwBIc0gQ/MmQIgXi97DiyeVxgzbFtewMzxEpLZLfkFa1OKrsOLwgpAfUw="}]},"_npmUser":{"name":"david.peterson","email":"david.peterson@callcorp.com"},"directories":{},"maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/platform-bridge_1.9.1_1788380893936_0.2761682255211808"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-24T17:47:37.674Z","modified":"2026-09-02T20:28:14.226Z","1.0.0":"2026-07-24T17:47:37.990Z","1.1.0":"2026-07-30T16:42:58.947Z","1.2.0":"2026-07-30T17:50:17.366Z","1.3.0":"2026-07-30T18:21:12.374Z","1.4.0":"2026-07-30T20:40:20.362Z","1.4.1":"2026-08-04T19:32:37.186Z","1.5.0":"2026-08-05T17:09:49.261Z","1.6.0":"2026-08-06T15:49:03.253Z","1.7.0":"2026-08-06T17:44:54.048Z","1.8.0":"2026-08-06T20:26:04.274Z","1.9.0":"2026-08-07T21:23:38.997Z","1.9.1":"2026-09-02T20:28:14.059Z"},"license":"UNLICENSED","keywords":["callcorp","platform-bridge","iframe","sandbox"],"description":"Guest/host postMessage bridge for sandboxed partner content in the CallCorp portal.","maintainers":[{"name":"bentonsmith","email":"benton@callcorp.com"},{"name":"david.peterson","email":"david.peterson@callcorp.com"},{"name":"shanekelly","email":"shane@callcorp.com"}],"readme":"# @callcorpacd/platform-bridge\r\n\r\nGuest/host **postMessage** bridge so partner content in a sandboxed iframe can call portal APIs **without** receiving access tokens.\r\n\r\nUsed by the Vue3 `SandboxedApp` control. Designed to grow into a published npm package (partners + internal apps).\r\n\r\n**Authoring a guest app (create / localhost / publish):** see [`AUTHORING.md`](AUTHORING.md).\r\n\r\n## Install (local / monorepo)\r\n\r\nVue3 already depends on this package via `file:../PlatformBridge` and Vite aliases:\r\n\r\n- `@callcorpacd/platform-bridge`\r\n- `@callcorpacd/platform-bridge/client`\r\n- `@callcorpacd/platform-bridge/host`\r\n- `@callcorpacd/platform-bridge/protocol`\r\n\r\n## Guest (inside the iframe)\r\n\r\n```js\r\nimport { createPlatformBridge } from '@callcorpacd/platform-bridge/client';\r\n\r\nconst platform = await createPlatformBridge();\r\n\r\n// Document Params / Route (interpolated on the host) arrive on handshake:\r\nconst { RecordId, Mode } = platform.context.params;\r\nconst path = platform.context.route;\r\nplatform.context.subscribe((params) => {\r\n  // Live updates when host Params or Route re-evaluate\r\n  // Read platform.context.route for the current path\r\n});\r\n\r\nconst data = await platform.api.get('api/v1/users/me/full-name');\r\nawait platform.api.post('api/v1/some-resource', { hello: 'world' });\r\n\r\nconst label = await platform.ui.translate('Save');\r\nconst canEdit = await platform.ui.hasAccess('guid-of-access-permission');\r\nawait platform.events.runEventActions('OnSave', { id: RecordId });\r\n\r\n// Realtime feeds (display-agnostic snapshots; host owns the WebSocket):\r\nconst kpi = await platform.realtime.open({ kind: 'widget', name: 'QueueDepth' });\r\nkpi.subscribe(({ data, revision }) => {\r\n  // render however you want — no grid dependency\r\n});\r\nawait kpi.close();\r\n\r\n// Server/session events (host owns SignalR + Eventing Subscribe):\r\nconst callSub = await platform.serverEvents.subscribe(\r\n  { sessionId: platform.context.params.SessionId, names: ['CallDisconnect'] },\r\n  (event) => { /* { sessionId, name, time, content, id } */ }\r\n);\r\nawait callSub.unsubscribe();\r\n\r\n// Portal EventBus (subscribe-only):\r\nconst busSub = await platform.bus.subscribe('SelectedCustomerChanged', (payload) => {});\r\nawait busSub.unsubscribe();\r\n```\r\n\r\n**v4 surface:**\r\n\r\n- `platform.protocolVersion` / `platform.capabilities` — negotiated version and host-advertised feature ids from ReadyAck\r\n- `platform.api.get | cachedGet | post | put | patch | delete`\r\n- `platform.context.params` — `Record<string, unknown>` from host `ControlData.Params` (top-level primitives are strings; nested objects/arrays preserved)\r\n- `platform.context.route` — `string` from host `ControlData.Route` (Shared Panel SPA path)\r\n- `platform.context.subscribe(listener)` — notified on `bridge.context.update` (Params and/or Route)\r\n- `platform.theme.get()` / `platform.theme.subscribe(listener)` — active portal / white-label color snapshot (`{ colors, name }`); also on ReadyAck\r\n- `platform.realtime.open({ kind, name, … })` — widget or named-list feed; returns a handle with `data`, `status`, `subscribe`, `setQuery` (lists), `close`\r\n- `platform.serverEvents.subscribe({ sessionId, names? }, listener)` — session events; returns `{ subscriptionId, unsubscribe() }`\r\n- `platform.bus.subscribe(name, listener)` — portal EventBus; returns `{ subscriptionId, unsubscribe() }`\r\n- `platform.ui.translate(text, isToolTip?)` — portal translation via host `Translate`\r\n- `platform.ui.hasAccess(id, name?)` — UI AccessPermission check via host `utils.document_cache.hasAccess`\r\n- `platform.events.runEventActions(name, data?)` — runs matching `ControlData.Events` actions on the host `SandboxedApp` (document actions only — not EventBus/server-event pub/sub)\r\n\r\nApply theme CSS vars in the guest with `@callcorpacd/design-tokens` → `applyBridgeTheme(platform)`.\r\n\r\n\r\n**Reserved (not implemented on the host yet):** `platform.navigate`. To extend the bridge, see [Extending the bridge](#extending-the-bridge-maintainers).\r\n\r\n### Published API\r\n\r\nPrefer curated **`/api/v1/...`** routes for guest demos and partner apps (e.g. `api/v1/users/me/full-name`). Discover the contract at:\r\n\r\n- OpenAPI: `{apiBase}/api/v1/openapi.json`\r\n- Human docs (Scalar): `{apiBase}/api/v1/docs`\r\n\r\nAllowlist published paths with `AllowedApiPrefixes: [\"api/v1/\"]` (or a narrower prefix). Legacy `Apps/...` remain callable when explicitly allowlisted.\r\n\r\n### Security\r\n\r\n- Do **not** call the band API with `fetch` and a token from guest code; use the bridge.\r\n- The host allowlists paths via `ControlData.AllowedApiPrefixes` on `SandboxedApp`.\r\n- Realtime feeds are gated by `AllowAllRealtimeFeeds` or `AllowedRealtimeFeeds` (see below).\r\n- Server/session events are gated by `AllowAllServerEvents` or `AllowedServerEventNames`.\r\n- EventBus subscriptions are gated by `AllowAllBusEvents` or `AllowedBusEvents`.\r\n- Absolute `http(s)` URLs are rejected by default.\r\n- Do not put secrets or tokens in `Params`.\r\n\r\n## Host (portal)\r\n\r\n```js\r\nimport { createHostBridgeHandler } from '@callcorpacd/platform-bridge/host';\r\nimport api from '@/Services/api';\r\n\r\nconst handler = createHostBridgeHandler({\r\n  getAllowedOrigin: () => contentOrigin,\r\n  // Required when multiple same-origin iframes are mounted — scopes Ready/Params to this frame.\r\n  getGuestWindow: () => iframeEl?.contentWindow || null,\r\n  getAllowedApiPrefixes: () => ['api/v1/', 'Document/'],\r\n  getContextParams: () => ({ RecordId: '123', Mode: 'edit' }),\r\n  getContextRoute: () => selectedMenuName || '',\r\n  getTheme: () => ({ colors: theme.getActiveTheme(), name: theme.getActiveThemeName() }),\r\n  getRealtimeSource: () => globalVars.RTData,\r\n  getAllowAllRealtimeFeeds: () => false,\r\n  getAllowedRealtimeFeeds: () => [{ kind: 'widget', name: 'QueueDepth' }],\r\n  watchRealtimeView: (viewKey, cb) => /* Vue $watch → unwatch */,\r\n  ui: {\r\n    translate: (text, isToolTip) => /* portal Translate */,\r\n    hasAccess: (id, name) => /* utils.document_cache.hasAccess(name || '', { ID: id, Enabled: true }) */,\r\n  },\r\n  events: { runEventActions: (name, data) => /* SandboxedApp.runEventActions */ },\r\n  getAllowAllServerEvents: () => false,\r\n  getAllowedServerEventNames: () => ['CallDisconnect'],\r\n  subscribeServerEvents: (opts, onEvent) => /* eventCoordinator → teardown */,\r\n  getAllowAllBusEvents: () => false,\r\n  getAllowedBusEvents: () => ['SelectedCustomerChanged'],\r\n  subscribeBus: (opts, onPayload) => /* EventBus.$on → teardown */,\r\n  api,\r\n});\r\nwindow.addEventListener('message', handler.handleMessage);\r\n// Later, when params change:\r\nhandler.pushContextUpdate();\r\n```\r\n\r\n`SandboxedApp.jsx` wires this for you from `ControlData` (`Params`, `Route`, `RTEventSource`, `AllowAllRealtimeFeeds`, `AllowedRealtimeFeeds`, `AllowAllServerEvents`, `AllowedServerEventNames`, `AllowAllBusEvents`, `AllowedBusEvents`).\r\n\r\n## Passing params from a UserControl document\r\n\r\n`ControlData.Params` is a map of JSON-serializable values. String fields (and other top-level primitives) may use portal interpolations (`{{...}}` or `{#...#}`). The host evaluates them and delivers the result as `platform.context.params`.\r\n\r\n```json\r\n{\r\n  \"ControlType\": \"SandboxedApp\",\r\n  \"ControlData\": {\r\n    \"Name\": \"PartnerApp\",\r\n    \"SourceKind\": \"PlatformFile\",\r\n    \"ContentPath\": \"PartnerContent/hello/index.html\",\r\n    \"AllowedApiPrefixes\": [\"api/v1/\"],\r\n    \"Params\": {\r\n      \"RecordId\": \"{{ParamByName(\\\"Id\\\")}}\",\r\n      \"Mode\": \"edit\",\r\n      \"Title\": \"Hello {# Root.UserName #}\",\r\n      \"AnotherArg\": {\r\n        \"ChildField\": \"And Value\",\r\n        \"ChildNumber\": 123\r\n      }\r\n    }\r\n  }\r\n}\r\n```\r\n\r\nKeys are static identifiers. Top-level primitives are coerced to strings (`null`/`undefined` → `''`). Nested objects/arrays are preserved (including nested numbers, booleans, and null). Use `platform.api.*` for large or dynamic data — do not put secrets in Params.\r\n\r\n## Passing route for Shared Panel SPAs\r\n\r\n`ControlData.Route` is a single interpolatable **string** (same `{{ }}` / `{# #}` rules as `Url`). The host delivers it as `platform.context.route` on handshake and pushes updates on `bridge.context.update` whenever the expression re-evaluates (for example when CallCenterHome updates `Control.SelectedMenuName`).\r\n\r\n```json\r\n{\r\n  \"ControlType\": \"SandboxedApp\",\r\n  \"ControlData\": {\r\n    \"Name\": \"PartnerSpa\",\r\n    \"SourceKind\": \"PlatformFile\",\r\n    \"ContentPath\": \"PartnerContent/spa/index.html\",\r\n    \"AllowedApiPrefixes\": [\"api/v1/\"],\r\n    \"Route\": \"{{Control.SelectedMenuName}}\",\r\n    \"Params\": {\r\n      \"Mode\": \"edit\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n```js\r\nconst platform = await createPlatformBridge();\r\nconsole.log(platform.context.route);\r\nplatform.context.subscribe(() => {\r\n  // Params and/or Route changed — drive vue-router / show-hide panels from context.route\r\n  router.replace(platform.context.route || '/');\r\n});\r\n```\r\n\r\nScaffold a demo with `create-callcorp-sandbox` and `--starter route` on any stack.\r\n\r\n## Realtime feeds\r\n\r\n`platform.realtime` exposes the portal RTEventHandler pipeline as **data only** — full snapshots, no UI opinions. The host page must still host an `RTEventHandler` that publishes the source (typically `GlobalVars.RTData`).\r\n\r\n```js\r\nconst agents = await platform.realtime.open({\r\n  kind: 'widget',\r\n  name: 'AgentStatusSummary',\r\n  filters: { CenterId: '...' },\r\n});\r\nconsole.log(agents.data); // opaque JSON\r\nagents.subscribe(({ data, revision, at }) => { /* … */ });\r\nawait agents.close();\r\n\r\nconst queue = await platform.realtime.open({\r\n  kind: 'list',\r\n  name: 'ActiveInteractions',\r\n  columns: ['Id', 'State', 'StartTime'],\r\n  filters: { _any_: 'smith' },\r\n  sort: { field: 'StartTime', descending: true },\r\n  range: { start: 0, length: 50 },\r\n});\r\n// queue.data => { total, rows }\r\nawait queue.setQuery({ range: { start: 50, length: 50 } });\r\nawait queue.close();\r\n```\r\n\r\n| Kind | Snapshot shape | RTEventHandler API |\r\n|------|----------------|--------------------|\r\n| `widget` | Opaque JSON (object or array) | `AddView` / `RemoveView` |\r\n| `list` | `{ total: number, rows: object[] }` | `GetNamedListForRealTime` / `RemoveNamedListForRealTime` |\r\n\r\n### SandboxedApp ControlData\r\n\r\n| Field | Role |\r\n|-------|------|\r\n| `RTEventSource` | Expression for the RT accessor (default `GlobalVars.RTData`) |\r\n| `AllowAllRealtimeFeeds` | When `true`, any `kind`/`name` is permitted; **supersedes** `AllowedRealtimeFeeds` |\r\n| `AllowedRealtimeFeeds` | `[{ kind, name }, …]` allowlist when `AllowAllRealtimeFeeds` is false/absent |\r\n\r\nDefault both unset → no realtime feeds (`not_allowed`).\r\n\r\n```json\r\n{\r\n  \"ControlType\": \"SandboxedApp\",\r\n  \"ControlData\": {\r\n    \"Name\": \"RealtimeDashboard\",\r\n    \"SourceKind\": \"PlatformFile\",\r\n    \"ContentPath\": \"PartnerContent/realtime/index.html\",\r\n    \"RTEventSource\": \"GlobalVars.RTData\",\r\n    \"AllowAllRealtimeFeeds\": false,\r\n    \"AllowedRealtimeFeeds\": [\r\n      { \"kind\": \"widget\", \"name\": \"AgentStatusSummary\" },\r\n      { \"kind\": \"list\", \"name\": \"ActiveInteractions\" }\r\n    ],\r\n    \"AllowedApiPrefixes\": [\"api/v1/\"]\r\n  }\r\n}\r\n```\r\n\r\nTrusted embeds may set `\"AllowAllRealtimeFeeds\": true` and omit the list. See [`samples/realtime/`](samples/realtime/) for a snapshot-logging guest.\r\n\r\n## Server events and EventBus\r\n\r\n`platform.serverEvents` exposes session events from the host WebSocket / `serverEventCoordinator` pipeline. Guests never talk to SignalR or `Apps/Eventing/*` directly.\r\n\r\n```js\r\nconst sub = await platform.serverEvents.subscribe(\r\n  {\r\n    sessionId: platform.context.params.SessionId,\r\n    names: ['InboundAnswered', 'CallDisconnect'], // omit = all names (requires AllowAllServerEvents)\r\n  },\r\n  (event) => {\r\n    // { sessionId, name, time, content, id }\r\n  },\r\n);\r\nawait sub.unsubscribe();\r\n```\r\n\r\n`platform.bus` listens to the portal in-memory EventBus (subscribe-only — no guest `emit`):\r\n\r\n```js\r\nconst sub = await platform.bus.subscribe('SelectedCustomerChanged', (payload) => {});\r\nawait sub.unsubscribe();\r\n```\r\n\r\n`platform.events.runEventActions` remains a separate document-action RPC on `ControlData.Events`. It is **not** EventBus or server-event pub/sub.\r\n\r\n### SandboxedApp ControlData\r\n\r\n| Field | Role |\r\n|-------|------|\r\n| `AllowAllServerEvents` | When `true`, any session event name (including “all names”) is permitted |\r\n| `AllowedServerEventNames` | `string[]` allowlist when allow-all is false; empty/absent → no server-event subscriptions |\r\n| `AllowAllBusEvents` | When `true`, any bus channel name is permitted |\r\n| `AllowedBusEvents` | `string[]` exact name allowlist when allow-all is false; empty/absent → no bus subscriptions |\r\n\r\nDefault all unset → subscriptions rejected (`not_allowed`). Prefer passing `sessionId` via interpolatable `Params`. See [`samples/events/`](samples/events/).\r\n\r\n```json\r\n{\r\n  \"ControlType\": \"SandboxedApp\",\r\n  \"ControlData\": {\r\n    \"Name\": \"CallListener\",\r\n    \"SourceKind\": \"PlatformFile\",\r\n    \"ContentPath\": \"PartnerContent/events/index.html\",\r\n    \"Params\": {\r\n      \"SessionId\": \"{{ParamByName(\\\"SessionId\\\")}}\"\r\n    },\r\n    \"AllowedServerEventNames\": [\"InboundAnswered\", \"CallDisconnect\"],\r\n    \"AllowedBusEvents\": [\"SelectedCustomerChanged\"],\r\n    \"AllowedApiPrefixes\": [\"api/v1/\"]\r\n  }\r\n}\r\n```\r\n\r\n## Protocol v4\r\n\r\n| Type | Direction | Purpose |\r\n|------|-----------|---------|\r\n| `bridge.ready` | guest → host | Handshake |\r\n| `bridge.ready.ack` | host → guest | Handshake complete; payload includes `{ protocolVersion, negotiatedVersion, minProtocolVersion, capabilities, params, route, theme? }` |\r\n| `bridge.ready.reject` | host → guest | Handshake rejected; e.g. `{ code: 'version_mismatch', min, max, guestVersion }` |\r\n| `bridge.context.update` | host → guest | `{ params, route }` when host Params and/or Route change |\r\n| `bridge.theme.update` | host → guest | `{ colors, name }` theme push |\r\n| `bridge.api.request` | guest → host | `{ method, url, data? }` |\r\n| `bridge.api.response` | host → guest | `{ result }` |\r\n| `bridge.api.error` | host → guest | `{ message, code, status? }` |\r\n| `bridge.realtime.open` | guest → host | `{ kind, name, filters?, columns?, sort?, range? }` |\r\n| `bridge.realtime.open.result` | host → guest | `{ subscriptionId, snapshot }` |\r\n| `bridge.realtime.setQuery` | guest → host | `{ subscriptionId, filters?, columns?, sort?, range? }` |\r\n| `bridge.realtime.setQuery.result` | host → guest | `{ snapshot }` |\r\n| `bridge.realtime.close` | guest → host | `{ subscriptionId }` |\r\n| `bridge.realtime.close.result` | host → guest | `{}` |\r\n| `bridge.realtime.update` | host → guest | `{ subscriptionId, snapshot, revision }` (push) |\r\n| `bridge.realtime.status` | host → guest | `{ subscriptionId?, status, message? }` (push) |\r\n| `bridge.realtime.error` | host → guest | `{ message, code }` |\r\n| `bridge.serverEvents.subscribe` | guest → host | `{ sessionId, names? }` |\r\n| `bridge.serverEvents.subscribe.result` | host → guest | `{ subscriptionId }` |\r\n| `bridge.serverEvents.unsubscribe` | guest → host | `{ subscriptionId }` |\r\n| `bridge.serverEvents.unsubscribe.result` | host → guest | `{}` |\r\n| `bridge.serverEvents.event` | host → guest | `{ subscriptionId, event }` (push) |\r\n| `bridge.serverEvents.error` | host → guest | `{ message, code }` |\r\n| `bridge.bus.subscribe` | guest → host | `{ name }` |\r\n| `bridge.bus.subscribe.result` | host → guest | `{ subscriptionId }` |\r\n| `bridge.bus.unsubscribe` | guest → host | `{ subscriptionId }` |\r\n| `bridge.bus.unsubscribe.result` | host → guest | `{}` |\r\n| `bridge.bus.event` | host → guest | `{ subscriptionId, payload }` (push) |\r\n| `bridge.bus.error` | host → guest | `{ message, code }` |\r\n| `bridge.ui.request` | guest → host | `{ method, args }` (e.g. `translate`) |\r\n| `bridge.ui.response` | host → guest | `{ result }` |\r\n| `bridge.ui.error` | host → guest | `{ message, code }` |\r\n| `bridge.events.request` | guest → host | `{ method, args }` (e.g. `runEventActions`) |\r\n| `bridge.events.response` | host → guest | `{ result }` |\r\n| `bridge.events.error` | host → guest | `{ message, code }` |\r\n\r\nChannel id: `callcorp.platform-bridge`. Version field must be in `[MIN_PROTOCOL_VERSION, PROTOCOL_VERSION]` (currently both `4`). The host negotiates the guest's version and replies on that session version. Out-of-range handshakes get `bridge.ready.reject` (`version_mismatch`) instead of a silent timeout.\r\n\r\nReadyAck also includes optional `capabilities` (string ids) and `negotiatedVersion` so guests can feature-detect without a protocol bump. Older guests ignore unknown ReadyAck fields.\r\n\r\n## Extending the bridge (maintainers)\r\n\r\nPartner guests only see the published client surface. New host capabilities (portal helpers, navigation, UI chrome, etc.) require coordinated changes across four layers:\r\n\r\n| Layer | File | Responsibility |\r\n|-------|------|----------------|\r\n| Protocol | `src/protocol.js` | `MessageType` / payload shapes; bump `PROTOCOL_VERSION` when shapes or required behavior change |\r\n| Guest | `src/client.js` | Public `platform.*` API; request/response correlation |\r\n| Host | `src/host.js` | Origin checks, dispatch, call into injected portal callbacks |\r\n| Portal wiring | `Vue3/.../SandboxedApp.jsx` | Pass host callbacks into `createHostBridgeHandler` (component mixins, `api`, params, …) |\r\n\r\n`samples/hello/` mirrors protocol/client for offline demos—update it when you change the wire format.\r\n\r\n### Design rules\r\n\r\n- Prefer a **request → response/error** pair with `requestId` (same pattern as `bridge.api.*`). Host → guest pushes (like `bridge.context.update`) are for broadcasts only.\r\n- Keep auth and secrets on the host. Guests receive only safe, intentional results.\r\n- Put portal UI helpers under the reserved `platform.ui` namespace; navigation under `platform.navigate`.\r\n- Session/WebSocket events live under `platform.serverEvents`; portal EventBus under `platform.bus`. Keep `platform.events` for document-action RPC (`runEventActions`) only.\r\n- When changing the guest surface, keep scaffolded template `AGENTS.md` files in `create-callcorp-sandbox/templates/**` in sync (see AUTHORING.md and [`AGENTS.md`](./AGENTS.md)).\r\n- **Additive** fields on existing messages (e.g. `params` / `capabilities` on ReadyAck) can stay on the current protocol version if older guests ignore unknown payload keys. New message types or required guest behavior → bump `PROTOCOL_VERSION`; host accepts `[MIN_PROTOCOL_VERSION, PROTOCOL_VERSION]` and sends `bridge.ready.reject` on mismatch. Raise `MIN_PROTOCOL_VERSION` only when deliberately force-upgrading old guests.\r\n- New optional features → ReadyAck `capabilities` + guest docs — not a protocol bump.\r\n- Wire host logic through **options callbacks** on `createHostBridgeHandler` so the package stays free of Vue/`methods.js` imports. `SandboxedApp` closes over `this`.\r\n\r\n### Checklist: add a host method\r\n\r\n1. **Protocol** — Add message types in `MessageType` (e.g. `bridge.ui.request` / `bridge.ui.response` / `bridge.ui.error`, or a dedicated `bridge.ui.translate` if you want a one-off). Document the payload.\r\n2. **Client** — Implement the guest method (usually `async`), post the request, resolve/reject from the matching response/error. Replace any `notImplemented` stub on that namespace.\r\n3. **Host** — In `handleMessage`, accept the new type (today non-`ApiRequest` traffic is ignored after Ready). Validate payload, call an injected option (e.g. `ui.translate`), reply with result or error. Never trust the guest for authorization decisions the host must enforce.\r\n4. **SandboxedApp** — Pass that option when creating the handler so the host can use component methods/services.\r\n5. **Docs / samples / agents** — Update this README’s guest surface list, `AUTHORING.md` if partners should call it, `samples/**` mirrors, **`AGENTS.md`** (this package), scaffold template `AGENTS.md` files, and in-repo `UIPages/*/AGENTS.md` when protocol/capability text drifts.\r\n6. **Version** — Prefer staying on the current `PROTOCOL_VERSION` for additive work; bump package semver as needed. Bump `PROTOCOL_VERSION` only for incompatible wire breaks; adjust `MIN_PROTOCOL_VERSION` only when forcing upgrades.\r\n\r\n### Implemented: `platform.ui.translate`, `platform.ui.hasAccess`, and `platform.events.runEventActions`\r\n\r\nThese follow the checklist above (generic `bridge.ui.*` / `bridge.events.*` RPC).\r\n\r\n```js\r\nconst label = await platform.ui.translate('Save');\r\nconst canEdit = await platform.ui.hasAccess('guid-here');\r\n// optional debug label: await platform.ui.hasAccess('guid-here', 'PartnerApp');\r\nawait platform.events.runEventActions('OnSave', { id: '123' });\r\n```\r\n\r\n`SandboxedApp.attachBridge` injects:\r\n\r\n```js\r\nui: {\r\n  translate: (text, isToolTip) => this.Translate(text, isToolTip),\r\n  hasAccess: (id, name) => utils.document_cache.hasAccess(name || '', { ID: id, Enabled: true }),\r\n},\r\nevents: {\r\n  runEventActions: (name, data) => this.runEventActions(name, data),\r\n},\r\n```\r\n\r\n**Notes:**\r\n\r\n- `Translate` reads portal translation lists / `controlData.ShouldTranslate` on the **host** instance—do not reimplement translation inside the iframe. Return only the translated string (or the original text).\r\n- Tooltip flag (`isToolTip`) is optional and boolean-coerced on the host.\r\n- `hasAccess` evaluates against the host permission table (`Apps/Permissions/GetUIElementsPermissionTable`). Pass the AccessPermission `id` string (required) and an optional debug `name`; the host wraps it as `{ ID: id, Enabled: true }`.\r\n- `runEventActions` looks up `ControlData.Events` by `Name` and runs that action list; unknown names are a no-op. Guest `data` is JSON-cloned before it reaches the host.\r\n\r\nThe same pattern applies to other mixin helpers or portal services: add protocol + client method + host dispatch + a thin `SandboxedApp` callback that closes over `this` / imported services.\r\n\r\n## Deploying partner content\r\n\r\nPreferred path for new apps: scaffold with `create-callcorp-sandbox`, `npm run build`, then `callcorp-sandbox publish` (see [`AUTHORING.md`](AUTHORING.md)). Vite `dist/` uses `base: './'`; `SandboxedApp` PlatformFile mode rewrites relative JS/CSS asset URLs to authenticated blob URLs.\r\n\r\nContent path convention: `PartnerContent/{AppId}/[version/]index.html`\r\n\r\n### Platform FileAPI (recommended for testing)\r\n\r\nUpload via CLI publish or the file browser. Minimal hand-built sample: [`samples/hello/`](samples/hello/).\r\n\r\n```json\r\n{\r\n  \"ControlType\": \"SandboxedApp\",\r\n  \"ControlData\": {\r\n    \"Name\": \"PartnerApp\",\r\n    \"SourceKind\": \"PlatformFile\",\r\n    \"ContentPath\": \"PartnerContent/hello/index.html\",\r\n    \"AllowedApiPrefixes\": [\"api/v1/\"],\r\n    \"Params\": {\r\n      \"Greeting\": \"Hello from the host\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n### Named sandbox (cross-tenant)\r\n\r\n`SourceKind: SandboxName` loads content owned by another tenant via a sandbox lookup ID. The host fetches through `Apps/SandboxedApp/File/{*FilePath}?ID=<SandboxName>`; the server resolves the PartnerContent root. `ContentPath` is relative to that root (e.g. `index.html`).\r\n\r\n```json\r\n{\r\n  \"ControlType\": \"SandboxedApp\",\r\n  \"ControlData\": {\r\n    \"Name\": \"PartnerApp\",\r\n    \"SourceKind\": \"SandboxName\",\r\n    \"SandboxName\": \"f92aa3cb-257f-41ea-ab20-9f31ebd97115\",\r\n    \"ContentPath\": \"index.html\",\r\n    \"AllowedApiPrefixes\": [\"api/v1/\"],\r\n    \"Params\": {\r\n      \"Greeting\": \"Hello from the host\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n### Live localhost (dev)\r\n\r\n```json\r\n{\r\n  \"ControlType\": \"SandboxedApp\",\r\n  \"ControlData\": {\r\n    \"Name\": \"PartnerAppDev\",\r\n    \"SourceKind\": \"Url\",\r\n    \"Url\": \"https://localhost:5174/\",\r\n    \"AllowedApiPrefixes\": [\"api/v1/\"],\r\n    \"Params\": {\r\n      \"Mode\": \"dev\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n### Mass Storage / S3\r\n\r\n```json\r\n{\r\n  \"ControlType\": \"SandboxedApp\",\r\n  \"ControlData\": {\r\n    \"Name\": \"PartnerApp\",\r\n    \"SourceKind\": \"MassStorage\",\r\n    \"OwnerId\": \"{# Root.CustomerID #}\",\r\n    \"ContentPath\": \"PartnerContent/my-app/index.html\",\r\n    \"AllowedApiPrefixes\": [\"api/v1/\", \"Document/\"],\r\n    \"Params\": {\r\n      \"RecordId\": \"{{ParamByName(\\\"Id\\\")}}\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n## Roadmap\r\n\r\n- Expand host capabilities (`navigate`, additional `ui` / `events` helpers) with a protocol version bump when required.\r\n- Optional row-level diff mode for large list feeds (snapshot mode is the v2 default).\r\n- Optional VuePortal `/partner-content/...` route for streaming FileBin without blob rewriting.\r\n- Designer schema: add `ControlData.Params` (`object`, `additionalProperties` allowing string or nested object/array JSON) and `ControlData.Route` (`string`) on `DynamicControl_SandboxedApp` (runtime already accepts them via `additionalProperties: true`).\r\n\r\nPublished for partners as public npm packages (`@callcorpacd/platform-bridge`, `@callcorpacd/sandbox-cli`, `create-callcorp-sandbox`). See [`../create-callcorp-sandbox/PARTNER.md`](../create-callcorp-sandbox/PARTNER.md).\r\n","readmeFilename":"README.md"}