feat(stalker): expose typed session IPC

This commit is contained in:
4gray committed 2026-07-27 10:41:32 +02:00
1 parent 854e922c1e
commit b76e65c2bf
5 files changed
+911 -3

No files matched your search

@@ -2,9 +2,9 @@ import type { ElectronBridgeApi } from '@iptvnator/shared/interfaces';
type ElectronBridgeMethodName = Extract<
{
[K in keyof ElectronBridgeApi]: ElectronBridgeApi[K] extends (
...args: never[]
) => unknown
[K in keyof ElectronBridgeApi]: NonNullable<
ElectronBridgeApi[K]
> extends (...args: never[]) => unknown
? K
: never;
}[keyof ElectronBridgeApi],
@@ -51,6 +51,105 @@ const tmdbCacheEntry = {
payload: '{"id":603}',
};
export const stalkerSessionPreloadCases: PreloadInvokeCase[] = [
{
method: 'stalkerSessionOpen',
args: [
{
descriptor: {
connectionMode: 'provisional',
macAddress: '00:1A:79:00:00:01',
playlistRef: 'playlist-1',
profilePreset: {
id: 'mag250-public-5_1-minimal-v1',
version: 1,
},
provisionalReason: 'import',
sourceUrl: 'https://portal.example/c/',
},
},
],
channel: 'STALKER_SESSION_OPEN',
forwardedArgs: [
{
descriptor: {
connectionMode: 'provisional',
macAddress: '00:1A:79:00:00:01',
playlistRef: 'playlist-1',
profilePreset: {
id: 'mag250-public-5_1-minimal-v1',
version: 1,
},
provisionalReason: 'import',
sourceUrl: 'https://portal.example/c/',
},
},
],
},
{
method: 'stalkerSessionContinue',
args: [
{
challengeRef: 'opaque-challenge',
response: {
approved: true,
kind: 'origin-approval',
},
},
],
channel: 'STALKER_SESSION_CONTINUE',
forwardedArgs: [
{
challengeRef: 'opaque-challenge',
response: {
approved: true,
kind: 'origin-approval',
},
},
],
},
{
method: 'stalkerSessionRequest',
args: [
{
leaseRef: 'opaque-lease',
operation: 'get-ordered-list',
parameters: {
category: '10',
page: 1,
},
},
],
channel: 'STALKER_SESSION_REQUEST',
forwardedArgs: [
{
leaseRef: 'opaque-lease',
operation: 'get-ordered-list',
parameters: {
category: '10',
page: 1,
},
},
],
},
{
method: 'stalkerSessionControl',
args: [
{
action: 'activate',
leaseRef: 'opaque-lease',
},
],
channel: 'STALKER_SESSION_CONTROL',
forwardedArgs: [
{
action: 'activate',
leaseRef: 'opaque-lease',
},
],
},
];
export const dbPreloadCases: PreloadInvokeCase[] = [
{
method: 'dbCreatePlaylist',
@@ -15,6 +15,7 @@ import {
dbPreloadCases,
epgPreloadCases,
operationId,
stalkerSessionPreloadCases,
} from './main.preload.spec-data';
type ExposedElectronApi = ElectronBridgeApi &
@@ -121,6 +122,49 @@ describe('main preload DB IPC contract', () => {
}
);
it.each(stalkerSessionPreloadCases)(
'$method invokes $channel with the expected arguments',
async ({ method, args, channel, forwardedArgs }) => {
const api = getExposedApi();
await callExposedApiMethod(api, method, args);
expect(mockIpcRenderer.invoke).toHaveBeenLastCalledWith(
channel,
...forwardedArgs
);
}
);
it('keeps Stalker session IPC narrow while preserving the legacy request bridge', async () => {
const api = getExposedApi();
const legacyPayload = {
macAddress: '00:1A:79:00:00:01',
params: {
action: 'get_profile',
},
url: 'https://portal.example/server/load.php',
};
expect(
Object.keys(api)
.filter((method) => method.startsWith('stalkerSession'))
.sort()
).toEqual([
'stalkerSessionContinue',
'stalkerSessionControl',
'stalkerSessionOpen',
'stalkerSessionRequest',
]);
await api.stalkerRequest(legacyPayload);
expect(mockIpcRenderer.invoke).toHaveBeenLastCalledWith(
'STALKER_REQUEST',
legacyPayload
);
});
it('forwards scoped user-agent override arguments', async () => {
const api = getExposedApi();
@@ -6,6 +6,10 @@ import {
APP_UPDATE_GET_STATUS,
APP_UPDATE_INSTALL,
APP_UPDATE_STATUS_CHANGED,
STALKER_SESSION_CONTINUE,
STALKER_SESSION_CONTROL,
STALKER_SESSION_OPEN,
STALKER_SESSION_REQUEST,
} from '@iptvnator/shared/interfaces/ipc-commands';
import {
attachEmbeddedMpvFrameView,
@@ -551,6 +555,14 @@ const electronApi: ElectronBridgeApi = {
serialNumber?: string;
requestId?: string;
}) => ipcRenderer.invoke('STALKER_REQUEST', payload),
stalkerSessionOpen: (request) =>
ipcRenderer.invoke(STALKER_SESSION_OPEN, request),
stalkerSessionContinue: (request) =>
ipcRenderer.invoke(STALKER_SESSION_CONTINUE, request),
stalkerSessionRequest: (request) =>
ipcRenderer.invoke(STALKER_SESSION_REQUEST, request),
stalkerSessionControl: (request) =>
ipcRenderer.invoke(STALKER_SESSION_CONTROL, request),
xtreamRequest: (payload: {
url: string;
params: Record<string, string>;
@@ -0,0 +1,397 @@
import {
STALKER_SESSION_APPLICATION_OPERATIONS,
STALKER_SESSION_CONTINUE,
STALKER_SESSION_CONTROL,
STALKER_SESSION_OPEN,
STALKER_SESSION_REQUEST,
type StalkerSessionConnectionOutcome,
type StalkerSessionControlOutcome,
type StalkerSessionRequestOutcome,
} from '@iptvnator/shared/interfaces';
import type { StalkerSessionManagerPort } from './stalker-session.events';
const mockHandlers = new Map<
string,
(event: MockInvokeEvent, request: unknown) => Promise<unknown>
>();
jest.mock('electron', () => ({
ipcMain: {
handle: jest.fn(
(
channel: string,
handler: (
event: MockInvokeEvent,
request: unknown
) => Promise<unknown>
) => {
mockHandlers.set(channel, handler);
}
),
},
}));
type MockSender = {
id: number;
once: jest.Mock;
};
type MockInvokeEvent = {
sender: MockSender;
};
const OPEN_REQUEST = {
descriptor: {
connectionMode: 'provisional',
macAddress: '00-1a-79-00-00-01',
playlistRef: 'playlist-1',
profilePreset: {
id: 'mag250-public-5_1-minimal-v1',
version: 1,
},
provisionalReason: 'import',
sourceUrl: 'https://portal.example/c/#fragment',
},
} as const;
const CONNECTION_OUTCOME: StalkerSessionConnectionOutcome = {
kind: 'failure',
reason: 'request-timeout',
requestId: 'request-open',
retryable: true,
stage: 'handshaking',
};
function createSender(id = 42): MockSender {
return {
id,
once: jest.fn(),
};
}
function createManager(): StalkerSessionManagerPort {
return {
cleanupSender: jest.fn(),
continue: jest.fn().mockResolvedValue(CONNECTION_OUTCOME),
control: jest.fn().mockResolvedValue({
action: 'activate',
kind: 'success',
requestId: 'request-control',
} satisfies StalkerSessionControlOutcome),
open: jest.fn().mockResolvedValue(CONNECTION_OUTCOME),
request: jest.fn().mockResolvedValue({
kind: 'success',
operation: STALKER_SESSION_APPLICATION_OPERATIONS.CatalogItems,
payload: {
items: [],
},
requestId: 'request-catalog',
} satisfies StalkerSessionRequestOutcome),
};
}
function invoke(
channel: string,
sender: MockSender,
request: unknown
): Promise<unknown> {
const handler = mockHandlers.get(channel);
if (!handler) {
throw new Error(`Missing test IPC handler: ${channel}`);
}
return handler({ sender }, request);
}
describe('StalkerSessionEvents', () => {
beforeEach(async () => {
jest.resetModules();
mockHandlers.clear();
});
it('registers exactly the four typed channels and binds normalized input to the sender', async () => {
const manager = createManager();
const sender = createSender();
const { default: StalkerSessionEvents } =
await import('./stalker-session.events');
StalkerSessionEvents.bootstrapStalkerSessionEvents(manager);
expect([...mockHandlers.keys()].sort()).toEqual(
[
STALKER_SESSION_OPEN,
STALKER_SESSION_CONTINUE,
STALKER_SESSION_REQUEST,
STALKER_SESSION_CONTROL,
].sort()
);
await expect(
invoke(STALKER_SESSION_OPEN, sender, OPEN_REQUEST)
).resolves.toBe(CONNECTION_OUTCOME);
expect(manager.open).toHaveBeenCalledWith(42, {
descriptor: {
...OPEN_REQUEST.descriptor,
macAddress: '00:1A:79:00:00:01',
sourceUrl: 'https://portal.example/c/',
},
});
expect(sender.once).toHaveBeenCalledTimes(1);
expect(sender.once).toHaveBeenCalledWith(
'destroyed',
expect.any(Function)
);
});
it('forwards continue, application request, and control through their typed manager methods', async () => {
const manager = createManager();
const sender = createSender(73);
const { default: StalkerSessionEvents } =
await import('./stalker-session.events');
const continueRequest = {
challengeRef: 'opaque-challenge',
response: {
approved: true,
kind: 'origin-approval',
},
} as const;
const operationRequest = {
leaseRef: 'opaque-lease',
operation: STALKER_SESSION_APPLICATION_OPERATIONS.CatalogItems,
parameters: {
category: '10',
page: 1,
},
} as const;
const controlRequest = {
action: 'activate',
leaseRef: 'opaque-lease',
} as const;
StalkerSessionEvents.bootstrapStalkerSessionEvents(manager);
await invoke(STALKER_SESSION_CONTINUE, sender, continueRequest);
await invoke(STALKER_SESSION_REQUEST, sender, operationRequest);
await invoke(STALKER_SESSION_CONTROL, sender, controlRequest);
expect(manager.continue).toHaveBeenCalledWith(73, continueRequest);
expect(manager.request).toHaveBeenCalledWith(73, operationRequest);
expect(manager.control).toHaveBeenCalledWith(73, controlRequest);
expect(sender.once).toHaveBeenCalledTimes(1);
});
it.each([
{
channel: STALKER_SESSION_OPEN,
request: {
...OPEN_REQUEST,
token: 'raw-top-level-token',
},
},
{
channel: STALKER_SESSION_OPEN,
request: {
descriptor: {
...OPEN_REQUEST.descriptor,
password: 'raw-descriptor-password',
},
},
},
{
channel: STALKER_SESSION_CONTINUE,
request: {
challengeRef: 'opaque-challenge',
response: {
approved: 'yes',
kind: 'origin-approval',
},
},
},
{
channel: STALKER_SESSION_CONTINUE,
request: {
challengeRef: 'opaque-challenge',
response: {
kind: 'credentials',
password: 'allowed-challenge-password',
token: 'raw-continuation-token',
username: 'allowed-challenge-user',
},
},
},
{
channel: STALKER_SESSION_REQUEST,
request: {
leaseRef: 'opaque-lease',
operation: 'handshake',
parameters: {},
},
},
{
channel: STALKER_SESSION_REQUEST,
request: {
leaseRef: 'opaque-lease',
operation: STALKER_SESSION_APPLICATION_OPERATIONS.CatalogItems,
parameters: {
token: 'raw-parameter-token',
},
},
},
{
channel: STALKER_SESSION_REQUEST,
request: {
leaseRef: 'opaque-lease',
operation: STALKER_SESSION_APPLICATION_OPERATIONS.CatalogItems,
parameters: {
query: {
credentials: {
password: 'raw-nested-secret',
},
},
},
},
},
{
channel: STALKER_SESSION_REQUEST,
request: {
headers: {
Authorization: 'Bearer raw-authorization',
},
leaseRef: 'opaque-lease',
operation: STALKER_SESSION_APPLICATION_OPERATIONS.CatalogItems,
parameters: {},
},
},
{
channel: STALKER_SESSION_CONTROL,
request: {
action: 'commit',
leaseRef: 'wrong-reference-kind',
},
},
{
channel: STALKER_SESSION_CONTROL,
request: {
action: 'delete-everything',
leaseRef: 'opaque-lease',
},
},
])(
'rejects malformed or secret-bearing $channel input before the manager',
async ({ channel, request }) => {
const manager = createManager();
const sender = createSender();
const { default: StalkerSessionEvents } =
await import('./stalker-session.events');
StalkerSessionEvents.bootstrapStalkerSessionEvents(manager);
const result = invoke(channel, sender, request);
await expect(result).rejects.toThrow(
'stalker-session-invalid-request'
);
await result.catch((error: unknown) => {
expect(String(error)).not.toMatch(
/raw-(?:top-level-token|descriptor-password|continuation-token|parameter-token|authorization|nested-secret)/
);
});
expect(manager.open).not.toHaveBeenCalled();
expect(manager.continue).not.toHaveBeenCalled();
expect(manager.request).not.toHaveBeenCalled();
expect(manager.control).not.toHaveBeenCalled();
}
);
it.each(['get_profile', 'do_auth'])(
'rejects raw full-auth operation %s',
async (operation) => {
const manager = createManager();
const sender = createSender();
const { default: StalkerSessionEvents } =
await import('./stalker-session.events');
StalkerSessionEvents.bootstrapStalkerSessionEvents(manager);
await expect(
invoke(STALKER_SESSION_REQUEST, sender, {
leaseRef: 'opaque-lease',
operation,
parameters: {},
})
).rejects.toThrow('stalker-session-invalid-request');
expect(manager.request).not.toHaveBeenCalled();
}
);
it('preserves sender binding for foreign or expired refs and returns the manager outcome unchanged', async () => {
const manager = createManager();
const sender = createSender(91);
const failure: StalkerSessionRequestOutcome = {
kind: 'failure',
reason: 'invalid-identity-input',
requestId: 'manager-owned-request-id',
retryable: false,
stage: 'ready',
};
(manager.request as jest.Mock).mockResolvedValueOnce(failure);
const { default: StalkerSessionEvents } =
await import('./stalker-session.events');
const request = {
leaseRef: 'foreign-or-expired-ref',
operation: STALKER_SESSION_APPLICATION_OPERATIONS.MainInfo,
parameters: {},
} as const;
StalkerSessionEvents.bootstrapStalkerSessionEvents(manager);
await expect(
invoke(STALKER_SESSION_REQUEST, sender, request)
).resolves.toBe(failure);
expect(manager.request).toHaveBeenCalledWith(91, request);
});
it('attaches one destroyed listener per sender and cleans that sender exactly once', async () => {
const manager = createManager();
const sender = createSender(64);
const { default: StalkerSessionEvents } =
await import('./stalker-session.events');
StalkerSessionEvents.bootstrapStalkerSessionEvents(manager);
await invoke(STALKER_SESSION_OPEN, sender, OPEN_REQUEST);
await invoke(STALKER_SESSION_CONTROL, sender, {
action: 'activate',
leaseRef: 'opaque-lease',
});
expect(sender.once).toHaveBeenCalledTimes(1);
const destroyed = sender.once.mock.calls[0][1] as () => void;
destroyed();
destroyed();
expect(manager.cleanupSender).toHaveBeenCalledTimes(1);
expect(manager.cleanupSender).toHaveBeenCalledWith(64);
});
it('does not expose manager error details to the renderer', async () => {
const manager = createManager();
const sender = createSender();
const secret = 'raw-manager-token';
(manager.open as jest.Mock).mockRejectedValueOnce(
new Error(`portal failed with ${secret}`)
);
const { default: StalkerSessionEvents } =
await import('./stalker-session.events');
StalkerSessionEvents.bootstrapStalkerSessionEvents(manager);
const result = invoke(STALKER_SESSION_OPEN, sender, OPEN_REQUEST);
await expect(result).rejects.toThrow(
'stalker-session-operation-failed'
);
await result.catch((error: unknown) => {
expect(String(error)).not.toContain(secret);
});
});
});
@@ -0,0 +1,356 @@
import { ipcMain } from 'electron';
import {
STALKER_MANAGED_REQUEST_HEADERS,
STALKER_RESERVED_REQUEST_PARAMETERS,
validateStalkerApplicationRequest,
} from '@iptvnator/portal/stalker/protocol';
import {
STALKER_SESSION_CONTINUE,
STALKER_SESSION_CONTROL,
STALKER_SESSION_OPEN,
STALKER_SESSION_REQUEST,
} from '@iptvnator/shared/interfaces';
import type {
StalkerSessionApplicationOperation,
StalkerSessionConnectionOutcome,
StalkerSessionContinueRequest,
StalkerSessionControlOutcome,
StalkerSessionControlRequest,
StalkerSessionOpenRequest,
StalkerSessionRequest,
StalkerSessionRequestOutcome,
} from '@iptvnator/shared/interfaces';
import { validateStalkerRuntimeInput } from '../services/stalker-session/stalker-runtime-validation';
const INVALID_REQUEST_CODE = 'stalker-session-invalid-request';
const OPERATION_FAILED_CODE = 'stalker-session-operation-failed';
const REFERENCE_MAX_BYTES = 512;
const CREDENTIAL_MAX_BYTES = 4096;
const PARAMETER_NODE_LIMIT = 256;
const FORBIDDEN_PARAMETER_KEYS = new Set<string>([
...STALKER_MANAGED_REQUEST_HEADERS,
...STALKER_RESERVED_REQUEST_PARAMETERS,
'credential',
'credentials',
'headers',
]);
type StalkerSessionSender = Pick<Electron.WebContents, 'id' | 'once'>;
type StalkerSessionInvokeEvent = {
sender: StalkerSessionSender;
};
export interface StalkerSessionManagerPort {
open(
senderId: number,
request: StalkerSessionOpenRequest
): Promise<StalkerSessionConnectionOutcome>;
continue(
senderId: number,
request: StalkerSessionContinueRequest
): Promise<StalkerSessionConnectionOutcome>;
request(
senderId: number,
request: StalkerSessionRequest
): Promise<StalkerSessionRequestOutcome>;
control(
senderId: number,
request: StalkerSessionControlRequest
): Promise<StalkerSessionControlOutcome>;
cleanupSender(senderId: number): void | Promise<void>;
}
class StalkerSessionIpcError extends Error {
constructor(
code: typeof INVALID_REQUEST_CODE | typeof OPERATION_FAILED_CODE
) {
super(code);
this.name = 'StalkerSessionIpcError';
}
}
export default class StalkerSessionEvents {
static bootstrapStalkerSessionEvents(
manager: StalkerSessionManagerPort
): Electron.IpcMain {
const cleanupAttached = new WeakSet<StalkerSessionSender>();
const bindSender = (event: StalkerSessionInvokeEvent): number => {
const sender = event.sender;
if (
!sender ||
!Number.isSafeInteger(sender.id) ||
sender.id < 0 ||
typeof sender.once !== 'function'
) {
invalidRequest();
}
if (!cleanupAttached.has(sender)) {
cleanupAttached.add(sender);
let cleaned = false;
const senderId = sender.id;
sender.once('destroyed', () => {
if (cleaned) {
return;
}
cleaned = true;
void Promise.resolve(manager.cleanupSender(senderId)).catch(
() => undefined
);
});
}
return sender.id;
};
ipcMain.handle(STALKER_SESSION_OPEN, async (event, input: unknown) => {
const senderId = bindSender(event);
return callManager(() =>
manager.open(senderId, validateOpenRequest(input))
);
});
ipcMain.handle(
STALKER_SESSION_CONTINUE,
async (event, input: unknown) => {
const senderId = bindSender(event);
return callManager(() =>
manager.continue(senderId, validateContinueRequest(input))
);
}
);
ipcMain.handle(
STALKER_SESSION_REQUEST,
async (event, input: unknown) => {
const senderId = bindSender(event);
return callManager(() =>
manager.request(senderId, validateApplicationRequest(input))
);
}
);
ipcMain.handle(
STALKER_SESSION_CONTROL,
async (event, input: unknown) => {
const senderId = bindSender(event);
return callManager(() =>
manager.control(senderId, validateControlRequest(input))
);
}
);
return ipcMain;
}
}
async function callManager<Result>(
operation: () => Promise<Result>
): Promise<Result> {
try {
return await operation();
} catch (error) {
if (error instanceof StalkerSessionIpcError) {
throw error;
}
throw new StalkerSessionIpcError(OPERATION_FAILED_CODE);
}
}
function validateOpenRequest(input: unknown): StalkerSessionOpenRequest {
const request = exactRecord(input, ['descriptor']);
try {
return {
descriptor: validateStalkerRuntimeInput(request['descriptor'])
.descriptor,
};
} catch {
return invalidRequest();
}
}
function validateContinueRequest(
input: unknown
): StalkerSessionContinueRequest {
const request = exactRecord(input, ['challengeRef', 'response']);
const challengeRef = opaqueRef(request['challengeRef']);
const response = request['response'];
if (!isRecord(response) || typeof response['kind'] !== 'string') {
return invalidRequest();
}
if (response['kind'] === 'origin-approval') {
const approval = exactRecord(response, ['approved', 'kind']);
if (typeof approval['approved'] !== 'boolean') {
return invalidRequest();
}
return {
challengeRef,
response: {
approved: approval['approved'],
kind: 'origin-approval',
},
};
}
if (response['kind'] === 'credentials') {
const credentials = exactRecord(response, [
'kind',
'password',
'username',
]);
if (
!boundedString(credentials['username'], CREDENTIAL_MAX_BYTES) ||
!boundedString(credentials['password'], CREDENTIAL_MAX_BYTES)
) {
return invalidRequest();
}
return {
challengeRef,
response: {
kind: 'credentials',
password: credentials['password'],
username: credentials['username'],
},
};
}
return invalidRequest();
}
function validateApplicationRequest(input: unknown): StalkerSessionRequest {
const request = exactRecord(input, ['leaseRef', 'operation', 'parameters']);
const operation = request['operation'];
const parameters = request['parameters'];
if (
typeof operation !== 'string' ||
!isRecord(parameters) ||
containsForbiddenParameter(parameters) ||
validateStalkerApplicationRequest({
operation,
parameters,
}).kind !== 'accepted'
) {
return invalidRequest();
}
const typedOperation = operation as StalkerSessionApplicationOperation;
return {
leaseRef: opaqueRef(request['leaseRef']),
operation: typedOperation,
parameters,
} as StalkerSessionRequest;
}
function validateControlRequest(input: unknown): StalkerSessionControlRequest {
if (!isRecord(input) || typeof input['action'] !== 'string') {
return invalidRequest();
}
switch (input['action']) {
case 'activate':
case 'deactivate':
case 'close':
case 'force-redetect': {
const request = exactRecord(input, ['action', 'leaseRef']);
return {
action: input['action'],
leaseRef: opaqueRef(request['leaseRef']),
};
}
case 'commit':
case 'discard': {
const request = exactRecord(input, ['action', 'attemptRef']);
return {
action: input['action'],
attemptRef: opaqueRef(request['attemptRef']),
};
}
default:
return invalidRequest();
}
}
function containsForbiddenParameter(value: unknown): boolean {
const pending = [value];
const visited = new WeakSet<object>();
let nodes = 0;
while (pending.length > 0) {
const current = pending.pop();
if (typeof current !== 'object' || current === null) {
continue;
}
if (visited.has(current)) {
continue;
}
visited.add(current);
const entries = Object.entries(current);
nodes += entries.length;
if (nodes > PARAMETER_NODE_LIMIT) {
return true;
}
for (const [key, child] of entries) {
if (FORBIDDEN_PARAMETER_KEYS.has(key.toLowerCase())) {
return true;
}
pending.push(child);
}
}
return false;
}
function exactRecord(
input: unknown,
expectedKeys: readonly string[]
): Readonly<Record<string, unknown>> {
if (!isRecord(input)) {
return invalidRequest();
}
const keys = Object.keys(input);
if (
keys.length !== expectedKeys.length ||
expectedKeys.some(
(key) => !Object.prototype.hasOwnProperty.call(input, key)
) ||
keys.some((key) => !expectedKeys.includes(key))
) {
return invalidRequest();
}
return input;
}
function opaqueRef(value: unknown): string {
if (!nonEmptyBoundedString(value, REFERENCE_MAX_BYTES)) {
return invalidRequest();
}
return value;
}
function isRecord(value: unknown): value is Record<string, unknown> {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
return false;
}
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
function boundedString(value: unknown, maxBytes: number): value is string {
return (
typeof value === 'string' &&
Buffer.byteLength(value, 'utf8') <= maxBytes
);
}
function nonEmptyBoundedString(
value: unknown,
maxBytes: number
): value is string {
return boundedString(value, maxBytes) && value.trim().length > 0;
}
function invalidRequest(): never {
throw new StalkerSessionIpcError(INVALID_REQUEST_CODE);
}