Skip to content

Commit 79ecb15

Browse files
committed
feat: oauth.
1 parent 66e426a commit 79ecb15

File tree

10 files changed

+375
-278
lines changed

10 files changed

+375
-278
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.DS_Store
12
.build
23
build
34
web_modules/

package.json

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "coding-plugin",
3-
"description": "Cat Coding - A Webview API Sample",
3+
"description": "Coding plugin for VS Code.",
44
"version": "0.0.1",
55
"publisher": "alcheung",
66
"license": "MIT",
@@ -12,9 +12,9 @@
1212
"Other"
1313
],
1414
"activationEvents": [
15-
"onCommand:catCoding.show",
16-
"onCommand:catCoding.login",
17-
"onWebviewPanel:catCoding",
15+
"onCommand:codingPlugin.show",
16+
"onCommand:codingPlugin.login",
17+
"onWebviewPanel:codingPlugin",
1818
"onView:treeviewSample"
1919
],
2020
"repository": {
@@ -25,14 +25,14 @@
2525
"contributes": {
2626
"commands": [
2727
{
28-
"command": "catCoding.show",
29-
"title": "Start cat coding session",
30-
"category": "Cat Coding"
28+
"command": "codingPlugin.show",
29+
"title": "Start coding session",
30+
"category": "Coding plugin"
3131
},
3232
{
33-
"command": "catCoding.login",
33+
"command": "codingPlugin.login",
3434
"title": "Login coding.net",
35-
"category": "Cat Coding"
35+
"category": "Coding plugin"
3636
}
3737
],
3838
"viewsContainers": {

src/coding.ts

+63-56
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { nanoid } from 'nanoid';
44
import { keychain } from './common/keychain';
55
import Logger from './common/logger'
66
import { CodingServer } from './codingServer';
7+
import { RepoInfo } from './typings/types';
78

89
interface SessionData {
910
id: string;
@@ -30,10 +31,16 @@ export const onDidChangeSessions = new vscode.EventEmitter<vscode.Authentication
3031
export class CodingAuthenticationProvider {
3132
private _sessions: vscode.AuthenticationSession[] = [];
3233
private _codingServer = new CodingServer();
33-
private _team: string;
34-
35-
public constructor(team: string) {
36-
this._team = team;
34+
private _repo: RepoInfo = {
35+
team: ``,
36+
project: ``,
37+
repo: ``,
38+
};
39+
40+
public constructor(repo: RepoInfo | null) {
41+
if (repo) {
42+
this._repo = repo;
43+
}
3744
}
3845

3946
public async initialize(context: vscode.ExtensionContext): Promise<void> {
@@ -47,45 +54,45 @@ export class CodingAuthenticationProvider {
4754
}
4855

4956
private async _checkForUpdates() {
50-
let storedSessions: vscode.AuthenticationSession[];
51-
try {
52-
storedSessions = await this._readSessions();
53-
} catch (e) {
54-
// Ignore, network request failed
55-
return;
56-
}
57-
58-
const added: string[] = [];
59-
const removed: string[] = [];
60-
61-
storedSessions.forEach(session => {
62-
const matchesExisting = this._sessions.some(s => s.id === session.id);
63-
// Another window added a session to the keychain, add it to our state as well
64-
if (!matchesExisting) {
65-
Logger.info('Adding session found in keychain');
66-
this._sessions.push(session);
67-
added.push(session.id);
68-
}
69-
});
70-
71-
this._sessions.map(session => {
72-
const matchesExisting = storedSessions.some(s => s.id === session.id);
73-
// Another window has logged out, remove from our state
74-
if (!matchesExisting) {
75-
Logger.info('Removing session no longer found in keychain');
76-
const sessionIndex = this._sessions.findIndex(s => s.id === session.id);
77-
if (sessionIndex > -1) {
78-
this._sessions.splice(sessionIndex, 1);
79-
}
80-
81-
removed.push(session.id);
82-
}
83-
});
84-
85-
if (added.length || removed.length) {
86-
onDidChangeSessions.fire({ added, removed, changed: [] });
87-
}
88-
}
57+
let storedSessions: vscode.AuthenticationSession[];
58+
try {
59+
storedSessions = await this._readSessions();
60+
} catch (e) {
61+
// Ignore, network request failed
62+
return;
63+
}
64+
65+
const added: string[] = [];
66+
const removed: string[] = [];
67+
68+
storedSessions.forEach(session => {
69+
const matchesExisting = this._sessions.some(s => s.id === session.id);
70+
// Another window added a session to the keychain, add it to our state as well
71+
if (!matchesExisting) {
72+
Logger.info('Adding session found in keychain');
73+
this._sessions.push(session);
74+
added.push(session.id);
75+
}
76+
});
77+
78+
this._sessions.map(session => {
79+
const matchesExisting = storedSessions.some(s => s.id === session.id);
80+
// Another window has logged out, remove from our state
81+
if (!matchesExisting) {
82+
Logger.info('Removing session no longer found in keychain');
83+
const sessionIndex = this._sessions.findIndex(s => s.id === session.id);
84+
if (sessionIndex > -1) {
85+
this._sessions.splice(sessionIndex, 1);
86+
}
87+
88+
removed.push(session.id);
89+
}
90+
});
91+
92+
if (added.length || removed.length) {
93+
onDidChangeSessions.fire({ added, removed, changed: [] });
94+
}
95+
}
8996

9097
private async _readSessions(): Promise<vscode.AuthenticationSession[]> {
9198
const storedSessions = await keychain.getToken() || await keychain.tryMigrate();
@@ -96,7 +103,7 @@ export class CodingAuthenticationProvider {
96103
const needsUserInfo = !session.account;
97104
let userInfo: { id: string, accountName: string };
98105
if (needsUserInfo) {
99-
userInfo = await this._codingServer.getUserInfo(this._team, session.accessToken);
106+
userInfo = await this._codingServer.getUserInfo(this._repo.team, session.accessToken);
100107
}
101108

102109
return {
@@ -126,8 +133,8 @@ export class CodingAuthenticationProvider {
126133
return [];
127134
}
128135

129-
private async _tokenToSession(team: string, token: string, scopes: string[]): Promise<vscode.AuthenticationSession> {
130-
const userInfo = await this._codingServer.getUserInfo(team, token);
136+
private async _tokenToSession(token: string, scopes: string[]): Promise<vscode.AuthenticationSession> {
137+
const userInfo = await this._codingServer.getUserInfo(this._repo.team, token);
131138

132139
return {
133140
id: nanoid(),
@@ -142,7 +149,7 @@ export class CodingAuthenticationProvider {
142149

143150
public async login(team: string, scopes: string = SCOPES): Promise<vscode.AuthenticationSession> {
144151
const { access_token: token } = await this._codingServer.login(team, scopes);
145-
const session = await this._tokenToSession(team, token, scopes.split(' '));
152+
const session = await this._tokenToSession(token, scopes.split(' '));
146153
await this._setToken(session);
147154
return session;
148155
}
@@ -163,16 +170,16 @@ export class CodingAuthenticationProvider {
163170
}
164171

165172
// @ts-ignore
166-
get sessions(): vscode.AuthenticationSession[] {
167-
return this._sessions;
173+
get sessions(): vscode.AuthenticationSession[] {
174+
return this._sessions;
168175
}
169-
176+
170177
public async logout(id: string) {
171-
const sessionIndex = this._sessions.findIndex(session => session.id === id);
172-
if (sessionIndex > -1) {
173-
this._sessions.splice(sessionIndex, 1);
174-
}
178+
const sessionIndex = this._sessions.findIndex(session => session.id === id);
179+
if (sessionIndex > -1) {
180+
this._sessions.splice(sessionIndex, 1);
181+
}
175182

176-
await this._storeSessions();
177-
}
183+
await this._storeSessions();
184+
}
178185
}

src/codingServer.ts

+55-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import * as vscode from 'vscode';
22
import { nanoid } from 'nanoid'
33
import got from 'got';
44

5-
import { PromiseAdapter, promiseFromEvent, parseQuery } from './common/utils';
65
import { AuthFailResult, AuthSuccessResult, UserResponse } from './typings/ResponseResult';
6+
import { PromiseAdapter, promiseFromEvent, parseQuery, parseCloneUrl } from './common/utils';
7+
import { GitService } from './common/gitService';
8+
import { RepoInfo } from './typings/types';
79

810
const AUTH_SERVER = `http://127.0.0.1:5000`;
911
const ClientId = `ff768664c96d04235b1cc4af1e3b37a8`;
@@ -22,6 +24,21 @@ const onDidManuallyProvideToken = new vscode.EventEmitter<string>();
2224
export class CodingServer {
2325
private _pendingStates = new Map<string, string[]>();
2426
private _codeExchangePromises = new Map<string, Promise<AuthSuccessResult>>();
27+
private _accessToken: string = ``;
28+
private _repo: RepoInfo = {
29+
team: ``,
30+
project: ``,
31+
repo: ``,
32+
};
33+
34+
constructor(sessions?: vscode.AuthenticationSession[], repo?: RepoInfo | null) {
35+
if (sessions?.length) {
36+
this._accessToken = sessions[sessions.length - 1].accessToken;
37+
}
38+
if (repo) {
39+
this._repo = repo;
40+
}
41+
}
2542

2643
public async login(team: string, scopes: string) {
2744
const state = nanoid();
@@ -31,7 +48,7 @@ export class CodingServer {
3148
const existingStates = this._pendingStates.get(scopes) || [];
3249
this._pendingStates.set(scopes, [...existingStates, state]);
3350

34-
const uri = vscode.Uri.parse(`${AUTH_SERVER}?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code`);
51+
const uri = vscode.Uri.parse(`${AUTH_SERVER}?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code&team=${team}`);
3552
await vscode.env.openExternal(uri);
3653

3754
let existingPromise = this._codeExchangePromises.get(scopes);
@@ -83,7 +100,7 @@ export class CodingServer {
83100
}
84101
};
85102

86-
public async getUserInfo(team: string, token: string) {
103+
public async getUserInfo(team: string, token: string = this._accessToken) {
87104
try {
88105
const result: UserResponse = await got.get(`https://${team}.coding.net/api/me`, {
89106
searchParams: {
@@ -96,5 +113,40 @@ export class CodingServer {
96113
}
97114
}
98115

116+
public static async getRepoParams() {
117+
const gitSrv = await GitService.getBuiltInGitApi();
118+
// TODO: multiple working repos
119+
const repoInstance = gitSrv?.repositories[0];
120+
121+
if (!repoInstance) {
122+
return null;
123+
}
124+
125+
const cloneUrl = await repoInstance.getConfig(`remote.origin.url`);
126+
return parseCloneUrl(cloneUrl);
127+
}
128+
129+
public async getMRList(
130+
team: string = this._repo.team,
131+
project: string = this._repo.project,
132+
repo: string = this._repo.repo,
133+
) {
134+
try {
135+
const result = await got.get(`https://${team}.coding.net/api/user/${team}/project/${project}/depot/${repo}/git/merges/query`, {
136+
searchParams: {
137+
status: `all`,
138+
sort: `action_at`,
139+
page: 1,
140+
PageSize: 15,
141+
sortDirection: `DESC`,
142+
access_token: this._accessToken,
143+
}
144+
}).json();
145+
146+
return result;
147+
} catch (err) {
148+
return err
149+
}
150+
}
99151
}
100152

src/common/gitService.ts

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import * as vscode from 'vscode';
2-
import { API as BuiltInGitApi, GitExtension } from '../typings/git';
2+
import { API as BuiltInGitApi, GitExtension, Repository } from '../typings/git';
33

44
export class GitService {
5-
static async getBuiltInGitApi(): Promise<BuiltInGitApi | undefined> {
6-
try {
7-
const extension = vscode.extensions.getExtension('vscode.git') as vscode.Extension<GitExtension>;
8-
if (extension !== undefined) {
9-
const gitExtension = extension.isActive ? extension.exports : await extension.activate();
5+
static async getBuiltInGitApi(): Promise<BuiltInGitApi | undefined> {
6+
try {
7+
const extension = vscode.extensions.getExtension('vscode.git') as vscode.Extension<GitExtension>;
8+
if (extension !== undefined) {
9+
const gitExtension = extension.isActive ? extension.exports : await extension.activate();
1010

11-
return gitExtension.getAPI(1);
12-
}
13-
} catch { }
11+
return gitExtension.getAPI(1);
12+
}
13+
} catch { }
1414

15-
return undefined;
16-
}
15+
return;
16+
}
17+
18+
static async getRemoteUrl(repo: Repository): Promise<string> {
19+
const url = await repo.getConfig(`remote.origin.url`);
20+
return url;
21+
}
1722
}

src/common/utils.ts

+18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Event, Disposable, Uri } from 'vscode';
2+
import { RepoInfo } from '../typings/types';
23

34
export interface PromiseAdapter<T, U> {
45
(
@@ -44,3 +45,20 @@ export function parseQuery(uri: Uri) {
4445
return prev;
4546
}, {});
4647
}
48+
49+
export function parseCloneUrl(url: string): RepoInfo | null {
50+
const reg = /^(https:\/\/|git@)e\.coding\.net(\/|:)(.*)\.git$/i;
51+
const result = url.match(reg);
52+
53+
if (!result) {
54+
return null;
55+
}
56+
57+
const str = result.pop();
58+
if (!str || !str?.includes(`/`)) {
59+
return null;
60+
}
61+
62+
const [team, project, repo] = str.split(`/`);
63+
return { team, project, repo: repo || project };
64+
}

0 commit comments

Comments
 (0)