{"_id":"@durgarao-sails/poc-bridge","name":"@durgarao-sails/poc-bridge","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@durgarao-sails/poc-bridge","version":"1.0.0","description":"The portal ↔ POC iframe contract, and both of its implementations.","license":"UNLICENSED","sideEffects":false,"repository":{"type":"git","url":"git+https://github.com/Yateesha-Pappala/self-service-portal.git","directory":"projects/poc-bridge"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"peerDependencies":{"@angular/common":"^21.0.0","@angular/core":"^21.0.0","rxjs":"^7.8.0"},"dependencies":{"tslib":"^2.3.0"},"_id":"@durgarao-sails/poc-bridge@1.0.0","gitHead":"898b749129718170e3926f321ede9ae32c3f5488","bugs":{"url":"https://github.com/Yateesha-Pappala/self-service-portal/issues"},"homepage":"https://github.com/Yateesha-Pappala/self-service-portal#readme","_nodeVersion":"22.18.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-N2231wIeRt1AJTK2ZsLN1tyF6hXg8BCO6QEunqzziZmqm2budaN1o+Tg5L5NnbT3HKUHenwNFVPoMXRy1sRziQ==","shasum":"1bf902e00d24dfbe087338619b6d210259df7305","tarball":"https://registry.npmjs.org/@durgarao-sails/poc-bridge/-/poc-bridge-1.0.0.tgz","fileCount":20,"unpackedSize":77200,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFPQC3DRhrtK3QZ2SzRFZBz/e/uUon2I4WJYBsD6G+wiAiAvORuDwyAvcxBx7fe7ZaK6GGkJvcRrylyJNKnnwbf/oQ=="}]},"_npmUser":{"name":"durgarao-sails","email":"durgarao.pinninti@sailssoftware.com"},"directories":{},"maintainers":[{"name":"durgarao-sails","email":"durgarao.pinninti@sailssoftware.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/poc-bridge_1.0.0_1788902253820_0.22125391158968477"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-08T21:17:33.626Z","1.0.0":"2026-09-08T21:17:33.958Z","modified":"2026-09-08T21:17:34.244Z"},"maintainers":[{"name":"durgarao-sails","email":"durgarao.pinninti@sailssoftware.com"}],"description":"The portal ↔ POC iframe contract, and both of its implementations.","homepage":"https://github.com/Yateesha-Pappala/self-service-portal#readme","repository":{"type":"git","url":"git+https://github.com/Yateesha-Pappala/self-service-portal.git","directory":"projects/poc-bridge"},"bugs":{"url":"https://github.com/Yateesha-Pappala/self-service-portal/issues"},"license":"UNLICENSED","readme":"# `@yateesha-pappala/poc-bridge`\r\n\r\nThe portal ↔ POC iframe contract, and both of its implementations.\r\n\r\nA POC runs in an iframe on its own origin, with no cookie and no shared JavaScript context with the\r\nportal. Everything crossing that boundary — identity, theme, sizing, navigation, session\r\nlifecycle — travels over `postMessage`. This package is that channel.\r\n\r\nThe design and the reasoning behind every rule here live in `self-service-api`'s\r\n[`docs/specs/poc-bridge-contract.md`](../../../self-service-api/docs/specs/poc-bridge-contract.md).\r\n**That spec and this library are reviewed as one artifact** — a contract change that lands without\r\nthe document is how the document stops being true.\r\n\r\n## Why one package rather than two implementations\r\n\r\nThe protocol has two halves that must never drift: this library's `PocFrameHost` (the portal's\r\niframe host) and its `PocBridge` (the POC's client). Declaring the message types once, in\r\n`lib/protocol.ts`, and importing them from both halves makes a disagreement a **compile error**\r\nrather than a runtime failure a POC team discovers in production.\r\n\r\nThat is not hypothetical. Before this package existed, the portal and `poc-integration-testbed`\r\neach hand-rolled a half, and they had already diverged on the theme message (`sails:theme` with a\r\n`mode` field on one side, `portal:theme` with a `theme` field on the other).\r\n\r\n## Layout and entry points\r\n\r\n```\r\nsrc/public-api.ts            @yateesha-pappala/poc-bridge       the contract alone — types, guards, versions\r\nsrc/host.ts                  @yateesha-pappala/poc-bridge/host  portal half — PocFrameHost (re-exports the contract)\r\nsrc/poc.ts                   @yateesha-pappala/poc-bridge/poc   POC half — PocBridge, provider, interceptor, harness\r\nsrc/lib/protocol.ts          the contract's implementation\r\nsrc/lib/host/ · src/lib/poc/ the two halves\r\n\r\npackage.json                 the published manifest — name, version, peer ranges\r\nng-package.json              ng-packagr's primary entry point → src/public-api.ts\r\nhost/ng-package.json         secondary entry point → ../src/host.ts\r\npoc/ng-package.json          secondary entry point → ../src/poc.ts\r\ntsconfig.lib{,.prod}.json    build config; prod adds partial compilation\r\n```\r\n\r\nThe `host/` and `poc/` directories hold nothing but an `ng-package.json`. Their **directory names\r\nare the subpath** a consumer imports — ng-packagr derives `@yateesha-pappala/poc-bridge/host` from\r\nthe folder, not from the file it points at, so renaming one of them is a breaking change for every\r\nPOC.\r\n\r\nThe halves are behind separate entry points for two concrete reasons, both measured rather than\r\nassumed. Exporting them from one barrel put the POC-side client into the **portal's production\r\nbundle** — its `@Injectable({providedIn: 'root'})` registration survives tree-shaking — and it\r\noffered `PocBridge` and `PocFrameHost` from the same import path, so reaching for the wrong one was\r\na runtime `NullInjectorError` rather than a compile error. Importing `@yateesha-pappala/poc-bridge` on its own\r\ngets the contract and neither implementation.\r\n\r\n## Using it from the portal\r\n\r\n`PocFrameHost` owns one embed. Attach it **before** pointing the iframe at the POC's URL — a POC\r\nthat loads quickly can otherwise emit `poc:ready` before any listener exists, and the handshake\r\ndeadlocks silently. See `src/app/components/poc-workspace/poc-workspace.ts` for the live example.\r\n\r\n```ts\r\nimport { PocFrameHost } from '@yateesha-pappala/poc-bridge/host';\r\n\r\nconst host = new PocFrameHost();\r\nhost.attach(iframe.contentWindow, new URL(launchUrl).origin, {\r\n  onReady: () => host.sendSession(launchResponse),\r\n  onRefresh: () => this.refreshLaunch(),\r\n});\r\n// only now point the frame at launchUrl\r\n```\r\n\r\n## Using it from a POC\r\n\r\n```ts\r\nimport { PocBridge, providePocBridge, pocBridgeInterceptor } from '@yateesha-pappala/poc-bridge/poc';\r\n\r\nbootstrapApplication(App, {\r\n  providers: [\r\n    providePocBridge({ portalOrigin: window.__SAILS__.portalOrigin }),\r\n    provideHttpClient(withInterceptors([pocBridgeInterceptor])),\r\n    provideAppInitializer(() => inject(PocBridge).waitForSession()),\r\n  ],\r\n});\r\n```\r\n\r\n`portalOrigin` is **required**, has no default, and may never be `*`. It is runtime configuration\r\nthe deploy pipeline injects as `PORTAL_ORIGIN` — never derived from `document.referrer` or\r\n`location.ancestorOrigins`, both of which mean trusting whatever page the POC finds itself inside.\r\nA missing or malformed value throws at bootstrap rather than degrading quietly.\r\n\r\nDeveloping a POC standalone, without running the portal:\r\n\r\n```ts\r\nif (!environment.production) {\r\n  startDevHarness({ token: localStorage.getItem('dev.pocToken')! });\r\n}\r\n```\r\n\r\nCall it before `bootstrapApplication` — it is what permits the bridge to speak at all on a page\r\nthat isn't framed. Mint a real token with `POST /pocs/{slug}/launch` while signed in to the portal.\r\n\r\n## Non-negotiables\r\n\r\nThese are enforced here so twenty POC teams don't each have to remember them:\r\n\r\n- `targetOrigin` is always an explicit origin, never `*`.\r\n- Every received message is checked for `origin`, and on the portal side `source`, before its\r\n  contents are read. Origin alone does not distinguish the POC's own document from a nested frame\r\n  inside it.\r\n- Unknown, malformed, and unsupported-version messages are ignored **silently on the wire** — the\r\n  `message` event is a shared bus that extensions, analytics and devtools also post to. See\r\n  Diagnostics below for how that stays debuggable.\r\n- The token is held in memory only. Never `localStorage`, never `sessionStorage`.\r\n- A POC's backend verifies `aud` against **its own configuration** (`POC_SLUG`), never against the\r\n  token. That check is what stops one POC replaying its token against another, and this library\r\n  cannot do it for you — it happens server-side.\r\n\r\n## Diagnostics\r\n\r\nSilence is correct on the wire and miserable to debug: a broken handshake produces no error, no\r\nrequest, and a POC that simply never comes alive. The line this library draws is **origin**.\r\n\r\n- A message from an origin we are *not* talking to is somebody else's traffic. Dropped in total\r\n  silence, always.\r\n- A message from the origin we *are* talking to, which then fails the source, shape or version\r\n  check, is almost certainly ours and broken. It is reported to an optional `onIgnored` handler\r\n  with an actionable `detail` string.\r\n\r\nThe portal passes `onIgnored` and logs it only under `isDevMode()`. On the POC side it defaults to\r\na `console.warn` in dev mode and to silence in production, so a POC author gets it for free.\r\n\r\n`onIgnored` also fires on a handshake **timeout**, which is the failure most worth explaining,\r\nbecause its two causes need different fixes:\r\n\r\n- *Heard nothing at all* — usually `portalOrigin` not matching the portal's real origin. The\r\n  browser silently discards a `postMessage` with the wrong `targetOrigin`, so `poc:ready` never\r\n  arrives and nothing anywhere raises an error.\r\n- *Heard the portal but dropped everything* — the portal is talking on a shape or version this\r\n  bridge can't read. The per-message warnings say which.\r\n\r\n## Testing\r\n\r\nSpecs live beside their sources and run with the portal's suite:\r\n\r\n```bash\r\nnpm test\r\n```\r\n\r\nThey are discovered via an explicit `include` in `angular.json`'s `test` target. The\r\n`@angular/build:unit-test` builder resolves `include` relative to **`sourceRoot`** (`src`), not the\r\nproject root its schema describes — so the pattern is listed under *both* interpretations\r\n(`../projects/poc-bridge/...` and `projects/poc-bridge/...`). Exactly one matches under either\r\nrule, and the config survives the builder being fixed.\r\n\r\nBecause a discovery failure would be silent — the specs stop running and the suite still reports\r\ngreen — `src/app/core/poc/poc-bridge-contract.spec.ts` is a canary in the app's own tree, which\r\ndiscovery cannot miss. It covers the guarantees whose silent loss would be worst.\r\n\r\n## Publishing\r\n\r\nPublished to **GitHub Packages** as `@yateesha-pappala/poc-bridge`, built by `ng-packagr`\r\n(`ng build poc-bridge` → `dist/poc-bridge`). The three entry points here are ng-packagr entry\r\npoints: `ng-package.json` at the package root is the primary, and `host/` and `poc/` each hold an\r\n`ng-package.json` pointing back at `../src/host.ts` and `../src/poc.ts`.\r\n\r\nThe scope is the GitHub account that owns this repository. GitHub Packages resolves an npm scope\r\nto an account and refuses anything else, so the package name is not a free choice — moving this\r\nrepo to another account or org is also a rename here.\r\n\r\nEach half builds into a **self-contained** bundle: `@yateesha-pappala/poc-bridge/host` carries no\r\n`@angular/core` import at all, and neither half carries the other. The contract is small enough\r\n(~130 lines emitted) that inlining it into both beats making every consumer load a second chunk\r\nfor it. This is the measurable form of the split described above — worth re-checking after a\r\nprotocol change:\r\n\r\n```bash\r\nnpm run build:poc-bridge\r\ncd dist/poc-bridge/fesm2022\r\ngrep -c '^import' *-host.mjs                        # 0 — the portal half pulls in no framework\r\ngrep '^export {' *-host.mjs | grep -c PocBridge     # 0 — and none of the POC client\r\ngrep '^export {' *-poc.mjs  | grep -c PocFrameHost  # 0 — nor the reverse\r\n```\r\n\r\nMatch against the `export {}` line rather than the whole file: `public-api.ts`'s own doc comment\r\nnames both classes and is inlined into both bundles, so a plain `grep PocBridge` reports a leak\r\nthat isn't there.\r\n\r\n### Cutting a release\r\n\r\nVersions are immutable once published, so the tag is checked against the manifest before anything\r\nis uploaded:\r\n\r\n```bash\r\nnpm version --prefix projects/poc-bridge 0.2.0 --no-git-tag-version\r\ngit commit -am 'poc-bridge 0.2.0'\r\ngit tag poc-bridge-v0.2.0\r\ngit push origin develop --tags\r\n```\r\n\r\n`.github/workflows/publish-poc-bridge.yml` runs the suite, builds, verifies that\r\n`poc-bridge-v0.2.0` matches the `version` in `projects/poc-bridge/package.json`, and publishes.\r\nRunning it from the Actions tab instead does a dry run — it builds and packs, and uploads the\r\nresult as an artifact, without publishing.\r\n\r\n### Consuming it from a POC repo\r\n\r\nGitHub Packages always requires authentication, including for reads. In the POC repo:\r\n\r\n```ini\r\n# .npmrc\r\n@yateesha-pappala:registry=https://npm.pkg.github.com\r\n//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}\r\n```\r\n\r\n```bash\r\nnpm install @yateesha-pappala/poc-bridge\r\n```\r\n\r\n`NODE_AUTH_TOKEN` is a PAT with `read:packages` locally, and `secrets.GITHUB_TOKEN` in that repo's\r\nown CI. Commit the `.npmrc` but never the token.\r\n\r\n### Why the portal still builds from source\r\n\r\nThe portal does *not* consume the published package. `tsconfig.json` maps all three entry points to\r\nthis directory's sources, so a protocol change is one edit and one test run rather than a\r\npublish-and-bump round trip — which matters because both halves of the contract are tested here.\r\n\r\nThat mapping is also what keeps the two consumption modes honest: the portal exercises the same\r\nsource a POC gets compiled, and the specs cover both halves. What it does *not* cover is the\r\npackaging itself — an entry point that fails to resolve once published is invisible to the portal.\r\n`npm run pack:poc-bridge` builds the tarball for inspection when that layout changes.\r\n","readmeFilename":"README.md","_rev":"1-84c0ca9fab3ec696e4bd363777c88f17"}