Skip to content

Commit 53a9277

Browse files
committed
refactor utils
1 parent 7c6d87b commit 53a9277

File tree

7 files changed

+110
-199
lines changed

7 files changed

+110
-199
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- introduce new API (breaking changes)
44
- add esbuild adjustments
55
- elven.js script is now much smaller
6+
- add some most crucial automatic tests
67
- ...
78

89
### [0.20.0](https://github.com/elven-js/elven.js/releases/tag/v0.20.0) (2024-10-13)

TODO.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
- move all previous signing providers (wallet connect is left)
22
- check what can be done with wallet connect to make it as small as possible
3+
- think about moving xPortal and webview integrations to separate files ????
4+
- add tests for at least most used utilities, maybe some core tools, check tests in MVX SDKs (more tests can be added later)
35
- prepare a new API
46
- for token operations
57
- for smart contracts interactions - the the best would be to pass the arguments as is (always requiring ABI without typed helpers ???)

esbuild.config.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const banner = `/*!
88
* These portions are licensed under the MIT License.
99
*
1010
* See the MultiversX repository for details: https://github.com/multiversx
11+
* See the attached MIT licence for elven.js: https://github.com/elven-js/elven.js/blob/main/LICENSE
1112
*/
1213
`;
1314

example/elven.js

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/utils.ts

Lines changed: 61 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,59 @@
1-
export const isValidHex = (value: string) =>
2-
value.match(/.{1,2}/g)?.length === 32;
1+
// Common constants and instances
2+
const base64Chars =
3+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
4+
const base64Map = new Uint8Array(256);
5+
for (let i = 0; i < base64Chars.length; i++) {
6+
base64Map[base64Chars.charCodeAt(i)] = i;
7+
}
8+
9+
const textEncoder = new TextEncoder();
10+
const textDecoder = new TextDecoder();
11+
12+
// Utility Functions
13+
export const isValidHex = (value: string): boolean =>
14+
/^[0-9a-fA-F]{64}$/.test(value);
315

4-
export const stringToBytes = (string: string) => {
5-
return new TextEncoder().encode(string);
16+
export const stringToBytes = (string: string): Uint8Array => {
17+
return textEncoder.encode(string);
618
};
719

8-
export const hexToBytes = (hex: string) => {
9-
const bytes = [];
10-
for (let i = 0; i < hex.length; i += 2) {
11-
bytes.push(parseInt(hex.slice(i, i + 2), 16));
12-
}
13-
return Uint8Array.from(bytes);
20+
export const bytesToString = (bytes: Uint8Array): string => {
21+
return textDecoder.decode(bytes);
1422
};
1523

16-
export const bytesToString = (uint8Array: Uint8Array) => {
17-
const decoder = new TextDecoder();
18-
const string = decoder.decode(uint8Array);
19-
return string;
24+
export const hexToBytes = (hex: string): Uint8Array => {
25+
if (!/^[0-9a-fA-F]+$/.test(hex) || hex.length % 2 !== 0) {
26+
throw new Error('Invalid hex string');
27+
}
28+
const bytes = new Uint8Array(hex.length / 2);
29+
for (let i = 0; i < bytes.length; i++) {
30+
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
31+
}
32+
return bytes;
2033
};
2134

22-
export const bytesToHex = (uint8Array: Uint8Array) => {
23-
return Array.from(uint8Array)
35+
export const bytesToHex = (bytes: Uint8Array): string => {
36+
return Array.from(bytes)
2437
.map((byte) => byte.toString(16).padStart(2, '0'))
2538
.join('');
2639
};
2740

28-
export const stringToHex = (string: string) => {
29-
const uint8Array = stringToBytes(string);
30-
return bytesToHex(uint8Array);
41+
export const stringToHex = (string: string): string => {
42+
return bytesToHex(stringToBytes(string));
3143
};
3244

33-
export const bytesFromBase64 = (base64String: string) => {
34-
// Base64 character set
35-
const base64Chars =
36-
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
37-
const base64Map = new Uint8Array(256);
38-
39-
// Initialize the inverse mapping from character code to Base64 value
40-
for (let i = 0; i < base64Chars.length; i++) {
41-
base64Map[base64Chars.charCodeAt(i)] = i;
42-
}
43-
44-
// Remove any characters not part of the Base64 character set
45+
// Base64 Encoding/Decoding Functions
46+
export const bytesFromBase64 = (base64String: string): Uint8Array => {
4547
base64String = base64String.replace(/[^A-Za-z0-9+/=]/g, '');
4648

47-
// Calculate padding
4849
let padding = 0;
4950
if (base64String.endsWith('==')) {
5051
padding = 2;
5152
} else if (base64String.endsWith('=')) {
5253
padding = 1;
5354
}
5455

55-
const byteLength = (base64String.length * 6) / 8 - padding;
56+
const byteLength = Math.floor((base64String.length * 6) / 8 - padding);
5657
const bytes = new Uint8Array(byteLength);
5758

5859
let buffer = 0;
@@ -61,15 +62,11 @@ export const bytesFromBase64 = (base64String: string) => {
6162

6263
for (let i = 0; i < base64String.length; i++) {
6364
const c = base64String.charAt(i);
64-
if (c === '=') {
65-
break; // Padding character, end of data
66-
}
65+
if (c === '=') break;
6766

68-
// Accumulate bits
6967
buffer = (buffer << 6) | base64Map[c.charCodeAt(0)];
7068
bitsCollected += 6;
7169

72-
// If we have 8 or more bits, extract the byte
7370
if (bitsCollected >= 8) {
7471
bitsCollected -= 8;
7572
bytes[byteIndex++] = (buffer >> bitsCollected) & 0xff;
@@ -79,42 +76,22 @@ export const bytesFromBase64 = (base64String: string) => {
7976
return bytes;
8077
};
8178

82-
export const toBase64FromStringOrBytes = (input: string | Uint8Array) => {
83-
// Base64 character set
84-
const base64Chars =
85-
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
86-
87-
let bytes;
88-
89-
// Convert input to Uint8Array if it's a string
90-
if (typeof input === 'string') {
91-
const encoder = new TextEncoder();
92-
bytes = encoder.encode(input);
93-
} else if (input instanceof Uint8Array) {
94-
bytes = input;
95-
} else {
96-
throw new Error('Input must be a string or Uint8Array');
97-
}
98-
79+
export const bytesToBase64 = (bytes: Uint8Array): string => {
9980
let base64 = '';
10081
const len = bytes.length;
10182

10283
for (let i = 0; i < len; i += 3) {
103-
// Collect three bytes (or pad with zeros)
10484
const byte1 = bytes[i];
10585
const byte2 = i + 1 < len ? bytes[i + 1] : 0;
10686
const byte3 = i + 2 < len ? bytes[i + 2] : 0;
10787

108-
// Combine the three bytes into a 24-bit number
10988
const combined = (byte1 << 16) | (byte2 << 8) | byte3;
11089

111-
// Extract four 6-bit values
11290
const enc1 = (combined >> 18) & 0x3f;
11391
const enc2 = (combined >> 12) & 0x3f;
11492
const enc3 = (combined >> 6) & 0x3f;
11593
const enc4 = combined & 0x3f;
11694

117-
// Append the Base64 characters
11895
base64 += base64Chars.charAt(enc1);
11996
base64 += base64Chars.charAt(enc2);
12097
base64 += i + 1 < len ? base64Chars.charAt(enc3) : '=';
@@ -124,7 +101,19 @@ export const toBase64FromStringOrBytes = (input: string | Uint8Array) => {
124101
return base64;
125102
};
126103

127-
export const combineBytes = (bytesArray: Uint8Array[]) => {
104+
export const stringFromBase64 = (base64String: string): string => {
105+
return bytesToString(bytesFromBase64(base64String));
106+
};
107+
108+
export const toBase64FromStringOrBytes = (
109+
input: string | Uint8Array
110+
): string => {
111+
const bytes = typeof input === 'string' ? stringToBytes(input) : input;
112+
return bytesToBase64(bytes);
113+
};
114+
115+
// Combine Bytes
116+
export const combineBytes = (bytesArray: Uint8Array[]): Uint8Array => {
128117
const totalLength = bytesArray.reduce((sum, bytes) => sum + bytes.length, 0);
129118
const combinedBytes = new Uint8Array(totalLength);
130119
let offset = 0;
@@ -137,61 +126,7 @@ export const combineBytes = (bytesArray: Uint8Array[]) => {
137126
return combinedBytes;
138127
};
139128

140-
export const stringFromBase64 = (base64String: string) => {
141-
// Base64 character set
142-
const base64Chars =
143-
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
144-
const base64Map = new Uint8Array(256);
145-
146-
// Initialize the inverse mapping from character code to Base64 value
147-
for (let i = 0; i < base64Chars.length; i++) {
148-
base64Map[base64Chars.charCodeAt(i)] = i;
149-
}
150-
151-
// Remove any characters not part of the Base64 character set
152-
base64String = base64String.replace(/[^A-Za-z0-9+/=]/g, '');
153-
154-
// Calculate padding
155-
let padding = 0;
156-
if (base64String.endsWith('==')) {
157-
padding = 2;
158-
} else if (base64String.endsWith('=')) {
159-
padding = 1;
160-
}
161-
162-
const byteLength = (base64String.length * 6) / 8 - padding;
163-
const bytes = new Uint8Array(byteLength);
164-
165-
let buffer = 0;
166-
let bitsCollected = 0;
167-
let byteIndex = 0;
168-
169-
for (let i = 0; i < base64String.length; i++) {
170-
const c = base64String.charAt(i);
171-
if (c === '=') {
172-
break; // Padding character, end of data
173-
}
174-
175-
// Accumulate bits
176-
buffer = (buffer << 6) | base64Map[c.charCodeAt(0)];
177-
bitsCollected += 6;
178-
179-
// If we have 8 or more bits, extract the byte
180-
if (bitsCollected >= 8) {
181-
bitsCollected -= 8;
182-
bytes[byteIndex++] = (buffer >> bitsCollected) & 0xff;
183-
}
184-
}
185-
186-
// Decode bytes to string
187-
const decoder = new TextDecoder();
188-
const decodedString = decoder.decode(bytes);
189-
190-
return decodedString;
191-
};
192-
193-
// ================== qs-like replacement start
194-
129+
// Query String Parsing and Stringifying
195130
function parseKey(key: string): Array<string | number> {
196131
const keys: Array<string | number> = [];
197132
const regex = /([^[\]]+)|\[(.*?)\]/g;
@@ -222,14 +157,10 @@ function setDeep(obj: any, keys: Array<string | number>, value: any) {
222157

223158
if (i === keys.length - 1) {
224159
if (key === '') {
225-
if (!Array.isArray(current)) {
226-
current = [];
227-
}
160+
if (!Array.isArray(current)) current = [];
228161
current.push(value);
229162
} else if (typeof key === 'number') {
230-
if (!Array.isArray(current)) {
231-
current = [];
232-
}
163+
if (!Array.isArray(current)) current = [];
233164
current[key] = value;
234165
} else {
235166
current[key] = value;
@@ -238,9 +169,7 @@ function setDeep(obj: any, keys: Array<string | number>, value: any) {
238169
const nextKey = keys[i + 1];
239170

240171
if (key === '') {
241-
if (!Array.isArray(current)) {
242-
current = [];
243-
}
172+
if (!Array.isArray(current)) current = [];
244173
if (
245174
current.length === 0 ||
246175
typeof current[current.length - 1] !== 'object'
@@ -249,9 +178,7 @@ function setDeep(obj: any, keys: Array<string | number>, value: any) {
249178
}
250179
current = current[current.length - 1];
251180
} else if (typeof key === 'number') {
252-
if (!Array.isArray(current)) {
253-
current = [];
254-
}
181+
if (!Array.isArray(current)) current = [];
255182
if (!current[key]) {
256183
current[key] = typeof nextKey === 'number' ? [] : {};
257184
}
@@ -320,29 +247,19 @@ export function stringifyQueryParams(params: Record<string, any>): string {
320247
return queryParams.join('&');
321248
}
322249

323-
// ================== qs-like replacement stop
250+
// Window and Document Utilities
251+
export const isWindowAvailable = (): boolean =>
252+
typeof window !== 'undefined' && typeof window.location !== 'undefined';
324253

325-
export const isWindowAvailable = () =>
326-
typeof window != 'undefined' && typeof window?.location != 'undefined';
327-
328-
export const getTargetOrigin = () => {
254+
export const getTargetOrigin = (): string => {
329255
if (isWindowAvailable()) {
330256
const ancestorOrigins = window.location.ancestorOrigins;
331257
return ancestorOrigins?.[ancestorOrigins.length - 1] ?? '*';
332258
}
333-
334259
return '*';
335260
};
336261

337-
export const getSafeWindow = () => {
338-
return typeof window !== 'undefined' ? window : ({} as any);
339-
};
340-
341-
export const getSafeDocument = () => {
342-
return typeof document !== 'undefined' ? document : ({} as any);
343-
};
344-
345-
export const isMobileWebview = () => {
346-
const safeWindow = getSafeWindow();
347-
return safeWindow.ReactNativeWebView || safeWindow.webkit;
262+
export const isMobileWebview = (): boolean => {
263+
const windowObj = window as any;
264+
return Boolean(windowObj?.ReactNativeWebView || windowObj?.webkit);
348265
};

src/core/webview-event-handler.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import {
2-
getTargetOrigin,
3-
isMobileWebview,
4-
getSafeWindow,
5-
getSafeDocument,
6-
} from './utils';
1+
import { getTargetOrigin, isMobileWebview } from './utils';
72
import {
83
WindowProviderResponseEnums,
94
ReplyWithPostMessagePayloadType,
@@ -50,14 +45,12 @@ export const webviewProviderEventHandler = <
5045
return;
5146
}
5247

53-
getSafeWindow().removeEventListener?.(
54-
'message',
55-
webviewProviderEventHandler(action, resolve)
56-
);
57-
getSafeDocument().removeEventListener?.(
58-
'message',
59-
webviewProviderEventHandler(action, resolve)
60-
);
48+
if (typeof window !== 'undefined') {
49+
window.removeEventListener?.(
50+
'message',
51+
webviewProviderEventHandler(action, resolve)
52+
);
53+
}
6154

6255
resolve({ type, payload });
6356
};

0 commit comments

Comments
 (0)