From 035eb200c06f7e2f5ab48d7c578e8e8ba9aee43a Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Tue, 24 Mar 2026 14:04:10 -0400 Subject: [PATCH 1/4] feat(callbacks): add connect metadata to callback payloads - Add optional payload-level metadata for `connectPluginVersion` and `connectState` so callback flows can carry Connect context even when the action itself has no server object. - Before this change, the callback envelope only preserved `actions`, `sender`, and `type`, which meant Connect sign-in/sign-out style callbacks had no shared place to send plugin version or runtime state. - That made it awkward to support newer Connect-aware flows without overloading unrelated action shapes or requiring server payloads where none exist. - This change threads a new `CallbackPayloadMetadata` object through client and server `send`/`generateUrl` helpers, includes it in encryption/serialization, and exposes the fields on parsed payload types. - Add round-trip tests and README examples, then rebuild `dist/` so published artifacts stay aligned with the source API. --- README.md | 8 +++++- dist/client.d.ts | 12 ++++---- dist/client.js | 14 ++++++---- dist/core.d.ts | 6 ++-- dist/index.js | 14 ++++++---- dist/server.d.ts | 6 ++-- dist/server.js | 11 +++++--- dist/types.d.ts | 12 ++++++++ src/__tests__/server.test.ts | 11 ++++++-- src/__tests__/useSharedCallback.test.ts | 37 +++++++++++++++++++++++-- src/client.ts | 10 +++++-- src/core.ts | 14 ++++++++-- src/server.ts | 6 +++- src/types.ts | 12 ++++++++ 14 files changed, 136 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 82185e6..743deed 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,12 @@ callback.send('https://example.com/callback', [ // user info } } -]); +], undefined, 'forUpc', undefined, { + connectPluginVersion: '2024.05.06.1049', + connectState: { + connectionStatus: 'CONNECTED', + }, +}); // Watch for incoming callbacks (client-only) const decrypted = callback.watcher(); @@ -51,6 +56,7 @@ const decrypted = callback.watcher(); - `UserInfo` - User information structure - `ExternalActions` - Union type of all external actions - `UpcActions` - Union type of all UPC actions +- `CallbackPayloadMetadata` - Optional top-level metadata to include with any callback payload - `QueryPayloads` - Union type of all payload types ### Store Interface diff --git a/dist/client.d.ts b/dist/client.d.ts index 677a547..c9e81ef 100644 --- a/dist/client.d.ts +++ b/dist/client.d.ts @@ -1,22 +1,22 @@ -import type { CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; +import type { CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; export declare const createCallback: (config: CallbackConfig) => { - send: (url: string, payload: SendPayloads, redirectType?: "newTab" | "replace" | null, sendType?: string, sender?: string) => void; + send: (url: string, payload: SendPayloads, redirectType?: "newTab" | "replace" | null, sendType?: string, sender?: string, metadata?: CallbackPayloadMetadata) => void; parse: (data: string, options?: { isDataURIEncoded?: boolean; }) => QueryPayloads; watcher: (options?: WatcherOptions) => QueryPayloads | undefined; - generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string) => string; + generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string, metadata?: CallbackPayloadMetadata) => string; }; /** * Backwards-compatible alias for older consumers. * This no longer returns a shared singleton; it is a plain factory. */ export declare const useCallback: (config: CallbackConfig) => { - send: (url: string, payload: SendPayloads, redirectType?: "newTab" | "replace" | null, sendType?: string, sender?: string) => void; + send: (url: string, payload: SendPayloads, redirectType?: "newTab" | "replace" | null, sendType?: string, sender?: string, metadata?: CallbackPayloadMetadata) => void; parse: (data: string, options?: { isDataURIEncoded?: boolean; }) => QueryPayloads; watcher: (options?: WatcherOptions) => QueryPayloads | undefined; - generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string) => string; + generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string, metadata?: CallbackPayloadMetadata) => string; }; -export type { CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; +export type { CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; diff --git a/dist/client.js b/dist/client.js index a84a995..e77f9eb 100644 --- a/dist/client.js +++ b/dist/client.js @@ -2048,15 +2048,17 @@ var decryptData = (encryptedData, encryptionKey) => { } return decryptedString; }; -var stringifyPayload = (payload, sender, sendType) => { +var stringifyPayload = (payload, sender, sendType, metadata = {}) => { return JSON.stringify({ actions: [...payload], + connectPluginVersion: metadata.connectPluginVersion, + connectState: metadata.connectState, sender, type: sendType }); }; -var createEncryptedPayload = (payload, sender, sendType, encryptionKey) => { - const stringifiedData = stringifyPayload(payload, sender, sendType); +var createEncryptedPayload = (payload, sender, sendType, metadata, encryptionKey) => { + const stringifiedData = stringifyPayload(payload, sender, sendType, metadata); return encryptData(stringifiedData, encryptionKey); }; var parseEncryptedPayload = (encryptedData, encryptionKey, options) => { @@ -2082,7 +2084,7 @@ var appendEncryptedDataToUrl = (url, encryptedData, useHash) => { // src/client.ts var createCallback = (config) => { const shouldUseHash = config.useHash !== false; - const send = (url, payload, redirectType, sendType, sender) => { + const send = (url, payload, redirectType, sendType, sender, metadata) => { if (typeof window === "undefined") { throw new Error("send() can only be called on the client side"); } @@ -2091,6 +2093,7 @@ var createCallback = (config) => { payload, defaultSender, sendType, + metadata, config.encryptionKey ); const destinationUrl = appendEncryptedDataToUrl( @@ -2143,12 +2146,13 @@ var createCallback = (config) => { } return parse(uriDecodedEncryptedData); }; - const generateUrl = (url, payload, sendType, sender) => { + const generateUrl = (url, payload, sendType, sender, metadata) => { const defaultSender = sender ?? (typeof window !== "undefined" ? window.location.href.replace("/Tools/Update", "/Tools") : ""); const encryptedMessage = createEncryptedPayload( payload, defaultSender, sendType, + metadata, config.encryptionKey ); return appendEncryptedDataToUrl(url, encryptedMessage, shouldUseHash); diff --git a/dist/core.d.ts b/dist/core.d.ts index 22abb9e..91162c7 100644 --- a/dist/core.d.ts +++ b/dist/core.d.ts @@ -1,4 +1,4 @@ -import type { QueryPayloads, SendPayloads } from "./types"; +import type { CallbackPayloadMetadata, QueryPayloads, SendPayloads } from "./types"; /** * Encrypts a string using AES encryption. */ @@ -11,11 +11,11 @@ export declare const decryptData: (encryptedData: string, encryptionKey: string) /** * Stringifies a payload into the standard callback data format. */ -export declare const stringifyPayload: (payload: SendPayloads, sender: string, sendType?: string) => string; +export declare const stringifyPayload: (payload: SendPayloads, sender: string, sendType?: string, metadata?: CallbackPayloadMetadata) => string; /** * Creates an encrypted data string from a payload. */ -export declare const createEncryptedPayload: (payload: SendPayloads, sender: string, sendType: string | undefined, encryptionKey: string) => string; +export declare const createEncryptedPayload: (payload: SendPayloads, sender: string, sendType: string | undefined, metadata: CallbackPayloadMetadata | undefined, encryptionKey: string) => string; /** * Parses an encrypted callback payload string into its typed structure. */ diff --git a/dist/index.js b/dist/index.js index a84a995..e77f9eb 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2048,15 +2048,17 @@ var decryptData = (encryptedData, encryptionKey) => { } return decryptedString; }; -var stringifyPayload = (payload, sender, sendType) => { +var stringifyPayload = (payload, sender, sendType, metadata = {}) => { return JSON.stringify({ actions: [...payload], + connectPluginVersion: metadata.connectPluginVersion, + connectState: metadata.connectState, sender, type: sendType }); }; -var createEncryptedPayload = (payload, sender, sendType, encryptionKey) => { - const stringifiedData = stringifyPayload(payload, sender, sendType); +var createEncryptedPayload = (payload, sender, sendType, metadata, encryptionKey) => { + const stringifiedData = stringifyPayload(payload, sender, sendType, metadata); return encryptData(stringifiedData, encryptionKey); }; var parseEncryptedPayload = (encryptedData, encryptionKey, options) => { @@ -2082,7 +2084,7 @@ var appendEncryptedDataToUrl = (url, encryptedData, useHash) => { // src/client.ts var createCallback = (config) => { const shouldUseHash = config.useHash !== false; - const send = (url, payload, redirectType, sendType, sender) => { + const send = (url, payload, redirectType, sendType, sender, metadata) => { if (typeof window === "undefined") { throw new Error("send() can only be called on the client side"); } @@ -2091,6 +2093,7 @@ var createCallback = (config) => { payload, defaultSender, sendType, + metadata, config.encryptionKey ); const destinationUrl = appendEncryptedDataToUrl( @@ -2143,12 +2146,13 @@ var createCallback = (config) => { } return parse(uriDecodedEncryptedData); }; - const generateUrl = (url, payload, sendType, sender) => { + const generateUrl = (url, payload, sendType, sender, metadata) => { const defaultSender = sender ?? (typeof window !== "undefined" ? window.location.href.replace("/Tools/Update", "/Tools") : ""); const encryptedMessage = createEncryptedPayload( payload, defaultSender, sendType, + metadata, config.encryptionKey ); return appendEncryptedDataToUrl(url, encryptedMessage, shouldUseHash); diff --git a/dist/server.d.ts b/dist/server.d.ts index 35e9a93..579d6a4 100644 --- a/dist/server.d.ts +++ b/dist/server.d.ts @@ -1,4 +1,4 @@ -import type { CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; +import type { CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; /** * Server-safe factory that exposes only parse and generateUrl. * @@ -9,6 +9,6 @@ export declare const createServerCallback: (config: CallbackConfig) => { parse: (data: string, options?: { isDataURIEncoded?: boolean; }) => QueryPayloads; - generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string) => string; + generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string, metadata?: CallbackPayloadMetadata) => string; }; -export type { CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; +export type { CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; diff --git a/dist/server.js b/dist/server.js index 1c39717..813efd9 100644 --- a/dist/server.js +++ b/dist/server.js @@ -2048,15 +2048,17 @@ var decryptData = (encryptedData, encryptionKey) => { } return decryptedString; }; -var stringifyPayload = (payload, sender, sendType) => { +var stringifyPayload = (payload, sender, sendType, metadata = {}) => { return JSON.stringify({ actions: [...payload], + connectPluginVersion: metadata.connectPluginVersion, + connectState: metadata.connectState, sender, type: sendType }); }; -var createEncryptedPayload = (payload, sender, sendType, encryptionKey) => { - const stringifiedData = stringifyPayload(payload, sender, sendType); +var createEncryptedPayload = (payload, sender, sendType, metadata, encryptionKey) => { + const stringifiedData = stringifyPayload(payload, sender, sendType, metadata); return encryptData(stringifiedData, encryptionKey); }; var parseEncryptedPayload = (encryptedData, encryptionKey, options) => { @@ -2084,12 +2086,13 @@ var createServerCallback = (config) => { const parse = (data, options) => { return parseEncryptedPayload(data, config.encryptionKey, options); }; - const generateUrl = (url, payload, sendType, sender) => { + const generateUrl = (url, payload, sendType, sender, metadata) => { const effectiveSender = sender ?? ""; const encryptedMessage = createEncryptedPayload( payload, effectiveSender, sendType, + metadata, config.encryptionKey ); const shouldUseHash = config.useHash !== false; diff --git a/dist/types.d.ts b/dist/types.d.ts index 8d608b4..af8bcff 100644 --- a/dist/types.d.ts +++ b/dist/types.d.ts @@ -94,15 +94,27 @@ export interface ServerTroubleshoot { export type ExternalActions = ExternalSignIn | ExternalSignOut | ExternalKeyActions | ExternalUpdateOsAction; export type UpcActions = ServerPayload | ServerTroubleshoot; export type SendPayloads = ExternalActions[] | UpcActions[]; +export type JsonPrimitive = string | number | boolean | null; +export type JsonValue = JsonPrimitive | JsonValue[] | { + [key: string]: JsonValue | undefined; +}; +export interface CallbackPayloadMetadata { + connectPluginVersion?: string; + connectState?: JsonValue; +} export interface ExternalPayload { type: "forUpc"; actions: ExternalActions[]; sender: string; + connectPluginVersion?: string; + connectState?: JsonValue; } export interface UpcPayload { actions: UpcActions[]; sender: string; type: "fromUpc"; + connectPluginVersion?: string; + connectState?: JsonValue; } export type QueryPayloads = ExternalPayload | UpcPayload; export interface WatcherOptions { diff --git a/src/__tests__/server.test.ts b/src/__tests__/server.test.ts index c3be895..0eb1864 100644 --- a/src/__tests__/server.test.ts +++ b/src/__tests__/server.test.ts @@ -13,8 +13,14 @@ describe('createServerCallback (server entry)', () => { const targetUrl = 'http://test.com/c' const sendType = 'forUpc' const sender = 'http://sender.com' + const metadata = { + connectPluginVersion: '2024.05.06.1049', + connectState: { + connectionStatus: 'CONNECTED', + }, + } - const generatedUrl = generateUrl(targetUrl, testActions, sendType, sender) + const generatedUrl = generateUrl(targetUrl, testActions, sendType, sender, metadata) const url = new URL(generatedUrl) const encryptedData = url.hash.startsWith('#data=') @@ -25,9 +31,10 @@ describe('createServerCallback (server entry)', () => { expect(decrypted).toEqual({ actions: testActions, + connectPluginVersion: metadata.connectPluginVersion, + connectState: metadata.connectState, sender, type: sendType, }) }) }) - diff --git a/src/__tests__/useSharedCallback.test.ts b/src/__tests__/useSharedCallback.test.ts index 1c49a8e..82b5079 100644 --- a/src/__tests__/useSharedCallback.test.ts +++ b/src/__tests__/useSharedCallback.test.ts @@ -66,13 +66,28 @@ describe('useCallback', () => { it('should open in new tab when redirectType is newTab', () => { const callback = useCallback(mockConfig) const testActions: ExternalSignOut[] = [{ type: 'signOut' }] + const metadata = { + connectPluginVersion: '2024.05.06.1049', + connectState: { + connectionStatus: 'CONNECTED', + } + } const testData = { actions: testActions, + connectPluginVersion: metadata.connectPluginVersion, + connectState: metadata.connectState, sender: 'http://test.com/Tools', type: 'test' } - callback.send('http://test.com/Tools', testActions, 'newTab', 'test', 'http://test.com/Tools') + callback.send( + 'http://test.com/Tools', + testActions, + 'newTab', + 'test', + 'http://test.com/Tools', + metadata + ) // Get the URL from the spy call const [[urlString]] = (window.open as any).mock.calls @@ -219,6 +234,10 @@ describe('useCallback', () => { const testActions: ExternalSignOut[] = [{ type: 'signOut' }] const testData = { actions: testActions, + connectPluginVersion: '2024.05.06.1049', + connectState: { + connectionStatus: 'CONNECTED', + }, sender: 'http://test.com/Tools', type: 'test' } @@ -564,8 +583,20 @@ describe('useCallback', () => { const targetUrl = 'http://test.com/c' const sendType = 'forUpc' const sender = 'http://test.com/Tools' + const metadata = { + connectPluginVersion: '2024.05.06.1049', + connectState: { + connectionStatus: 'CONNECTED', + } + } - const generatedUrl = callback.generateUrl(targetUrl, testActions, sendType, sender) + const generatedUrl = callback.generateUrl( + targetUrl, + testActions, + sendType, + sender, + metadata + ) const url = new URL(generatedUrl) expect(url.origin + url.pathname).toBe(targetUrl) @@ -578,6 +609,8 @@ describe('useCallback', () => { const decryptedData = callback.parse(encryptedData) expect(decryptedData).toEqual({ actions: testActions, + connectPluginVersion: metadata.connectPluginVersion, + connectState: metadata.connectState, sender, type: sendType }) diff --git a/src/client.ts b/src/client.ts index 9ae4ef7..7a884f9 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,4 +1,5 @@ import type { + CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, @@ -53,7 +54,8 @@ export const createCallback = (config: CallbackConfig) => { payload: SendPayloads, redirectType?: "newTab" | "replace" | null, sendType?: string, - sender?: string + sender?: string, + metadata?: CallbackPayloadMetadata ) => { if (typeof window === "undefined") { throw new Error("send() can only be called on the client side"); @@ -66,6 +68,7 @@ export const createCallback = (config: CallbackConfig) => { payload, defaultSender, sendType, + metadata, config.encryptionKey ); @@ -149,7 +152,8 @@ export const createCallback = (config: CallbackConfig) => { url: string, payload: SendPayloads, sendType?: string, - sender?: string + sender?: string, + metadata?: CallbackPayloadMetadata ): string => { const defaultSender = sender ?? @@ -161,6 +165,7 @@ export const createCallback = (config: CallbackConfig) => { payload, defaultSender, sendType, + metadata, config.encryptionKey ); @@ -183,6 +188,7 @@ export const useCallback = createCallback; // Re-export all types for convenience from the client entry. export type { + CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, diff --git a/src/core.ts b/src/core.ts index 0c10fc3..2fa4766 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,6 +1,10 @@ import AES from "crypto-js/aes.js"; import Utf8 from "crypto-js/enc-utf8.js"; -import type { QueryPayloads, SendPayloads } from "./types"; +import type { + CallbackPayloadMetadata, + QueryPayloads, + SendPayloads, +} from "./types"; /** * Encrypts a string using AES encryption. @@ -39,10 +43,13 @@ export const decryptData = ( export const stringifyPayload = ( payload: SendPayloads, sender: string, - sendType?: string + sendType?: string, + metadata: CallbackPayloadMetadata = {} ): string => { return JSON.stringify({ actions: [...payload], + connectPluginVersion: metadata.connectPluginVersion, + connectState: metadata.connectState, sender, type: sendType, }); @@ -55,9 +62,10 @@ export const createEncryptedPayload = ( payload: SendPayloads, sender: string, sendType: string | undefined, + metadata: CallbackPayloadMetadata | undefined, encryptionKey: string ): string => { - const stringifiedData = stringifyPayload(payload, sender, sendType); + const stringifiedData = stringifyPayload(payload, sender, sendType, metadata); return encryptData(stringifiedData, encryptionKey); }; diff --git a/src/server.ts b/src/server.ts index f0d2e5a..4cac8ed 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,4 +1,5 @@ import type { + CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, @@ -62,13 +63,15 @@ export const createServerCallback = (config: CallbackConfig) => { url: string, payload: SendPayloads, sendType?: string, - sender?: string + sender?: string, + metadata?: CallbackPayloadMetadata ): string => { const effectiveSender = sender ?? ""; const encryptedMessage = createEncryptedPayload( payload, effectiveSender, sendType, + metadata, config.encryptionKey ); @@ -84,6 +87,7 @@ export const createServerCallback = (config: CallbackConfig) => { // Re-export all types for convenience from the server entry. export type { + CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, diff --git a/src/types.ts b/src/types.ts index 5837527..bcd4895 100644 --- a/src/types.ts +++ b/src/types.ts @@ -162,16 +162,28 @@ export type UpcActions = ServerPayload | ServerTroubleshoot; export type SendPayloads = ExternalActions[] | UpcActions[]; +export type JsonPrimitive = string | number | boolean | null; +export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue | undefined }; + +export interface CallbackPayloadMetadata { + connectPluginVersion?: string; + connectState?: JsonValue; +} + export interface ExternalPayload { type: "forUpc"; actions: ExternalActions[]; sender: string; + connectPluginVersion?: string; + connectState?: JsonValue; } export interface UpcPayload { actions: UpcActions[]; sender: string; type: "fromUpc"; + connectPluginVersion?: string; + connectState?: JsonValue; } export type QueryPayloads = ExternalPayload | UpcPayload; From f92e3e6cee8520fb79ce4b81c4ef935580d4f51b Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Tue, 24 Mar 2026 14:11:18 -0400 Subject: [PATCH 2/4] fix(types): keep connect data in callback server state - Model `connectPluginVersion` and `connectState` on `ServerData` so callback actions keep Connect details inside the existing `server` payload used by the web app. - The previous change added a trailing metadata argument to `send()` and `generateUrl()`, which would have diverged from real callers and implied a second callback data channel outside the server action payload. - In `../api/web`, account and purchase callbacks already send a `server` object, so the extra API surface was the wrong fit and risked pushing usage away from established call patterns. - Remove the added metadata parameter and payload-envelope fields, restore the original helper signatures, and cover the new fields through round-trip tests on server-backed callback actions instead. - Rebuild `dist/` and keep README examples aligned with the unchanged public helper API. --- README.md | 8 +-- dist/client.d.ts | 12 ++--- dist/client.js | 14 ++--- dist/core.d.ts | 6 +-- dist/index.js | 14 ++--- dist/server.d.ts | 6 +-- dist/server.js | 11 ++-- dist/types.d.ts | 10 +--- src/__tests__/server.test.ts | 27 ++++++---- src/__tests__/useSharedCallback.test.ts | 69 +++++++++++-------------- src/client.ts | 10 +--- src/core.ts | 14 ++--- src/server.ts | 6 +-- src/types.ts | 11 +--- 14 files changed, 84 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index 743deed..82185e6 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,7 @@ callback.send('https://example.com/callback', [ // user info } } -], undefined, 'forUpc', undefined, { - connectPluginVersion: '2024.05.06.1049', - connectState: { - connectionStatus: 'CONNECTED', - }, -}); +]); // Watch for incoming callbacks (client-only) const decrypted = callback.watcher(); @@ -56,7 +51,6 @@ const decrypted = callback.watcher(); - `UserInfo` - User information structure - `ExternalActions` - Union type of all external actions - `UpcActions` - Union type of all UPC actions -- `CallbackPayloadMetadata` - Optional top-level metadata to include with any callback payload - `QueryPayloads` - Union type of all payload types ### Store Interface diff --git a/dist/client.d.ts b/dist/client.d.ts index c9e81ef..677a547 100644 --- a/dist/client.d.ts +++ b/dist/client.d.ts @@ -1,22 +1,22 @@ -import type { CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; +import type { CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; export declare const createCallback: (config: CallbackConfig) => { - send: (url: string, payload: SendPayloads, redirectType?: "newTab" | "replace" | null, sendType?: string, sender?: string, metadata?: CallbackPayloadMetadata) => void; + send: (url: string, payload: SendPayloads, redirectType?: "newTab" | "replace" | null, sendType?: string, sender?: string) => void; parse: (data: string, options?: { isDataURIEncoded?: boolean; }) => QueryPayloads; watcher: (options?: WatcherOptions) => QueryPayloads | undefined; - generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string, metadata?: CallbackPayloadMetadata) => string; + generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string) => string; }; /** * Backwards-compatible alias for older consumers. * This no longer returns a shared singleton; it is a plain factory. */ export declare const useCallback: (config: CallbackConfig) => { - send: (url: string, payload: SendPayloads, redirectType?: "newTab" | "replace" | null, sendType?: string, sender?: string, metadata?: CallbackPayloadMetadata) => void; + send: (url: string, payload: SendPayloads, redirectType?: "newTab" | "replace" | null, sendType?: string, sender?: string) => void; parse: (data: string, options?: { isDataURIEncoded?: boolean; }) => QueryPayloads; watcher: (options?: WatcherOptions) => QueryPayloads | undefined; - generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string, metadata?: CallbackPayloadMetadata) => string; + generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string) => string; }; -export type { CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; +export type { CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; diff --git a/dist/client.js b/dist/client.js index e77f9eb..a84a995 100644 --- a/dist/client.js +++ b/dist/client.js @@ -2048,17 +2048,15 @@ var decryptData = (encryptedData, encryptionKey) => { } return decryptedString; }; -var stringifyPayload = (payload, sender, sendType, metadata = {}) => { +var stringifyPayload = (payload, sender, sendType) => { return JSON.stringify({ actions: [...payload], - connectPluginVersion: metadata.connectPluginVersion, - connectState: metadata.connectState, sender, type: sendType }); }; -var createEncryptedPayload = (payload, sender, sendType, metadata, encryptionKey) => { - const stringifiedData = stringifyPayload(payload, sender, sendType, metadata); +var createEncryptedPayload = (payload, sender, sendType, encryptionKey) => { + const stringifiedData = stringifyPayload(payload, sender, sendType); return encryptData(stringifiedData, encryptionKey); }; var parseEncryptedPayload = (encryptedData, encryptionKey, options) => { @@ -2084,7 +2082,7 @@ var appendEncryptedDataToUrl = (url, encryptedData, useHash) => { // src/client.ts var createCallback = (config) => { const shouldUseHash = config.useHash !== false; - const send = (url, payload, redirectType, sendType, sender, metadata) => { + const send = (url, payload, redirectType, sendType, sender) => { if (typeof window === "undefined") { throw new Error("send() can only be called on the client side"); } @@ -2093,7 +2091,6 @@ var createCallback = (config) => { payload, defaultSender, sendType, - metadata, config.encryptionKey ); const destinationUrl = appendEncryptedDataToUrl( @@ -2146,13 +2143,12 @@ var createCallback = (config) => { } return parse(uriDecodedEncryptedData); }; - const generateUrl = (url, payload, sendType, sender, metadata) => { + const generateUrl = (url, payload, sendType, sender) => { const defaultSender = sender ?? (typeof window !== "undefined" ? window.location.href.replace("/Tools/Update", "/Tools") : ""); const encryptedMessage = createEncryptedPayload( payload, defaultSender, sendType, - metadata, config.encryptionKey ); return appendEncryptedDataToUrl(url, encryptedMessage, shouldUseHash); diff --git a/dist/core.d.ts b/dist/core.d.ts index 91162c7..22abb9e 100644 --- a/dist/core.d.ts +++ b/dist/core.d.ts @@ -1,4 +1,4 @@ -import type { CallbackPayloadMetadata, QueryPayloads, SendPayloads } from "./types"; +import type { QueryPayloads, SendPayloads } from "./types"; /** * Encrypts a string using AES encryption. */ @@ -11,11 +11,11 @@ export declare const decryptData: (encryptedData: string, encryptionKey: string) /** * Stringifies a payload into the standard callback data format. */ -export declare const stringifyPayload: (payload: SendPayloads, sender: string, sendType?: string, metadata?: CallbackPayloadMetadata) => string; +export declare const stringifyPayload: (payload: SendPayloads, sender: string, sendType?: string) => string; /** * Creates an encrypted data string from a payload. */ -export declare const createEncryptedPayload: (payload: SendPayloads, sender: string, sendType: string | undefined, metadata: CallbackPayloadMetadata | undefined, encryptionKey: string) => string; +export declare const createEncryptedPayload: (payload: SendPayloads, sender: string, sendType: string | undefined, encryptionKey: string) => string; /** * Parses an encrypted callback payload string into its typed structure. */ diff --git a/dist/index.js b/dist/index.js index e77f9eb..a84a995 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2048,17 +2048,15 @@ var decryptData = (encryptedData, encryptionKey) => { } return decryptedString; }; -var stringifyPayload = (payload, sender, sendType, metadata = {}) => { +var stringifyPayload = (payload, sender, sendType) => { return JSON.stringify({ actions: [...payload], - connectPluginVersion: metadata.connectPluginVersion, - connectState: metadata.connectState, sender, type: sendType }); }; -var createEncryptedPayload = (payload, sender, sendType, metadata, encryptionKey) => { - const stringifiedData = stringifyPayload(payload, sender, sendType, metadata); +var createEncryptedPayload = (payload, sender, sendType, encryptionKey) => { + const stringifiedData = stringifyPayload(payload, sender, sendType); return encryptData(stringifiedData, encryptionKey); }; var parseEncryptedPayload = (encryptedData, encryptionKey, options) => { @@ -2084,7 +2082,7 @@ var appendEncryptedDataToUrl = (url, encryptedData, useHash) => { // src/client.ts var createCallback = (config) => { const shouldUseHash = config.useHash !== false; - const send = (url, payload, redirectType, sendType, sender, metadata) => { + const send = (url, payload, redirectType, sendType, sender) => { if (typeof window === "undefined") { throw new Error("send() can only be called on the client side"); } @@ -2093,7 +2091,6 @@ var createCallback = (config) => { payload, defaultSender, sendType, - metadata, config.encryptionKey ); const destinationUrl = appendEncryptedDataToUrl( @@ -2146,13 +2143,12 @@ var createCallback = (config) => { } return parse(uriDecodedEncryptedData); }; - const generateUrl = (url, payload, sendType, sender, metadata) => { + const generateUrl = (url, payload, sendType, sender) => { const defaultSender = sender ?? (typeof window !== "undefined" ? window.location.href.replace("/Tools/Update", "/Tools") : ""); const encryptedMessage = createEncryptedPayload( payload, defaultSender, sendType, - metadata, config.encryptionKey ); return appendEncryptedDataToUrl(url, encryptedMessage, shouldUseHash); diff --git a/dist/server.d.ts b/dist/server.d.ts index 579d6a4..35e9a93 100644 --- a/dist/server.d.ts +++ b/dist/server.d.ts @@ -1,4 +1,4 @@ -import type { CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; +import type { CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; /** * Server-safe factory that exposes only parse and generateUrl. * @@ -9,6 +9,6 @@ export declare const createServerCallback: (config: CallbackConfig) => { parse: (data: string, options?: { isDataURIEncoded?: boolean; }) => QueryPayloads; - generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string, metadata?: CallbackPayloadMetadata) => string; + generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string) => string; }; -export type { CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; +export type { CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; diff --git a/dist/server.js b/dist/server.js index 813efd9..1c39717 100644 --- a/dist/server.js +++ b/dist/server.js @@ -2048,17 +2048,15 @@ var decryptData = (encryptedData, encryptionKey) => { } return decryptedString; }; -var stringifyPayload = (payload, sender, sendType, metadata = {}) => { +var stringifyPayload = (payload, sender, sendType) => { return JSON.stringify({ actions: [...payload], - connectPluginVersion: metadata.connectPluginVersion, - connectState: metadata.connectState, sender, type: sendType }); }; -var createEncryptedPayload = (payload, sender, sendType, metadata, encryptionKey) => { - const stringifiedData = stringifyPayload(payload, sender, sendType, metadata); +var createEncryptedPayload = (payload, sender, sendType, encryptionKey) => { + const stringifiedData = stringifyPayload(payload, sender, sendType); return encryptData(stringifiedData, encryptionKey); }; var parseEncryptedPayload = (encryptedData, encryptionKey, options) => { @@ -2086,13 +2084,12 @@ var createServerCallback = (config) => { const parse = (data, options) => { return parseEncryptedPayload(data, config.encryptionKey, options); }; - const generateUrl = (url, payload, sendType, sender, metadata) => { + const generateUrl = (url, payload, sendType, sender) => { const effectiveSender = sender ?? ""; const encryptedMessage = createEncryptedPayload( payload, effectiveSender, sendType, - metadata, config.encryptionKey ); const shouldUseHash = config.useHash !== false; diff --git a/dist/types.d.ts b/dist/types.d.ts index af8bcff..7092406 100644 --- a/dist/types.d.ts +++ b/dist/types.d.ts @@ -37,6 +37,8 @@ export interface ActivationCodeData { } export interface ServerData { activationCodeData?: ActivationCodeData | null; + connectPluginVersion?: string; + connectState?: JsonValue; description?: string; deviceCount?: number; expireTime?: number; @@ -98,23 +100,15 @@ export type JsonPrimitive = string | number | boolean | null; export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue | undefined; }; -export interface CallbackPayloadMetadata { - connectPluginVersion?: string; - connectState?: JsonValue; -} export interface ExternalPayload { type: "forUpc"; actions: ExternalActions[]; sender: string; - connectPluginVersion?: string; - connectState?: JsonValue; } export interface UpcPayload { actions: UpcActions[]; sender: string; type: "fromUpc"; - connectPluginVersion?: string; - connectState?: JsonValue; } export type QueryPayloads = ExternalPayload | UpcPayload; export interface WatcherOptions { diff --git a/src/__tests__/server.test.ts b/src/__tests__/server.test.ts index 0eb1864..7a900bd 100644 --- a/src/__tests__/server.test.ts +++ b/src/__tests__/server.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest' import { createServerCallback } from '../server' -import type { ExternalSignOut } from '../types' +import type { ServerPayload } from '../types' describe('createServerCallback (server entry)', () => { const config = { @@ -9,18 +9,25 @@ describe('createServerCallback (server entry)', () => { it('should round-trip data via generateUrl and parse without using window', () => { const { parse, generateUrl } = createServerCallback(config) - const testActions: ExternalSignOut[] = [{ type: 'signOut' }] + const testActions: ServerPayload[] = [ + { + type: 'signIn', + server: { + connectPluginVersion: '2024.05.06.1049', + connectState: { + connectionStatus: 'CONNECTED', + }, + guid: 'test-guid', + registered: false, + state: 'ENOCONN', + }, + }, + ] const targetUrl = 'http://test.com/c' const sendType = 'forUpc' const sender = 'http://sender.com' - const metadata = { - connectPluginVersion: '2024.05.06.1049', - connectState: { - connectionStatus: 'CONNECTED', - }, - } - const generatedUrl = generateUrl(targetUrl, testActions, sendType, sender, metadata) + const generatedUrl = generateUrl(targetUrl, testActions, sendType, sender) const url = new URL(generatedUrl) const encryptedData = url.hash.startsWith('#data=') @@ -31,8 +38,6 @@ describe('createServerCallback (server entry)', () => { expect(decrypted).toEqual({ actions: testActions, - connectPluginVersion: metadata.connectPluginVersion, - connectState: metadata.connectState, sender, type: sendType, }) diff --git a/src/__tests__/useSharedCallback.test.ts b/src/__tests__/useSharedCallback.test.ts index 82b5079..254081d 100644 --- a/src/__tests__/useSharedCallback.test.ts +++ b/src/__tests__/useSharedCallback.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest' import AES from 'crypto-js/aes.js' import Utf8 from 'crypto-js/enc-utf8.js' -import type { ExternalSignOut } from '../types' +import type { ExternalSignOut, ServerPayload } from '../types' let useCallback: any @@ -66,28 +66,13 @@ describe('useCallback', () => { it('should open in new tab when redirectType is newTab', () => { const callback = useCallback(mockConfig) const testActions: ExternalSignOut[] = [{ type: 'signOut' }] - const metadata = { - connectPluginVersion: '2024.05.06.1049', - connectState: { - connectionStatus: 'CONNECTED', - } - } const testData = { actions: testActions, - connectPluginVersion: metadata.connectPluginVersion, - connectState: metadata.connectState, sender: 'http://test.com/Tools', type: 'test' } - callback.send( - 'http://test.com/Tools', - testActions, - 'newTab', - 'test', - 'http://test.com/Tools', - metadata - ) + callback.send('http://test.com/Tools', testActions, 'newTab', 'test', 'http://test.com/Tools') // Get the URL from the spy call const [[urlString]] = (window.open as any).mock.calls @@ -231,13 +216,22 @@ describe('useCallback', () => { describe('parse function', () => { it('should correctly parse valid encrypted data', () => { const callback = useCallback(mockConfig) - const testActions: ExternalSignOut[] = [{ type: 'signOut' }] + const testActions: ServerPayload[] = [ + { + type: 'signIn', + server: { + connectPluginVersion: '2024.05.06.1049', + connectState: { + connectionStatus: 'CONNECTED', + }, + guid: 'test-guid', + registered: false, + state: 'ENOCONN', + }, + }, + ] const testData = { actions: testActions, - connectPluginVersion: '2024.05.06.1049', - connectState: { - connectionStatus: 'CONNECTED', - }, sender: 'http://test.com/Tools', type: 'test' } @@ -579,24 +573,25 @@ describe('useCallback', () => { describe('generateUrl function', () => { it('should generate a URL with encrypted data', () => { const callback = useCallback(mockConfig) - const testActions: ExternalSignOut[] = [{ type: 'signOut' }] + const testActions: ServerPayload[] = [ + { + type: 'signIn', + server: { + connectPluginVersion: '2024.05.06.1049', + connectState: { + connectionStatus: 'CONNECTED', + }, + guid: 'test-guid', + registered: false, + state: 'ENOCONN', + }, + }, + ] const targetUrl = 'http://test.com/c' const sendType = 'forUpc' const sender = 'http://test.com/Tools' - const metadata = { - connectPluginVersion: '2024.05.06.1049', - connectState: { - connectionStatus: 'CONNECTED', - } - } - const generatedUrl = callback.generateUrl( - targetUrl, - testActions, - sendType, - sender, - metadata - ) + const generatedUrl = callback.generateUrl(targetUrl, testActions, sendType, sender) const url = new URL(generatedUrl) expect(url.origin + url.pathname).toBe(targetUrl) @@ -609,8 +604,6 @@ describe('useCallback', () => { const decryptedData = callback.parse(encryptedData) expect(decryptedData).toEqual({ actions: testActions, - connectPluginVersion: metadata.connectPluginVersion, - connectState: metadata.connectState, sender, type: sendType }) diff --git a/src/client.ts b/src/client.ts index 7a884f9..9ae4ef7 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,5 +1,4 @@ import type { - CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, @@ -54,8 +53,7 @@ export const createCallback = (config: CallbackConfig) => { payload: SendPayloads, redirectType?: "newTab" | "replace" | null, sendType?: string, - sender?: string, - metadata?: CallbackPayloadMetadata + sender?: string ) => { if (typeof window === "undefined") { throw new Error("send() can only be called on the client side"); @@ -68,7 +66,6 @@ export const createCallback = (config: CallbackConfig) => { payload, defaultSender, sendType, - metadata, config.encryptionKey ); @@ -152,8 +149,7 @@ export const createCallback = (config: CallbackConfig) => { url: string, payload: SendPayloads, sendType?: string, - sender?: string, - metadata?: CallbackPayloadMetadata + sender?: string ): string => { const defaultSender = sender ?? @@ -165,7 +161,6 @@ export const createCallback = (config: CallbackConfig) => { payload, defaultSender, sendType, - metadata, config.encryptionKey ); @@ -188,7 +183,6 @@ export const useCallback = createCallback; // Re-export all types for convenience from the client entry. export type { - CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, diff --git a/src/core.ts b/src/core.ts index 2fa4766..0c10fc3 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,10 +1,6 @@ import AES from "crypto-js/aes.js"; import Utf8 from "crypto-js/enc-utf8.js"; -import type { - CallbackPayloadMetadata, - QueryPayloads, - SendPayloads, -} from "./types"; +import type { QueryPayloads, SendPayloads } from "./types"; /** * Encrypts a string using AES encryption. @@ -43,13 +39,10 @@ export const decryptData = ( export const stringifyPayload = ( payload: SendPayloads, sender: string, - sendType?: string, - metadata: CallbackPayloadMetadata = {} + sendType?: string ): string => { return JSON.stringify({ actions: [...payload], - connectPluginVersion: metadata.connectPluginVersion, - connectState: metadata.connectState, sender, type: sendType, }); @@ -62,10 +55,9 @@ export const createEncryptedPayload = ( payload: SendPayloads, sender: string, sendType: string | undefined, - metadata: CallbackPayloadMetadata | undefined, encryptionKey: string ): string => { - const stringifiedData = stringifyPayload(payload, sender, sendType, metadata); + const stringifiedData = stringifyPayload(payload, sender, sendType); return encryptData(stringifiedData, encryptionKey); }; diff --git a/src/server.ts b/src/server.ts index 4cac8ed..f0d2e5a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,5 +1,4 @@ import type { - CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, @@ -63,15 +62,13 @@ export const createServerCallback = (config: CallbackConfig) => { url: string, payload: SendPayloads, sendType?: string, - sender?: string, - metadata?: CallbackPayloadMetadata + sender?: string ): string => { const effectiveSender = sender ?? ""; const encryptedMessage = createEncryptedPayload( payload, effectiveSender, sendType, - metadata, config.encryptionKey ); @@ -87,7 +84,6 @@ export const createServerCallback = (config: CallbackConfig) => { // Re-export all types for convenience from the server entry. export type { - CallbackPayloadMetadata, CallbackConfig, QueryPayloads, SendPayloads, diff --git a/src/types.ts b/src/types.ts index bcd4895..a0d9704 100644 --- a/src/types.ts +++ b/src/types.ts @@ -90,6 +90,8 @@ export interface ActivationCodeData { export interface ServerData { activationCodeData?: ActivationCodeData | null; + connectPluginVersion?: string; + connectState?: JsonValue; description?: string; deviceCount?: number; expireTime?: number; @@ -165,25 +167,16 @@ export type SendPayloads = ExternalActions[] | UpcActions[]; export type JsonPrimitive = string | number | boolean | null; export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue | undefined }; -export interface CallbackPayloadMetadata { - connectPluginVersion?: string; - connectState?: JsonValue; -} - export interface ExternalPayload { type: "forUpc"; actions: ExternalActions[]; sender: string; - connectPluginVersion?: string; - connectState?: JsonValue; } export interface UpcPayload { actions: UpcActions[]; sender: string; type: "fromUpc"; - connectPluginVersion?: string; - connectState?: JsonValue; } export type QueryPayloads = ExternalPayload | UpcPayload; From 591458cf00e2158752f932f07aebe934205c1548 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Tue, 24 Mar 2026 14:14:54 -0400 Subject: [PATCH 3/4] fix(types): narrow connectState to known statuses - Replace the loose `connectState` JSON value with a typed string union covering the existing Connect minigraph states: `PRE_INIT`, `CONNECTING`, `CONNECTED`, `PING_FAILURE`, and `ERROR_RETRYING`. - Leaving the field as untyped JSON made the callback contract too permissive and would have let unrelated shapes slip into server callback state without type help. - The Connect codebase already has a clear set of status strings, so this change aligns shared-callbacks with the existing source of truth instead of inventing a looser parallel model. - Update the round-trip tests to send the string form and re-export the new `ConnectState` type from the client and server entrypoints. - Rebuild `dist/` so the generated declarations and runtime bundles match the narrowed public type surface. --- dist/client.d.ts | 4 ++-- dist/server.d.ts | 4 ++-- dist/types.d.ts | 7 ++----- src/__tests__/server.test.ts | 4 +--- src/__tests__/useSharedCallback.test.ts | 8 ++------ src/client.ts | 2 ++ src/server.ts | 2 ++ src/types.ts | 12 ++++++++---- 8 files changed, 21 insertions(+), 22 deletions(-) diff --git a/dist/client.d.ts b/dist/client.d.ts index 677a547..f03b7d2 100644 --- a/dist/client.d.ts +++ b/dist/client.d.ts @@ -1,4 +1,4 @@ -import type { CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; +import type { CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ConnectState, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; export declare const createCallback: (config: CallbackConfig) => { send: (url: string, payload: SendPayloads, redirectType?: "newTab" | "replace" | null, sendType?: string, sender?: string) => void; parse: (data: string, options?: { @@ -19,4 +19,4 @@ export declare const useCallback: (config: CallbackConfig) => { watcher: (options?: WatcherOptions) => QueryPayloads | undefined; generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string) => string; }; -export type { CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; +export type { CallbackConfig, QueryPayloads, SendPayloads, WatcherOptions, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ConnectState, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; diff --git a/dist/server.d.ts b/dist/server.d.ts index 35e9a93..3266842 100644 --- a/dist/server.d.ts +++ b/dist/server.d.ts @@ -1,4 +1,4 @@ -import type { CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; +import type { CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ConnectState, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload } from "./types.js"; /** * Server-safe factory that exposes only parse and generateUrl. * @@ -11,4 +11,4 @@ export declare const createServerCallback: (config: CallbackConfig) => { }) => QueryPayloads; generateUrl: (url: string, payload: SendPayloads, sendType?: string, sender?: string) => string; }; -export type { CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; +export type { CallbackConfig, QueryPayloads, SendPayloads, SignIn, SignOut, OemSignOut, Troubleshoot, Recover, Replace, TrialExtend, TrialStart, Purchase, Redeem, Renew, Upgrade, UpdateOs, DowngradeOs, Manage, MyKeys, LinkKey, Activate, AccountActionTypes, AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, ConnectState, ServerState, ServerData, UserInfo, ExternalSignIn, ExternalSignOut, ExternalKeyActions, ExternalUpdateOsAction, ServerPayload, ServerTroubleshoot, ExternalActions, UpcActions, ExternalPayload, UpcPayload, }; diff --git a/dist/types.d.ts b/dist/types.d.ts index 7092406..41c313a 100644 --- a/dist/types.d.ts +++ b/dist/types.d.ts @@ -21,6 +21,7 @@ export type AccountKeyActionTypes = Recover | Replace | TrialExtend | TrialStart export type PurchaseActionTypes = Purchase | Redeem | Renew | Upgrade | Activate; export type ServerActionTypes = AccountActionTypes | AccountKeyActionTypes | PurchaseActionTypes; export type ServerState = "BASIC" | "PLUS" | "PRO" | "TRIAL" | "EEXPIRED" | "ENOKEYFILE" | "EGUID" | "EGUID1" | "ETRIAL" | "ENOKEYFILE2" | "ENOKEYFILE1" | "ENOFLASH" | "ENOFLASH1" | "ENOFLASH2" | "ENOFLASH3" | "ENOFLASH4" | "ENOFLASH5" | "ENOFLASH6" | "ENOFLASH7" | "EBLACKLISTED" | "EBLACKLISTED1" | "EBLACKLISTED2" | "ENOCONN" | "STARTER" | "UNLEASHED" | "LIFETIME" | "STALE" | undefined; +export type ConnectState = "PRE_INIT" | "CONNECTING" | "CONNECTED" | "PING_FAILURE" | "ERROR_RETRYING"; export interface ActivationCodeData { __typename?: "ActivationCode"; background?: string | null; @@ -38,7 +39,7 @@ export interface ActivationCodeData { export interface ServerData { activationCodeData?: ActivationCodeData | null; connectPluginVersion?: string; - connectState?: JsonValue; + connectState?: ConnectState; description?: string; deviceCount?: number; expireTime?: number; @@ -96,10 +97,6 @@ export interface ServerTroubleshoot { export type ExternalActions = ExternalSignIn | ExternalSignOut | ExternalKeyActions | ExternalUpdateOsAction; export type UpcActions = ServerPayload | ServerTroubleshoot; export type SendPayloads = ExternalActions[] | UpcActions[]; -export type JsonPrimitive = string | number | boolean | null; -export type JsonValue = JsonPrimitive | JsonValue[] | { - [key: string]: JsonValue | undefined; -}; export interface ExternalPayload { type: "forUpc"; actions: ExternalActions[]; diff --git a/src/__tests__/server.test.ts b/src/__tests__/server.test.ts index 7a900bd..d3766da 100644 --- a/src/__tests__/server.test.ts +++ b/src/__tests__/server.test.ts @@ -14,9 +14,7 @@ describe('createServerCallback (server entry)', () => { type: 'signIn', server: { connectPluginVersion: '2024.05.06.1049', - connectState: { - connectionStatus: 'CONNECTED', - }, + connectState: 'CONNECTED', guid: 'test-guid', registered: false, state: 'ENOCONN', diff --git a/src/__tests__/useSharedCallback.test.ts b/src/__tests__/useSharedCallback.test.ts index 254081d..1a49e00 100644 --- a/src/__tests__/useSharedCallback.test.ts +++ b/src/__tests__/useSharedCallback.test.ts @@ -221,9 +221,7 @@ describe('useCallback', () => { type: 'signIn', server: { connectPluginVersion: '2024.05.06.1049', - connectState: { - connectionStatus: 'CONNECTED', - }, + connectState: 'CONNECTED', guid: 'test-guid', registered: false, state: 'ENOCONN', @@ -578,9 +576,7 @@ describe('useCallback', () => { type: 'signIn', server: { connectPluginVersion: '2024.05.06.1049', - connectState: { - connectionStatus: 'CONNECTED', - }, + connectState: 'CONNECTED', guid: 'test-guid', registered: false, state: 'ENOCONN', diff --git a/src/client.ts b/src/client.ts index 9ae4ef7..c3553bb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -25,6 +25,7 @@ import type { AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, + ConnectState, ServerState, ServerData, UserInfo, @@ -209,6 +210,7 @@ export type { AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, + ConnectState, ServerState, ServerData, UserInfo, diff --git a/src/server.ts b/src/server.ts index f0d2e5a..b76e95f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -24,6 +24,7 @@ import type { AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, + ConnectState, ServerState, ServerData, UserInfo, @@ -109,6 +110,7 @@ export type { AccountKeyActionTypes, PurchaseActionTypes, ServerActionTypes, + ConnectState, ServerState, ServerData, UserInfo, diff --git a/src/types.ts b/src/types.ts index a0d9704..9e64557 100644 --- a/src/types.ts +++ b/src/types.ts @@ -73,6 +73,13 @@ export type ServerState = | "STALE" | undefined; +export type ConnectState = + | "PRE_INIT" + | "CONNECTING" + | "CONNECTED" + | "PING_FAILURE" + | "ERROR_RETRYING"; + export interface ActivationCodeData { __typename?: "ActivationCode"; background?: string | null; @@ -91,7 +98,7 @@ export interface ActivationCodeData { export interface ServerData { activationCodeData?: ActivationCodeData | null; connectPluginVersion?: string; - connectState?: JsonValue; + connectState?: ConnectState; description?: string; deviceCount?: number; expireTime?: number; @@ -164,9 +171,6 @@ export type UpcActions = ServerPayload | ServerTroubleshoot; export type SendPayloads = ExternalActions[] | UpcActions[]; -export type JsonPrimitive = string | number | boolean | null; -export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue | undefined }; - export interface ExternalPayload { type: "forUpc"; actions: ExternalActions[]; From 80391ea66acee22a6ffe0a7c7dc4bae44646e904 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Tue, 24 Mar 2026 15:45:03 -0400 Subject: [PATCH 4/4] test(server): cover generateUrl default sender fallback --- src/__tests__/server.test.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/__tests__/server.test.ts b/src/__tests__/server.test.ts index d3766da..65102aa 100644 --- a/src/__tests__/server.test.ts +++ b/src/__tests__/server.test.ts @@ -40,4 +40,32 @@ describe('createServerCallback (server entry)', () => { type: sendType, }) }) + + it('should default sender to an empty string when generateUrl omits it', () => { + const { parse, generateUrl } = createServerCallback(config) + const testActions: ServerPayload[] = [ + { + type: 'signIn', + server: { + connectPluginVersion: '2024.05.06.1049', + connectState: 'CONNECTED', + guid: 'test-guid', + registered: false, + state: 'ENOCONN', + }, + }, + ] + + const generatedUrl = generateUrl('http://test.com/c', testActions, 'forUpc') + const url = new URL(generatedUrl) + const encryptedData = url.hash.startsWith('#data=') + ? url.hash.slice('#data='.length) + : url.searchParams.get('data') || '' + + expect(parse(encryptedData)).toEqual({ + actions: testActions, + sender: '', + type: 'forUpc', + }) + }) })