Skip to content

Commit 5632ca1

Browse files
committed
Encodes/decodes bootstrap state to base64
- Avoids JSON encoding issue in an HTML attribute Improves base64 decode performance
1 parent e674c5e commit 5632ca1

File tree

11 files changed

+48
-14
lines changed

11 files changed

+48
-14
lines changed

src/avatars.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { EventEmitter, Uri } from 'vscode';
2+
import { base64 } from '@env/base64';
23
import { md5 } from '@env/crypto';
34
import type { GravatarDefaultStyle } from './config';
45
import type { StoredAvatar } from './constants.storage';
@@ -9,7 +10,7 @@ import { configuration } from './system/-webview/configuration';
910
import { getContext } from './system/-webview/context';
1011
import { debounce } from './system/function/debounce';
1112
import { filterMap } from './system/iterable';
12-
import { base64, equalsIgnoreCase } from './system/string';
13+
import { equalsIgnoreCase } from './system/string';
1314
import type { ContactPresenceStatus } from './vsls/vsls';
1415

1516
const maxSmallIntegerV8 = 2 ** 30 - 1; // Max number that can be stored in V8's smis (small integers)

src/env/browser/base64.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { fromCharCode } = String;
22
const textEncoder = new TextEncoder();
3+
const textDecoder = new TextDecoder();
34

45
export function base64(s: string): string;
56
export function base64(bytes: Uint8Array): string;
@@ -18,8 +19,32 @@ export function fromBase64(s: string): Uint8Array {
1819

1920
const len = decoded.length;
2021
const bytes = new Uint8Array(len);
21-
for (let i = 0; i < len; i++) {
22+
23+
// Unrolled loop for better performance on larger strings
24+
let i = 0;
25+
const end = len - (len % 8);
26+
for (; i < end; i += 8) {
27+
bytes[i] = decoded.charCodeAt(i);
28+
bytes[i + 1] = decoded.charCodeAt(i + 1);
29+
bytes[i + 2] = decoded.charCodeAt(i + 2);
30+
bytes[i + 3] = decoded.charCodeAt(i + 3);
31+
bytes[i + 4] = decoded.charCodeAt(i + 4);
32+
bytes[i + 5] = decoded.charCodeAt(i + 5);
33+
bytes[i + 6] = decoded.charCodeAt(i + 6);
34+
bytes[i + 7] = decoded.charCodeAt(i + 7);
35+
}
36+
// Handle remaining bytes
37+
for (; i < len; i++) {
2238
bytes[i] = decoded.charCodeAt(i);
2339
}
40+
2441
return bytes;
2542
}
43+
44+
/**
45+
* Decodes a base64-encoded string directly to a UTF-8 string.
46+
* More efficient than fromBase64().toString() for string conversion.
47+
*/
48+
export function fromBase64ToString(s: string): string {
49+
return textDecoder.decode(fromBase64(s));
50+
}

src/env/node/base64.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,11 @@ export function base64(data: string | Uint8Array): string {
77
export function fromBase64(s: string): Uint8Array {
88
return Buffer.from(s, 'base64') as unknown as Uint8Array;
99
}
10+
11+
/**
12+
* Decodes a base64-encoded string directly to a UTF-8 string.
13+
* More efficient than fromBase64().toString() for string conversion.
14+
*/
15+
export function fromBase64ToString(s: string): string {
16+
return Buffer.from(s, 'base64').toString('utf8');
17+
}

src/plus/integrations/providers/azure/azure.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { HttpsProxyAgent } from 'https-proxy-agent';
22
import type { CancellationToken, Disposable } from 'vscode';
33
import { window } from 'vscode';
4+
import { base64 } from '@env/base64';
45
import type { RequestInit, Response } from '@env/fetch';
56
import { fetch, getProxyAgent, wrapForForcedInsecureSSL } from '@env/fetch';
67
import { isWeb } from '@env/platform';
@@ -25,7 +26,6 @@ import { Logger } from '../../../../system/logger';
2526
import type { LogScope } from '../../../../system/logger.scope';
2627
import { getLogScope } from '../../../../system/logger.scope';
2728
import { maybeStopWatch } from '../../../../system/stopwatch';
28-
import { base64 } from '../../../../system/string';
2929
import type {
3030
AzureGitCommit,
3131
AzureProjectDescriptor,

src/plus/integrations/providers/azureDevOps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { AuthenticationSession, CancellationToken, EventEmitter } from 'vscode';
22
import { window } from 'vscode';
3+
import { base64 } from '@env/base64';
34
import { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../../../constants.integrations';
45
import type { Container } from '../../../container';
56
import type { Account, UnidentifiedAuthor } from '../../../git/models/author';
@@ -9,7 +10,6 @@ import type { IssueOrPullRequest, IssueOrPullRequestType } from '../../../git/mo
910
import type { PullRequest, PullRequestMergeMethod, PullRequestState } from '../../../git/models/pullRequest';
1011
import type { RepositoryMetadata } from '../../../git/models/repositoryMetadata';
1112
import { flatSettled } from '../../../system/promise';
12-
import { base64 } from '../../../system/string';
1313
import type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider';
1414
import type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService';
1515
import type { IntegrationConnectionChangeEvent } from '../integrationService';

src/plus/integrations/providers/github/github.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { Endpoints, OctokitResponse, RequestParameters } from '@octokit/typ
55
import type { HttpsProxyAgent } from 'https-proxy-agent';
66
import type { CancellationToken, Event } from 'vscode';
77
import { Disposable, EventEmitter, Uri, window } from 'vscode';
8+
import { base64 } from '@env/base64';
89
import { fetch, getProxyAgent, wrapForForcedInsecureSSL } from '@env/fetch';
910
import { isWeb } from '@env/platform';
1011
import type { Container } from '../../../../container';
@@ -45,7 +46,6 @@ import { Logger } from '../../../../system/logger';
4546
import type { LogScope } from '../../../../system/logger.scope';
4647
import { getLogScope } from '../../../../system/logger.scope';
4748
import { maybeStopWatch } from '../../../../system/stopwatch';
48-
import { base64 } from '../../../../system/string';
4949
import type { Version } from '../../../../system/version';
5050
import { fromString, satisfies } from '../../../../system/version';
5151
import type {

src/plus/integrations/providers/providersApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ProviderApis from '@gitkraken/provider-apis';
22
import { version as codeVersion, env } from 'vscode';
3+
import { base64 } from '@env/base64';
34
import type { Response as FetchResponse } from '@env/fetch';
45
import { fetch as _fetch, getProxyAgent } from '@env/fetch';
56
import { getPlatform } from '@env/platform';
@@ -19,7 +20,6 @@ import {
1920
} from '../../../errors';
2021
import type { PagedResult } from '../../../git/gitProvider';
2122
import type { PullRequest, PullRequestMergeMethod } from '../../../git/models/pullRequest';
22-
import { base64 } from '../../../system/string';
2323
import type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService';
2424
import type {
2525
GetAzureProjectsForResourceFn,

src/system/string.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import { hrtime } from '@env/hrtime';
88
import { CharCode } from '../constants';
99
import { getNumericFormat } from './date';
1010

11-
export { fromBase64, base64 } from '@env/base64';
12-
1311
export function capitalize(s: string): string {
1412
return `${s[0].toLocaleUpperCase()}${s.slice(1)}`;
1513
}

src/uris/deepLinks/deepLinkService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { QuickPickItem, SecretStorageChangeEvent } from 'vscode';
22
import { Disposable, env, EventEmitter, ProgressLocation, Range, Uri, window, workspace } from 'vscode';
3+
import { fromBase64ToString } from '@env/base64';
34
import type { OpenCloudPatchCommandArgs } from '../../commands/patches';
45
import type { StoredDeepLinkContext, StoredNamedRef } from '../../constants.storage';
56
import type { Container } from '../../container';
@@ -29,7 +30,6 @@ import { debug } from '../../system/decorators/log';
2930
import { once } from '../../system/event';
3031
import { Logger } from '../../system/logger';
3132
import { maybeUri, normalizePath } from '../../system/path';
32-
import { fromBase64 } from '../../system/string';
3333
import { isWalkthroughSupported } from '../../telemetry/walkthroughStateProvider';
3434
import { showInspectView } from '../../webviews/commitDetails/actions';
3535
import type { ShowWipArgs } from '../../webviews/commitDetails/protocol';
@@ -1236,7 +1236,7 @@ export class DeepLinkService implements Disposable {
12361236
const type = this._context.params?.get('type');
12371237
let prEntityId = this._context.params?.get('prEntityId') ?? undefined;
12381238
if (prEntityId != null) {
1239-
prEntityId = fromBase64(prEntityId).toString();
1239+
prEntityId = fromBase64ToString(prEntityId);
12401240
}
12411241

12421242
void (await executeCommand<OpenCloudPatchCommandArgs>('gitlens.openCloudPatch', {

src/webviews/apps/shared/appHost.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { provide } from '@lit/context';
22
import type { ReactiveControllerHost } from 'lit';
33
import { html, LitElement } from 'lit';
44
import { property } from 'lit/decorators.js';
5+
import { fromBase64ToString } from '@env/base64';
56
import type { CustomEditorIds, WebviewIds, WebviewViewIds } from '../../../constants.views';
67
import type { Deferrable } from '../../../system/function/debounce';
78
import { debounce } from '../../../system/function/debounce';
@@ -55,8 +56,8 @@ export abstract class GlAppHost<
5556
@provide({ context: telemetryContext })
5657
protected _telemetry!: TelemetryContext;
5758

58-
@property({ type: Object, noAccessor: true })
59-
private bootstrap!: State;
59+
@property({ type: String, noAccessor: true })
60+
private bootstrap!: string;
6061
protected onThemeUpdated?(e: ThemeChangeEvent): void;
6162

6263
get state(): State {
@@ -83,7 +84,7 @@ export abstract class GlAppHost<
8384
this._ipc = new HostIpc(this.name);
8485
this._ipc.sendCommand(WebviewReadyCommand, undefined);
8586

86-
const state = this.bootstrap;
87+
const state: State = JSON.parse(fromBase64ToString(this.bootstrap));
8788
this.bootstrap = undefined!;
8889
this._ipc.replaceIpcPromisesWithPromises(state);
8990
this.onPersistState?.(state);

0 commit comments

Comments
 (0)