Skip to content

Commit f9f06fc

Browse files
chore: replace UTF-8 shims with native TextEncoder/TextDecoder
Util.stringToUtf8ByteArray and Util.utf8ByteArrayToString were Google-Closure-derived (Apache 2.0, 2008) manual UTF-8 codecs — the file even still carried the 'TODO(user): Use native implementations if/when available' comment. Native TextEncoder and TextDecoder have been globals in browsers and Node 14+ for years; they're behavior-equivalent for our call sites and correct per WHATWG. Replaced 20 call sites across 9 files: CommandControlMessage (SET_CLIPBOARD + PUSH_FILE encode/decode), FileListingClient, ListFilesModal, DeviceMessage (clipboard + magic bytes), VideoSettings (codecOptions + encoderName), multiplexer/Message (close reason), multiplexer/Multiplexer (text frame data), server mw/FileListing (protocol command parsing), server mw/WebsocketMultiplexer (channel code). Dropped the two functions from Util.ts and removed now-unused 'import Util' from 5 files. Net: -82 lines from Util.ts, +22/-29 across callers. 0 tsc errors, 89/89 tests pass, webpack clean.
1 parent 1400545 commit f9f06fc

10 files changed

Lines changed: 22 additions & 111 deletions

File tree

src/app/Util.ts

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -102,88 +102,6 @@ export default class Util {
102102
}
103103

104104
// https://github.com/google/closure-library/blob/51e5a5ac373aefa354a991816ec418d730e29a7e/closure/goog/crypt/crypt.js#L117
105-
/*
106-
Copyright 2008 The Closure Library Authors. All Rights Reserved.
107-
Licensed under the Apache License, Version 2.0 (the "License");
108-
you may not use this file except in compliance with the License.
109-
You may obtain a copy of the License at
110-
111-
http://www.apache.org/licenses/LICENSE-2.0
112-
113-
Unless required by applicable law or agreed to in writing, software
114-
distributed under the License is distributed on an "AS-IS" BASIS,
115-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
116-
See the License for the specific language governing permissions and
117-
limitations under the License.
118-
*/
119-
/* tslint:disable */
120-
/**
121-
* Converts a JS string to a UTF-8 "byte" array.
122-
* @param {string} str 16-bit unicode string.
123-
* @return {!Array<number>} UTF-8 byte array.
124-
*/
125-
static stringToUtf8ByteArray = (str: string) => {
126-
// TODO(user): Use native implementations if/when available
127-
var out = [],
128-
p = 0;
129-
for (var i = 0; i < str.length; i++) {
130-
var c = str.charCodeAt(i);
131-
if (c < 128) {
132-
out[p++] = c;
133-
} else if (c < 2048) {
134-
out[p++] = (c >> 6) | 192;
135-
out[p++] = (c & 63) | 128;
136-
} else if ((c & 0xfc00) == 0xd800 && i + 1 < str.length && (str.charCodeAt(i + 1) & 0xfc00) == 0xdc00) {
137-
// Surrogate Pair
138-
c = 0x10000 + ((c & 0x03ff) << 10) + (str.charCodeAt(++i) & 0x03ff);
139-
out[p++] = (c >> 18) | 240;
140-
out[p++] = ((c >> 12) & 63) | 128;
141-
out[p++] = ((c >> 6) & 63) | 128;
142-
out[p++] = (c & 63) | 128;
143-
} else {
144-
out[p++] = (c >> 12) | 224;
145-
out[p++] = ((c >> 6) & 63) | 128;
146-
out[p++] = (c & 63) | 128;
147-
}
148-
}
149-
return Uint8Array.from(out);
150-
};
151-
152-
/**
153-
* Converts a UTF-8 byte array to JavaScript's 16-bit Unicode.
154-
* @param {Uint8Array|Array<number>} bytes UTF-8 byte array.
155-
* @return {string} 16-bit Unicode string.
156-
*/
157-
static utf8ByteArrayToString(bytes: Uint8Array): string {
158-
// TODO(user): Use native implementations if/when available
159-
var out = [],
160-
pos = 0,
161-
c = 0;
162-
while (pos < bytes.length) {
163-
var c1 = bytes[pos++];
164-
if (c1 < 128) {
165-
out[c++] = String.fromCharCode(c1);
166-
} else if (c1 > 191 && c1 < 224) {
167-
var c2 = bytes[pos++];
168-
out[c++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
169-
} else if (c1 > 239 && c1 < 365) {
170-
// Surrogate Pair
171-
var c2 = bytes[pos++];
172-
var c3 = bytes[pos++];
173-
var c4 = bytes[pos++];
174-
var u = (((c1 & 7) << 18) | ((c2 & 63) << 12) | ((c3 & 63) << 6) | (c4 & 63)) - 0x10000;
175-
out[c++] = String.fromCharCode(0xd800 + (u >> 10));
176-
out[c++] = String.fromCharCode(0xdc00 + (u & 1023));
177-
} else {
178-
var c2 = bytes[pos++];
179-
var c3 = bytes[pos++];
180-
out[c++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
181-
}
182-
}
183-
return out.join('');
184-
}
185-
/* tslint:enable */
186-
187105
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
188106
static supportsPassive(): boolean {
189107
if (typeof Util.supportsPassiveValue === 'boolean') {

src/app/VideoSettings.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { BinaryReader } from './BinaryReader';
22
import { BinaryWriter } from './BinaryWriter';
33
import Rect from './Rect';
44
import Size from './Size';
5-
import Util from './Util';
65

76
interface Settings {
87
crop?: Rect | null;
@@ -81,12 +80,12 @@ export default class VideoSettings {
8180
const codecOptionsLength = reader.readInt32BE();
8281
if (codecOptionsLength) {
8382
const codecOptionsBytes = reader.readBytes(codecOptionsLength);
84-
codecOptions = Util.utf8ByteArrayToString(codecOptionsBytes);
83+
codecOptions = new TextDecoder().decode(codecOptionsBytes);
8584
}
8685
const encoderNameLength = reader.readInt32BE();
8786
if (encoderNameLength) {
8887
const encoderNameBytes = reader.readBytes(encoderNameLength);
89-
encoderName = Util.utf8ByteArrayToString(encoderNameBytes);
88+
encoderName = new TextDecoder().decode(encoderNameBytes);
9089
}
9190
return new VideoSettings(
9291
{
@@ -145,11 +144,11 @@ export default class VideoSettings {
145144
let codecOptionsBytes;
146145
let encoderNameBytes;
147146
if (this.codecOptions) {
148-
codecOptionsBytes = Util.stringToUtf8ByteArray(this.codecOptions);
147+
codecOptionsBytes = new TextEncoder().encode(this.codecOptions);
149148
additionalLength += codecOptionsBytes.length;
150149
}
151150
if (this.encoderName) {
152-
encoderNameBytes = Util.stringToUtf8ByteArray(this.encoderName);
151+
encoderNameBytes = new TextEncoder().encode(this.encoderName);
153152
additionalLength += encoderNameBytes.length;
154153
}
155154
const writer = new BinaryWriter(VideoSettings.BASE_BUFFER_LENGTH + additionalLength);

src/app/controlMessage/CommandControlMessage.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { BinaryReader } from '../BinaryReader';
22
import { BinaryWriter } from '../BinaryWriter';
3-
import Util from '../Util';
43
import type VideoSettings from '../VideoSettings';
54
import { ControlMessage } from './ControlMessage';
65

@@ -58,7 +57,7 @@ export class CommandControlMessage extends ControlMessage {
5857

5958
public static createSetClipboardCommand(text: string, paste = false, sequence = 0n): CommandControlMessage {
6059
const event = new CommandControlMessage(ControlMessage.TYPE_SET_CLIPBOARD);
61-
const textBytes: Uint8Array | null = text ? Util.stringToUtf8ByteArray(text) : null;
60+
const textBytes: Uint8Array | null = text ? new TextEncoder().encode(text) : null;
6261
const textLength = textBytes ? textBytes.length : 0;
6362
// type(1) + sequence(8) + paste(1) + textLength(4) + text
6463
const writer = new BinaryWriter(1 + 8 + 1 + 4 + textLength)
@@ -103,7 +102,7 @@ export class CommandControlMessage extends ControlMessage {
103102

104103
private static createPushFileStartCommand(id: number, fileName: string, fileSize: number): CommandControlMessage {
105104
const event = new CommandControlMessage(ControlMessage.TYPE_PUSH_FILE);
106-
const text = Util.stringToUtf8ByteArray(fileName);
105+
const text = new TextEncoder().encode(fileName);
107106
const typeField = 1;
108107
const idField = 2;
109108
const stateField = 1;
@@ -185,7 +184,7 @@ export class CommandControlMessage extends ControlMessage {
185184
} else if (state === FilePushState.START) {
186185
fileSize = reader.readUInt32BE();
187186
const textLength = reader.readUInt16BE();
188-
fileName = Util.utf8ByteArrayToString(reader.readBytes(textLength));
187+
fileName = new TextDecoder().decode(reader.readBytes(textLength));
189188
}
190189
return { id, state, chunk, fileName, fileSize };
191190
}

src/app/googDevice/DeviceMessage.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { BinaryReader } from '../BinaryReader';
2-
import Util from '../Util';
32

43
export default class DeviceMessage {
54
public static TYPE_CLIPBOARD = 0;
65
public static TYPE_ACK_CLIPBOARD = 1;
76
public static TYPE_UHID_OUTPUT = 2;
87
public static TYPE_PUSH_RESPONSE = 101; // custom, not used with vanilla scrcpy v3.x
98

10-
public static readonly MAGIC_BYTES_MESSAGE = Util.stringToUtf8ByteArray('scrcpy_message');
9+
public static readonly MAGIC_BYTES_MESSAGE = new TextEncoder().encode('scrcpy_message');
1110

1211
constructor(
1312
public readonly type: number,
@@ -36,7 +35,7 @@ export default class DeviceMessage {
3635
const reader = new BinaryReader(this.data, 1);
3736
const length = reader.readInt32BE();
3837
const textBytes = reader.readBytes(length);
39-
return Util.utf8ByteArrayToString(textBytes);
38+
return new TextDecoder().decode(textBytes);
4039
}
4140

4241
public getAckSequence(): bigint {

src/app/googDevice/client/FileListingClient.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ export class FileListingClient extends ManagerClient<ParamsFileListing, never> i
393393
const size = statView.getUint32(4, true);
394394
const mtime = statView.getUint32(8, true);
395395
const namelen = statView.getUint32(12, true);
396-
const name = Util.utf8ByteArrayToString(stat.subarray(16, 16 + namelen));
396+
const name = new TextDecoder().decode(stat.subarray(16, 16 + namelen));
397397
this.addEntry(new Entry(name, mode, size, mtime));
398398
return;
399399
}
@@ -432,7 +432,7 @@ export class FileListingClient extends ManagerClient<ParamsFileListing, never> i
432432
case Protocol.FAIL: {
433433
const dataView = new DataView(data.buffer, data.byteOffset);
434434
const length = dataView.getUint32(4, true);
435-
const message = Util.utf8ByteArrayToString(data.subarray(8, 8 + length));
435+
const message = new TextDecoder().decode(data.subarray(8, 8 + length));
436436
console.error(TAG, `FAIL: ${message}`);
437437
return;
438438
}
@@ -592,7 +592,7 @@ export class FileListingClient extends ManagerClient<ParamsFileListing, never> i
592592
}
593593

594594
protected getChannelInitData(): Uint8Array {
595-
const serial = Util.stringToUtf8ByteArray(this.serial);
595+
const serial = new TextEncoder().encode(this.serial);
596596
return new BinaryWriter(4 + 4 + serial.byteLength)
597597
.writeString(ChannelCode.FSLS)
598598
.writeUInt32LE(serial.length)

src/app/googDevice/client/ListFilesModal.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { ChannelCode } from '../../../common/ChannelCode';
55
import { Multiplexer } from '../../../packages/multiplexer/Multiplexer';
66
import { BinaryWriter } from '../../BinaryWriter';
77
import { Modal } from '../../ui/Modal';
8-
import Util from '../../Util';
98
import { Entry } from '../Entry';
109
import { createFileIconForEntry } from './FileIconUtils';
1110
import { AdbkitFilePushStream } from '../filePush/AdbkitFilePushStream';
@@ -506,7 +505,7 @@ export class ListFilesModal extends Modal implements DragAndPushListener {
506505
// ── Directory listing protocol ──
507506

508507
private getChannelInitData(): Uint8Array {
509-
const serial = Util.stringToUtf8ByteArray(this.udid);
508+
const serial = new TextEncoder().encode(this.udid);
510509
return new BinaryWriter(4 + 4 + serial.byteLength)
511510
.writeString(ChannelCode.FSLS)
512511
.writeUInt32LE(serial.length)
@@ -619,7 +618,7 @@ export class ListFilesModal extends Modal implements DragAndPushListener {
619618
const size = statView.getUint32(4, true);
620619
const mtime = statView.getUint32(8, true);
621620
const namelen = statView.getUint32(12, true);
622-
const name = Util.utf8ByteArrayToString(stat.subarray(16, 16 + namelen));
621+
const name = new TextDecoder().decode(stat.subarray(16, 16 + namelen));
623622
const entry = new Entry(name, mode, size, mtime);
624623
// Skip '.' and '..'
625624
if (name !== '.' && name !== '..') {
@@ -682,7 +681,7 @@ export class ListFilesModal extends Modal implements DragAndPushListener {
682681
case Protocol.FAIL: {
683682
const dataView = new DataView(data.buffer, data.byteOffset);
684683
const length = dataView.getUint32(4, true);
685-
const message = Util.utf8ByteArrayToString(data.subarray(8, 8 + length));
684+
const message = new TextDecoder().decode(data.subarray(8, 8 + length));
686685
console.error(TAG, `FAIL: ${message}`);
687686
return;
688687
}

src/packages/multiplexer/Message.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import Util from '../../app/Util';
21
import { CloseEventClass } from './CloseEventClass';
32
import { MessageType } from './MessageType';
43

@@ -14,7 +13,7 @@ export class Message {
1413
}
1514

1615
public static fromCloseEvent(id: number, code: number, reason?: string): Message {
17-
const reasonBytes = reason ? Util.stringToUtf8ByteArray(reason) : new Uint8Array(0);
16+
const reasonBytes = reason ? new TextEncoder().encode(reason) : new Uint8Array(0);
1817
const buf = new Uint8Array(2 + 4 + reasonBytes.byteLength);
1918
const view = new DataView(buf.buffer);
2019
view.setUint16(0, code, true);
@@ -54,7 +53,7 @@ export class Message {
5453
code = view.getUint16(0, true);
5554
if (this.data.byteLength > 6) {
5655
const length = view.getUint32(2, true);
57-
reason = Util.utf8ByteArrayToString(new Uint8Array(this.data, 6, length));
56+
reason = new TextDecoder().decode(new Uint8Array(this.data, 6, length));
5857
}
5958
}
6059
return new CloseEventClass('close', {

src/packages/multiplexer/Multiplexer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export class Multiplexer extends TypedEmitter<MultiplexerEvents> implements WebS
101101
if (data) {
102102
const { channel } = data;
103103
const msg = new MessageEventClass('message', {
104-
data: Util.utf8ByteArrayToString(new Uint8Array(message.data)),
104+
data: new TextDecoder().decode(new Uint8Array(message.data)),
105105
lastEventId: event.lastEventId,
106106
origin: event.origin,
107107
source: event.source,

src/server/goog-device/mw/FileListing.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import Util from '../../../app/Util';
21
import Protocol from '../../../common/AdbProtocol';
32
import { ChannelCode } from '../../../common/ChannelCode';
43
import type { Multiplexer } from '../../../packages/multiplexer/Multiplexer';
@@ -23,7 +22,7 @@ export class FileListing extends Mw {
2322
}
2423
const buffer = Buffer.from(data);
2524
const length = buffer.readInt32LE(0);
26-
const serial = Util.utf8ByteArrayToString(buffer.slice(4, 4 + length));
25+
const serial = new TextDecoder().decode(buffer.slice(4, 4 + length));
2726
FileListing.log.info(`processChannel: accepted for serial="${serial}"`);
2827
return new FileListing(ws, serial);
2928
}
@@ -53,7 +52,7 @@ export class FileListing extends Mw {
5352
return;
5453
}
5554
let offset = 0;
56-
const cmd = Util.utf8ByteArrayToString(data.slice(offset, 4));
55+
const cmd = new TextDecoder().decode(data.slice(offset, 4));
5756
offset += 4;
5857
switch (cmd) {
5958
case Protocol.LIST:
@@ -62,7 +61,7 @@ export class FileListing extends Mw {
6261
const length = data.readUInt32LE(offset);
6362
offset += 4;
6463
const pathBuffer = data.slice(offset, offset + length);
65-
const pathString = Util.utf8ByteArrayToString(pathBuffer);
64+
const pathString = new TextDecoder().decode(pathBuffer);
6665
FileListing.handle(cmd, serial, pathString, channel).catch((error: Error) => {
6766
FileListing.log.error(error.message);
6867
});

src/server/mw/WebsocketMultiplexer.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type WS from 'ws';
2-
import Util from '../../app/Util';
32
import { ACTION } from '../../common/Action';
43
import { Multiplexer } from '../../packages/multiplexer/Multiplexer';
54
import { Logger } from '../Logger';
@@ -51,7 +50,7 @@ export class WebsocketMultiplexer extends Mw {
5150
let processed = false;
5251
for (const mwFactory of WebsocketMultiplexer.mwFactories.values()) {
5352
try {
54-
const code = Util.utf8ByteArrayToString(Buffer.from(data).slice(0, 4));
53+
const code = new TextDecoder().decode(Buffer.from(data).slice(0, 4));
5554
const buffer = data.byteLength > 4 ? data.slice(4) : undefined;
5655
const mw = mwFactory.processChannel(channel, code, buffer);
5756
if (mw) {

0 commit comments

Comments
 (0)