diff --git a/CHANGELOG.md b/CHANGELOG.md index daf518e..d41876c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,31 @@ All notable changes to the `stream-connect-sdk` npm package. The companion React Native hook (`stream-connect-sdk-hook`) is on its own release line; see [`sdk-hook/docs/README.md`](./sdk-hook/docs/README.md). +## 0.8.2 + +### Refuse to initialize on insecure (plain HTTP) host pages + +`StreamConnect()` now checks `window.isSecureContext` at init and +refuses to mount the SDK if the host page is served over plain HTTP +from a non-loopback host. The browser-level secure-context check is +true on HTTPS and on the loopback hosts (`localhost`, `127.0.0.1`, +`[::1]`) — same set the new server-side CORS policy allows — so +local development against `vite`, `webpack-dev-server`, etc. is +unaffected. + +The previous behavior on a plain-HTTP page was a generic +CORS-blocked error in the browser console on the first +`/sdk-api/*` request, with no actionable explanation. The SDK now +fails fast with a clear console error, calls the optional +`handleInitErrors` callback with the same message, and links to +the [origin-policy docs](https://developers.tpastream.com/connect/origin-policy) +explaining the requirement and how to fix it. + +Members were never charged the network round-trip on a broken +plain-HTTP integration in the first place, so the practical effect +is shifting the error from "browser console mystery" to "init-time +diagnostic that names the problem." + ## 0.8.1 ### Handle expired connectAccessToken without a confusing error diff --git a/assets/sdk/entries/sdk-core.tsx b/assets/sdk/entries/sdk-core.tsx index c642ce6..00f1883 100644 --- a/assets/sdk/entries/sdk-core.tsx +++ b/assets/sdk/entries/sdk-core.tsx @@ -9,7 +9,31 @@ import type { SDKInitOptions } from '../types-init'; // stylesheet so customers driving their own UI via the renderXxx // callbacks aren't forced to ship our ~50 KB of Tailwind output. -const VERSION = '0.8.1'; +const VERSION = '0.8.2'; + +const ORIGIN_POLICY_DOCS_URL = + 'https://developers.tpastream.com/connect/origin-policy'; + +// `window.isSecureContext` is the W3C primitive browsers use to gate +// any API that handles sensitive data (WebAuthn, getUserMedia, service +// workers). It is true on HTTPS pages and on the loopback hosts the +// browser treats as secure — `localhost`, `127.0.0.1`, `[::1]`, file://, +// chrome-extension:// — and false for plain `http://` everywhere else. +// +// We mirror that on the server (stream/security/sdk_cors.py) — the +// `/sdk-api/*` CORS regex only echoes Access-Control-Allow-Origin for +// HTTPS or loopback HTTP. So an integration loaded into a plain-HTTP +// host page would hit a generic browser CORS-blocked error on the very +// first API call, with no actionable explanation. Catch it here at +// init() instead and report it clearly to the console + handleInitErrors +// so the developer goes straight to the docs. +const isSecureSDKContext = (): boolean => { + // SSR / non-browser harnesses (Jest with jsdom unset, etc.) can't + // be evaluated — fall through, the later `document.querySelector` + // path errors with its own message. + if (typeof window === 'undefined') return true; + return window.isSecureContext; +}; // Track one React root per container element. Some host pages call // StreamConnect() more than once against the same `el` (e.g. on a @@ -181,6 +205,15 @@ const StreamConnect = (options: SDKInitOptions) => { return; } + if (!isSecureSDKContext()) { + const origin = + typeof window !== 'undefined' ? window.location.origin : '(unknown)'; + const msg = `[stream-connect-sdk] init failed: host page must be served over HTTPS (or from http://localhost, http://127.0.0.1, or http://[::1] for local development). The current page origin ${origin} is insecure, and the SDK transmits member credentials — sending those over plain HTTP would expose them in transit. See ${ORIGIN_POLICY_DOCS_URL} for the fix.`; + console.error(msg); + options.handleInitErrors?.(new Error(msg)); + return; + } + console.log(`TPAStream Connect SDK v${VERSION}`); const normalized = normalizeOptions(options); diff --git a/docs/migration-0.7-to-0.8.md b/docs/migration-0.7-to-0.8.md index d5f832c..d58a35c 100644 --- a/docs/migration-0.7-to-0.8.md +++ b/docs/migration-0.7-to-0.8.md @@ -91,6 +91,12 @@ If you deliberately want the legacy fallthrough behavior — e.g. you're doing c If you use `renderChoosePayer={false}` / `renderPayerForm={false}` / `renderEndWidget={false}` and drive those steps from `doneChoosePayer` / `doneCreatedForm` / `doneEasyEnroll`, the data passed into your callbacks is unchanged. +### 9. Host page must be HTTPS (0.8.2) + +Starting in 0.8.2, `StreamConnect()` refuses to mount if the host page is served over plain `http://` from a non-loopback host. `localhost`, `127.0.0.1`, and `[::1]` are still allowed so local development is unaffected. Staging or internal hostnames served over plain HTTP are not. + +If you embed the SDK in a member-facing page that's currently on plain HTTP, that page must move to HTTPS before 0.8.x will mount. See the [Origin Policy doc](https://developers.tpastream.com/connect/origin-policy) for the full rules, the exact console error developers will see, and how to fix it. + ## What you do NOT need to do - You do not need to add a `theme` option. The default appearance applies automatically. diff --git a/package.json b/package.json index f7ad3f0..d8ee6dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stream-connect-sdk", - "version": "0.8.1", + "version": "0.8.2", "description": "A JavaScript SDK implementing TPAStream's Connect Platform", "scripts": { "build": "webpack --config webpack.prod-sdk.js --mode=production",