This commit is contained in:
Eugene Pankov
2021-07-25 14:00:22 +02:00
parent d9d019e15f
commit e11193b807
24 changed files with 961 additions and 193 deletions

View File

@@ -5,10 +5,11 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms'
import { RouterModule } from '@angular/router'
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'
import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http'
import { ClipboardModule } from '@angular/cdk/clipboard'
import { TransferHttpCacheModule } from '@nguniversal/common'
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'
import { UniversalInterceptor } from './interceptor'
import { AppComponent } from './components/app.component'
import { MainComponent } from './components/main.component'
import { ConfigModalComponent } from './components/configModal.component'
@@ -17,7 +18,7 @@ import { HomeComponent } from './components/home.component'
import { LoginComponent } from './components/login.component'
import { InstanceInfoResolver } from './api'
import '@fortawesome/fontawesome-svg-core/styles.css'
// import '@fortawesome/fontawesome-svg-core/styles.css'
const ROUTES = [
{
@@ -25,28 +26,28 @@ const ROUTES = [
component: HomeComponent,
resolve: {
instanceInfo: InstanceInfoResolver,
}
},
},
{
path: 'app',
component: MainComponent,
resolve: {
instanceInfo: InstanceInfoResolver,
}
},
},
{
path: 'login',
component: LoginComponent,
resolve: {
instanceInfo: InstanceInfoResolver,
}
},
},
]
@NgModule({
imports: [
BrowserModule.withServerTransition({
appId: 'tabby'
appId: 'tabby',
}),
TransferHttpCacheModule,
BrowserAnimationsModule,
@@ -60,6 +61,13 @@ const ROUTES = [
ClipboardModule,
RouterModule.forRoot(ROUTES),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UniversalInterceptor,
multi: true,
},
],
declarations: [
AppComponent,
MainComponent,
@@ -68,6 +76,6 @@ const ROUTES = [
ConfigModalComponent,
SettingsModalComponent,
],
bootstrap: [AppComponent]
bootstrap: [AppComponent],
})
export class AppModule { }

View File

@@ -1,9 +1,7 @@
import { HTTP_INTERCEPTORS } from '@angular/common/http'
import { NgModule } from '@angular/core'
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server'
import { AppModule } from './app.module'
import { AppComponent } from './components/app.component'
import { UniversalInterceptor } from './ssr-interceptor'
@NgModule({
imports: [
@@ -13,13 +11,6 @@ import { UniversalInterceptor } from './ssr-interceptor'
ServerModule,
ServerTransferStateModule,
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UniversalInterceptor,
multi: true
}
],
// Since the bootstrapped component is not inherited from your
// imported AppModule, it needs to be repeated here.
bootstrap: [AppComponent],

View File

@@ -6,11 +6,18 @@ import { InstanceInfo, Version } from '../api'
import { faCoffee, faDownload, faSignInAlt } from '@fortawesome/free-solid-svg-icons'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
import { ActivatedRoute } from '@angular/router'
import { CommonService } from '../services/common.service'
class DemoConnector {
constructor (targetWindow: Window, private version: Version) {
targetWindow['tabbyWebDemoDataPath'] = `${this.getDistURL()}/${version.version}/tabby-web-demo/data`
constructor (
targetWindow: Window,
private commonService: CommonService,
private version: Version,
) {
this.getDistURL().then(distURL => {
targetWindow['tabbyWebDemoDataPath'] = `${distURL}/${version.version}/tabby-web-demo/data`
})
}
async loadConfig (): Promise<string> {
@@ -22,15 +29,15 @@ class DemoConnector {
}`
}
async saveConfig (content: string): Promise<void> {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
async saveConfig (_content: string): Promise<void> { }
getAppVersion (): string {
return this.version.version
}
getDistURL (): string {
return '/app-dist'
async getDistURL (): Promise<string> {
return await this.commonService.getBackendURL() + '/app-dist'
}
getPluginsToLoad (): string[] {
@@ -94,12 +101,14 @@ export class HomeComponent {
constructor (
private http: HttpClient,
private commonService: CommonService,
route: ActivatedRoute,
) {
window.addEventListener('message', this.connectorRequestHandler)
this.instanceInfo = route.snapshot.data.instanceInfo
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
connectorRequestHandler = event => {
if (event.data === 'request-connector') {
this.iframe.nativeElement.contentWindow['__connector__'] = this.connector
@@ -107,14 +116,14 @@ export class HomeComponent {
}
}
async ngAfterViewInit () {
async ngAfterViewInit (): Promise<void> {
const versions = await this.http.get('/api/1/versions').toPromise()
versions.sort((a, b) => -semverCompare(a.version, b.version))
this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, versions[0])
this.iframe.nativeElement.src = '/terminal'
this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, this.commonService, versions[0])
this.iframe.nativeElement.src = '/terminal.html'
}
ngOnDestroy () {
ngOnDestroy (): void {
window.removeEventListener('message', this.connectorRequestHandler)
}
}

View File

@@ -80,7 +80,7 @@ export class MainComponent {
async loadApp (config, version) {
this.showApp = true
this.iframe.nativeElement.src = '/terminal'
this.iframe.nativeElement.src = '/terminal.html'
await this.http.patch(`/api/1/configs/${config.id}`, {
last_used_with_version: version.version,
}).toPromise()

24
src/interceptor.ts Normal file
View File

@@ -0,0 +1,24 @@
import { Injectable } from '@angular/core'
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'
import { Observable, from } from 'rxjs'
import { switchMap } from 'rxjs/operators'
import { CommonService } from './services/common.service'
@Injectable()
export class UniversalInterceptor implements HttpInterceptor {
constructor (private commonService: CommonService) { }
intercept (request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!request.url.startsWith('//') && request.url.startsWith('/')) {
return from(this.commonService.getBackendURL()).pipe(switchMap((baseUrl: string) => {
const endpoint = request.url
request = request.clone({
url: `${baseUrl}${endpoint}`,
})
return next.handle(request)
}))
}
return next.handle(request)
}
}

View File

@@ -1,12 +1,13 @@
import 'zone.js/dist/zone-node';
import { enableProdMode } from '@angular/core';
require('source-map-support').install()
import 'zone.js/dist/zone-node';
import './ssr-polyfills'
import { enableProdMode } from '@angular/core';
// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
import './ssr-polyfills'
import * as express from 'express'
import { join } from 'path'
@@ -37,15 +38,15 @@ app.use('/static', express.static(DIST_FOLDER, {
maxAge: '1y'
}));
var proxy = require('express-http-proxy');
// var proxy = require('express-http-proxy');
app.get(['/', '/login'], (req, res) => {
app.get('*', (req, res) => {
res.render('index', { req });
});
app.use('/', proxy('http://tabby.local:8000/api/', {
}))
// app.use('/', proxy('http://tabby.local:8000/api/', {
// }))
// Start up the Node server
app.listen(PORT, () => {

View File

@@ -2,9 +2,10 @@ import { Buffer } from 'buffer'
import { Subject } from 'rxjs'
import { debounceTime } from 'rxjs/operators'
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { LoginService } from '../services/login.service'
import { Injectable, Injector } from '@angular/core'
import { Config, Gateway, Version } from '../api'
import { LoginService } from './login.service'
import { CommonService } from './common.service'
export class SocketProxy {
connect$ = new Subject<void>()
@@ -21,12 +22,21 @@ export class SocketProxy {
port: number
}
constructor (private appConnector: AppConnectorService) { }
private appConnector: AppConnectorService
private loginService: LoginService
async connect (options) {
constructor (
injector: Injector,
) {
this.appConnector = injector.get(AppConnectorService)
this.loginService = injector.get(LoginService)
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async connect (options: any): Promise<void> {
this.options = options
this.url = this.appConnector.loginService.user.custom_connection_gateway
this.authToken = this.appConnector.loginService.user.custom_connection_gateway_token
this.url = this.loginService.user.custom_connection_gateway
this.authToken = this.loginService.user.custom_connection_gateway_token
if (!this.url) {
try {
const gateway = await this.appConnector.chooseConnectionGateway()
@@ -120,9 +130,11 @@ export class AppConnectorService {
sockets: SocketProxy[] = []
constructor (
private injector: Injector,
private http: HttpClient,
public loginService: LoginService,
private commonService: CommonService,
) {
this.configUpdate.pipe(debounceTime(1000)).subscribe(async content => {
const result = await this.http.patch(`/api/1/configs/${this.config.id}`, { content }).toPromise()
Object.assign(this.config, result)
@@ -147,8 +159,8 @@ export class AppConnectorService {
return this.version.version
}
getDistURL (): string {
return '../app-dist'
async getDistURL (): Promise<string> {
return await this.commonService.getBackendURL() + '/app-dist'
}
getPluginsToLoad (): string[] {
@@ -168,7 +180,7 @@ export class AppConnectorService {
}
createSocket () {
const socket = new SocketProxy(this)
const socket = new SocketProxy(this.injector)
this.sockets.push(socket)
socket.close$.subscribe(() => {
this.sockets = this.sockets.filter(x => x !== socket)

View File

@@ -0,0 +1,18 @@
import { Injectable } from '@angular/core'
@Injectable({ providedIn: 'root' })
export class CommonService {
private backendURL?: string
async getBackendURL (): Promise<string> {
if (!this.backendURL) {
const config = await (await fetch('/config.json')).json()
this.backendURL = config.backendURL
if (this.backendURL.endsWith('/')) {
this.backendURL = this.backendURL.slice(0, -1)
}
}
return this.backendURL
}
}

View File

@@ -1,45 +0,0 @@
/**
* This interceptor ensures that the app makes requests
* with relative paths correctly server-side.
* Requests which start with a dot (ex. ./assets/...)
* or relative ones ( ex. /assets/...) will be converted
* to absolute paths
*/
import { Inject, Injectable, Injector, PLATFORM_ID } from '@angular/core';
import { isPlatformServer } from '@angular/common';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { Request } from 'express';
import { Observable } from 'rxjs';
@Injectable()
export class UniversalInterceptor implements HttpInterceptor {
constructor(
private readonly injector: Injector,
@Inject(PLATFORM_ID) private readonly platformId: any) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const isServer = isPlatformServer(this.platformId);
if (isServer && !request.url.startsWith('//') && (request.url.startsWith('./') || request.url.startsWith('/'))) {
const serverRequest = this.injector.get(REQUEST) as Request;
console.log(serverRequest)
const baseUrl = `${serverRequest.protocol}://${serverRequest.get('Host')}`;
let endpoint = request.url;
/**
* ISSUE https://github.com/angular/angular/issues/19224
* HttpClient doesn't support relative requests server-side
*/
if (endpoint.startsWith('.')) {
endpoint = endpoint.substring(1);
}
// Now the endpoint starts with '/'
request = request.clone({
url: `${baseUrl}${endpoint}`
});
}
return next.handle(request);
}
}

View File

@@ -2,7 +2,7 @@ import * as domino from 'domino';
import * as fs from 'fs';
import * as path from 'path';
const template = fs.readFileSync(path.join(process.cwd(), 'build-server', 'index.html')).toString();
const template = fs.readFileSync(path.join(process.cwd(), 'build', 'index.html')).toString();
const win = domino.createWindow(template);
global['window'] = win;

View File

@@ -31,7 +31,7 @@ async function start () {
await (await fetch(url)).text()
}
const baseUrl = `${connector.getDistURL()}/${appVersion}`
const baseUrl = `${await connector.getDistURL()}/${appVersion}`
const coreURLs = [
`${baseUrl}/tabby-web-container/dist/preload.js`,
`${baseUrl}/tabby-web-container/dist/bundle.js`,