oauth update, author association badges, bug-fixes

This commit is contained in:
Jeremy Danyow 2017-12-10 21:04:27 -08:00
parent f37b11407c
commit dfdff63ad5
No known key found for this signature in database
GPG Key ID: 96C45DE6B2C1DF40
9 changed files with 4633 additions and 98 deletions

4583
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -77,7 +77,7 @@
background-color: $bg-blue-light;
}
&.repo-owner .comment-header > :last-child::after {
.author-association-badge {
float: right;
margin-top: -1px;
padding: 2px 5px;
@ -85,7 +85,6 @@
border-radius: $border-radius;
font-size: $font-size-small;
font-weight: $font-weight-semibold;
content: 'Owner';
}
}

View File

@ -1,10 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>utterances</title>
<title>login | utterances</title>
<script>
window.opener.oautherized(window.location.search.substring(1));
window.close();
opener.notifyAuthorized();
close();
</script>
</head>
<body>

View File

@ -1,8 +1,13 @@
import { IssueComment } from './github';
import { timeAgo } from './time-ago';
import { isOwner } from './page-attributes';
const avatarArgs = '?v=3&s=88';
const displayAssociations: { [association: string]: string; } = {
COLLABORATOR: 'Collaborator',
CONTRIBUTOR: 'Contributor',
MEMBER: 'Member',
OWNER: 'Owner'
};
export class CommentComponent {
public readonly element: HTMLElement;
@ -11,15 +16,13 @@ export class CommentComponent {
public comment: IssueComment,
private currentUser: string | null
) {
const { user, html_url, created_at, body_html } = comment;
const { user, html_url, created_at, body_html, author_association } = comment;
this.element = document.createElement('article');
this.element.classList.add('timeline-comment');
if (user.login === currentUser) {
this.element.classList.add('current-user');
}
if (isOwner(user.login)) {
this.element.classList.add('repo-owner');
}
const association = displayAssociations[author_association];
this.element.innerHTML = `
<a class="avatar" href="${user.html_url}" target="_blank">
<img alt="@${user.login}" height="44" width="44"
@ -30,6 +33,7 @@ export class CommentComponent {
<a class="text-link" href="${user.html_url}" target="_blank"><strong>${user.login}</strong></a>
commented
<a class="text-link" href="${html_url}" target="_blank">${timeAgo(Date.now(), new Date(created_at))}</a>
${association ? `<span class="author-association-badge">${association}</span>` : ''}
</header>
<div class="markdown-body">
${body_html}
@ -49,11 +53,6 @@ export class CommentComponent {
} else {
this.element.classList.remove('current-user');
}
if (isOwner(user.login)) {
this.element.classList.add('repo-owner');
} else {
this.element.classList.remove('repo-owner');
}
const avatarAnchor = this.element.firstElementChild as HTMLAnchorElement;
const avatarImg = avatarAnchor.firstElementChild as HTMLImageElement;
@ -86,20 +85,13 @@ export class CommentComponent {
if (this.currentUser === currentUser) {
return;
}
const commentDiv = this.element.firstElementChild as HTMLDivElement;
if (this.comment.user.login === this.currentUser) {
commentDiv.classList.add('current-user');
} else {
commentDiv.classList.remove('current-user');
}
if (isOwner(this.comment.user.login)) {
this.element.classList.add('repo-owner');
} else {
this.element.classList.remove('repo-owner');
}
this.currentUser = currentUser;
if (this.comment.user.login === this.currentUser) {
this.element.classList.add('current-user');
} else {
this.element.classList.remove('current-user');
}
}
private retargetLinks() {

View File

@ -1,13 +1,11 @@
import { token } from './oauth';
import { decodeBase64UTF8 } from './encoding';
import { UTTERANCES_API } from './utterances-api';
const GITHUB_API = 'https://api.github.com/';
const GITHUB_ENCODING__HTML_JSON = 'application/vnd.github.VERSION.html+json';
const GITHUB_ENCODING__HTML = 'application/vnd.github.VERSION.html';
const GITHUB_ENCODING__REACTIONS_PREVIEW = 'application/vnd.github.squirrel-girl-preview';
// const UTTERANCES_API = 'https://utterances-oauth.herokuapp.com';
const UTTERANCES_API = 'https://utterances-oauth.azurewebsites.net';
const PAGE_SIZE = 100;
let owner: string;
@ -184,6 +182,7 @@ export function createIssue(issueTerm: string, documentUrl: string, title: strin
})
});
request.headers.set('Accept', GITHUB_ENCODING__REACTIONS_PREVIEW);
request.headers.set('Authorization', `token ${token.value}`);
return fetch(request).then<Issue>(response => {
if (!response.ok) {
throw new Error('Error creating comments container issue');
@ -231,6 +230,15 @@ export interface User {
type: string;
}
export type CommentAuthorAssociation =
'COLLABORATOR'
| 'CONTRIBUTOR'
| 'FIRST_TIMER'
| 'FIRST_TIME_CONTRIBUTOR'
| 'MEMBER'
| 'NONE'
| 'OWNER';
export interface Issue {
url: string;
repository_url: string;
@ -272,6 +280,7 @@ export interface Issue {
hooray: number;
url: string;
};
author_association: CommentAuthorAssociation;
}
interface FileContentsResponse {
@ -296,6 +305,7 @@ export interface IssueComment {
user: User;
created_at: string;
updated_at: string;
author_association: CommentAuthorAssociation;
}
export interface CommentsPage {

View File

@ -49,7 +49,6 @@
repo="utterance/utterances"
branch="gh-pages"
issue-term="homepage"
owners="jdanyow"
async>
</script>

View File

@ -1,13 +1,11 @@
import { param, deparam } from './deparam';
import { UTTERANCES_API } from './utterances-api';
import { param } from './deparam';
const authorizeUri = 'https://github.com/login/oauth/authorize';
// const tokenUri = 'https://utterances-oauth.herokuapp.com/access-token';
const tokenUri = 'https://utterances-oauth.azurewebsites.net/access-token';
const authorizeUrl = `${UTTERANCES_API}/authorize`;
const tokenUrl = `${UTTERANCES_API}/token`;
// tslint:disable-next-line:variable-name
const redirect_uri = 'https://utteranc.es/authorized.html';
// tslint:disable-next-line:variable-name
const client_id = '1a560753410b181458de';
const scopes = 'public_repo';
const redirect_uri = `${location.origin}/authorized.html`;
const scope = 'public_repo';
class Token {
private readonly storageKey = 'OAUTH_TOKEN';
@ -38,55 +36,15 @@ class Token {
export const token = new Token();
interface AuthorizeResponse {
code: string;
state: string;
}
function popup(url: string) {
let resolve: (response: AuthorizeResponse) => void;
(window as any).oautherized = (query: string) => {
(window as any).oautherized = null;
resolve(deparam(query) as any);
};
const promise = new Promise<AuthorizeResponse>(r => resolve = r);
window.open(url);
return promise;
}
function requestAuthorizationCode() {
const args = {
client_id,
redirect_uri,
scope: scopes,
state: Math.floor(Math.random() * 100000).toString()
};
const url = `${authorizeUri}?${param(args)}`;
return popup(url)
.then(result => {
if (!(result.code && result.state)) {
throw new Error('Redirect did not include code and state parameters.');
}
if (result.state !== args.state) {
throw new Error('State mismatch.');
}
return result;
});
}
function requestAccessToken({ code, state }: AuthorizeResponse) {
const args = { code, state };
const url = `${tokenUri}?${param(args)}`;
return fetch(url)
.then<{ access_token: string; }>(response => response.json());
}
export function login() {
return requestAuthorizationCode()
.then(response => requestAccessToken(response))
.then(({ access_token }) => token.value = access_token)
.catch(reason => {
token.value = null;
throw reason;
});
window.open(`${authorizeUrl}?${param({ scope, redirect_uri })}`);
return new Promise(resolve => (window as any).notifyAuthorized = resolve)
.then(() => fetch(tokenUrl, { mode: 'cors', credentials: 'include' }))
.then(response => {
if (response.ok) {
return response.json();
}
return response.text().then(text => Promise.reject(`Error retrieving token:\n${text}`));
})
.then(t => { token.value = t; }, reason => { token.value = null; throw reason; });
}

View File

@ -40,7 +40,6 @@ function readPageAttributes() {
return {
owner: matches[1],
otherOwners: params.owners === undefined ? [] : params.owners.split(','),
repo: matches[2],
branch: 'branch' in params ? params.branch : 'master',
configPath: 'config-path' in params ? params['config-path'] : 'utterances.json',
@ -54,10 +53,3 @@ function readPageAttributes() {
}
export const pageAttributes = readPageAttributes();
export function isOwner(login: string) {
const { owner, otherOwners } = pageAttributes;
const ignoreCase = { sensitivity: 'base' };
return login.localeCompare(owner, undefined, ignoreCase) === 0
|| otherOwners.find && otherOwners.find(o => login.localeCompare(o, undefined, ignoreCase) === 0);
}

2
src/utterances-api.ts Normal file
View File

@ -0,0 +1,2 @@
// export const UTTERANCES_API = 'http://localhost:5000';
export const UTTERANCES_API = 'https://utterances-oauth.azurewebsites.net';