Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emoji status-related functions #580

Merged
merged 16 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": [
"docs",
"custom-playground",
"vue-template",
"svelte-template",
"nextjs-template",
"reactjs-template",
"solidjs-template"
Expand Down
5 changes: 5 additions & 0 deletions .changeset/hip-birds-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@telegram-apps/sdk": minor
---

Add emoji status-related functionality.
5 changes: 5 additions & 0 deletions .changeset/hungry-mails-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@telegram-apps/bridge": minor
---

Add methods and events connected with custom emoji set.
1 change: 1 addition & 0 deletions apps/docs/.vitepress/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const packagesLinksGenerator = (prefix: string = '') => {
]),
],
'Utilities': [{ url: 'utils', page: false }, fromEntries([
scope('emoji-status'),
scope('links'),
scope('privacy'),
scope('uncategorized'),
Expand Down
56 changes: 56 additions & 0 deletions apps/docs/packages/telegram-apps-sdk/2-x/utils/emoji-status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Emoji Status

## `requestEmojiStatusAccess`

To request access to user emoji status update, use the `requestEmojiStatusAccess` function:

::: code-group

```ts [Using isAvailable]
import { requestEmojiStatusAccess } from '@telegram-apps/sdk';

if (requestEmojiStatusAccess.isAvailable()) {
const status = await requestEmojiStatusAccess();
}
```

```ts [Using ifAvailable]
import { requestEmojiStatusAccess } from '@telegram-apps/sdk';

const status = await requestEmojiStatusAccess.ifAvailable();
```

:::

## `setEmojiStatus`

To set an emoji status on user's behalf, use the `setEmojiStatus` function.

As the first argument, it accepts a custom emoji id. Optionally, you can pass the second
argument determining for how many seconds the status must be set.

::: code-group

```ts [Using isAvailable]
import { setEmojiStatus } from '@telegram-apps/sdk';

if (setEmojiStatus.isAvailable()) {
// Set for unlimited period of time.
await setEmojiStatus('5361800828313167608');

// Set for 1 day.
await setEmojiStatus('5361800828313167608', 86400);
}
```

```ts [Using ifAvailable]
import { setEmojiStatus } from '@telegram-apps/sdk';

// Set for unlimited period of time.
await setEmojiStatus.ifAvailable('5361800828313167608');

// Set for 1 day.
await setEmojiStatus.ifAvailable('5361800828313167608', 86400);
```

:::
26 changes: 26 additions & 0 deletions apps/docs/platform/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,32 @@ Custom method invocation completed.
| result | `unknown` | _Optional_. Method invocation result. |
| error | `string` | _Optional_. Method invocation error code. |

### `emoji_status_access_requested`

Available since: **v8.0**

Access to set custom emoji status was requested.

| Field | Type | Description |
|--------|----------|------------------------------------------------------------|
| status | `string` | Request status. Possible values: `allowed` or `cancelled`. |

### `emoji_status_failed`

Available since: **v8.0**

Failed to set custom emoji status.

| Field | Type | Description |
|-------|----------|-----------------------------------------------------------------------------------------|
| error | `string` | Emoji set failure reason. Possible values: `SUGGESTED_EMOJI_INVALID` or `USER_DECLINED` |

### `emoji_status_set`

Available since: **v8.0**

Custom emoji status set.

### `fullscreen_changed`

Available since: **v8.0**
Expand Down
19 changes: 18 additions & 1 deletion apps/docs/platform/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Opens the biometric access settings for bots. Useful when you need to request bi
access to users who haven't granted it yet.

> [!INFO]
> This method can be called only in response to user interaction with the Mini App interface
> This method can be called only in response to user interaction with the Mini App interface
> (e.g. a click inside the Mini App or on the main button)

### `web_app_biometry_request_access`
Expand Down Expand Up @@ -388,6 +388,12 @@ Requests the current content safe area information from Telegram.
As a result, Telegram triggers the
[**`content_safe_area_changed`**](events.md#content-safe-area-changed) event.

### `web_app_request_emoji_status_access`

Available since: **v8.0**

Shows a native popup requesting permission for the bot to manage user's emoji status.

### `web_app_request_fullscreen`

Available since: **v8.0**
Expand Down Expand Up @@ -445,6 +451,17 @@ Updates the Mini App bottom bar background color.
|-------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| color | `string` | The Mini App bottom bar background color in `#RRGGBB` format, or one of the values: `bg_color`, `secondary_bg_color` or `bottom_bar_bg_color` |

### `web_app_set_emoji_status`

Available since: **v8.0**

Opens a dialog allowing the user to set the specified custom emoji as their status.

| Field | Type | Description |
|-----------------|----------|----------------------------------------------------|
| custom_emoji_id | `string` | Custom emoji identifier to set. |
| duration | `number` | _Optional_. The status expiration time in seconds. |

### `web_app_set_header_color`

Available since: **v6.1**
Expand Down
29 changes: 28 additions & 1 deletion packages/bridge/src/events/types/events.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { RGB } from '@telegram-apps/types';

import type {
import {
PhoneRequestedStatus,
InvoiceStatus,
WriteAccessRequestedStatus,
Expand All @@ -9,6 +9,8 @@ import type {
BiometryTokenUpdateStatus,
SafeAreaInsets,
FullScreenErrorStatus,
EmojiStatusAccessRequestedStatus,
EmojiStatusFailedError,
} from './misc.js';

/**
Expand Down Expand Up @@ -150,6 +152,31 @@ export interface Events {
*/
error?: string;
};
/**
* Request to set custom emoji status was requested.
* @see https://docs.telegram-mini-apps.com/platform/events#emoji-status-access-requested
* @since v8.0
*/
emoji_status_access_requested: {
/**
* Request status.
*/
status: EmojiStatusAccessRequestedStatus;
};
/**
* Failed to set custom emoji status.
* @see https://docs.telegram-mini-apps.com/platform/events#emoji-status-failed
* @since v8.0
*/
emoji_status_failed: {
error: EmojiStatusFailedError;
};
/**
* Custom emoji status set.
* @see https://docs.telegram-mini-apps.com/platform/events#emoji-status-set
* @since v8.0
*/
emoji_status_set: never;
/**
* App entered or exited fullscreen mode.
* @since v8.0
Expand Down
4 changes: 4 additions & 0 deletions packages/bridge/src/events/types/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export type InvoiceStatus =

export type PhoneRequestedStatus = 'sent' | 'cancelled' | string;

export type EmojiStatusAccessRequestedStatus = 'allowed' | string;

export type EmojiStatusFailedError = 'SUGGESTED_EMOJI_INVALID' | 'USER_DECLINED' | string;

export type WriteAccessRequestedStatus = 'allowed' | string;

export type BiometryType = 'finger' | 'face' | string;
Expand Down
4 changes: 3 additions & 1 deletion packages/bridge/src/methods/supports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ describe.each<[
]],
['8.0', [
'web_app_request_fullscreen',
'web_app_exit_fullscreen'
'web_app_exit_fullscreen',
'web_app_set_emoji_status',
'web_app_request_emoji_status_access',
]],
])('%s', (version, methods) => {
const higher = increaseVersion(version, 1);
Expand Down
2 changes: 2 additions & 0 deletions packages/bridge/src/methods/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ export function supports(
case 'web_app_request_content_safe_area':
case 'web_app_request_fullscreen':
case 'web_app_exit_fullscreen':
case 'web_app_set_emoji_status':
case 'web_app_request_emoji_status_access':
return versionLessOrEqual('8.0', paramOrVersion);
default:
return [
Expand Down
21 changes: 21 additions & 0 deletions packages/bridge/src/methods/types/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,12 @@ export interface Methods {
* @see https://docs.telegram-mini-apps.com/platform/methods#web-app-request-content-safe-area
*/
web_app_request_content_safe_area: CreateMethodParams;
/**
* Shows a native popup requesting permission for the bot to manage user's emoji status.
* @since v8.0
* @see https://docs.telegram-mini-apps.com/platform/methods#web-app-request-emoji-status-access
*/
web_app_request_emoji_status_access: CreateMethodParams;
/**
* Requests to open the mini app in fullscreen.
* @since v8.0
Expand Down Expand Up @@ -344,6 +350,21 @@ export interface Methods {
*/
color: BottomBarColor;
}>;
/**
* Opens a dialog allowing the user to set the specified custom emoji as their status.
* @since v8.0
* @see https://docs.telegram-mini-apps.com/platform/methods#web-app-set-emoji-status
*/
web_app_set_emoji_status: CreateMethodParams<{
/**
* Custom emoji identifier to set.
*/
custom_emoji_id: string;
/**
* The status expiration time in seconds.
*/
duration?: number;
}>;
/**
* Updates the Mini App header color.
* @since v6.1
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export const ERR_NOT_INITIALIZED = 'ERR_NOT_INITIALIZED';
export const ERR_NOT_SUPPORTED = 'ERR_NOT_SUPPORTED';
export const ERR_NOT_MOUNTED = 'ERR_NOT_MOUNTED';
export const ERR_FULLSCREEN_FAILED = 'ERR_FULLSCREEN_FAILED';
export const ERR_EMOJI_STATUS_SET_FAILED = 'ERR_EMOJI_STATUS_SET_FAILED';
2 changes: 2 additions & 0 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from '@/scopes/components/settings-button/exports.js';
export * from '@/scopes/components/swipe-behavior/exports.js';
export * from '@/scopes/components/theme-params/exports.js';
export * from '@/scopes/components/viewport/exports.js';
export * from '@/scopes/utilities/emoji-status/exports.js';
export * from '@/scopes/utilities/links/exports.js';
export * from '@/scopes/utilities/privacy/exports.js';
export * from '@/scopes/utilities/uncategorized/exports.js';
Expand All @@ -41,6 +42,7 @@ export {
ERR_ALREADY_MOUNTING,
ERR_ALREADY_REQUESTING,
ERR_FULLSCREEN_FAILED,
ERR_EMOJI_STATUS_SET_FAILED,
} from '@/errors.js';
export { init, type InitOptions } from '@/init.js';

Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/src/scopes/createMountFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function createMountFn<R>(
mount: (options?: AsyncOptions) => R | CancelablePromise<R>,
onMounted: (result: R) => void,
isMounted: Signal<boolean>,
promise: Signal<CancelablePromise<R> | undefined>,
promise: Signal<CancelablePromise<Awaited<R>> | undefined>,
error: Signal<Error | undefined>,
): (options?: AsyncOptions) => CancelablePromise<void> {
const noConcurrent = signalifyAsyncFn(
Expand Down
63 changes: 54 additions & 9 deletions packages/sdk/src/scopes/signalifyAsyncFn.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,69 @@
import { batch, type Signal } from '@telegram-apps/signals';
import { type AsyncOptions, CancelablePromise, type TypedError } from '@telegram-apps/bridge';
import { CancelablePromise, If, type TypedError } from '@telegram-apps/bridge';
import { AnyFn } from '@/types.js';

type AllowedFn<R> = (options?: AsyncOptions) => R | CancelablePromise<R>;
type Signalified<Fn extends AnyFn, Cancelable extends boolean> = (...args: Parameters<Fn>) => If<
Cancelable,
CancelablePromise<ReturnType<Fn>>,
PromiseLike<ReturnType<Fn>>
>;

/**
* Function doing the following:
* 1. Prevents the wrapped function from being called concurrently.
* 2. Being called, updates the passed promise and error signals.
*
* As a result, the function returns a new one, returning a cancelable promise.
* @param fn - function to wrap.
* @param createPendingError - function that creates error in case of concurrent call
* @param promise - signal containing the execution promise
* @param error - signal containing the last call error.
* @param cancelable - is result cancelable. True by default.
*/
export function signalifyAsyncFn<Fn extends AnyFn>(
fn: Fn,
createPendingError: () => TypedError<any>,
promise: Signal<CancelablePromise<Awaited<ReturnType<Fn>>> | undefined>,
error: Signal<Error | undefined>,
cancelable?: true,
): Signalified<Fn, true>;

/**
* Function doing the following:
* 1. Prevents the wrapped function from being called concurrently.
* 2. Being called, updates the passed promise and error signals.
*
* As a result, the function returns a new one, returning a non-cancelable promise.
* @param fn - function to wrap.
* @param createPendingError - function that creates error in case of concurrent call
* @param promise - signal containing the execution promise
* @param error - signal containing the last call error.
* @param cancelable - is result cancelable. True by default.
*/
export function signalifyAsyncFn<Fn extends AnyFn>(
fn: Fn,
createPendingError: () => TypedError<any>,
promise: Signal<Promise<Awaited<ReturnType<Fn>>> | undefined>,
error: Signal<Error | undefined>,
cancelable: false,
): Signalified<Fn, false>;

// #__NO_SIDE_EFFECTS__
export function signalifyAsyncFn<Fn extends AllowedFn<Result>, Result>(
export function signalifyAsyncFn<Fn extends AnyFn>(
fn: Fn,
createPendingError: () => TypedError<any>,
promise: Signal<CancelablePromise<Result> | undefined>,
promise:
| Signal<CancelablePromise<Awaited<ReturnType<Fn>>> | undefined>
| Signal<Promise<Awaited<ReturnType<Fn>>> | undefined>,
error: Signal<Error | undefined>,
): Fn {
return Object.assign((options?: AsyncOptions): CancelablePromise<Result> => {
return CancelablePromise
cancelable?: boolean,
): Signalified<Fn, boolean> {
const PromiseConstructor = cancelable === undefined || cancelable
? CancelablePromise
: Promise;

return Object.assign((...args: Parameters<Fn>): PromiseLike<ReturnType<Fn>> => {
return PromiseConstructor
.resolve()
.then(async () => {
// Check if the operation is currently not in progress.
Expand All @@ -32,10 +75,12 @@ export function signalifyAsyncFn<Fn extends AllowedFn<Result>, Result>(

// Start performing the wrapped function.
batch(() => {
promise.set(CancelablePromise.resolve(fn(options)));
promise.set(
(PromiseConstructor as typeof Promise).resolve(fn(...args)) as unknown as any,
);
error.set(undefined);
});
let result: [completed: true, result: Result] | [completed: false, err: Error];
let result: [completed: true, result: ReturnType<Fn>] | [completed: false, err: Error];
try {
result = [true, await promise()!];
} catch (e) {
Expand Down
Loading