{"_id":"@ariscott/cp-web-components","name":"@ariscott/cp-web-components","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.1":{"name":"@ariscott/cp-web-components","version":"0.1.1","description":"Embeddable Company Portal web components (devices).","type":"module","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"main":"./dist/cp-web-components.js","module":"./dist/cp-web-components.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/cp-web-components.js"},"./proxy":{"types":"./dist/server/cp-proxy.d.ts","import":"./dist/server/cp-proxy.js"},"./proxy/vite":{"types":"./dist/server/vite-plugin.d.ts","import":"./dist/server/vite-plugin.js"}},"scripts":{"dev":"vite","build":"vite build && vite build --config vite.proxy.config.ts && tsc --emitDeclarationOnly","preview":"vite preview"},"dependencies":{"@fluentui/web-components":"^2.6.1","@microsoft/fast-element":"^1.14.0"},"devDependencies":{"@types/node":"^26.1.1","typescript":"^5.5.0","vite":"^5.4.0"},"_id":"@ariscott/cp-web-components@0.1.1","_nodeVersion":"26.4.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-6fW+8+XU2Ww9gTNQWlI9SucC/pn03sGJCsi+6FARZ/VdOcX7vm9DePW61S3fWHY6LR9EsokR+44BKfs6wccSVw==","shasum":"851755ad6c3b02b645d6a24cad77ebd3045c6020","tarball":"https://registry.npmjs.org/@ariscott/cp-web-components/-/cp-web-components-0.1.1.tgz","fileCount":21,"unpackedSize":288490,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDJYkrgVieZKC5Q5jvs7o/9ExHcWkVJwdhQnl6vZuhFQwIhAMyakS3rzOFqK8NGa0FttoQq0g1KLIxE4bAZPazEU6bX"}]},"_npmUser":{"name":"ariscott","email":"t-ariscott@microsoft.com"},"directories":{},"maintainers":[{"name":"ariscott","email":"t-ariscott@microsoft.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cp-web-components_0.1.1_1784565928070_0.7097448047572341"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-20T16:45:27.880Z","0.1.1":"2026-07-20T16:45:28.226Z","modified":"2026-07-20T16:45:28.430Z"},"maintainers":[{"name":"ariscott","email":"t-ariscott@microsoft.com"}],"description":"Embeddable Company Portal web components (devices).","readme":"# @ariscott/cp-web-components\n\nEmbeddable **Company Portal web components** for showing a user's enrolled\ndevices and apps. Build once, drop into any portal (no framework required), and\nextend with slots, events, and theming — **without ever writing Intune API calls\nyourself**. All data access goes through a single, swappable client.\n\n> v1 covers: list enrolled devices, view device info + non-compliance reasons,\n> browse a device's apps, view app info, and install an app (with live status).\n\n## Components\n\n| Element | Purpose |\n| --- | --- |\n| `<cp-devices>` | All-in-one master-detail flow: the device list, and on selection the device's details **in the same place** with a Back button. Drop-in. |\n| `<cp-device-list>` | Lists the signed-in user's enrolled devices. Emits `device-selected`. |\n| `<cp-device-details device-id=\"…\">` | Shows one device's info + compliance. Set `show-back` to render a Back button (emits `back`). |\n| `<cp-apps>` | All-in-one apps flow: a device picker on top, the apps for the chosen device, and on selection the app's details in the same place with a Back button. Drop-in. |\n| `<cp-app-list>` | Lists the apps for the current device context. Shows a per-app install-status tag. Emits `app-selected`. |\n| `<cp-app-details app-id=\"…\">` | Shows one app's info (icon, description, version, publisher, link), an **Install** button, and live install status. Set `show-back` to render a Back button (emits `back`). |\n| `<cp-auth>` | Credential-collection UI: pick environment, paste cookie / XSRF / token, and connect. Configures the client for the whole page and emits `client-change`. Use with the shipped proxy (below). |\n\n## Install\n\n```bash\nnpm install @ariscott/cp-web-components\n```\n\n## Quick start\n\nThe simplest option is the all-in-one `<cp-devices>` element:\n\n```html\n<cp-devices list-heading=\"Enrolled devices\" details-heading=\"Device details\"></cp-devices>\n\n<script type=\"module\">\n  import { CpClient, setDefaultClient } from \"@ariscott/cp-web-components\";\n\n  // Configure ONE client for the page. Start with mock data:\n  setDefaultClient(CpClient.mock());\n</script>\n```\n\nPrefer to lay out the list and details yourself? Compose the two primitives:\n\n```html\n<cp-device-list id=\"list\"></cp-device-list>\n<cp-device-details id=\"details\"></cp-device-details>\n\n<script type=\"module\">\n  import { CpClient, setDefaultClient } from \"@ariscott/cp-web-components\";\n\n  setDefaultClient(CpClient.mock());\n\n  // Wire selection -> details.\n  const list = document.getElementById(\"list\");\n  const details = document.getElementById(\"details\");\n  list.addEventListener(\"device-selected\", (e) => {\n    details.deviceId = e.detail.id;\n  });\n</script>\n```\n\n### Apps for a device\n\n`<cp-apps>` adds a device picker on top; choosing a device loads that device's\napps (it calls the hidden `currentDevice` API for you), and selecting an app\nshows its details in the same place with a Back button:\n\n```html\n<cp-apps list-heading=\"Apps\" details-heading=\"App details\"></cp-apps>\n\n<script type=\"module\">\n  import { CpClient, setDefaultClient } from \"@ariscott/cp-web-components\";\n  setDefaultClient(CpClient.mock());\n</script>\n```\n\nComposing the primitives yourself? Call `client.setCurrentDevice(deviceId)`\nbefore reading `client.listApps()`, then wire `app-selected` to\n`<cp-app-details>.appId`.\n\nImporting the package registers the custom elements automatically.\n\n## Configuring the data source\n\nComponents never call an API directly — they call whatever `CpClient` you\nconfigure. Pick one:\n\n```js\n// Realistic fake data, no auth. Great for development/demos.\nsetDefaultClient(CpClient.mock());\n\n// Production-safe: your own backend proxy authenticates the user and forwards\n// to Intune server-side. Intune endpoints/tokens never reach the browser.\nsetDefaultClient(CpClient.proxy({ baseUrl: \"/api/cp\" }));\n```\n\nYour proxy implements these routes returning the public `Device` / `App`\nshapes:\n\n```\nGET  {baseUrl}/devices          -> Device[]\nGET  {baseUrl}/devices/{id}     -> Device\nPOST {baseUrl}/currentDevice    body:{ deviceId } -> 200\nGET  {baseUrl}/apps             -> App[]\nGET  {baseUrl}/apps/{id}        -> App\n```\n\nBecause components depend only on the `CpDataSource` interface, the underlying\nIntune API surface stays encapsulated — swapping data sources requires **no\ncomponent changes**.\n\n## Live data (demo / internal use)\n\nThe browser **cannot call Intune directly** — cross-origin responses are blocked\nby CORS and JS can't set the `Cookie` header. So a small **server-side proxy**\nmust re-apply the credentials and forward the request. That proxy ships **inside\nthis package**, so you don't have to write it.\n\n**1. Mount the proxy on your server** (Express, raw Node, or Vite dev):\n\n```js\n// Express\nimport { createCpProxy } from \"@ariscott/cp-web-components/proxy\";\napp.use(\"/cp-proxy\", createCpProxy()); // SSRF-guarded to *.microsoft.com\n```\n\n```ts\n// Vite dev server (vite.config.ts)\nimport { cpProxyPlugin } from \"@ariscott/cp-web-components/proxy/vite\";\nexport default defineConfig({ plugins: [cpProxyPlugin()] });\n```\n\n**2. Drop `<cp-auth>` on the page** to collect credentials and connect. It calls\n`setDefaultClient(...)` for you, so every `<cp-*>` element picks up the live\nclient:\n\n```html\n<cp-auth proxy-base-url=\"/cp-proxy\"></cp-auth>\n<cp-apps></cp-apps>\n\n<script type=\"module\">\n  import \"@ariscott/cp-web-components\";\n</script>\n```\n\nOr wire the client yourself instead of `<cp-auth>`:\n\n```js\nimport { CpClient, setDefaultClient } from \"@ariscott/cp-web-components\";\nsetDefaultClient(\n  CpClient.webCpViaProxy({\n    host: \"portal.manage.microsoft.com\",\n    proxyBaseUrl: \"/cp-proxy\",\n    cookie: \"…\",      // full session cookie\n    xsrfToken: \"…\",   // XSRF-TOKEN value\n  }),\n);\n```\n\n> The cookie/XSRF paste flow is intended for demos and internal use. For a\n> production app, back the components with `CpClient.proxy({ baseUrl })` against\n> your own authenticated backend instead.\n\n## Extending\n\n### Events\n\n| Event | Detail | Fired by |\n| --- | --- | --- |\n| `device-selected` | `Device` | `<cp-device-list>` when a row is activated |\n| `app-selected` | `App` | `<cp-app-list>` when a row is activated |\n| `back` | — | `<cp-device-details>` / `<cp-app-details>` when `show-back` is set and Back is clicked |\n| `client-change` | `CpClient` | `<cp-auth>` when a client is connected (live or sample) |\n\n### Slots\n\n`<cp-device-list>` / `<cp-app-list>`: `header` (card heading), `loading`, `empty`, `error`\n`<cp-device-details>` / `<cp-app-details>`: `header`, `loading`, `empty`, `error`, `extra` (append custom fields/actions), `back-label`\n`<cp-apps>`: `device-label` (label next to the device picker)\n\nBoth components also accept a `heading` attribute rendered inside the card:\n\n```html\n<cp-device-list heading=\"Enrolled devices\"></cp-device-list>\n<cp-device-details heading=\"Details\"></cp-device-details>\n```\n\n```html\n<cp-device-list>\n  <div slot=\"empty\">You have no enrolled devices yet.</div>\n</cp-device-list>\n\n<cp-device-details>\n  <button slot=\"extra\">Contact IT</button>\n</cp-device-details>\n```\n\n### CSS parts\n\nStyle internals from outside the shadow DOM via `::part()`:\n\n`root`, `list`, `item`, `name`, `sub`, `compliance` (device-list) and\n`root`, `name`, `compliance`, `fields`, `field-label`, `field-value` (details).\n\n```css\ncp-device-list::part(item) {\n  border-radius: 12px;\n}\n```\n\n### Theming\n\nComponents consume [Fluent design tokens](https://web-components.fluentui.dev/)\n(CSS custom properties), so they follow your Fluent theme automatically —\nincluding light/dark:\n\n```js\nimport { baseLayerLuminance, StandardLuminance } from \"@fluentui/web-components\";\nbaseLayerLuminance.setValueFor(document.body, StandardLuminance.DarkMode);\n```\n\nCompliance colors are overridable via `--cp-color-success`, `--cp-color-danger`,\n`--cp-color-warning`, `--cp-color-neutral`.\n\n## Development\n\n```bash\nnpm run dev     # demo host at http://localhost:5173\nnpm run build   # library build -> dist/ (+ .d.ts)\n```\n\nThe demo runs on mock data and includes a field to paste a **dev-only** bearer\ntoken to load live data.\n\n## Data sources & auth notes\n\n- The supported, durable path is a **proxy/BFF** (`CpClient.proxy`) that holds\n  auth server-side. Prefer this for anything customers depend on.\n- A live web-CP adapter exists for prototyping. The web Company Portal\n  authenticates with the **session cookie** plus an **`X-XSRF-TOKEN`** header —\n  copy both from the portal's devtools. A bearer token is also supported. This\n  targets Intune's **internal, unsupported** API and is dev-only.\n\n  - **From Node / an MCP app** (server-side), call it directly — headers like\n    `Cookie` are allowed there:\n\n    ```js\n    CpClient.webCp({ cookie: \"<Cookie header>\", xsrfToken: \"<XSRF-TOKEN>\" });\n    ```\n\n  - **From a browser**, route through a same-origin proxy (browsers forbid the\n    `Cookie` header on fetch, and a full Intune cookie is too large for a\n    request header). The pasted secrets are POSTed to the proxy in the request\n    **body** and re-applied server-side. The Vite dev server includes this proxy\n    at `/cp-proxy`:\n\n    ```js\n    CpClient.webCpViaProxy({ cookie: \"<Cookie header>\", xsrfToken: \"<XSRF-TOKEN>\" });\n    ```\n\n- There is currently **no supported non-admin Microsoft Graph endpoint** for a\n  user to list their own managed devices, which is why the proxy pattern (or the\n  Company Portal itself) is the recommended route.\n- There is currently **no supported non-admin Microsoft Graph endpoint** for a\n  user to list their own managed devices, which is why the proxy pattern (or the\n  Company Portal itself) is the recommended route.\n","readmeFilename":"README.md","_rev":"1-1c23a3e126a71b622f9673f481b801ad"}