diff --git a/package.json b/package.json index 2f762db75..353090c10 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,15 @@ "main": "./out/extension.js", "runme": { "features": { + "HostedPlayground": { + "enabled": false, + "conditions": { + "enabledForExtensions": { + "stateful.platform": false, + "stateful.runme": true + } + } + }, "NewTreeProvider": { "enabled": false, "conditions": { diff --git a/src/extension/extension.ts b/src/extension/extension.ts index 6482d5277..14f2ae2a8 100644 --- a/src/extension/extension.ts +++ b/src/extension/extension.ts @@ -94,6 +94,7 @@ import getLogger from './logger' import { EnvironmentManager } from './environment/manager' import ContextState from './contextState' import { RunmeIdentity } from './grpc/serializerTypes' +import * as features from './features' export class RunmeExtension { protected serializer?: SerializerBase @@ -532,6 +533,12 @@ export class RunmeExtension { }) } }) + + // only ever enabled in hosted playground + if (features.isOnInContextState(FeatureName.HostedPlayground)) { + await features.addTrustedDomains() + await features.autoOpenTerminal() + } } protected handleMasking(kernel: Kernel, maskingIsOn: boolean): (e: NotebookUiEvent) => void { diff --git a/src/extension/features/addTrustedDomains.ts b/src/extension/features/addTrustedDomains.ts new file mode 100644 index 000000000..8b5dbca81 --- /dev/null +++ b/src/extension/features/addTrustedDomains.ts @@ -0,0 +1,37 @@ +import { Uri, workspace } from 'vscode' +import { parse as jsoncParse } from 'jsonc-parser' + +import { FeatureName } from '../../types' +import getLogger from '../logger' + +import { isOnInContextState } from '.' + +const logger = getLogger('AddTrustedDomains') +const trustedDomain = 'https://*.stateful.com' + +export async function addTrustedDomains() { + if (!isOnInContextState(FeatureName.HostedPlayground)) { + return + } + + try { + const uri = Uri.parse('trustedDomains:/Trusted Domains') + const bytes = await workspace.fs.readFile(uri) + const json = jsoncParse(Buffer.from(bytes).toString('utf8')) as string[] + const isTrusted = json.some((entry) => entry === trustedDomain) + + if (!isTrusted) { + json.push(trustedDomain) + await workspace.fs.writeFile(uri, Buffer.from(JSON.stringify(json))) + } + } catch (error) { + let message + if (error instanceof Error) { + message = error.message + } else { + message = JSON.stringify(error) + } + + logger.error(message) + } +} diff --git a/src/extension/features/autoOpenTerminal.ts b/src/extension/features/autoOpenTerminal.ts new file mode 100644 index 000000000..c78746af7 --- /dev/null +++ b/src/extension/features/autoOpenTerminal.ts @@ -0,0 +1,17 @@ +import { commands } from 'vscode' + +import { FeatureName } from '../../types' +import ContextState from '../contextState' + +import { isOnInContextState } from '.' + +export async function autoOpenTerminal() { + if (!isOnInContextState(FeatureName.HostedPlayground)) { + return + } + + if (!ContextState.getKey(`${FeatureName.HostedPlayground}.autoOpenTerminal`)) { + await commands.executeCommand('workbench.action.terminal.new') + await ContextState.addKey(`${FeatureName.HostedPlayground}.autoOpenTerminal`, true) + } +} diff --git a/src/extension/features.ts b/src/extension/features/index.ts similarity index 72% rename from src/extension/features.ts rename to src/extension/features/index.ts index ef665c0fc..dfb8411cf 100644 --- a/src/extension/features.ts +++ b/src/extension/features/index.ts @@ -1,7 +1,9 @@ -import features, { FEATURES_CONTEXT_STATE_KEY } from '../features' -import { FeatureName } from '../types' +export * from './addTrustedDomains' +export * from './autoOpenTerminal' -import ContextState from './contextState' +import features, { FEATURES_CONTEXT_STATE_KEY } from '../../features' +import { FeatureName } from '../../types' +import ContextState from '../contextState' export function isOnInContextState(featureName: FeatureName): boolean { const snapshot = ContextState.getKey(FEATURES_CONTEXT_STATE_KEY) @@ -14,10 +16,6 @@ export function isOnInContextState(featureName: FeatureName): boolean { return features.isOn(featureName, featureState$) } -export default { - isOnInContextState, -} - export function getFeaturesContext() { const snapshot = ContextState.getKey(FEATURES_CONTEXT_STATE_KEY) if (!snapshot) { @@ -28,3 +26,7 @@ export function getFeaturesContext() { return featureState$.getValue().context } + +export default { + isOnInContextState, +} diff --git a/src/types.ts b/src/types.ts index ec453b969..00c290348 100644 --- a/src/types.ts +++ b/src/types.ts @@ -772,6 +772,7 @@ export enum FeatureName { RequireStatefulAuth = 'RequireStatefulAuth', CopySelectionToClipboard = 'CopySelectionToClipboard', NewTreeProvider = 'NewTreeProvider', + HostedPlayground = 'HostedPlayground', } export type Feature = { diff --git a/tests/extension/features.test.ts b/tests/extension/features.test.ts index 9d5c80bf8..028b4bec4 100644 --- a/tests/extension/features.test.ts +++ b/tests/extension/features.test.ts @@ -190,3 +190,33 @@ describe('Feature Store', () => { expect(features.isOn(FeatureName.Gist, featureState$)).toBe(true) }) }) + +describe('Hosted Playground', () => { + let featureState$: FeatureObserver + + beforeEach(() => { + featureState$ = features.loadState(packageJSON, { + os: 'linux', + }) + }) + + it('should always be default disabled in Runme', () => { + const initialContext: FeatureContext = { + extensionId: ExtensionName.StatefulRunme, + } + + features.updateState(featureState$, initialContext) + + expect(features.isOn(FeatureName.HostedPlayground, featureState$)).toBe(false) + }) + + it('should always be default disabled in Stateful', () => { + const initialContext: FeatureContext = { + extensionId: ExtensionName.StatefulPlatform, + } + + features.updateState(featureState$, initialContext) + + expect(features.isOn(FeatureName.HostedPlayground, featureState$)).toBe(false) + }) +})