diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 40670441412c3..ab0d2b6a7813a 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -141,6 +141,7 @@ export interface EnvInit { shell: string; uiKind: UIKind, appName: string; + appHost: string; } export interface PluginAPI { diff --git a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts index bd69ccf2ac528..013340b9adaaa 100644 --- a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts +++ b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts @@ -488,6 +488,7 @@ export class HostedPluginSupport { return undefined; } + const isElectron = environment.electron.is(); await manager.$init({ preferences: getPreferences(this.preferenceProviderProvider, this.workspaceService.tryGetRoots()), globalState, @@ -496,8 +497,9 @@ export class HostedPluginSupport { queryParams: getQueryParameters(), language: nls.locale || 'en', shell: defaultShell, - uiKind: environment.electron.is() ? UIKind.Desktop : UIKind.Web, - appName: FrontendApplicationConfigProvider.get().applicationName + uiKind: isElectron ? UIKind.Desktop : UIKind.Web, + appName: FrontendApplicationConfigProvider.get().applicationName, + appHost: isElectron ? 'desktop' : 'web' // TODO: 'web' could be the embedder's name, e.g. 'github.dev' }, extApi, webview: { diff --git a/packages/plugin-ext/src/hosted/browser/worker/worker-env-ext.ts b/packages/plugin-ext/src/hosted/browser/worker/worker-env-ext.ts index f931c63ae9fcb..b2b931f64065a 100644 --- a/packages/plugin-ext/src/hosted/browser/worker/worker-env-ext.ts +++ b/packages/plugin-ext/src/hosted/browser/worker/worker-env-ext.ts @@ -34,4 +34,8 @@ export class WorkerEnvExtImpl extends EnvExtImpl { throw new Error('There is no app root in worker context'); } + get isNewAppInstall(): boolean { + return false; + } + } diff --git a/packages/plugin-ext/src/plugin/env.ts b/packages/plugin-ext/src/plugin/env.ts index 6441172d6a9bf..8033ca7c12b3b 100644 --- a/packages/plugin-ext/src/plugin/env.ts +++ b/packages/plugin-ext/src/plugin/env.ts @@ -14,6 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** +import { Emitter, Event } from '@theia/core/lib/common/event'; import * as theia from '@theia/plugin'; import { RPCProtocol } from '../common/rpc-protocol'; import { EnvMain, PLUGIN_RPC_CONTEXT } from '../common/plugin-api-rpc'; @@ -29,11 +30,18 @@ export abstract class EnvExtImpl { private ui: theia.UIKind; private envMachineId: string; private envSessionId: string; + private host: string; + private _isTelemetryEnabled: boolean; + private _remoteName: string | undefined; + private onDidChangeTelemetryEnabledEmitter = new Emitter(); constructor(rpc: RPCProtocol) { this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.ENV_MAIN); this.envSessionId = v4(); this.envMachineId = v4(); + // we don't support telemetry at the moment + this._isTelemetryEnabled = false; + this._remoteName = undefined; } getEnvVariable(envVarName: string): Promise { @@ -73,6 +81,10 @@ export abstract class EnvExtImpl { this.ui = uiKind; } + setAppHost(appHost: string): void { + this.host = appHost; + } + getClientOperatingSystem(): Promise { return this.proxy.$getClientOperatingSystem(); } @@ -83,6 +95,24 @@ export abstract class EnvExtImpl { abstract get appRoot(): string; + abstract get isNewAppInstall(): boolean; + + get appHost(): string { + return this.host; + } + + get isTelemetryEnabled(): boolean { + return this._isTelemetryEnabled; + } + + get onDidChangeTelemetryEnabled(): Event { + return this.onDidChangeTelemetryEnabledEmitter.event; + } + + get remoteName(): string | undefined { + return this._remoteName; + } + get language(): string { return this.lang; } diff --git a/packages/plugin-ext/src/plugin/node/env-node-ext.ts b/packages/plugin-ext/src/plugin/node/env-node-ext.ts index efdaaca491158..c1e6f75a5fee8 100644 --- a/packages/plugin-ext/src/plugin/node/env-node-ext.ts +++ b/packages/plugin-ext/src/plugin/node/env-node-ext.ts @@ -19,6 +19,7 @@ import { EnvExtImpl } from '../env'; import { RPCProtocol } from '../../common/rpc-protocol'; import { createHash } from 'crypto'; import { v4 } from 'uuid'; +import fs = require('fs'); /** * Provides machineId using mac address. It's only possible on node side @@ -27,6 +28,7 @@ import { v4 } from 'uuid'; export class EnvNodeExtImpl extends EnvExtImpl { private macMachineId: string; + private _isNewAppInstall: boolean; constructor(rpc: RPCProtocol) { super(rpc); @@ -37,7 +39,7 @@ export class EnvNodeExtImpl extends EnvExtImpl { this.macMachineId = createHash('sha256').update(macAddress, 'utf8').digest('hex'); } }); - + this._isNewAppInstall = this.computeIsNewAppInstall(); } /** @@ -54,4 +56,14 @@ export class EnvNodeExtImpl extends EnvExtImpl { return __dirname; } + get isNewAppInstall(): boolean { + return this._isNewAppInstall; + } + + private computeIsNewAppInstall(): boolean { + const creation = fs.statSync(__filename).birthtimeMs; + const current = Date.now(); + const dayMs = 24 * 3600 * 1000; + return (current - creation) < dayMs; + } } diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 91dd775f2360f..2765168513acd 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -639,7 +639,14 @@ export function createAPIFactory( const env: typeof theia.env = Object.freeze({ get appName(): string { return envExt.appName; }, get appRoot(): string { return envExt.appRoot; }, + get appHost(): string { return envExt.appHost; }, get language(): string { return envExt.language; }, + get isNewAppInstall(): boolean { return envExt.isNewAppInstall; }, + get isTelemetryEnabled(): boolean { return envExt.isTelemetryEnabled; }, + get onDidChangeTelemetryEnabled(): theia.Event { + return envExt.onDidChangeTelemetryEnabled; + }, + get remoteName(): string | undefined { return envExt.remoteName; }, get machineId(): string { return envExt.machineId; }, get sessionId(): string { return envExt.sessionId; }, get uriScheme(): string { return envExt.uriScheme; }, diff --git a/packages/plugin-ext/src/plugin/plugin-manager.ts b/packages/plugin-ext/src/plugin/plugin-manager.ts index 617f525dd5588..2f19285f0a598 100644 --- a/packages/plugin-ext/src/plugin/plugin-manager.ts +++ b/packages/plugin-ext/src/plugin/plugin-manager.ts @@ -201,6 +201,7 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { this.envExt.setShell(params.env.shell); this.envExt.setUIKind(params.env.uiKind); this.envExt.setApplicationName(params.env.appName); + this.envExt.setAppHost(params.env.appHost); this.preferencesManager.init(params.preferences); diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 802364d6bfc79..07770e0bb4c5c 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -6473,6 +6473,14 @@ export module '@theia/plugin' { */ export const appRoot: string; + /** + * The hosted location of the application + * On desktop this is 'desktop' + * In the web this is the specified embedder i.e. 'github.dev', 'codespaces', or 'web' if the embedder + * does not provide that information + */ + export const appHost: string; + /** * The custom uri scheme the editor registers to in the operating system. */ @@ -6483,6 +6491,35 @@ export module '@theia/plugin' { */ export const language: string; + /** + * Indicates that this is a fresh install of the application. + * `true` if within the first day of installation otherwise `false`. + */ + export const isNewAppInstall: boolean; + + /** + * Indicates whether the users has telemetry enabled. + * Can be observed to determine if the extension should send telemetry. + */ + export const isTelemetryEnabled: boolean; + + /** + * An {@link Event} which fires when the user enabled or disables telemetry. + * `true` if the user has enabled telemetry or `false` if the user has disabled telemetry. + */ + export const onDidChangeTelemetryEnabled: Event; + + /** + * The name of a remote. Defined by extensions, popular samples are `wsl` for the Windows + * Subsystem for Linux or `ssh-remote` for remotes using a secure shell. + * + * *Note* that the value is `undefined` when there is no remote extension host but that the + * value is defined in all extension hosts (local and remote) in case a remote extension host + * exists. Use {@link Extension.extensionKind} to know if + * a specific extension runs remote or not. + */ + export const remoteName: string | undefined; + /** * The detected default shell for the extension host. */