Skip to content

Commit cb62a2b

Browse files
committed
First draft for an vscode extension api
Signed-off-by: Jonah Iden <jonah.iden@typefox.io>
1 parent e60af44 commit cb62a2b

3 files changed

Lines changed: 200 additions & 0 deletions

File tree

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// ******************************************************************************
2+
// Copyright 2024 TypeFox GmbH
3+
// This program and the accompanying materials are made available under the
4+
// terms of the MIT License, which is available in the project root.
5+
// ******************************************************************************
6+
7+
import * as vscode from 'vscode';
8+
import { CollaborationInstance, PendingUser, PeerWithColor } from './collaboration-instance.js';
9+
import { CollaborationRoomService } from './collaboration-room-service.js';
10+
11+
export type OpenCollaborationApiCapability =
12+
| 'session.lifecycle'
13+
| 'session.users'
14+
| 'session.pending-users'
15+
| 'session.permissions';
16+
17+
export interface OpenCollaborationConnection {
18+
readonly roomId: string;
19+
readonly serverUrl: string;
20+
readonly host: boolean;
21+
readonly ready: Promise<void>;
22+
readonly permissions: Readonly<{ readonly: boolean }>;
23+
readonly onDidUsersChange: vscode.Event<void>;
24+
readonly onDidPendingUsersChange: vscode.Event<void>;
25+
readonly onDidClose: vscode.Event<void>;
26+
getConnectedUsers(): Promise<readonly PeerWithColor[]>;
27+
getPendingUsers(): readonly PendingUser[];
28+
leave(): Promise<void>;
29+
}
30+
31+
export interface ConnectionClosedEvent {
32+
readonly roomId: string;
33+
readonly serverUrl: string;
34+
readonly host: boolean;
35+
}
36+
37+
export interface OpenCollaborationApiV1 extends vscode.Disposable {
38+
readonly apiVersion: 1;
39+
readonly capabilities: readonly OpenCollaborationApiCapability[];
40+
readonly onDidOpenConnection: vscode.Event<OpenCollaborationConnection>;
41+
readonly onDidCloseConnection: vscode.Event<ConnectionClosedEvent>;
42+
readonly onDidChangeConnection: vscode.Event<OpenCollaborationConnection | undefined>;
43+
getCurrentConnection(): OpenCollaborationConnection | undefined;
44+
createRoom(): Promise<void>;
45+
joinRoom(roomId?: string): Promise<void>;
46+
leaveCurrentRoom(): Promise<boolean>;
47+
}
48+
49+
class OpenCollaborationConnectionImpl implements OpenCollaborationConnection {
50+
51+
constructor(private readonly instance: CollaborationInstance) {
52+
}
53+
54+
get roomId(): string {
55+
return this.instance.roomId;
56+
}
57+
58+
get serverUrl(): string {
59+
return this.instance.serverUrl;
60+
}
61+
62+
get host(): boolean {
63+
return this.instance.host;
64+
}
65+
66+
get ready(): Promise<void> {
67+
return this.instance.ready;
68+
}
69+
70+
get permissions(): Readonly<{ readonly: boolean }> {
71+
return this.instance.permissions;
72+
}
73+
74+
get onDidUsersChange(): vscode.Event<void> {
75+
return this.instance.onDidUsersChange;
76+
}
77+
78+
get onDidPendingUsersChange(): vscode.Event<void> {
79+
return this.instance.onDidPendingChange;
80+
}
81+
82+
get onDidClose(): vscode.Event<void> {
83+
return this.instance.onDidDispose;
84+
}
85+
86+
getConnectedUsers(): Promise<readonly PeerWithColor[]> {
87+
return this.instance.connectedUsers;
88+
}
89+
90+
getPendingUsers(): readonly PendingUser[] {
91+
return this.instance.pendingUsers;
92+
}
93+
94+
async leave(): Promise<void> {
95+
await this.instance.leave();
96+
}
97+
}
98+
99+
class OpenCollaborationApiV1Impl implements OpenCollaborationApiV1 {
100+
101+
readonly apiVersion = 1 as const;
102+
readonly capabilities: readonly OpenCollaborationApiCapability[] = [
103+
'session.lifecycle',
104+
'session.users',
105+
'session.pending-users',
106+
'session.permissions'
107+
];
108+
109+
private readonly onDidOpenConnectionEmitter = new vscode.EventEmitter<OpenCollaborationConnection>();
110+
readonly onDidOpenConnection = this.onDidOpenConnectionEmitter.event;
111+
112+
private readonly onDidCloseConnectionEmitter = new vscode.EventEmitter<ConnectionClosedEvent>();
113+
readonly onDidCloseConnection = this.onDidCloseConnectionEmitter.event;
114+
115+
private readonly onDidChangeConnectionEmitter = new vscode.EventEmitter<OpenCollaborationConnection | undefined>();
116+
readonly onDidChangeConnection = this.onDidChangeConnectionEmitter.event;
117+
118+
private readonly toDispose: vscode.Disposable[] = [];
119+
private currentInstance: CollaborationInstance | undefined;
120+
121+
constructor(private readonly roomService: CollaborationRoomService) {
122+
this.toDispose.push(this.onDidOpenConnectionEmitter);
123+
this.toDispose.push(this.onDidCloseConnectionEmitter);
124+
this.toDispose.push(this.onDidChangeConnectionEmitter);
125+
this.toDispose.push(this.roomService.onDidJoinRoom(instance => this.onDidJoinRoom(instance)));
126+
127+
// Handle already-active sessions during extension reactivation.
128+
if (CollaborationInstance.Current) {
129+
this.onDidJoinRoom(CollaborationInstance.Current);
130+
}
131+
}
132+
133+
getCurrentConnection(): OpenCollaborationConnection | undefined {
134+
const current = this.currentInstance;
135+
if (!current) {
136+
return undefined;
137+
}
138+
return new OpenCollaborationConnectionImpl(current);
139+
}
140+
141+
async createRoom(): Promise<void> {
142+
await this.roomService.createRoom();
143+
}
144+
145+
async joinRoom(roomId?: string): Promise<void> {
146+
await this.roomService.joinRoom(roomId);
147+
}
148+
149+
async leaveCurrentRoom(): Promise<boolean> {
150+
const current = this.currentInstance;
151+
if (!current) {
152+
return false;
153+
}
154+
await current.leave();
155+
return true;
156+
}
157+
158+
dispose(): void {
159+
for (const disposable of this.toDispose.splice(0, this.toDispose.length)) {
160+
disposable.dispose();
161+
}
162+
}
163+
164+
private onDidJoinRoom(instance: CollaborationInstance): void {
165+
this.currentInstance = instance;
166+
const connection = new OpenCollaborationConnectionImpl(instance);
167+
this.onDidOpenConnectionEmitter.fire(connection);
168+
this.onDidChangeConnectionEmitter.fire(connection);
169+
170+
const closeDisposable = instance.onDidDispose(() => {
171+
if (this.currentInstance !== instance) {
172+
return;
173+
}
174+
this.currentInstance = undefined;
175+
this.onDidCloseConnectionEmitter.fire({
176+
roomId: instance.roomId,
177+
serverUrl: instance.serverUrl,
178+
host: instance.host
179+
});
180+
this.onDidChangeConnectionEmitter.fire(undefined);
181+
closeDisposable.dispose();
182+
});
183+
this.toDispose.push(closeDisposable);
184+
}
185+
}
186+
187+
export function createOpenCollaborationApi(roomService: CollaborationRoomService): OpenCollaborationApiV1 {
188+
return new OpenCollaborationApiV1Impl(roomService);
189+
}

packages/open-collaboration-vscode/src/extension-web.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { closeSharedEditors, removeWorkspaceFolders } from './utils/workspace.js
1212
import { createContainer } from './inversify.js';
1313
import { Commands } from './commands.js';
1414
import { Fetch } from './collaboration-connection-provider.js';
15+
import { CollaborationRoomService } from './collaboration-room-service.js';
16+
import { createOpenCollaborationApi, OpenCollaborationApiV1 } from './api.js';
1517

1618
initializeProtocol({
1719
cryptoModule: globalThis.crypto
@@ -22,6 +24,10 @@ export async function activate(context: vscode.ExtensionContext) {
2224
container.bind(Fetch).toConstantValue(fetch);
2325
const commands = container.get(Commands);
2426
commands.initialize();
27+
const roomService = container.get(CollaborationRoomService);
28+
const api = createOpenCollaborationApi(roomService);
29+
context.subscriptions.push(api);
30+
return api satisfies OpenCollaborationApiV1;
2531
}
2632

2733
export async function deactivate(): Promise<void> {

packages/open-collaboration-vscode/src/extension.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Commands } from './commands.js';
1616
import { Fetch } from './collaboration-connection-provider.js';
1717
import fetch from 'node-fetch';
1818
import { ChatWebview } from './chat-webview/chat-webview.js';
19+
import { createOpenCollaborationApi, OpenCollaborationApiV1 } from './api.js';
1920

2021
initializeProtocol({
2122
cryptoModule: crypto.webcrypto
@@ -28,6 +29,8 @@ export async function activate(context: vscode.ExtensionContext) {
2829
commands.initialize();
2930
container.get(ChatWebview).register();
3031
const roomService = container.get(CollaborationRoomService);
32+
const api = createOpenCollaborationApi(roomService);
33+
context.subscriptions.push(api);
3134

3235
const connection = await roomService.tryConnect();
3336
if (connection) {
@@ -38,6 +41,8 @@ export async function activate(context: vscode.ExtensionContext) {
3841
await closeSharedEditors();
3942
removeWorkspaceFolders();
4043
}
44+
45+
return api satisfies OpenCollaborationApiV1;
4146
}
4247

4348
export async function deactivate(): Promise<void> {

0 commit comments

Comments
 (0)