[Refactoring] Make sure VirtualClass instances are never returned from API methods
This commit is contained in:
parent
caa0ba76c5
commit
73eade5289
@ -10,22 +10,23 @@ import { buildApiUser } from '../apiBuilders/users';
|
||||
import { buildApiBotInlineMediaResult, buildApiBotInlineResult, buildBotSwitchPm } from '../apiBuilders/bots';
|
||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { addEntitiesWithPhotosToLocalDb, addUserToLocalDb, deserializeBytes } from '../helpers';
|
||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||
|
||||
export function init() {
|
||||
}
|
||||
|
||||
export function answerCallbackButton(
|
||||
{
|
||||
chatId, accessHash, messageId, data,
|
||||
}: {
|
||||
chatId: string; accessHash?: string; messageId: number; data: string;
|
||||
},
|
||||
) {
|
||||
return invokeRequest(new GramJs.messages.GetBotCallbackAnswer({
|
||||
export async function answerCallbackButton({
|
||||
chatId, accessHash, messageId, data,
|
||||
}: {
|
||||
chatId: string; accessHash?: string; messageId: number; data: string;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.messages.GetBotCallbackAnswer({
|
||||
peer: buildInputPeer(chatId, accessHash),
|
||||
msgId: messageId,
|
||||
data: deserializeBytes(data),
|
||||
}));
|
||||
|
||||
return result ? omitVirtualClassFields(result) : undefined;
|
||||
}
|
||||
|
||||
export async function fetchTopInlineBots() {
|
||||
|
||||
@ -164,7 +164,7 @@ export async function joinGroupCall({
|
||||
data: JSON.stringify(params),
|
||||
}),
|
||||
inviteHash,
|
||||
}), true);
|
||||
}));
|
||||
|
||||
if (!result) return undefined;
|
||||
|
||||
@ -187,7 +187,7 @@ export async function createGroupCall({
|
||||
const result = await invokeRequest(new GramJs.phone.CreateGroupCall({
|
||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||
randomId,
|
||||
}), true);
|
||||
}));
|
||||
|
||||
if (!result) return undefined;
|
||||
|
||||
|
||||
@ -508,7 +508,7 @@ export async function createChannel({
|
||||
broadcast: true,
|
||||
title,
|
||||
about,
|
||||
}), true);
|
||||
}));
|
||||
|
||||
// `createChannel` can return a lot of different update types according to docs,
|
||||
// but currently channel creation returns only `Updates` type.
|
||||
@ -537,7 +537,7 @@ export async function createChannel({
|
||||
await invokeRequest(new GramJs.channels.InviteToChannel({
|
||||
channel: buildInputEntity(channel.id, channel.accessHash) as GramJs.InputChannel,
|
||||
users: users.map(({ id, accessHash }) => buildInputEntity(id, accessHash)) as GramJs.InputUser[],
|
||||
}), true, noErrorUpdate);
|
||||
}), undefined, noErrorUpdate);
|
||||
} catch (err) {
|
||||
// `noErrorUpdate` will cause an exception which we don't want either
|
||||
}
|
||||
@ -606,7 +606,7 @@ export async function createGroupChat({
|
||||
const result = await invokeRequest(new GramJs.messages.CreateChat({
|
||||
title,
|
||||
users: users.map(({ id, accessHash }) => buildInputEntity(id, accessHash)) as GramJs.InputUser[],
|
||||
}), true, true);
|
||||
}), undefined, true);
|
||||
|
||||
// `createChat` can return a lot of different update types according to docs,
|
||||
// but currently chat creation returns only `Updates` type.
|
||||
@ -975,12 +975,12 @@ export function setDiscussionGroup({
|
||||
return invokeRequest(new GramJs.channels.SetDiscussionGroup({
|
||||
broadcast: buildInputPeer(channel.id, channel.accessHash),
|
||||
group: chat ? buildInputPeer(chat.id, chat.accessHash) : new GramJs.InputChannelEmpty(),
|
||||
}));
|
||||
}), true);
|
||||
}
|
||||
|
||||
export async function migrateChat(chat: ApiChat) {
|
||||
const result = await invokeRequest(
|
||||
new GramJs.messages.MigrateChat({ chatId: buildInputEntity(chat.id) as BigInt.BigInteger }), true,
|
||||
new GramJs.messages.MigrateChat({ chatId: buildInputEntity(chat.id) as BigInt.BigInteger }),
|
||||
);
|
||||
|
||||
// `migrateChat` can return a lot of different update types according to docs,
|
||||
@ -1049,16 +1049,16 @@ export async function openChatByInvite(hash: string) {
|
||||
return { chatId: chat.id };
|
||||
}
|
||||
|
||||
export function addChatMembers(chat: ApiChat, users: ApiUser[], noErrorUpdate = false) {
|
||||
export async function addChatMembers(chat: ApiChat, users: ApiUser[], noErrorUpdate = false) {
|
||||
try {
|
||||
if (chat.type === 'chatTypeChannel' || chat.type === 'chatTypeSuperGroup') {
|
||||
return invokeRequest(new GramJs.channels.InviteToChannel({
|
||||
return await invokeRequest(new GramJs.channels.InviteToChannel({
|
||||
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||
users: users.map((user) => buildInputEntity(user.id, user.accessHash)) as GramJs.InputUser[],
|
||||
}), true, noErrorUpdate);
|
||||
}
|
||||
|
||||
return Promise.all(users.map((user) => {
|
||||
return await Promise.all(users.map((user) => {
|
||||
return invokeRequest(new GramJs.messages.AddChatUser({
|
||||
chatId: buildInputEntity(chat.id) as BigInt.BigInteger,
|
||||
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
|
||||
@ -1139,7 +1139,7 @@ function updateLocalDb(result: (
|
||||
}
|
||||
|
||||
export async function importChatInvite({ hash }: { hash: string }) {
|
||||
const updates = await invokeRequest(new GramJs.messages.ImportChatInvite({ hash }), true);
|
||||
const updates = await invokeRequest(new GramJs.messages.ImportChatInvite({ hash }));
|
||||
if (!(updates instanceof GramJs.Updates) || !updates.chats.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -161,9 +161,21 @@ function handleGramJsUpdate(update: any) {
|
||||
|
||||
export async function invokeRequest<T extends GramJs.AnyRequest>(
|
||||
request: T,
|
||||
shouldHandleUpdates = false,
|
||||
shouldReturnTrue: true,
|
||||
shouldThrow?: boolean,
|
||||
): Promise<true | undefined>;
|
||||
|
||||
export async function invokeRequest<T extends GramJs.AnyRequest>(
|
||||
request: T,
|
||||
shouldReturnTrue?: boolean,
|
||||
shouldThrow?: boolean,
|
||||
): Promise<T['__response'] | undefined>;
|
||||
|
||||
export async function invokeRequest<T extends GramJs.AnyRequest>(
|
||||
request: T,
|
||||
shouldReturnTrue = false,
|
||||
shouldThrow = false,
|
||||
): Promise<T['__response'] | undefined> {
|
||||
) {
|
||||
if (!isConnected) {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
@ -186,35 +198,9 @@ export async function invokeRequest<T extends GramJs.AnyRequest>(
|
||||
console.log(`[GramJs/client] INVOKE RESPONSE ${request.className}`, result);
|
||||
}
|
||||
|
||||
if (shouldHandleUpdates) {
|
||||
type ResultWithUpdates =
|
||||
typeof result
|
||||
& { updates?: GramJs.Updates | GramJs.UpdatesCombined };
|
||||
handleUpdatesFromRequest(request, result);
|
||||
|
||||
let updatesContainer;
|
||||
if (result instanceof GramJs.Updates || result instanceof GramJs.UpdatesCombined) {
|
||||
updatesContainer = result;
|
||||
} else if ('updates' in (result as ResultWithUpdates) && (
|
||||
(result as ResultWithUpdates).updates instanceof GramJs.Updates
|
||||
|| (result as ResultWithUpdates).updates instanceof GramJs.UpdatesCombined
|
||||
)) {
|
||||
updatesContainer = (result as ResultWithUpdates).updates;
|
||||
}
|
||||
|
||||
if (updatesContainer) {
|
||||
injectUpdateEntities(updatesContainer);
|
||||
|
||||
updatesContainer.updates.forEach((update) => {
|
||||
updater(update, request);
|
||||
});
|
||||
} else if (result instanceof GramJs.UpdatesTooLong) {
|
||||
// TODO Implement
|
||||
} else {
|
||||
updater(result as GramJs.TypeUpdates, request);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return shouldReturnTrue ? result && true : result;
|
||||
} catch (err) {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
@ -233,6 +219,36 @@ export async function invokeRequest<T extends GramJs.AnyRequest>(
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdatesFromRequest<T extends GramJs.AnyRequest>(request: T, result: T['__response']) {
|
||||
let manyUpdates;
|
||||
let singleUpdate;
|
||||
|
||||
if (result instanceof GramJs.UpdatesCombined || result instanceof GramJs.Updates) {
|
||||
manyUpdates = result;
|
||||
} else if (typeof result === 'object' && 'updates' in result && (
|
||||
result.updates instanceof GramJs.Updates || result.updates instanceof GramJs.UpdatesCombined
|
||||
)) {
|
||||
manyUpdates = result.updates;
|
||||
} else if (
|
||||
result instanceof GramJs.UpdateShortMessage
|
||||
|| result instanceof GramJs.UpdateShortChatMessage
|
||||
|| result instanceof GramJs.UpdateShort
|
||||
|| result instanceof GramJs.UpdateShortSentMessage
|
||||
) {
|
||||
singleUpdate = result;
|
||||
}
|
||||
|
||||
if (manyUpdates) {
|
||||
injectUpdateEntities(manyUpdates);
|
||||
|
||||
manyUpdates.updates.forEach((update) => {
|
||||
updater(update, request);
|
||||
});
|
||||
} else if (singleUpdate) {
|
||||
updater(singleUpdate, request);
|
||||
}
|
||||
}
|
||||
|
||||
export function downloadMedia(
|
||||
args: { url: string; mediaFormat: ApiMediaFormat; start?: number; end?: number; isHtmlAllowed?: boolean },
|
||||
onProgress?: ApiOnProgress,
|
||||
|
||||
@ -10,19 +10,11 @@ export function init(_onUpdate: OnApiUpdate) {
|
||||
onUpdate = _onUpdate;
|
||||
}
|
||||
|
||||
export async function checkChatUsername(
|
||||
{ username }: { username: string },
|
||||
) {
|
||||
try {
|
||||
const result = await invokeRequest(new GramJs.channels.CheckUsername({
|
||||
channel: new GramJs.InputChannelEmpty(),
|
||||
username,
|
||||
}), undefined, true);
|
||||
|
||||
return result!;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
export function checkChatUsername({ username }: { username: string }) {
|
||||
return invokeRequest(new GramJs.channels.CheckUsername({
|
||||
channel: new GramJs.InputChannelEmpty(),
|
||||
username,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function setChatUsername(
|
||||
|
||||
@ -48,7 +48,7 @@ export function updateProfile({
|
||||
firstName: firstName || '',
|
||||
lastName: lastName || '',
|
||||
about: about || '',
|
||||
}));
|
||||
}), true);
|
||||
}
|
||||
|
||||
export function checkUsername(username: string) {
|
||||
@ -56,14 +56,14 @@ export function checkUsername(username: string) {
|
||||
}
|
||||
|
||||
export function updateUsername(username: string) {
|
||||
return invokeRequest(new GramJs.account.UpdateUsername({ username }));
|
||||
return invokeRequest(new GramJs.account.UpdateUsername({ username }), true);
|
||||
}
|
||||
|
||||
export async function updateProfilePhoto(file: File) {
|
||||
const inputFile = await uploadFile(file);
|
||||
return invokeRequest(new GramJs.photos.UploadProfilePhoto({
|
||||
file: inputFile,
|
||||
}));
|
||||
}), true);
|
||||
}
|
||||
|
||||
export async function uploadProfilePhoto(file: File) {
|
||||
@ -311,7 +311,7 @@ export async function fetchLangPack({ sourceLangPacks, langCode }: {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { langPack: Object.assign({}, ...collections.reverse()) };
|
||||
return { langPack: Object.assign({}, ...collections.reverse()) as typeof collections[0] };
|
||||
}
|
||||
|
||||
export async function fetchLangStrings({ langPack, langCode, keys }: {
|
||||
|
||||
@ -157,7 +157,7 @@ export function updateContact({
|
||||
firstName: firstName || '',
|
||||
lastName: lastName || '',
|
||||
})],
|
||||
}));
|
||||
}), true);
|
||||
}
|
||||
|
||||
export function addContact({
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Api } from '../../../lib/gramjs';
|
||||
import { ApiInitialArgs, ApiOnProgress, OnApiUpdate } from '../../types';
|
||||
import { Methods, MethodArgs, MethodResponse } from '../methods/types';
|
||||
import { WorkerMessageEvent, ThenArg, OriginRequest } from './types';
|
||||
import { WorkerMessageEvent, OriginRequest } from './types';
|
||||
|
||||
import { DEBUG } from '../../../config';
|
||||
import generateIdFor from '../../../util/generateIdFor';
|
||||
@ -53,11 +54,36 @@ export function callApi<T extends keyof Methods>(fnName: T, ...args: MethodArgs<
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return makeRequest({
|
||||
const promise = makeRequest({
|
||||
type: 'callMethod',
|
||||
name: fnName,
|
||||
args,
|
||||
}) as MethodResponse<T>;
|
||||
});
|
||||
|
||||
// Some TypeScript magic to make sure `VirtualClass` is never returned from any method
|
||||
if (DEBUG) {
|
||||
(async () => {
|
||||
try {
|
||||
type ForbiddenTypes =
|
||||
Api.VirtualClass<any>
|
||||
| (Api.VirtualClass<any> | undefined)[];
|
||||
type ForbiddenResponses =
|
||||
ForbiddenTypes
|
||||
| (AnyLiteral & { [k: string]: ForbiddenTypes });
|
||||
|
||||
// Unwrap all chained promises
|
||||
const response = await promise;
|
||||
// Make sure responses do not include `VirtualClass` instances
|
||||
const allowedResponse: Exclude<typeof response, ForbiddenResponses> = response;
|
||||
// Suppress "unused variable" constraint
|
||||
void allowedResponse;
|
||||
} catch (err) {
|
||||
// Do noting
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
return promise as MethodResponse<T>;
|
||||
}
|
||||
|
||||
export function cancelApiProgress(progressCallback: ApiOnProgress) {
|
||||
@ -105,7 +131,7 @@ function makeRequest(message: OriginRequest) {
|
||||
const requestState = { messageId } as RequestStates;
|
||||
|
||||
// Re-wrap type because of `postMessage`
|
||||
const promise: Promise<ThenArg<MethodResponse<keyof Methods>>> = new Promise((resolve, reject) => {
|
||||
const promise: Promise<MethodResponse<keyof Methods>> = new Promise((resolve, reject) => {
|
||||
Object.assign(requestState, { resolve, reject });
|
||||
});
|
||||
|
||||
|
||||
@ -8,8 +8,6 @@ declare const self: WorkerGlobalScope;
|
||||
|
||||
handleErrors();
|
||||
|
||||
// TODO Re-use `util/createWorkerInterface.ts`
|
||||
|
||||
const callbackState = new Map<string, ApiOnProgress>();
|
||||
|
||||
if (DEBUG) {
|
||||
@ -45,6 +43,12 @@ onmessage = async (message: OriginMessageEvent) => {
|
||||
}
|
||||
|
||||
const response = await callApi(name, ...args);
|
||||
|
||||
if (DEBUG && typeof response === 'object' && 'CONSTRUCTOR_ID' in response) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`[GramJs/worker] \`${name}\`: Unexpected response \`${(response as any).className}\``);
|
||||
}
|
||||
|
||||
const { arrayBuffer } = (typeof response === 'object' && 'arrayBuffer' in response && response) || {};
|
||||
|
||||
if (messageId) {
|
||||
|
||||
3
src/lib/gramjs/tl/api.d.ts
vendored
3
src/lib/gramjs/tl/api.d.ts
vendored
@ -16,8 +16,7 @@ namespace Api {
|
||||
type Client = any; // To be defined.
|
||||
type Utils = any; // To be defined.
|
||||
|
||||
type X = unknown;
|
||||
type Type = unknown;
|
||||
type X = AnyLiteral;
|
||||
type Bool = boolean;
|
||||
type int = number;
|
||||
type int128 = BigInteger;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// Not sure what they are for.
|
||||
const WEIRD_TYPES = new Set(['Bool', 'X', 'Type'])
|
||||
const RAW_TYPES = new Set(['Bool', 'X'])
|
||||
|
||||
module.exports = ({ types, constructors, functions }) => {
|
||||
function groupByKey(collection, key) {
|
||||
@ -55,9 +55,10 @@ ${indent}};`.trim()
|
||||
function renderRequests(requests, indent) {
|
||||
return requests.map(({ name, argsConfig, result }) => {
|
||||
const argKeys = Object.keys(argsConfig)
|
||||
const renderedResult = renderResult(result);
|
||||
|
||||
if (!argKeys.length) {
|
||||
return `export class ${upperFirst(name)} extends Request<void, ${renderResult(result)}> {};`
|
||||
return `export class ${upperFirst(name)} extends Request<void, ${renderedResult}> {};`
|
||||
}
|
||||
|
||||
let hasRequiredArgs = argKeys.some((argName) => argName !== 'flags' && !argsConfig[argName].isFlag)
|
||||
@ -68,7 +69,7 @@ ${indent} ${argKeys.map((argName) => `
|
||||
${renderArg(argName, argsConfig[argName])};
|
||||
`.trim())
|
||||
.join(`\n${indent} `)}
|
||||
${indent}}${!hasRequiredArgs ? ` | void` : ''}>, ${renderResult(result)}> {
|
||||
${indent}}${!hasRequiredArgs ? ` | void` : ''}>, ${renderedResult}> {
|
||||
${indent} ${argKeys.map((argName) => `
|
||||
${renderArg(argName, argsConfig[argName])};
|
||||
`.trim())
|
||||
@ -98,7 +99,7 @@ ${indent}};`.trim()
|
||||
}
|
||||
|
||||
function renderValueType(type, isVector, isTlType) {
|
||||
if (WEIRD_TYPES.has(type)) {
|
||||
if (RAW_TYPES.has(type)) {
|
||||
return type
|
||||
}
|
||||
|
||||
@ -148,8 +149,7 @@ namespace Api {
|
||||
type Client = any; // To be defined.
|
||||
type Utils = any; // To be defined.
|
||||
|
||||
type X = unknown;
|
||||
type Type = unknown;
|
||||
type X = AnyLiteral;
|
||||
type Bool = boolean;
|
||||
type int = number;
|
||||
type int128 = BigInteger;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user