@habit.analytics/habit-smartlink-reactcomponent
Version:
A React component for Habit SmartLink integration.
93 lines (92 loc) • 4.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useSmartlinkParentMessaging = void 0;
var react_1 = require("react");
var types_1 = require("./types");
/**
* @file useSmartlinkParentMessaging.ts
* @description Parent-side hook for smartlink iframes.
*
* Responsibilities this hook owns:
* - Deriving and enforcing pluginOrigin on all sends and receives
* - Origin check on every incoming message (no route registry — origin only)
* - Applying SMARTLINK_RESIZE to the iframe element (with optional onResize override)
* - Emitting all messages to subscribers with an isValid flag
* - Exposing named senders as conveniences
*
* Responsibilities this hook does NOT own:
* - Deciding what to send when SMARTLINK_READY fires
* - Handling step completion or cancellation
* - Error handling beyond passing SMARTLINK_ERROR to subscribers
*
* All of the above are consumer responsibilities.
*/
var useSmartlinkParentMessaging = function (_a) {
var pluginUrl = _a.pluginUrl, iframeRef = _a.iframeRef, onResize = _a.onResize;
var pluginOrigin = pluginUrl ? new URL(pluginUrl).origin : null;
/*------------------------------ SUBSCRIBERS STORE --------------------------------*/
var subscribersRef = (0, react_1.useRef)(new Set());
var subscribe = (0, react_1.useCallback)(function (handler) {
subscribersRef.current.add(handler);
return function () {
subscribersRef.current.delete(handler);
};
}, []);
var subscribeTo = (0, react_1.useCallback)(function (type, handler) {
return subscribe(function (message, event, isValid) {
if (message.type !== type)
return;
handler(message, event, isValid);
});
}, [subscribe]);
var emitToSubscribers = (0, react_1.useCallback)(function (msg, event, isValid) {
subscribersRef.current.forEach(function (fn) { return fn(msg, event, isValid); });
}, []);
/*---------------------------------- SENDER ---------------------------------------*/
var sendMessage = (0, react_1.useCallback)(function (message) {
var _a;
if (!((_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) || !pluginOrigin)
return;
iframeRef.current.contentWindow.postMessage(message, pluginOrigin);
}, [iframeRef, pluginOrigin]);
var sendInit = (0, react_1.useCallback)(function (requestId) {
sendMessage((0, types_1.createSmartlinkMessage)("SMARTLINK_INIT", undefined, requestId));
}, [sendMessage]);
/*--------------------------------- RECEIVER --------------------------------------*/
(0, react_1.useEffect)(function () {
var iframe = iframeRef.current;
var handleMessage = function (event) {
var _a;
if (event.origin !== pluginOrigin)
return;
var data = event.data;
if (!data || typeof data !== "object" || !("type" in data))
return;
/*
* Smartlink uses origin check only — no route registry revalidation.
* isValid is true as long as the origin matches pluginOrigin.
*/
var isValid = true;
if (data.type === "SMARTLINK_RESIZE") {
var _b = (_a = data.payload) !== null && _a !== void 0 ? _a : {}, height = _b.height, width = _b.width;
if (typeof height === "number" && iframe) {
var requested = { height: height, width: width };
var override = onResize === null || onResize === void 0 ? void 0 : onResize(requested);
var resolved = override !== null && override !== void 0 ? override : requested;
iframe.style.height = "".concat(resolved.height, "px");
if (resolved.width !== undefined) {
iframe.style.width = "".concat(resolved.width, "px");
}
}
}
emitToSubscribers(data, event, isValid);
};
window.addEventListener("message", handleMessage);
return function () {
window.removeEventListener("message", handleMessage);
};
}, [pluginUrl, pluginOrigin, iframeRef, onResize, emitToSubscribers]);
/*-------------------------------- EXPORT API -------------------------------------*/
return { sendMessage: sendMessage, sendInit: sendInit, subscribe: subscribe, subscribeTo: subscribeTo };
};
exports.useSmartlinkParentMessaging = useSmartlinkParentMessaging;