mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-18 01:25:37 -05:00
* Fix initial build breaks from 1.67 merge (#2514) * Update yarn lock files * Update build scripts * Fix tsconfig * Build breaks * WIP * Update yarn lock files * Misc breaks * Updates to package.json * Breaks * Update yarn * Fix breaks * Breaks * Build breaks * Breaks * Breaks * Breaks * Breaks * Breaks * Missing file * Breaks * Breaks * Breaks * Breaks * Breaks * Fix several runtime breaks (#2515) * Missing files * Runtime breaks * Fix proxy ordering issue * Remove commented code * Fix breaks with opening query editor * Fix post merge break * Updates related to setup build and other breaks (#2516) * Fix bundle build issues * Update distro * Fix distro merge and update build JS files * Disable pipeline steps * Remove stats call * Update license name * Make new RPM dependencies a warning * Fix extension manager version checks * Update JS file * Fix a few runtime breaks * Fixes * Fix runtime issues * Fix build breaks * Update notebook tests (part 1) * Fix broken tests * Linting errors * Fix hygiene * Disable lint rules * Bump distro * Turn off smoke tests * Disable integration tests * Remove failing "activate" test * Remove failed test assertion * Disable other broken test * Disable query history tests * Disable extension unit tests * Disable failing tasks
248 lines
7.6 KiB
TypeScript
248 lines
7.6 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import Logger from './logger';
|
|
import { Event, EventEmitter, ExtensionContext, SecretStorage, SecretStorageChangeEvent } from 'vscode';
|
|
|
|
export interface IDidChangeInOtherWindowEvent<T> {
|
|
added: string[];
|
|
updated: string[];
|
|
removed: Array<{ key: string; value: T }>;
|
|
}
|
|
|
|
export class BetterTokenStorage<T> {
|
|
// set before and after _tokensPromise is set so getTokens can handle multiple operations.
|
|
private _operationInProgress = false;
|
|
// the current state. Don't use this directly and call getTokens() so that you ensure you
|
|
// have awaited for all operations.
|
|
private _tokensPromise: Promise<Map<string, T>> = Promise.resolve(new Map());
|
|
|
|
// The vscode SecretStorage instance for this extension.
|
|
private readonly _secretStorage: SecretStorage;
|
|
|
|
private _didChangeInOtherWindow = new EventEmitter<IDidChangeInOtherWindowEvent<T>>();
|
|
public onDidChangeInOtherWindow: Event<IDidChangeInOtherWindowEvent<T>> = this._didChangeInOtherWindow.event;
|
|
|
|
/**
|
|
*
|
|
* @param keylistKey The key in the secret storage that will hold the list of keys associated with this instance of BetterTokenStorage
|
|
* @param context the vscode Context used to register disposables and retreive the vscode.SecretStorage for this instance of VS Code
|
|
*/
|
|
constructor(private keylistKey: string, context: ExtensionContext) {
|
|
this._secretStorage = context.secrets;
|
|
context.subscriptions.push(context.secrets.onDidChange((e) => this.handleSecretChange(e)));
|
|
this.initialize();
|
|
}
|
|
|
|
private initialize(): void {
|
|
this._operationInProgress = true;
|
|
this._tokensPromise = new Promise((resolve, _) => {
|
|
this._secretStorage.get(this.keylistKey).then(
|
|
keyListStr => {
|
|
if (!keyListStr) {
|
|
resolve(new Map());
|
|
return;
|
|
}
|
|
|
|
const keyList: Array<string> = JSON.parse(keyListStr);
|
|
// Gather promises that contain key value pairs our of secret storage
|
|
const promises = keyList.map(key => new Promise<{ key: string; value: string | undefined }>((res, rej) => {
|
|
this._secretStorage.get(key).then((value) => {
|
|
res({ key, value });
|
|
}, rej);
|
|
}));
|
|
Promise.allSettled(promises).then((results => {
|
|
const tokens = new Map<string, T>();
|
|
results.forEach(p => {
|
|
if (p.status === 'fulfilled' && p.value.value) {
|
|
const secret = this.parseSecret(p.value.value);
|
|
tokens.set(p.value.key, secret);
|
|
} else if (p.status === 'rejected') {
|
|
Logger.error(p.reason);
|
|
} else {
|
|
Logger.error('Key was not found in SecretStorage.');
|
|
}
|
|
});
|
|
resolve(tokens);
|
|
}));
|
|
},
|
|
err => {
|
|
Logger.error(err);
|
|
resolve(new Map());
|
|
});
|
|
});
|
|
this._operationInProgress = false;
|
|
}
|
|
|
|
async get(key: string): Promise<T | undefined> {
|
|
const tokens = await this.getTokens();
|
|
return tokens.get(key);
|
|
}
|
|
|
|
async getAll(): Promise<T[]> {
|
|
const tokens = await this.getTokens();
|
|
const values = new Array<T>();
|
|
for (const [_, value] of tokens) {
|
|
values.push(value);
|
|
}
|
|
return values;
|
|
}
|
|
|
|
async store(key: string, value: T): Promise<void> {
|
|
const tokens = await this.getTokens();
|
|
|
|
const isAddition = !tokens.has(key);
|
|
tokens.set(key, value);
|
|
const valueStr = this.serializeSecret(value);
|
|
this._operationInProgress = true;
|
|
this._tokensPromise = new Promise((resolve, _) => {
|
|
const promises = [this._secretStorage.store(key, valueStr)];
|
|
|
|
// if we are adding a secret we need to update the keylist too
|
|
if (isAddition) {
|
|
promises.push(this.updateKeyList(tokens));
|
|
}
|
|
|
|
Promise.allSettled(promises).then(results => {
|
|
results.forEach(r => {
|
|
if (r.status === 'rejected') {
|
|
Logger.error(r.reason);
|
|
}
|
|
});
|
|
resolve(tokens);
|
|
});
|
|
});
|
|
this._operationInProgress = false;
|
|
}
|
|
|
|
async delete(key: string): Promise<void> {
|
|
const tokens = await this.getTokens();
|
|
if (!tokens.has(key)) {
|
|
return;
|
|
}
|
|
tokens.delete(key);
|
|
|
|
this._operationInProgress = true;
|
|
this._tokensPromise = new Promise((resolve, _) => {
|
|
Promise.allSettled([
|
|
this._secretStorage.delete(key),
|
|
this.updateKeyList(tokens)
|
|
]).then(results => {
|
|
results.forEach(r => {
|
|
if (r.status === 'rejected') {
|
|
Logger.error(r.reason);
|
|
}
|
|
});
|
|
resolve(tokens);
|
|
});
|
|
});
|
|
this._operationInProgress = false;
|
|
}
|
|
|
|
async deleteAll(): Promise<void> {
|
|
const tokens = await this.getTokens();
|
|
const promises = [];
|
|
for (const [key] of tokens) {
|
|
promises.push(this.delete(key));
|
|
}
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
private async updateKeyList(tokens: Map<string, T>) {
|
|
const keyList = [];
|
|
for (const [key] of tokens) {
|
|
keyList.push(key);
|
|
}
|
|
|
|
const keyListStr = JSON.stringify(keyList);
|
|
await this._secretStorage.store(this.keylistKey, keyListStr);
|
|
}
|
|
|
|
protected parseSecret(secret: string): T {
|
|
return JSON.parse(secret);
|
|
}
|
|
|
|
protected serializeSecret(secret: T): string {
|
|
return JSON.stringify(secret);
|
|
}
|
|
|
|
// This is the one way to get tokens to ensure all other operations that
|
|
// came before you have been processed.
|
|
private async getTokens(): Promise<Map<string, T>> {
|
|
let tokens;
|
|
do {
|
|
tokens = await this._tokensPromise;
|
|
} while (this._operationInProgress);
|
|
return tokens;
|
|
}
|
|
|
|
// This is a crucial function that handles whether or not the token has changed in
|
|
// a different window of VS Code and sends the necessary event if it has.
|
|
// Scenarios this should cover:
|
|
// * Added in another window
|
|
// * Updated in another window
|
|
// * Deleted in another window
|
|
// * Added in this window
|
|
// * Updated in this window
|
|
// * Deleted in this window
|
|
private async handleSecretChange(e: SecretStorageChangeEvent) {
|
|
const key = e.key;
|
|
|
|
// The KeyList is only a list of keys to aid initial start up of VS Code to know which
|
|
// Keys are associated with this handler.
|
|
if (key === this.keylistKey) {
|
|
return;
|
|
}
|
|
const tokens = await this.getTokens();
|
|
|
|
this._operationInProgress = true;
|
|
this._tokensPromise = new Promise((resolve, _) => {
|
|
this._secretStorage.get(key).then(
|
|
storageSecretStr => {
|
|
if (!storageSecretStr) {
|
|
// true -> secret was deleted in another window
|
|
// false -> secret was deleted in this window
|
|
if (tokens.has(key)) {
|
|
const value = tokens.get(key)!;
|
|
tokens.delete(key);
|
|
this._didChangeInOtherWindow.fire({ added: [], updated: [], removed: [{ key, value }] });
|
|
}
|
|
return tokens;
|
|
}
|
|
|
|
const storageSecret = this.parseSecret(storageSecretStr);
|
|
const cachedSecret = tokens.get(key);
|
|
|
|
if (!cachedSecret) {
|
|
// token was added in another window
|
|
tokens.set(key, storageSecret);
|
|
this._didChangeInOtherWindow.fire({ added: [key], updated: [], removed: [] });
|
|
return tokens;
|
|
}
|
|
|
|
const cachedSecretStr = this.serializeSecret(cachedSecret);
|
|
if (storageSecretStr !== cachedSecretStr) {
|
|
// token was updated in another window
|
|
tokens.set(key, storageSecret);
|
|
this._didChangeInOtherWindow.fire({ added: [], updated: [key], removed: [] });
|
|
}
|
|
|
|
// what's in our token cache and what's in storage must be the same
|
|
// which means this should cover the last two scenarios of
|
|
// Added in this window & Updated in this window.
|
|
return tokens;
|
|
},
|
|
err => {
|
|
Logger.error(err);
|
|
resolve(tokens);
|
|
}).then(resolve, err => {
|
|
Logger.error(err);
|
|
resolve(tokens);
|
|
});
|
|
});
|
|
this._operationInProgress = false;
|
|
}
|
|
}
|