@tma.js/sdk 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tma.js/sdk",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "TypeScript Source Development Kit for Telegram Mini Apps client application.",
5
5
  "author": "Vladislav Kibenko <wolfram.deus@gmail.com>",
6
6
  "homepage": "https://github.com/Telegram-Mini-Apps/tma.js#readme",
@@ -24,8 +24,6 @@ interface Methods {
24
24
  saveStorageValue: { key: string; value: string };
25
25
  }
26
26
 
27
- const stringArray = array().of(string());
28
-
29
27
  function objectFromKeys<K extends string, V>(keys: K[], value: V): Record<K, V> {
30
28
  return keys.reduce<Record<K, V>>((acc, key) => {
31
29
  acc[key] = value;
@@ -93,7 +91,7 @@ export class CloudStorage {
93
91
  async getKeys(options?: WiredRequestOptions): Promise<string[]> {
94
92
  const result = await this.invokeCustomMethod('getStorageKeys', {}, options);
95
93
 
96
- return stringArray.parse(result);
94
+ return array().of(string()).parse(result);
97
95
  }
98
96
 
99
97
  /**
package/src/index.ts CHANGED
@@ -134,6 +134,12 @@ export {
134
134
  type QRScannerEventName,
135
135
  type QRScannerEvents,
136
136
  } from './qr-scanner/index.js';
137
+ export {
138
+ SettingsButton,
139
+ type SettingsButtonEventName,
140
+ type SettingsButtonEventListener,
141
+ type SettingsButtonEvents,
142
+ } from './settings-button/index.js';
137
143
  export { supports } from './supports/index.js';
138
144
  export {
139
145
  parseThemeParams,
@@ -0,0 +1,25 @@
1
+ import { SettingsButton } from '~/settings-button/index.js';
2
+ import { getStorageValue, saveStorageValue } from '~/storage.js';
3
+ import type { PostEvent } from '~/bridge/index.js';
4
+
5
+ /**
6
+ * Creates SettingsButton instance using last locally saved data also saving each state in
7
+ * the storage.
8
+ * @param isPageReload - was current page reloaded.
9
+ * @param version - platform version.
10
+ * @param postEvent - Bridge postEvent function
11
+ */
12
+ export function createSettingsButton(
13
+ isPageReload: boolean,
14
+ version: string,
15
+ postEvent: PostEvent,
16
+ ): SettingsButton {
17
+ const { isVisible = false } = isPageReload ? getStorageValue('settings-button') || {} : {};
18
+ const component = new SettingsButton(isVisible, version, postEvent);
19
+
20
+ component.on('change', () => {
21
+ saveStorageValue('settings-button', { isVisible: component.isVisible });
22
+ });
23
+
24
+ return component;
25
+ }
@@ -3,6 +3,15 @@ import { requestViewport, Viewport } from '~/viewport/index.js';
3
3
  import type { PostEvent } from '~/bridge/index.js';
4
4
  import type { Platform } from '~/types/index.js';
5
5
 
6
+ /**
7
+ * Returns true in case, specified platform supports calling Mini Apps
8
+ * "web_app_request_viewport" method.
9
+ * @param platform - platform identifier.
10
+ */
11
+ function isRequestSupportedPlatform(platform: Platform): boolean {
12
+ return !['macos', 'web', 'weba'].includes(platform);
13
+ }
14
+
6
15
  /**
7
16
  * Attempts to create Viewport instance using known parameters and local storage.
8
17
  * @param isPageReload - was page reloaded.
@@ -14,7 +23,7 @@ function tryCreate(
14
23
  platform: Platform,
15
24
  postEvent: PostEvent,
16
25
  ): Viewport | null {
17
- if (isPageReload || platform === 'macos' || platform === 'web' || platform === 'weba') {
26
+ if (isPageReload || !isRequestSupportedPlatform(platform)) {
18
27
  return new Viewport({
19
28
  height: window.innerHeight,
20
29
  isExpanded: true,
@@ -73,10 +82,12 @@ export function createViewportSync(
73
82
  }),
74
83
  );
75
84
 
76
- viewport.sync({ postEvent, timeout: 100 }).catch((e) => {
77
- // eslint-disable-next-line no-console
78
- console.error('Unable to actualize viewport state', e);
79
- });
85
+ if (isRequestSupportedPlatform(platform)) {
86
+ viewport.sync({ postEvent, timeout: 100 }).catch((e) => {
87
+ // eslint-disable-next-line no-console
88
+ console.error('Unable to actualize viewport state', e);
89
+ });
90
+ }
80
91
 
81
92
  return viewport;
82
93
  }
@@ -3,5 +3,6 @@ export * from './createClosingBehavior.js';
3
3
  export * from './createMainButton.js';
4
4
  export * from './createMiniApp.js';
5
5
  export * from './createRequestIdGenerator.js';
6
+ export * from './createSettingsButton.js';
6
7
  export * from './createThemeParams.js';
7
8
  export * from './createViewport.js';
package/src/init/init.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  createClosingBehavior,
8
8
  createMainButton,
9
9
  createMiniApp,
10
- createRequestIdGenerator,
10
+ createRequestIdGenerator, createSettingsButton,
11
11
  createThemeParams, createViewportAsync,
12
12
  createViewportSync,
13
13
  } from '~/init/creators/index.js';
@@ -23,7 +23,9 @@ import type { InitOptions, InitResult } from './types.js';
23
23
 
24
24
  type ComputedInitResult<O> = O extends { async: true } ? Promise<InitResult> : InitResult;
25
25
 
26
- export function init<O extends InitOptions>(options: O): ComputedInitResult<O> {
26
+ export function init(): InitResult;
27
+ export function init<O extends InitOptions>(options: O): ComputedInitResult<O>;
28
+ export function init(options: InitOptions = {}): InitResult | Promise<InitResult> {
27
29
  const {
28
30
  async = false,
29
31
  cssVars = false,
@@ -84,6 +86,7 @@ export function init<O extends InitOptions>(options: O): ComputedInitResult<O> {
84
86
  popup: new Popup(version, postEvent),
85
87
  postEvent,
86
88
  qrScanner: new QRScanner(version, postEvent),
89
+ settingsButton: createSettingsButton(isPageReload, version, postEvent),
87
90
  themeParams: createThemeParams(themeParams),
88
91
  utils: new Utils(version, createRequestId, postEvent),
89
92
  ...(initData
@@ -112,7 +115,7 @@ export function init<O extends InitOptions>(options: O): ComputedInitResult<O> {
112
115
  ...result,
113
116
  viewport: vp,
114
117
  };
115
- }) as ComputedInitResult<O>;
118
+ });
116
119
  }
117
120
 
118
121
  processCSSVars(
@@ -122,10 +125,10 @@ export function init<O extends InitOptions>(options: O): ComputedInitResult<O> {
122
125
  viewport,
123
126
  );
124
127
 
125
- return { ...result, viewport } as ComputedInitResult<O>;
128
+ return { ...result, viewport };
126
129
  } catch (e) {
127
130
  if (async) {
128
- return Promise.reject(e) as unknown as ComputedInitResult<O>;
131
+ return Promise.reject(e);
129
132
  }
130
133
  throw e;
131
134
  }
package/src/init/types.ts CHANGED
@@ -9,6 +9,7 @@ import type { MainButton } from '~/main-button/index.js';
9
9
  import type { MiniApp } from '~/mini-app/index.js';
10
10
  import type { Popup } from '~/popup/index.js';
11
11
  import type { QRScanner } from '~/qr-scanner/index.js';
12
+ import type { SettingsButton } from '~/settings-button/index.js';
12
13
  import type { ThemeParams } from '~/theme-params/index.js';
13
14
  import type { CreateRequestIdFunc } from '~/types/index.js';
14
15
  import type { Utils } from '~/utils/index.js';
@@ -28,6 +29,7 @@ export interface InitResult {
28
29
  popup: Popup;
29
30
  postEvent: PostEvent;
30
31
  qrScanner: QRScanner;
32
+ settingsButton: SettingsButton;
31
33
  themeParams: ThemeParams;
32
34
  utils: Utils;
33
35
  viewport: Viewport;
@@ -1,13 +1,11 @@
1
1
  import { number } from './number.js';
2
2
  import { createValueParserGenerator } from '../createValueParserGenerator.js';
3
3
 
4
- const num = number();
5
-
6
4
  /**
7
5
  * Returns parser to parse value as Date.
8
6
  */
9
7
  export const date = createValueParserGenerator<Date>((value) => (
10
8
  value instanceof Date
11
9
  ? value
12
- : new Date(num.parse(value) * 1000)
10
+ : new Date(number().parse(value) * 1000)
13
11
  ), 'Date');
@@ -4,9 +4,7 @@ import type { RGB } from '~/colors/index.js';
4
4
  import { string } from './string.js';
5
5
  import { createValueParserGenerator } from '../createValueParserGenerator.js';
6
6
 
7
- const str = string();
8
-
9
7
  /**
10
8
  * Returns parser to parse value as RGB color.
11
9
  */
12
- export const rgb = createValueParserGenerator<RGB>((value) => toRGB(str.parse(value)), 'rgb');
10
+ export const rgb = createValueParserGenerator<RGB>((value) => toRGB(string().parse(value)), 'rgb');
@@ -0,0 +1,85 @@
1
+ import {
2
+ off,
3
+ on,
4
+ type PostEvent,
5
+ postEvent as defaultPostEvent,
6
+ } from '~/bridge/index.js';
7
+ import { EventEmitter } from '~/event-emitter/index.js';
8
+ import { State } from '~/state/index.js';
9
+ import { createSupportsFunc, type SupportsFunc } from '~/supports/index.js';
10
+ import type { Version } from '~/version/index.js';
11
+
12
+ import type { SettingsButtonEvents, SettingsButtonState } from './types.js';
13
+
14
+ type Emitter = EventEmitter<SettingsButtonEvents>;
15
+
16
+ export class SettingsButton {
17
+ private readonly ee: Emitter = new EventEmitter();
18
+
19
+ private readonly state: State<SettingsButtonState>;
20
+
21
+ constructor(
22
+ isVisible: boolean,
23
+ version: Version,
24
+ private readonly postEvent: PostEvent = defaultPostEvent,
25
+ ) {
26
+ this.state = new State({ isVisible }, this.ee);
27
+ this.supports = createSupportsFunc(version, {
28
+ show: 'web_app_setup_settings_button',
29
+ hide: 'web_app_setup_settings_button',
30
+ });
31
+ }
32
+
33
+ private set isVisible(visible: boolean) {
34
+ this.state.set('isVisible', visible);
35
+ this.postEvent('web_app_setup_settings_button', { is_visible: visible });
36
+ }
37
+
38
+ /**
39
+ * True if SettingsButton is currently visible.
40
+ */
41
+ get isVisible(): boolean {
42
+ return this.state.get('isVisible');
43
+ }
44
+
45
+ /**
46
+ * Hides the SettingsButton.
47
+ */
48
+ hide(): void {
49
+ this.isVisible = false;
50
+ }
51
+
52
+ /**
53
+ * Adds event listener.
54
+ * @param event - event name.
55
+ * @param listener - event listener.
56
+ */
57
+ on: Emitter['on'] = (event, listener) => (
58
+ event === 'click'
59
+ ? on('settings_button_pressed', listener)
60
+ : this.ee.on(event, listener)
61
+ );
62
+
63
+ /**
64
+ * Removes event listener.
65
+ * @param event - event name.
66
+ * @param listener - event listener.
67
+ */
68
+ off: Emitter['off'] = (event, listener) => (
69
+ event === 'click'
70
+ ? off('settings_button_pressed', listener)
71
+ : this.ee.off(event, listener)
72
+ );
73
+
74
+ /**
75
+ * Shows the SettingsButton.
76
+ */
77
+ show(): void {
78
+ this.isVisible = true;
79
+ }
80
+
81
+ /**
82
+ * Checks if specified method is supported by current component.
83
+ */
84
+ supports: SupportsFunc<'show' | 'hide'>;
85
+ }
@@ -0,0 +1,2 @@
1
+ export * from './SettingsButton.js';
2
+ export * from './types.js';
@@ -0,0 +1,15 @@
1
+ import type { MiniAppsEventListener } from '~/bridge/index.js';
2
+ import type { StateEvents } from '~/state/index.js';
3
+
4
+ export interface SettingsButtonState {
5
+ isVisible: boolean;
6
+ }
7
+
8
+ export interface SettingsButtonEvents extends StateEvents<SettingsButtonState> {
9
+ click: MiniAppsEventListener<'settings_button_pressed'>;
10
+ }
11
+
12
+ export type SettingsButtonEventName = keyof SettingsButtonEvents;
13
+
14
+ export type SettingsButtonEventListener<E extends SettingsButtonEventName> =
15
+ SettingsButtonEvents[E];
package/src/storage.ts CHANGED
@@ -21,6 +21,9 @@ interface StorageParams {
21
21
  text: string;
22
22
  textColor: RGB;
23
23
  };
24
+ 'settings-button': {
25
+ isVisible: boolean
26
+ };
24
27
  viewport: {
25
28
  height: number;
26
29
  isExpanded: boolean;
@@ -7,14 +7,16 @@ import {
7
7
  import { keyToLocal } from './keys.js';
8
8
  import type { ThemeParamsParsed } from './types.js';
9
9
 
10
- const rgbOptional = rgb().optional();
11
-
12
10
  export const themeParamsParser = createValueParserGenerator<ThemeParamsParsed>(
13
- (value) => Object
14
- .entries(toRecord(value))
15
- .reduce<ThemeParamsParsed>((acc, [k, v]) => {
16
- acc[keyToLocal(k)] = rgbOptional.parse(v);
17
- return acc;
18
- }, {}),
11
+ (value) => {
12
+ const rgbOptional = rgb().optional();
13
+
14
+ return Object
15
+ .entries(toRecord(value))
16
+ .reduce<ThemeParamsParsed>((acc, [k, v]) => {
17
+ acc[keyToLocal(k)] = rgbOptional.parse(v);
18
+ return acc;
19
+ }, {});
20
+ },
19
21
  'ThemeParams',
20
22
  );