@blazium/ton-connect-mobile 1.0.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 ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@blazium/ton-connect-mobile",
3
+ "version": "1.0.0",
4
+ "description": "Production-ready TON Connect Mobile SDK for React Native and Expo. Implements the real TonConnect protocol for mobile applications using deep links and callbacks.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/blaziumdev/ton-connect-mobile.git"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "prepublishOnly": "npm run build",
14
+ "example": "cd example && npm start"
15
+ },
16
+ "keywords": [
17
+ "ton",
18
+ "tonconnect",
19
+ "react-native",
20
+ "expo",
21
+ "blockchain",
22
+ "wallet",
23
+ "mobile",
24
+ "sdk"
25
+ ],
26
+ "author": "BlaziumDev",
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "tweetnacl": "^1.0.3",
30
+ "tweetnacl-util": "^0.15.1",
31
+ "react-native-get-random-values": "^1.9.0"
32
+ },
33
+ "peerDependencies": {
34
+ "react-native": "*"
35
+ },
36
+ "peerDependenciesMeta": {
37
+ "expo-linking": {
38
+ "optional": true
39
+ },
40
+ "expo-crypto": {
41
+ "optional": true
42
+ },
43
+ "@react-native-async-storage/async-storage": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "devDependencies": {
48
+ "@types/react": "^18.2.0",
49
+ "@types/react-native": "^0.72.0",
50
+ "typescript": "^5.0.0"
51
+ },
52
+ "files": [
53
+ "dist",
54
+ "src"
55
+ ]
56
+ }
57
+
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Expo platform adapter
3
+ * Handles deep linking, storage, and crypto for Expo environments
4
+ */
5
+
6
+ // Type declarations for runtime globals
7
+ declare const require: {
8
+ (id: string): any;
9
+ };
10
+
11
+ import { PlatformAdapter } from '../types';
12
+
13
+ // Dynamic imports to handle optional dependencies
14
+ let Linking: any;
15
+ let Crypto: any;
16
+ let AsyncStorage: any;
17
+
18
+ try {
19
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
20
+ Linking = require('expo-linking');
21
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
22
+ Crypto = require('expo-crypto');
23
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
24
+ AsyncStorage = require('@react-native-async-storage/async-storage').default;
25
+ } catch {
26
+ // Dependencies not available
27
+ }
28
+
29
+ /**
30
+ * Expo platform adapter implementation
31
+ */
32
+ export class ExpoAdapter implements PlatformAdapter {
33
+ private urlListeners: Array<(url: string) => void> = [];
34
+ private subscription: { remove: () => void } | null = null;
35
+
36
+ constructor() {
37
+ // Set up URL listener
38
+ this.setupURLListener();
39
+ }
40
+
41
+ private setupURLListener(): void {
42
+ if (!Linking) {
43
+ return;
44
+ }
45
+ // Listen for deep links when app is already open
46
+ this.subscription = Linking.addEventListener('url', (event: { url: string }) => {
47
+ this.urlListeners.forEach((listener) => listener(event.url));
48
+ });
49
+ }
50
+
51
+ async openURL(url: string): Promise<boolean> {
52
+ if (!Linking) {
53
+ throw new Error('expo-linking is not available');
54
+ }
55
+ try {
56
+ const canOpen = await Linking.canOpenURL(url);
57
+ if (!canOpen) {
58
+ return false;
59
+ }
60
+ await Linking.openURL(url);
61
+ return true;
62
+ } catch (error) {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ async getInitialURL(): Promise<string | null> {
68
+ if (!Linking) {
69
+ return null;
70
+ }
71
+ try {
72
+ return await Linking.getInitialURL();
73
+ } catch (error) {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ addURLListener(callback: (url: string) => void): () => void {
79
+ this.urlListeners.push(callback);
80
+
81
+ // Return unsubscribe function
82
+ return () => {
83
+ const index = this.urlListeners.indexOf(callback);
84
+ if (index > -1) {
85
+ this.urlListeners.splice(index, 1);
86
+ }
87
+ };
88
+ }
89
+
90
+ async setItem(key: string, value: string): Promise<void> {
91
+ if (!AsyncStorage) {
92
+ throw new Error('@react-native-async-storage/async-storage is not available');
93
+ }
94
+ try {
95
+ await AsyncStorage.setItem(key, value);
96
+ } catch (error: any) {
97
+ throw new Error(`Failed to set storage item: ${error?.message || error}`);
98
+ }
99
+ }
100
+
101
+ async getItem(key: string): Promise<string | null> {
102
+ if (!AsyncStorage) {
103
+ return null;
104
+ }
105
+ try {
106
+ return await AsyncStorage.getItem(key);
107
+ } catch (error) {
108
+ return null;
109
+ }
110
+ }
111
+
112
+ async removeItem(key: string): Promise<void> {
113
+ if (!AsyncStorage) {
114
+ return;
115
+ }
116
+ try {
117
+ await AsyncStorage.removeItem(key);
118
+ } catch (error) {
119
+ // Ignore errors on remove
120
+ }
121
+ }
122
+
123
+ async randomBytes(length: number): Promise<Uint8Array> {
124
+ if (Crypto) {
125
+ try {
126
+ // Use expo-crypto for random bytes
127
+ const randomString = await Crypto.getRandomBytesAsync(length);
128
+ return randomString;
129
+ } catch (error) {
130
+ // Fall through to crypto.getRandomValues check
131
+ }
132
+ }
133
+
134
+ // HIGH FIX: Try crypto.getRandomValues as fallback
135
+ // eslint-disable-next-line no-undef
136
+ if (typeof globalThis !== 'undefined' && (globalThis as any).crypto && (globalThis as any).crypto.getRandomValues) {
137
+ const bytes = new Uint8Array(length);
138
+ (globalThis as any).crypto.getRandomValues(bytes);
139
+ return bytes;
140
+ }
141
+
142
+ // HIGH FIX: Throw error instead of using insecure Math.random()
143
+ throw new Error(
144
+ 'Cryptographically secure random number generation not available. ' +
145
+ 'Please ensure expo-crypto is installed or use react-native-get-random-values'
146
+ );
147
+ }
148
+
149
+ /**
150
+ * Cleanup resources
151
+ */
152
+ destroy(): void {
153
+ if (this.subscription) {
154
+ this.subscription.remove();
155
+ this.subscription = null;
156
+ }
157
+ this.urlListeners = [];
158
+ }
159
+ }
160
+
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Platform adapter exports
3
+ */
4
+
5
+ export { ExpoAdapter } from './expo';
6
+ export { ReactNativeAdapter } from './react-native';
7
+ export { WebAdapter } from './web';
8
+
@@ -0,0 +1,148 @@
1
+ /**
2
+ * React Native platform adapter
3
+ * Handles deep linking and storage for React Native CLI environments
4
+ */
5
+
6
+ // Type declarations for runtime globals
7
+ declare const require: {
8
+ (id: string): any;
9
+ };
10
+
11
+ import { PlatformAdapter } from '../types';
12
+
13
+ // Dynamic imports to handle optional dependencies
14
+ let Linking: any;
15
+ let AsyncStorage: any;
16
+
17
+ try {
18
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
19
+ const RN = require('react-native');
20
+ Linking = RN.Linking;
21
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
22
+ AsyncStorage = require('@react-native-async-storage/async-storage').default;
23
+ } catch {
24
+ // Dependencies not available
25
+ }
26
+
27
+ /**
28
+ * React Native platform adapter implementation
29
+ */
30
+ export class ReactNativeAdapter implements PlatformAdapter {
31
+ private urlListeners: Array<(url: string) => void> = [];
32
+ private subscription: { remove: () => void } | null = null;
33
+
34
+ constructor() {
35
+ // Set up URL listener
36
+ this.setupURLListener();
37
+ }
38
+
39
+ private setupURLListener(): void {
40
+ if (!Linking) {
41
+ return;
42
+ }
43
+ // Listen for deep links when app is already open
44
+ this.subscription = Linking.addEventListener('url', (event: { url: string }) => {
45
+ this.urlListeners.forEach((listener) => listener(event.url));
46
+ });
47
+ }
48
+
49
+ async openURL(url: string): Promise<boolean> {
50
+ if (!Linking) {
51
+ throw new Error('react-native Linking is not available');
52
+ }
53
+ try {
54
+ const canOpen = await Linking.canOpenURL(url);
55
+ if (!canOpen) {
56
+ return false;
57
+ }
58
+ await Linking.openURL(url);
59
+ return true;
60
+ } catch (error) {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ async getInitialURL(): Promise<string | null> {
66
+ if (!Linking) {
67
+ return null;
68
+ }
69
+ try {
70
+ return await Linking.getInitialURL();
71
+ } catch (error) {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ addURLListener(callback: (url: string) => void): () => void {
77
+ this.urlListeners.push(callback);
78
+
79
+ // Return unsubscribe function
80
+ return () => {
81
+ const index = this.urlListeners.indexOf(callback);
82
+ if (index > -1) {
83
+ this.urlListeners.splice(index, 1);
84
+ }
85
+ };
86
+ }
87
+
88
+ async setItem(key: string, value: string): Promise<void> {
89
+ if (!AsyncStorage) {
90
+ throw new Error('@react-native-async-storage/async-storage is not available');
91
+ }
92
+ try {
93
+ await AsyncStorage.setItem(key, value);
94
+ } catch (error: any) {
95
+ throw new Error(`Failed to set storage item: ${error?.message || error}`);
96
+ }
97
+ }
98
+
99
+ async getItem(key: string): Promise<string | null> {
100
+ if (!AsyncStorage) {
101
+ return null;
102
+ }
103
+ try {
104
+ return await AsyncStorage.getItem(key);
105
+ } catch (error) {
106
+ return null;
107
+ }
108
+ }
109
+
110
+ async removeItem(key: string): Promise<void> {
111
+ if (!AsyncStorage) {
112
+ return;
113
+ }
114
+ try {
115
+ await AsyncStorage.removeItem(key);
116
+ } catch (error) {
117
+ // Ignore errors on remove
118
+ }
119
+ }
120
+
121
+ async randomBytes(length: number): Promise<Uint8Array> {
122
+ // HIGH FIX: Use crypto.getRandomValues if available (with polyfill)
123
+ // eslint-disable-next-line no-undef
124
+ if (typeof globalThis !== 'undefined' && (globalThis as any).crypto && (globalThis as any).crypto.getRandomValues) {
125
+ const bytes = new Uint8Array(length);
126
+ (globalThis as any).crypto.getRandomValues(bytes);
127
+ return bytes;
128
+ }
129
+
130
+ // HIGH FIX: Throw error instead of using insecure Math.random()
131
+ throw new Error(
132
+ 'Cryptographically secure random number generation not available. ' +
133
+ 'Please install react-native-get-random-values: npm install react-native-get-random-values'
134
+ );
135
+ }
136
+
137
+ /**
138
+ * Cleanup resources
139
+ */
140
+ destroy(): void {
141
+ if (this.subscription) {
142
+ this.subscription.remove();
143
+ this.subscription = null;
144
+ }
145
+ this.urlListeners = [];
146
+ }
147
+ }
148
+
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Web platform adapter
3
+ * Handles deep linking and storage for web environments
4
+ */
5
+
6
+ // Type declarations for browser APIs
7
+ declare const window: {
8
+ location: {
9
+ href: string;
10
+ };
11
+ open(url: string, target?: string): any;
12
+ addEventListener(type: string, listener: () => void): void;
13
+ removeEventListener(type: string, listener: () => void): void;
14
+ localStorage: {
15
+ setItem(key: string, value: string): void;
16
+ getItem(key: string): string | null;
17
+ removeItem(key: string): void;
18
+ };
19
+ crypto: {
20
+ getRandomValues(array: Uint8Array): Uint8Array;
21
+ };
22
+ } | undefined;
23
+
24
+ import { PlatformAdapter } from '../types';
25
+
26
+ /**
27
+ * Web platform adapter implementation
28
+ * Uses browser APIs for deep linking and localStorage for storage
29
+ */
30
+ export class WebAdapter implements PlatformAdapter {
31
+ private urlListeners: Array<(url: string) => void> = [];
32
+ private isListening = false;
33
+
34
+ constructor() {
35
+ // Set up URL listener
36
+ this.setupURLListener();
37
+ }
38
+
39
+ private setupURLListener(): void {
40
+ // Listen for hash changes (web deep links)
41
+ if (typeof window !== 'undefined') {
42
+ window.addEventListener('hashchange', () => {
43
+ this.handleURLChange();
44
+ });
45
+
46
+ // Also check on popstate (browser back/forward)
47
+ window.addEventListener('popstate', () => {
48
+ this.handleURLChange();
49
+ });
50
+
51
+ this.isListening = true;
52
+ }
53
+ }
54
+
55
+ private handleURLChange(): void {
56
+ if (typeof window === 'undefined') return;
57
+
58
+ const url = window.location.href;
59
+ this.urlListeners.forEach((listener) => {
60
+ try {
61
+ listener(url);
62
+ } catch (error) {
63
+ // Ignore errors in listeners
64
+ }
65
+ });
66
+ }
67
+
68
+ async openURL(url: string): Promise<boolean> {
69
+ try {
70
+ if (typeof window !== 'undefined') {
71
+ // For web, we can use window.open or window.location
72
+ // For deep links to mobile wallets, we'll use window.location
73
+ if (url.startsWith('tonconnect://')) {
74
+ // Try to open in new window/tab, fallback to current window
75
+ const opened = window.open(url, '_blank');
76
+ if (!opened) {
77
+ // Popup blocked, try current window
78
+ window.location.href = url;
79
+ }
80
+ return true;
81
+ } else {
82
+ window.location.href = url;
83
+ return true;
84
+ }
85
+ }
86
+ return false;
87
+ } catch (error) {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ async getInitialURL(): Promise<string | null> {
93
+ if (typeof window !== 'undefined') {
94
+ return window.location.href;
95
+ }
96
+ return null;
97
+ }
98
+
99
+ addURLListener(callback: (url: string) => void): () => void {
100
+ this.urlListeners.push(callback);
101
+
102
+ // Immediately check current URL
103
+ if (typeof window !== 'undefined') {
104
+ try {
105
+ callback(window.location.href);
106
+ } catch (error) {
107
+ // Ignore errors
108
+ }
109
+ }
110
+
111
+ // Return unsubscribe function
112
+ return () => {
113
+ const index = this.urlListeners.indexOf(callback);
114
+ if (index > -1) {
115
+ this.urlListeners.splice(index, 1);
116
+ }
117
+ };
118
+ }
119
+
120
+ async setItem(key: string, value: string): Promise<void> {
121
+ if (typeof window === 'undefined' || !window.localStorage) {
122
+ throw new Error('localStorage is not available');
123
+ }
124
+ try {
125
+ window.localStorage.setItem(key, value);
126
+ } catch (error: any) {
127
+ throw new Error(`Failed to set storage item: ${error?.message || error}`);
128
+ }
129
+ }
130
+
131
+ async getItem(key: string): Promise<string | null> {
132
+ if (typeof window === 'undefined' || !window.localStorage) {
133
+ return null;
134
+ }
135
+ try {
136
+ return window.localStorage.getItem(key);
137
+ } catch (error) {
138
+ return null;
139
+ }
140
+ }
141
+
142
+ async removeItem(key: string): Promise<void> {
143
+ if (typeof window === 'undefined' || !window.localStorage) {
144
+ return;
145
+ }
146
+ try {
147
+ window.localStorage.removeItem(key);
148
+ } catch (error) {
149
+ // Ignore errors on remove
150
+ }
151
+ }
152
+
153
+ async randomBytes(length: number): Promise<Uint8Array> {
154
+ // Use crypto.getRandomValues (available in modern browsers)
155
+ if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
156
+ const bytes = new Uint8Array(length);
157
+ window.crypto.getRandomValues(bytes);
158
+ return bytes;
159
+ }
160
+
161
+ // Fallback: should not happen in modern browsers
162
+ throw new Error(
163
+ 'Cryptographically secure random number generation not available. ' +
164
+ 'Please use a modern browser with crypto.getRandomValues support.'
165
+ );
166
+ }
167
+
168
+ /**
169
+ * Cleanup resources
170
+ */
171
+ destroy(): void {
172
+ this.urlListeners = [];
173
+ this.isListening = false;
174
+ }
175
+ }
176
+