mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
fix(stalker): correlate session operation contracts
This commit is contained in:
1 parent
29fa4d0f87
commit
f8a544d287
3 files changed
+324
-46
No files matched your search
@@ -11,10 +11,13 @@ import {
|
||||
} from './ipc-command.class';
|
||||
import {
|
||||
STALKER_SESSION_APPLICATION_OPERATIONS,
|
||||
type StalkerSessionApplicationOperation,
|
||||
type StalkerSessionContinueRequest,
|
||||
type StalkerSessionControlRequest,
|
||||
type StalkerSessionOpenRequest,
|
||||
type StalkerSessionOperationContractMap,
|
||||
type StalkerSessionRequest,
|
||||
type StalkerSessionRequestOutcome,
|
||||
} from './stalker-session.interface';
|
||||
|
||||
describe('typed IPC commands', () => {
|
||||
@@ -96,7 +99,7 @@ describe('typed IPC commands', () => {
|
||||
const response: IpcCommandResponse<typeof STALKER_SESSION_REQUEST> = {
|
||||
kind: 'success',
|
||||
operation: STALKER_SESSION_APPLICATION_OPERATIONS.CatalogItems,
|
||||
payload: { js: { data: [] } },
|
||||
payload: { items: [], page: 1, total: 0 },
|
||||
requestId: 'request-id',
|
||||
};
|
||||
|
||||
@@ -104,6 +107,41 @@ describe('typed IPC commands', () => {
|
||||
expect(response.kind).toBe('success');
|
||||
});
|
||||
|
||||
it('correlates each operation with its request and sanitized result', () => {
|
||||
type MissingOperations = Exclude<
|
||||
StalkerSessionApplicationOperation,
|
||||
keyof StalkerSessionOperationContractMap
|
||||
>;
|
||||
type ExtraOperations = Exclude<
|
||||
keyof StalkerSessionOperationContractMap,
|
||||
StalkerSessionApplicationOperation
|
||||
>;
|
||||
const mapIsExhaustive: [
|
||||
MissingOperations,
|
||||
ExtraOperations,
|
||||
] extends [never, never]
|
||||
? true
|
||||
: false = true;
|
||||
|
||||
const mismatchedRequest: StalkerSessionRequest = {
|
||||
leaseRef: 'opaque-lease',
|
||||
operation: STALKER_SESSION_APPLICATION_OPERATIONS.CatalogItems,
|
||||
// @ts-expect-error Catalog requests cannot use EPG parameters.
|
||||
parameters: { channelId: 'channel-1' },
|
||||
};
|
||||
const mismatchedResult: StalkerSessionRequestOutcome = {
|
||||
kind: 'success',
|
||||
operation: STALKER_SESSION_APPLICATION_OPERATIONS.CreateLink,
|
||||
// @ts-expect-error create-link cannot return a catalog collection.
|
||||
payload: { items: [] },
|
||||
requestId: 'request-id',
|
||||
};
|
||||
|
||||
expect(mapIsExhaustive).toBe(true);
|
||||
expect(mismatchedRequest.operation).toBe('get-ordered-list');
|
||||
expect(mismatchedResult.kind).toBe('success');
|
||||
});
|
||||
|
||||
it('maps STALKER_SESSION_CONTROL to exactly one request and response type', () => {
|
||||
const request: IpcCommandRequest<typeof STALKER_SESSION_CONTROL> = {
|
||||
action: 'commit',
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
export const STALKER_SESSION_APPLICATION_OPERATIONS = {
|
||||
CatalogCategories: 'get-categories',
|
||||
CatalogGenres: 'get-genres',
|
||||
CatalogItems: 'get-ordered-list',
|
||||
CatalogAllChannels: 'get-all-channels',
|
||||
CatalogSearch: 'search',
|
||||
SeriesSeasons: 'get-series-seasons',
|
||||
SeriesEpisodes: 'get-series-episodes',
|
||||
ShortEpg: 'get-short-epg',
|
||||
EpgInfo: 'get-epg-info',
|
||||
CreateLink: 'create-link',
|
||||
Favorites: 'favorites',
|
||||
MainInfo: 'get-main-info',
|
||||
} as const;
|
||||
|
||||
export type StalkerSessionApplicationOperation =
|
||||
(typeof STALKER_SESSION_APPLICATION_OPERATIONS)[keyof typeof STALKER_SESSION_APPLICATION_OPERATIONS];
|
||||
|
||||
export const STALKER_SESSION_CONTENT_TYPES = {
|
||||
Itv: 'itv',
|
||||
Radio: 'radio',
|
||||
Series: 'series',
|
||||
Vod: 'vod',
|
||||
} as const;
|
||||
|
||||
export type StalkerSessionContentType =
|
||||
(typeof STALKER_SESSION_CONTENT_TYPES)[keyof typeof STALKER_SESSION_CONTENT_TYPES];
|
||||
|
||||
export type StalkerSessionEntityId = string | number;
|
||||
export type StalkerSessionNoParameters = Readonly<Record<string, never>>;
|
||||
|
||||
export interface StalkerSessionCategoryParameters {
|
||||
contentType: 'radio' | 'series' | 'vod';
|
||||
}
|
||||
|
||||
export type StalkerSessionGenreParameters = StalkerSessionNoParameters;
|
||||
|
||||
export interface StalkerSessionCatalogParameters {
|
||||
contentType?: StalkerSessionContentType;
|
||||
category?: StalkerSessionEntityId;
|
||||
genre?: StalkerSessionEntityId;
|
||||
page?: number;
|
||||
query?: string;
|
||||
sortBy?: string;
|
||||
itemId?: StalkerSessionEntityId;
|
||||
seasonId?: StalkerSessionEntityId;
|
||||
}
|
||||
|
||||
export type StalkerSessionAllChannelsParameters = StalkerSessionNoParameters;
|
||||
|
||||
export interface StalkerSessionSearchParameters {
|
||||
contentType: StalkerSessionContentType;
|
||||
query: string;
|
||||
category?: StalkerSessionEntityId;
|
||||
page?: number;
|
||||
}
|
||||
|
||||
export interface StalkerSessionSeriesSeasonsParameters {
|
||||
seriesId: StalkerSessionEntityId;
|
||||
}
|
||||
|
||||
export interface StalkerSessionSeriesEpisodesParameters {
|
||||
seriesId: StalkerSessionEntityId;
|
||||
seasonId: StalkerSessionEntityId;
|
||||
}
|
||||
|
||||
export interface StalkerSessionShortEpgParameters {
|
||||
channelId: StalkerSessionEntityId;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface StalkerSessionEpgInfoParameters {
|
||||
fromTimestamp?: number;
|
||||
toTimestamp?: number;
|
||||
}
|
||||
|
||||
export interface StalkerSessionCreateLinkParameters {
|
||||
contentType: StalkerSessionContentType | 'episode';
|
||||
command: string;
|
||||
itemId?: StalkerSessionEntityId;
|
||||
seriesId?: StalkerSessionEntityId;
|
||||
seasonNumber?: number;
|
||||
episodeNumber?: number;
|
||||
}
|
||||
|
||||
export interface StalkerSessionFavoritesParameters {
|
||||
contentType: StalkerSessionContentType;
|
||||
itemId: StalkerSessionEntityId;
|
||||
favorite: boolean;
|
||||
}
|
||||
|
||||
export type StalkerSessionMainInfoParameters = StalkerSessionNoParameters;
|
||||
|
||||
export interface StalkerSessionCategory {
|
||||
id: StalkerSessionEntityId;
|
||||
name: string;
|
||||
alias?: string;
|
||||
parentId?: StalkerSessionEntityId;
|
||||
}
|
||||
|
||||
export interface StalkerSessionCatalogItem {
|
||||
id: StalkerSessionEntityId;
|
||||
contentType: StalkerSessionContentType;
|
||||
name?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
command?: string;
|
||||
posterUrl?: string;
|
||||
categoryId?: StalkerSessionEntityId;
|
||||
genreId?: StalkerSessionEntityId;
|
||||
year?: number;
|
||||
rating?: number;
|
||||
durationSeconds?: number;
|
||||
isSeries?: boolean;
|
||||
}
|
||||
|
||||
export interface StalkerSessionCatalogResult {
|
||||
items: readonly StalkerSessionCatalogItem[];
|
||||
page?: number;
|
||||
total?: number;
|
||||
hasMore?: boolean;
|
||||
}
|
||||
|
||||
export interface StalkerSessionCategoriesResult {
|
||||
items: readonly StalkerSessionCategory[];
|
||||
}
|
||||
|
||||
export interface StalkerSessionSeason {
|
||||
id: StalkerSessionEntityId;
|
||||
number: number;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface StalkerSessionSeasonsResult {
|
||||
seasons: readonly StalkerSessionSeason[];
|
||||
}
|
||||
|
||||
export interface StalkerSessionEpisode {
|
||||
id: StalkerSessionEntityId;
|
||||
seriesId: StalkerSessionEntityId;
|
||||
seasonId: StalkerSessionEntityId;
|
||||
seasonNumber: number;
|
||||
episodeNumber: number;
|
||||
title?: string;
|
||||
command?: string;
|
||||
thumbnailUrl?: string;
|
||||
}
|
||||
|
||||
export interface StalkerSessionEpisodesResult {
|
||||
episodes: readonly StalkerSessionEpisode[];
|
||||
}
|
||||
|
||||
export interface StalkerSessionEpgProgram {
|
||||
id?: StalkerSessionEntityId;
|
||||
channelId: StalkerSessionEntityId;
|
||||
title: string;
|
||||
description?: string;
|
||||
startsAt: number;
|
||||
endsAt: number;
|
||||
}
|
||||
|
||||
export interface StalkerSessionEpgResult {
|
||||
programs: readonly StalkerSessionEpgProgram[];
|
||||
}
|
||||
|
||||
export interface StalkerSessionCreateLinkResult {
|
||||
streamUrl: string;
|
||||
playbackContextRef?: string;
|
||||
title?: string;
|
||||
thumbnail?: string | null;
|
||||
isLive?: boolean;
|
||||
userAgent?: string;
|
||||
referer?: string;
|
||||
origin?: string;
|
||||
}
|
||||
|
||||
export interface StalkerSessionFavoritesResult {
|
||||
success: boolean;
|
||||
favorite: boolean;
|
||||
}
|
||||
|
||||
export interface StalkerSessionAccountSummary {
|
||||
name?: string;
|
||||
status?: string;
|
||||
tariffPlan?: string;
|
||||
accountBalance?: string;
|
||||
expiresAt?: string;
|
||||
}
|
||||
|
||||
export interface StalkerSessionMainInfoResult {
|
||||
account: StalkerSessionAccountSummary;
|
||||
}
|
||||
|
||||
export interface StalkerSessionOperationContract<
|
||||
Parameters,
|
||||
Result,
|
||||
> {
|
||||
parameters: Parameters;
|
||||
result: Result;
|
||||
}
|
||||
|
||||
export interface StalkerSessionOperationContractMap {
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.CatalogCategories]: StalkerSessionOperationContract<
|
||||
StalkerSessionCategoryParameters,
|
||||
StalkerSessionCategoriesResult
|
||||
>;
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.CatalogGenres]: StalkerSessionOperationContract<
|
||||
StalkerSessionGenreParameters,
|
||||
StalkerSessionCategoriesResult
|
||||
>;
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.CatalogItems]: StalkerSessionOperationContract<
|
||||
StalkerSessionCatalogParameters,
|
||||
StalkerSessionCatalogResult
|
||||
>;
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.CatalogAllChannels]: StalkerSessionOperationContract<
|
||||
StalkerSessionAllChannelsParameters,
|
||||
StalkerSessionCatalogResult
|
||||
>;
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.CatalogSearch]: StalkerSessionOperationContract<
|
||||
StalkerSessionSearchParameters,
|
||||
StalkerSessionCatalogResult
|
||||
>;
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.SeriesSeasons]: StalkerSessionOperationContract<
|
||||
StalkerSessionSeriesSeasonsParameters,
|
||||
StalkerSessionSeasonsResult
|
||||
>;
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.SeriesEpisodes]: StalkerSessionOperationContract<
|
||||
StalkerSessionSeriesEpisodesParameters,
|
||||
StalkerSessionEpisodesResult
|
||||
>;
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.ShortEpg]: StalkerSessionOperationContract<
|
||||
StalkerSessionShortEpgParameters,
|
||||
StalkerSessionEpgResult
|
||||
>;
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.EpgInfo]: StalkerSessionOperationContract<
|
||||
StalkerSessionEpgInfoParameters,
|
||||
StalkerSessionEpgResult
|
||||
>;
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.CreateLink]: StalkerSessionOperationContract<
|
||||
StalkerSessionCreateLinkParameters,
|
||||
StalkerSessionCreateLinkResult
|
||||
>;
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.Favorites]: StalkerSessionOperationContract<
|
||||
StalkerSessionFavoritesParameters,
|
||||
StalkerSessionFavoritesResult
|
||||
>;
|
||||
[STALKER_SESSION_APPLICATION_OPERATIONS.MainInfo]: StalkerSessionOperationContract<
|
||||
StalkerSessionMainInfoParameters,
|
||||
StalkerSessionMainInfoResult
|
||||
>;
|
||||
}
|
||||
|
||||
export type StalkerSessionOperationParameters<
|
||||
Operation extends StalkerSessionApplicationOperation,
|
||||
> = StalkerSessionOperationContractMap[Operation]['parameters'];
|
||||
|
||||
export type StalkerSessionOperationResult<
|
||||
Operation extends StalkerSessionApplicationOperation,
|
||||
> = StalkerSessionOperationContractMap[Operation]['result'];
|
||||
@@ -1,3 +1,12 @@
|
||||
import type {
|
||||
StalkerSessionAccountSummary,
|
||||
StalkerSessionApplicationOperation,
|
||||
StalkerSessionOperationParameters,
|
||||
StalkerSessionOperationResult,
|
||||
} from './stalker-session-operations.interface';
|
||||
|
||||
export * from './stalker-session-operations.interface';
|
||||
|
||||
export const STALKER_SESSION_CONNECTION_MODES = {
|
||||
PersistedOpen: 'persisted-open',
|
||||
Provisional: 'provisional',
|
||||
@@ -23,24 +32,6 @@ export const STALKER_SESSION_REQUEST_RECIPES = {
|
||||
export type StalkerSessionRequestRecipe =
|
||||
(typeof STALKER_SESSION_REQUEST_RECIPES)[keyof typeof STALKER_SESSION_REQUEST_RECIPES];
|
||||
|
||||
export const STALKER_SESSION_APPLICATION_OPERATIONS = {
|
||||
CatalogCategories: 'get-categories',
|
||||
CatalogGenres: 'get-genres',
|
||||
CatalogItems: 'get-ordered-list',
|
||||
CatalogAllChannels: 'get-all-channels',
|
||||
CatalogSearch: 'search',
|
||||
SeriesSeasons: 'get-series-seasons',
|
||||
SeriesEpisodes: 'get-series-episodes',
|
||||
ShortEpg: 'get-short-epg',
|
||||
EpgInfo: 'get-epg-info',
|
||||
CreateLink: 'create-link',
|
||||
Favorites: 'favorites',
|
||||
MainInfo: 'get-main-info',
|
||||
} as const;
|
||||
|
||||
export type StalkerSessionApplicationOperation =
|
||||
(typeof STALKER_SESSION_APPLICATION_OPERATIONS)[keyof typeof STALKER_SESSION_APPLICATION_OPERATIONS];
|
||||
|
||||
export const STALKER_SESSION_CONTROL_ACTIONS = {
|
||||
Activate: 'activate',
|
||||
Deactivate: 'deactivate',
|
||||
@@ -178,14 +169,6 @@ export interface StalkerSessionCapabilities {
|
||||
playbackContext: boolean;
|
||||
}
|
||||
|
||||
export interface StalkerSessionAccountSummary {
|
||||
name?: string;
|
||||
status?: string;
|
||||
tariffPlan?: string;
|
||||
accountBalance?: string;
|
||||
expiresAt?: string;
|
||||
}
|
||||
|
||||
export interface StalkerSessionPersistenceDraft {
|
||||
stalkerSourceUrl?: string;
|
||||
portalUrl: string;
|
||||
@@ -251,26 +234,24 @@ export type StalkerSessionConnectionOutcome =
|
||||
| StalkerSessionCredentialsRequiredOutcome
|
||||
| StalkerSessionFailureOutcome;
|
||||
|
||||
export type StalkerSessionParameterValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
export type StalkerSessionRequest = {
|
||||
[Operation in StalkerSessionApplicationOperation]: {
|
||||
leaseRef: StalkerSessionLeaseRef;
|
||||
operation: Operation;
|
||||
parameters: StalkerSessionOperationParameters<Operation>;
|
||||
requestId?: string;
|
||||
};
|
||||
}[StalkerSessionApplicationOperation];
|
||||
|
||||
export interface StalkerSessionRequest {
|
||||
leaseRef: StalkerSessionLeaseRef;
|
||||
operation: StalkerSessionApplicationOperation;
|
||||
parameters: Readonly<Record<string, StalkerSessionParameterValue>>;
|
||||
requestId?: string;
|
||||
}
|
||||
|
||||
export interface StalkerSessionRequestSuccess {
|
||||
kind: 'success';
|
||||
requestId: string;
|
||||
operation: StalkerSessionApplicationOperation;
|
||||
payload: unknown;
|
||||
playbackContextRef?: StalkerPlaybackContextRef;
|
||||
}
|
||||
export type StalkerSessionRequestSuccess = {
|
||||
[Operation in StalkerSessionApplicationOperation]: {
|
||||
kind: 'success';
|
||||
requestId: string;
|
||||
operation: Operation;
|
||||
payload: StalkerSessionOperationResult<Operation>;
|
||||
playbackContextRef?: StalkerPlaybackContextRef;
|
||||
};
|
||||
}[StalkerSessionApplicationOperation];
|
||||
|
||||
export type StalkerSessionRequestOutcome =
|
||||
| StalkerSessionRequestSuccess
|
||||
|
||||
Reference in new issue
Block a user