Skip to content

[WC-3452] Add pusher module to the repo#2258

Open
r0b1n wants to merge 5 commits into
mainfrom
feat/pusher-module
Open

[WC-3452] Add pusher module to the repo#2258
r0b1n wants to merge 5 commits into
mainfrom
feat/pusher-module

Conversation

@r0b1n

@r0b1n r0b1n commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

This adds pusher module to the repo, has old widget as an asses to copy into the module.

@r0b1n r0b1n requested a review from a team as a code owner June 9, 2026 14:19
@github-actions

This comment has been minimized.

Comment thread packages/modules/pusher/assets/Pusher_widget_legacy_1.2.0.mpk Outdated
@r0b1n r0b1n force-pushed the feat/pusher-module branch from 9e445e5 to 0455139 Compare June 9, 2026 15:36
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

gjulivan
gjulivan previously approved these changes Jun 11, 2026
@r0b1n r0b1n force-pushed the feat/pusher-module branch from 4cd3a12 to 56a23fa Compare June 11, 2026 12:25
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@r0b1n r0b1n force-pushed the feat/pusher-module branch from fd463f9 to acfb4d9 Compare June 15, 2026 07:36
Comment thread packages/pluggableWidgets/pusher-web/src/Pusher.tsx Outdated
Comment thread packages/pluggableWidgets/pusher-web/src/utils/getChannelName.ts
Comment thread packages/pluggableWidgets/pusher-web/src/Pusher.tsx Outdated
Comment thread packages/pluggableWidgets/pusher-web/src/utils/getChannelName.ts
@r0b1n r0b1n force-pushed the feat/pusher-module branch from acfb4d9 to fab3036 Compare June 15, 2026 07:48
@github-actions

This comment has been minimized.

@r0b1n r0b1n force-pushed the feat/pusher-module branch from fab3036 to f95239d Compare June 15, 2026 08:19
@github-actions

This comment has been minimized.

@r0b1n r0b1n force-pushed the feat/pusher-module branch from 4d1b69c to 9429277 Compare June 16, 2026 07:48
@github-actions

Copy link
Copy Markdown
Contributor

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
packages/pluggableWidgets/pusher-web/src/Pusher.tsx Main widget component — subscription setup
packages/pluggableWidgets/pusher-web/src/Pusher.xml Widget manifest
packages/pluggableWidgets/pusher-web/src/utils/PusherListener.ts Pusher connection/channel management class
packages/pluggableWidgets/pusher-web/src/utils/getChannelName.ts Channel name derivation from ObjectItem
packages/pluggableWidgets/pusher-web/src/utils/fetchPusherConfig.ts Fetches Pusher key/cluster from backend
packages/pluggableWidgets/pusher-web/src/hooks/usePusherSubscribe.ts React hook managing Pusher lifecycle
packages/pluggableWidgets/pusher-web/src/__tests__/Pusher.spec.tsx Unit tests
packages/pluggableWidgets/pusher-web/src/Pusher.editorConfig.ts Studio Pro design-time config
packages/pluggableWidgets/pusher-web/typings/PusherProps.d.ts Generated typings
packages/modules/pusher/scripts/*.ts Module build/release scripts
packages/modules/pusher/package.json Module manifest
packages/modules/pusher/CHANGELOG.md Module changelog
packages/pluggableWidgets/pusher-web/CHANGELOG.md Widget changelog

Skipped (out of scope): pnpm-lock.yaml, dist/

⚠️ CI checks could not be retrieved — verify all checks pass before merging.


Findings

🚨 High — Private symbol access to extract entity name

File: packages/pluggableWidgets/pusher-web/src/utils/getChannelName.ts line 21
Problem: extractEntityName uses Object.getOwnPropertySymbols(object)[0] to reach the internal mxObject and call .getEntity(). This depends on Mendix Client's private object layout — it will silently break whenever that internal structure changes, and it bypasses TypeScript safety with as any. There is no public Pluggable Widgets API to get the entity name from an ObjectItem this way.
Fix: The entity name is available via the datasource property's type in the XML and through objectSource's type metadata. If the channel name must encode the entity, use the ListValue or datasource type string from PusherPreviewProps at design-time validation, or document the channel naming convention so the server side controls the channel name independently. If the entity name is truly required at runtime, open a feature request against the Mendix Pluggable Widgets API — don't rely on symbol-keyed internals.

// Remove this function and its caller. Channel naming must not rely on
// private SDK symbols. Either receive the channel name explicitly as a
// widget property (type="string"), or derive it purely from object.id.
function extractEntityName(object: ObjectItem): string {
    const mxObj = (object as any)[Object.getOwnPropertySymbols(object)[0]];
    ...
}

🔶 Medium — Unsubscribe/resubscribe on every render

File: packages/pluggableWidgets/pusher-web/src/hooks/usePusherSubscribe.ts lines 33–46
Problem: The second useEffect depends on [listener, subscription]. The subscription object is produced by useMemo in Pusher.tsx with eventHandlers as a dependency. Because Mendix passes eventHandlers as a new array reference each render, subscription gets a new reference each render too, causing the effect cleanup (listener.unsubscribe()) and body (listener.subscribe()) to run on every render. listener.unsubscribe() does a full Pusher channel unsubscribe — events can be missed during the reconnect window.
Fix: Use a ref to hold the latest subscription and update it imperatively instead of driving it through state/effect dependencies.

export function usePusherSubscribe(subscription?: SubscriptionConfig): void {
    const [listener, setListener] = useState<PusherListener | null>(null);
    const subscriptionRef = useRef(subscription);

    // Keep ref current without triggering effect
    useLayoutEffect(() => {
        subscriptionRef.current = subscription;
    });

    // Only re-subscribe when listener or channelName changes
    useEffect(() => {
        if (!listener) return;
        const sub = subscriptionRef.current;
        if (!sub) { listener.unsubscribe(); return; }
        listener.subscribe(sub);
        return () => { listener.unsubscribe(); };
    }, [listener, subscription?.channelName]);   // ← channel name is stable; handlers update via ref
}

🔶 Medium — Placeholder unit test with no real coverage

File: packages/pluggableWidgets/pusher-web/src/__tests__/Pusher.spec.tsx lines 1–10
Problem: The spec is a expect(true).toBe(true) stub. The TODO comment lists the exact missing coverage: PusherListener lifecycle, config fetching, event handling, and action execution. Merging with this file means the widget ships with 0% meaningful test coverage.
Fix: Implement at minimum:

  • getChannelName — returns undefined for unresolved/loading value; returns private-Entity.guid when available.
  • PusherListener.subscribe() / unsubscribe() / destroy() — mock pusher-js and assert channel methods are called.
  • Pusher component — mock usePusherSubscribe, assert the subscription config is built correctly from props.
import { render } from "@testing-library/react";
import { EditableValueBuilder } from "@mendix/widget-plugin-test-utils";
import { getChannelName } from "../utils/getChannelName";

describe("getChannelName", () => {
    it("returns undefined when objectSource has no value", () => {
        const source = new EditableValueBuilder<ObjectItem>().isLoading().build() as any;
        expect(getChannelName(source)).toBeUndefined();
    });
    // ...
});

🔶 Medium — objectSource.status not checked before reading .value

File: packages/pluggableWidgets/pusher-web/src/utils/getChannelName.ts line 4
Problem: objectSource.value is accessed without first checking objectSource.status. Per Mendix data API conventions (see AGENTS.md), you must check .status before reading .value — when status is "loading", .value may hold a stale value from a previous render. The null guard if (!object) handles the crash case but doesn't distinguish "loading" from "unavailable".
Fix:

export function getChannelName(objectSource: DynamicValue<ObjectItem>): string | undefined {
    if (objectSource.status !== "available") {
        return undefined;
    }
    const object = objectSource.value;
    ...
}

⚠️ Low — HTTP response check uses exact status code instead of response.ok

File: packages/pluggableWidgets/pusher-web/src/utils/fetchPusherConfig.ts line 33
Note: response.status !== 200 rejects any 2xx other than 200 (e.g. 201). The idiomatic check is !response.ok, which covers the full 200–299 success range.

if (!response.ok) {
    console.error(`[fetchPusherConfig] Unexpected response: HTTP ${response.status}`);
    return null;
}

Positives

  • PusherListener.subscribe() correctly uses a global channel handler + Map lookup rather than re-binding individual event handlers, so updating handlers doesn't require a channel re-subscribe — good design.
  • The AbortController pattern in usePusherSubscribe's first useEffect correctly guards against state updates after unmount.
  • editorConfig.ts includes a runtime duplicate-action-name check that surfaces a clear error in Studio Pro — a nice developer experience detail.
  • CHANGELOG entries exist for both the widget package and the module package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants