Implement new structure
This commit is contained in:
78
src/common/api.ts
Normal file
78
src/common/api.ts
Normal file
@ -0,0 +1,78 @@
|
||||
export interface Application {
|
||||
readonly comment?: string
|
||||
readonly directory?: string
|
||||
readonly exec?: string
|
||||
readonly icon?: string
|
||||
readonly loaded?: boolean
|
||||
readonly name: string
|
||||
readonly path: string
|
||||
readonly sessionId?: string
|
||||
}
|
||||
|
||||
export interface ApplicationsResponse {
|
||||
readonly applications: ReadonlyArray<Application>
|
||||
}
|
||||
|
||||
export enum SessionError {
|
||||
NotFound = 4000,
|
||||
FailedToStart,
|
||||
Starting,
|
||||
InvalidState,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface CreateSessionResponse {
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export interface ExecutableApplication extends Application {
|
||||
exec: string
|
||||
}
|
||||
|
||||
export const isExecutableApplication = (app: Application): app is ExecutableApplication => {
|
||||
return !!(app as ExecutableApplication).exec
|
||||
}
|
||||
|
||||
export interface RunningApplication extends ExecutableApplication {
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export const isRunningApplication = (app: Application): app is RunningApplication => {
|
||||
return !!(app as RunningApplication).sessionId
|
||||
}
|
||||
|
||||
export interface RecentResponse {
|
||||
readonly recent: ReadonlyArray<Application>
|
||||
readonly running: ReadonlyArray<RunningApplication>
|
||||
}
|
||||
|
||||
export interface FileEntry {
|
||||
readonly type: "file" | "directory"
|
||||
readonly name: string
|
||||
readonly size: number
|
||||
}
|
||||
|
||||
export interface FilesResponse {
|
||||
files: FileEntry[]
|
||||
}
|
||||
|
||||
export interface HealthRequest {
|
||||
readonly event: "health"
|
||||
}
|
||||
|
||||
export type ClientMessage = HealthRequest
|
||||
|
||||
export interface HealthResponse {
|
||||
readonly event: "health"
|
||||
readonly connections: number
|
||||
}
|
||||
|
||||
export type ServerMessage = HealthResponse
|
||||
|
||||
export interface ReadyMessage {
|
||||
protocol: string
|
||||
}
|
40
src/common/emitter.ts
Normal file
40
src/common/emitter.ts
Normal file
@ -0,0 +1,40 @@
|
||||
export interface Disposable {
|
||||
dispose(): void
|
||||
}
|
||||
|
||||
export interface Event<T> {
|
||||
(listener: (value: T) => void): Disposable
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitter typecasts for a single event type.
|
||||
*/
|
||||
export class Emitter<T> {
|
||||
private listeners: Array<(value: T) => void> = []
|
||||
|
||||
public get event(): Event<T> {
|
||||
return (cb: (value: T) => void): Disposable => {
|
||||
this.listeners.push(cb)
|
||||
|
||||
return {
|
||||
dispose: (): void => {
|
||||
const i = this.listeners.indexOf(cb)
|
||||
if (i !== -1) {
|
||||
this.listeners.splice(i, 1)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event with a value.
|
||||
*/
|
||||
public emit(value: T): void {
|
||||
this.listeners.forEach((cb) => cb(value))
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.listeners = []
|
||||
}
|
||||
}
|
24
src/common/http.ts
Normal file
24
src/common/http.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export enum HttpCode {
|
||||
Ok = 200,
|
||||
Redirect = 302,
|
||||
NotFound = 404,
|
||||
BadRequest = 400,
|
||||
Unauthorized = 401,
|
||||
LargePayload = 413,
|
||||
ServerError = 500,
|
||||
}
|
||||
|
||||
export class HttpError extends Error {
|
||||
public constructor(message: string, public readonly code: number) {
|
||||
super(message)
|
||||
this.name = this.constructor.name
|
||||
}
|
||||
}
|
||||
|
||||
export enum ApiEndpoint {
|
||||
applications = "/applications",
|
||||
files = "/files",
|
||||
login = "/login",
|
||||
recent = "/recent",
|
||||
session = "/session",
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import { Event } from "vs/base/common/event";
|
||||
import { IChannel, IServerChannel } from "vs/base/parts/ipc/common/ipc";
|
||||
import { createDecorator } from "vs/platform/instantiation/common/instantiation";
|
||||
import { ReadWriteConnection } from "vs/server/node_modules/@coder/node-browser/out/common/connection";
|
||||
|
||||
export const INodeProxyService = createDecorator<INodeProxyService>("nodeProxyService");
|
||||
|
||||
export interface INodeProxyService extends ReadWriteConnection {
|
||||
_serviceBrand: any;
|
||||
send(message: string): void;
|
||||
onMessage: Event<string>;
|
||||
onUp: Event<void>;
|
||||
onClose: Event<void>;
|
||||
onDown: Event<void>;
|
||||
}
|
||||
|
||||
export class NodeProxyChannel implements IServerChannel {
|
||||
constructor(private service: INodeProxyService) {}
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
switch (event) {
|
||||
case "onMessage": return this.service.onMessage;
|
||||
}
|
||||
throw new Error(`Invalid listen ${event}`);
|
||||
}
|
||||
|
||||
async call(_: unknown, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case "send": return this.service.send(args[0]);
|
||||
}
|
||||
throw new Error(`Invalid call ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeProxyChannelClient {
|
||||
_serviceBrand: any;
|
||||
|
||||
public readonly onMessage: Event<string>;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
this.onMessage = this.channel.listen<string>("onMessage");
|
||||
}
|
||||
|
||||
public send(data: string): void {
|
||||
this.channel.call("send", [data]);
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
import { ITelemetryData } from "vs/base/common/actions";
|
||||
import { Event } from "vs/base/common/event";
|
||||
import { IChannel, IServerChannel } from "vs/base/parts/ipc/common/ipc";
|
||||
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from "vs/platform/telemetry/common/gdprTypings";
|
||||
import { ITelemetryInfo, ITelemetryService } from "vs/platform/telemetry/common/telemetry";
|
||||
|
||||
export class TelemetryChannel implements IServerChannel {
|
||||
constructor(private service: ITelemetryService) {}
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Invalid listen ${event}`);
|
||||
}
|
||||
|
||||
call(_: unknown, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case "publicLog": return this.service.publicLog(args[0], args[1], args[2]);
|
||||
case "publicLog2": return this.service.publicLog2(args[0], args[1], args[2]);
|
||||
case "setEnabled": return Promise.resolve(this.service.setEnabled(args[0]));
|
||||
case "getTelemetryInfo": return this.service.getTelemetryInfo();
|
||||
}
|
||||
throw new Error(`Invalid call ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class TelemetryChannelClient implements ITelemetryService {
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(private readonly channel: IChannel) {}
|
||||
|
||||
public publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise<void> {
|
||||
return this.channel.call("publicLog", [eventName, data, anonymizeFilePaths]);
|
||||
}
|
||||
|
||||
public publicLog2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>, anonymizeFilePaths?: boolean): Promise<void> {
|
||||
return this.channel.call("publicLog2", [eventName, data, anonymizeFilePaths]);
|
||||
}
|
||||
|
||||
public setEnabled(value: boolean): void {
|
||||
this.channel.call("setEnable", [value]);
|
||||
}
|
||||
|
||||
public getTelemetryInfo(): Promise<ITelemetryInfo> {
|
||||
return this.channel.call("getTelemetryInfo");
|
||||
}
|
||||
|
||||
public get isOptedIn(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,10 +1,48 @@
|
||||
import { logger } from "@coder/logger"
|
||||
|
||||
export interface Options {
|
||||
logLevel?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a string up to the delimiter. If the delimiter doesn't exist the first
|
||||
* item will have all the text and the second item will be an empty string.
|
||||
*/
|
||||
export const split = (str: string, delimiter: string): [string, string] => {
|
||||
const index = str.indexOf(delimiter);
|
||||
return index !== -1
|
||||
? [str.substring(0, index).trim(), str.substring(index + 1)]
|
||||
: [str, ""];
|
||||
};
|
||||
const index = str.indexOf(delimiter)
|
||||
return index !== -1 ? [str.substring(0, index).trim(), str.substring(index + 1)] : [str, ""]
|
||||
}
|
||||
|
||||
export const plural = (count: number): string => (count === 1 ? "" : "s")
|
||||
|
||||
export const generateUuid = (length = 24): string => {
|
||||
const possible = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
return Array(length)
|
||||
.fill(1)
|
||||
.map(() => possible[Math.floor(Math.random() * possible.length)])
|
||||
.join("")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options embedded in the HTML from the server.
|
||||
*/
|
||||
export const getOptions = <T extends Options>(): T => {
|
||||
const el = document.getElementById("coder-options")
|
||||
try {
|
||||
if (!el) {
|
||||
throw new Error("no options element")
|
||||
}
|
||||
const value = el.getAttribute("data-settings")
|
||||
if (!value) {
|
||||
throw new Error("no options value")
|
||||
}
|
||||
const options = JSON.parse(value)
|
||||
if (typeof options.logLevel !== "undefined") {
|
||||
logger.level = options.logLevel
|
||||
}
|
||||
return options
|
||||
} catch (error) {
|
||||
logger.warn(error.message)
|
||||
return {} as T
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user