Skip to content

Commit

Permalink
Refactor license verification code -- still can't verify it as sim-se…
Browse files Browse the repository at this point in the history
…rver always returns 'corrupted' error
  • Loading branch information
kmagiera committed Dec 17, 2024
1 parent fec0053 commit a88850d
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 208 deletions.
9 changes: 0 additions & 9 deletions packages/vscode-extension/src/common/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,6 @@ export enum ActivateDeviceResult {
connectionFailed,
}

export enum RefreshLicenseTokenResult {
succeeded,
licenseExpired,
licenseCanceled,
unableToVerify,
seatRemoved,
connectionFailed,
}

export interface ProjectEventMap {
log: { type: string };
projectStateChanged: ProjectState;
Expand Down
66 changes: 9 additions & 57 deletions packages/vscode-extension/src/devices/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,24 @@ import { exec, ChildProcess, lineReader } from "../utilities/subprocess";
import { Logger } from "../Logger";
import { RecordingData, TouchPoint } from "../common/Project";
import { simulatorServerBinary } from "../utilities/simulatorServerBinary";
import { watchLicenseTokenChange, getLicenseToken } from "../utilities/license";

interface VideoRecordingPromiseHandlers {
resolve: (value: RecordingData) => void;
reject: (reason?: any) => void;
}

interface LicenseCheckPromiseHandler {
resolve: (value: SimServerLicenseValidationResult) => void;
reject: (reason?: SimServerLicenseValidationResult) => void;
}

export enum SimServerLicenseValidationResult {
Success,
Corrupted,
Expired,
FingerprintMismatch,
}

export class Preview implements Disposable {
private videoRecordingPromises = new Map<string, VideoRecordingPromiseHandlers>();
private lastLicenseCheckPromise: LicenseCheckPromiseHandler | undefined;
private subprocess?: ChildProcess;
private tokenChangeListener?: Disposable;
public streamURL?: string;

constructor(private args: string[]) {}

dispose() {
this.subprocess?.kill();
this.tokenChangeListener?.dispose();
}

private sendCommandOrThrow(command: string) {
Expand All @@ -42,30 +32,6 @@ export class Preview implements Disposable {
stdin.write(command);
}

public checkLicense(token: string): Promise<SimServerLicenseValidationResult> {
const stdin = this.subprocess?.stdin;
if (!stdin) {
throw new Error("sim-server process not available");
}

let resolvePromise: (value: SimServerLicenseValidationResult) => void;
let rejectPromise: (reason?: any) => void;
const promise = new Promise<SimServerLicenseValidationResult>((resolve, reject) => {
resolvePromise = resolve;
rejectPromise = reject;
});

const lastPromise = this.lastLicenseCheckPromise;
if (lastPromise) {
promise.then(lastPromise.resolve, lastPromise.reject);
}

const newPromiseHandler = { resolve: resolvePromise!, reject: rejectPromise! };
this.lastLicenseCheckPromise = newPromiseHandler;
stdin.write(`token ${token}\n`);
return promise;
}

private saveVideoWithID(videoId: string): Promise<RecordingData> {
const stdin = this.subprocess?.stdin;
if (!stdin) {
Expand Down Expand Up @@ -100,6 +66,12 @@ export class Preview implements Disposable {
});
this.subprocess = subprocess;

this.tokenChangeListener = watchLicenseTokenChange((token) => {
if (token) {
this.sendCommandOrThrow(`token ${token}\n`);
}
});

return new Promise<string>((resolve, reject) => {
subprocess.catch(reject).then(() => {
// we expect the preview server to produce a line with the URL
Expand Down Expand Up @@ -158,26 +130,6 @@ export class Preview implements Disposable {
} else if (handlers && videoErrorMatch) {
handlers.reject(new Error(videoErrorMatch[2]));
}
} else if (line.includes("token_valid") || line.includes("token_invalid")) {
if (line.includes("token_valid")) {
this.lastLicenseCheckPromise?.resolve(SimServerLicenseValidationResult.Success);
this.lastLicenseCheckPromise = undefined;
}
const failureReason = line.split(" ", 2)[1];

let result;

switch (failureReason) {
case "expired":
result = SimServerLicenseValidationResult.Expired;
case "fingerprint_mismatch":
result = SimServerLicenseValidationResult.FingerprintMismatch;
case "corrupted":
default:
result = SimServerLicenseValidationResult.Corrupted;
}
this.lastLicenseCheckPromise?.resolve(result);
this.lastLicenseCheckPromise = undefined;
}
Logger.info("sim-server:", line);
});
Expand Down
4 changes: 0 additions & 4 deletions packages/vscode-extension/src/project/deviceSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,6 @@ export class DeviceSession implements Disposable {
return previewUrl;
}

public async checkLicense(token: string) {
return this.device.checkLicense(token);
}

private async startDebugger() {
if (this.debugSession) {
this.debugSession.dispose();
Expand Down
95 changes: 15 additions & 80 deletions packages/vscode-extension/src/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
ProjectInterface,
ProjectState,
RecordingData,
RefreshLicenseTokenResult,
ReloadAction,
StartupMessage,
TouchPoint,
Expand All @@ -34,15 +33,17 @@ import { Devtools } from "./devtools";
import { AppEvent, DeviceSession, EventDelegate } from "./deviceSession";
import { BuildCache } from "../builders/BuildCache";
import { PanelLocation } from "../common/WorkspaceConfig";
import { activateDevice, getLicenseToken, refreshToken, removeLicense } from "../utilities/license";
import { isMoreThan24HoursAgo } from "../utilities/timeComparisons";
import { SimServerLicenseValidationResult } from "../devices/preview";
import {
activateDevice,
watchLicenseTokenChange,
getLicenseToken,
refreshTokenPeriodically,
} from "../utilities/license";

const DEVICE_SETTINGS_KEY = "device_settings_v4";
const LAST_SELECTED_DEVICE_KEY = "last_selected_device";
const PREVIEW_ZOOM_KEY = "preview_zoom";
const DEEP_LINKS_HISTORY_KEY = "deep_links_history";
const LAST_TOKEN_REFRESH_TIMESTAMP_KEY = "last_token_refresh_timestamp";

const DEEP_LINKS_HISTORY_LIMIT = 50;

Expand All @@ -60,6 +61,8 @@ export class Project
private isCachedBuildStale: boolean;

private fileWatcher: Disposable;
private licenseWatcher: Disposable;
private licenseUpdater: Disposable;

private deviceSession: DeviceSession | undefined;

Expand Down Expand Up @@ -100,6 +103,11 @@ export class Project
this.fileWatcher = watchProjectFiles(() => {
this.checkIfNativeChanged();
});
this.licenseUpdater = refreshTokenPeriodically();
this.licenseWatcher = watchLicenseTokenChange(async () => {
const hasActiveLicense = await this.hasActiveLicense();
this.eventEmitter.emit("licenseActivationChanged", hasActiveLicense);
});
}

//#region Build progress
Expand Down Expand Up @@ -282,6 +290,8 @@ export class Project
this.devtools?.dispose();
this.deviceManager.removeListener("deviceRemoved", this.removeDeviceListener);
this.fileWatcher.dispose();
this.licenseWatcher.dispose();
this.licenseUpdater.dispose();
}

private async reloadMetro() {
Expand Down Expand Up @@ -499,81 +509,9 @@ export class Project
public async activateLicense(activationKey: string) {
const computerName = os.hostname();
const activated = await activateDevice(activationKey, computerName);
if (activated === ActivateDeviceResult.succeeded) {
this.eventEmitter.emit("licenseActivationChanged", true);
}
return activated;
}

private async checkOrRefreshLicense() {
const oldToken = await getLicenseToken();
if (!oldToken) {
return false;
}

const refresh = async () => {
const res = await refreshToken(oldToken);
switch (res) {
case RefreshLicenseTokenResult.succeeded:
extensionContext.globalState.update(LAST_TOKEN_REFRESH_TIMESTAMP_KEY, new Date());
return true;
case RefreshLicenseTokenResult.connectionFailed:
return false;
case RefreshLicenseTokenResult.licenseCanceled:
window.showInformationMessage(
"It looks like you have cancelled your subscription, to renew it visit customer portal, or contact your administration."
);
await removeLicense();
return false;
case RefreshLicenseTokenResult.licenseExpired:
window.showWarningMessage(
"It looks like you your license has expired, to renew it visit customer portal, or contact your administration."
);
await removeLicense();
return false;
case RefreshLicenseTokenResult.seatRemoved:
await removeLicense();
return false;
case RefreshLicenseTokenResult.unableToVerify:
return false;
}
};

const lastRefresh = extensionContext.globalState.get<Date>(LAST_TOKEN_REFRESH_TIMESTAMP_KEY);

if (!lastRefresh || isMoreThan24HoursAgo(lastRefresh)) {
const result = await refresh();
if (!result) {
return false;
}
}

const result = await this.deviceSession?.checkLicense(oldToken);
if (result === undefined) {
return false;
}

switch (result) {
case SimServerLicenseValidationResult.Success:
return true;
case SimServerLicenseValidationResult.Expired:
return await refresh();
case SimServerLicenseValidationResult.FingerprintMismatch:
window.showWarningMessage(
"It looks like you your license token is assigned for different device, please activate your device."
);
await removeLicense();
return false;
case SimServerLicenseValidationResult.Corrupted:
window.showWarningMessage(
"It looks like you your license token is corrupted, please activate your device."
);
await removeLicense();
default:
return false;
}
}

public async hasActiveLicense() {
return !!(await getLicenseToken());
}
Expand Down Expand Up @@ -721,9 +659,6 @@ export class Project
previewURL,
status: "running",
});

// This needs to be triggered here as we need to ensure that the simulator service was booted
this.checkOrRefreshLicense();
} catch (e) {
Logger.error("Couldn't start device session", e);

Expand Down
Loading

0 comments on commit a88850d

Please sign in to comment.