Commit 5263c95a authored by Mark Harding's avatar Mark Harding
Browse files

(wip): Server Side Rendering - work in progress

parent 8d20be4c
Pipeline #65460743 failed with stage
in 5 minutes and 18 seconds
......@@ -16,6 +16,7 @@
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"extractCss": true,
"assets": [
"src/assets",
"src/favicon.ico"
......@@ -98,6 +99,22 @@
"**/node_modules/**"
]
}
},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/server",
"main": "src/main.server.ts",
"tsConfig": "src/tsconfig.server.json"
},
"configurations": {
"production": {
"fileReplacements": [{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}]
}
}
}
}
},
......
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { join } from 'path';
import { readFileSync } from 'fs';
import { renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';
import * as express from 'express';
const domino = require('domino');
const socketEngine = require('@nguniversal/socket-engine');
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Express server
const app = express();
const PORT = process.env.PORT || 4201;
const DIST_FOLDER = join(process.cwd(), 'dist/en');
const template = readFileSync(join(DIST_FOLDER, 'index.php')).toString();
const win = domino.createWindow(template);
const setGlobalVars = require('indexeddbshim');
global['window'] = win;
global['Node'] = win.Node;
global['navigator'] = win.navigator;
global['Event'] = win.Event;
global['Event']['prototype'] = win.Event.prototype;
global['document'] = win.document;
global['window']['Promise'] = global.Promise;
global['window']['Minds'] = {
cdn_urn: '/',
cdn_assets_url: '/en/',
blockchain: {
network_address: "https://www.minds.com/api/v2/blockchain/proxy/",
token: {
abi: [],
}
},
};
global['window']['localStorage'] = {
getItem: () => { },
setItem: () => { },
removeItem: () => { },
};
global['localStorage'] = global['window']['localStorage'];
global['window']['scrollTo'] = (pos) => { };
Object.defineProperty(window.document, 'cookie', {
writable: true,
value: 'myCookie=omnomnom',
});
Object.defineProperty(window.document, 'referrer', {
writable: true,
value: '',
});
Object.defineProperty(window.document, 'localStorage', {
writable: true,
value: global['window']['localStorage'],
});
setGlobalVars(null, {
checkOrigin: false
});
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main');
const { provideModuleMap } = require('@nguniversal/module-map-ngfactory-loader');
socketEngine.startSocketEngine(AppServerModuleNgFactory, [ provideModuleMap(LAZY_MODULE_MAP) ]);
/*app.engine('html', (_, options, callback) => {
renderModuleFactory(AppServerModuleNgFactory, {
// Our index.html
document: template,
url: options.req.url,
// DI so that we can get lazy-loading to work differently (since we need it to just instantly render it)
extraProviders: [
provideModuleMap(LAZY_MODULE_MAP)
]
}).then(html => {
callback(null, html);
});
});
app.set('view engine', 'html');
app.set('views', DIST_FOLDER);
// Server static files from dist folder
app.get('*.*', express.static(DIST_FOLDER));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});*/
import { NgModule } from '@angular/core';
import { MindsModule } from './app.module';
import { Minds } from './app.component';
@NgModule({
imports: [
MindsModule,
],
bootstrap: [Minds],
})
export class AppBrowserModule {}
import { ChangeDetectorRef, Component, NgZone } from '@angular/core';
import {
ChangeDetectorRef,
Component,
NgZone,
PLATFORM_ID,
Inject,
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { NotificationService } from './modules/notifications/notification.service';
import { AnalyticsService } from './services/analytics';
......@@ -51,13 +58,12 @@ export class Minds {
public featuresService: FeaturesService,
public themeService: ThemeService,
private bannedService: BannedService,
@Inject(PLATFORM_ID) private platformId,
) {
this.name = 'Minds';
}
async ngOnInit() {
this.notificationService.getNotifications();
this.session.isLoggedIn(async (is) => {
if (is) {
this.showOnboarding = await this.onboardingService.showModal();
......@@ -97,7 +103,7 @@ export class Minds {
this.themeService.setUp();
if (this.session.isLoggedIn()) {
if (isPlatformBrowser(this.platformId) && this.session.isLoggedIn()) {
this.blockListService.sync();
}
}
......@@ -105,6 +111,6 @@ export class Minds {
ngOnDestroy() {
this.loginReferrer.unlisten();
this.scrollToTop.unlisten();
this.paramsSubscription.unsubscribe();
//this.paramsSubscription.unsubscribe();
}
}
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
......@@ -69,12 +69,13 @@ import { HttpClientModule } from "@angular/common/http";
MINDS_PLUGIN_DECLARATIONS,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
BrowserModule.withServerTransition({ appId: 'm-app' }),
BrowserTransferStateModule,
//BrowserAnimationsModule,
ReactiveFormsModule,
FormsModule,
HttpClientModule,
RouterModule.forRoot(MindsAppRoutes),
RouterModule.forRoot(MindsAppRoutes, { initialNavigation: 'enabled' }),
CaptchaModule,
CommonModule,
WalletModule,
......
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
import { ServerTransferStateModule } from '@angular/platform-server';
import { MindsModule } from './app.module';
import { Minds } from './app.component';
@NgModule({
imports: [
MindsModule,
ServerModule,
ModuleMapLoaderModule,
ServerTransferStateModule,
],
bootstrap: [Minds],
})
export class AppServerModule {}
......@@ -6,7 +6,7 @@ import { HttpClient, HttpHeaders } from "@angular/common/http";
*/
export class MindsHttpClient {
base: string = '/';
base: string = 'https://eggman.minds.com/';
cookie: Cookie = new Cookie();
static _(http: HttpClient) {
......
......@@ -59,6 +59,7 @@ import { InlineEditorComponent } from './components/editors/inline-editor.compon
import { AttachmentService } from "../services/attachment";
import { MaterialBoundSwitchComponent } from './components/material/bound-switch.component';
import { IfFeatureDirective } from './directives/if-feature.directive';
import { IfBrowserDirective } from './directives/if-browser.directive';
import { MindsEmoji } from './components/emoji/emoji';
import { CategoriesSelectorComponent } from './components/categories/selector/selector.component';
import { CategoriesSelectedComponent } from './components/categories/selected/selected.component';
......@@ -166,6 +167,7 @@ import { HorizontalInfiniteScroll } from "./components/infinite-scroll/horizonta
MaterialBoundSwitchComponent,
IfFeatureDirective,
IfBrowserDirective,
CategoriesSelectorComponent,
CategoriesSelectedComponent,
......@@ -250,6 +252,7 @@ import { HorizontalInfiniteScroll } from "./components/infinite-scroll/horizonta
MaterialBoundSwitchComponent,
IfFeatureDirective,
IfBrowserDirective,
CategoriesSelectorComponent,
CategoriesSelectedComponent,
......
import {
Directive,
EmbeddedViewRef,
Inject,
Input,
PLATFORM_ID,
TemplateRef,
ViewContainerRef,
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Directive({
selector: '[mIfBrowser]'
})
export class IfBrowserDirective {
private _elseTemplateRef: TemplateRef<any>;
private _viewRef: EmbeddedViewRef<any>;
private _elseViewRef: EmbeddedViewRef<any>;
constructor(
private _templateRef: TemplateRef<any>,
private _viewContainerRef: ViewContainerRef,
@Inject(PLATFORM_ID) private platformId,
) {
console.log('constructing IfBrowserDirective');
this._update();
}
_update() {
console.log(this.platformId, isPlatformBrowser);
if (isPlatformBrowser(this.platformId)) {
if (!this._viewRef) {
this._viewContainerRef.clear();
this._elseViewRef = void 0;
if (this._templateRef) {
this._viewRef = this._viewContainerRef.createEmbeddedView(this._templateRef);
}
}
} else {
if (!this._elseViewRef) {
this._viewContainerRef.clear();
this._viewRef = void 0;
if (this._elseTemplateRef) {
this._elseViewRef = this._viewContainerRef.createEmbeddedView(this._elseTemplateRef);
}
}
}
}
}
......@@ -7,7 +7,7 @@ import AsyncLock from "../../helpers/async-lock";
import MindsClientHttpAdapter from "../../lib/minds-sync/adapters/MindsClientHttpAdapter.js";
import browserStorageAdapterFactory from "../../helpers/browser-storage-adapter-factory";
import BlockListSync from "../../lib/minds-sync/services/BlockListSync.js";
import AsyncStatus from "../../helpers/async-status";
import { AsyncStatus } from "../../helpers/async-status";
@Injectable()
export class BlockListService {
......@@ -39,13 +39,13 @@ export class BlockListService {
// Prune on session changes
this.session.isLoggedIn((is: boolean) => {
/*this.session.isLoggedIn((is: boolean) => {
if (is) {
this.sync();
} else {
this.prune();
}
});
});*/
}
async sync() {
......
......@@ -11,7 +11,7 @@ import MindsClientHttpAdapter from "../../lib/minds-sync/adapters/MindsClientHtt
import browserStorageAdapterFactory from "../../helpers/browser-storage-adapter-factory";
import BoostedContentSync from '../../lib/minds-sync/services/BoostedContentSync.js';
import AsyncStatus from "../../helpers/async-status";
import { AsyncStatus } from "../../helpers/async-status";
@Injectable()
export class BoostedContentService {
......@@ -69,7 +69,7 @@ export class BoostedContentService {
// Garbage collection
this.boostedContentSync.gc();
setTimeout(() => this.boostedContentSync.gc(), 5 * 60 * 1000); // Every 5 minutes
//setTimeout(() => this.boostedContentSync.gc(), 5 * 60 * 1000); // Every 5 minutes
// Rating changes hook
this.settingsService.ratingChanged.subscribe(rating => this.boostedContentSync.changeRating(rating));
......
......@@ -4,7 +4,7 @@ import { Client } from "../../services/api";
import MindsClientHttpAdapter from '../../lib/minds-sync/adapters/MindsClientHttpAdapter.js';
import browserStorageAdapterFactory from "../../helpers/browser-storage-adapter-factory";
import EntitiesSync from '../../lib/minds-sync/services/EntitiesSync.js';
import AsyncStatus from "../../helpers/async-status";
import { AsyncStatus } from "../../helpers/async-status";
import normalizeUrn from "../../helpers/normalize-urn";
@Injectable()
......@@ -12,10 +12,9 @@ export class EntitiesService {
protected entitiesSync: EntitiesSync;
protected status = new AsyncStatus();
constructor(
protected client: Client
protected client: Client,
protected status: AsyncStatus,
) {
this.setUp();
}
......@@ -36,7 +35,7 @@ export class EntitiesService {
// Garbage collection
this.entitiesSync.gc();
setTimeout(() => this.entitiesSync.gc(), 15 * 60 * 1000); // Every 15 minutes
//setTimeout(() => this.entitiesSync.gc(), 15 * 60 * 1000); // Every 15 minutes
}
async single(guid: string): Promise<Object | false> {
......@@ -65,10 +64,13 @@ export class EntitiesService {
const urns = guids.map(guid => normalizeUrn(guid));
if (!this.entitiesSync)
await this.setUp();
return await this.entitiesSync.get(urns);
}
static _(client: Client) {
return new EntitiesService(client);
static _(client: Client, status: AsyncStatus) {
return new EntitiesService(client, status);
}
}
......@@ -11,7 +11,7 @@ import browserStorageAdapterFactory from "../../helpers/browser-storage-adapter-
import FeedsSync from '../../lib/minds-sync/services/FeedsSync.js';
import hashCode from "../../helpers/hash-code";
import AsyncStatus from "../../helpers/async-status";
import { AsyncStatus } from "../../helpers/async-status";
export type FeedsServiceGetParameters = {
endpoint: string;
......@@ -34,26 +34,28 @@ export type FeedsServiceGetResponse = {
@Injectable()
export class FeedsService {
protected feedsSync: FeedsSync;
protected status = new AsyncStatus();
//protected feedsSync: FeedsSync;
constructor(
protected client: Client,
protected session: Session,
protected entitiesService: EntitiesService,
protected blockListService: BlockListService,
protected status: AsyncStatus,
protected feedsSync: FeedsSync,
) {
console.log('constructed feeds sync');
this.setUp();
}
async setUp() {
console.log('setup called');
this.feedsSync = new FeedsSync(
new MindsClientHttpAdapter(this.client),
await browserStorageAdapterFactory('minds-feeds-190314'),
15,
);
console.log('setup completed');
this.feedsSync.setResolvers({
stringHash: value => hashCode(value),
currentUser: () => this.session.getLoggedInUser() && this.session.getLoggedInUser().guid,
......@@ -70,12 +72,12 @@ export class FeedsService {
// Garbage collection
this.feedsSync.gc();
setTimeout(() => this.feedsSync.gc(), 15 * 60 * 1000); // Every 15 minutes
//setTimeout(() => this.feedsSync.gc(), 15 * 60 * 1000); // Every 15 minutes
}
async get(opts: FeedsServiceGetParameters): Promise<FeedsServiceGetResponse> {
await this.status.untilReady();
try {
const { entities, next } = await this.feedsSync.get(opts);
......@@ -99,7 +101,9 @@ export class FeedsService {
session: Session,
entitiesService: EntitiesService,
blockListService: BlockListService,
status: AsyncStatus,
feedsSync: FeedsSync,
) {
return new FeedsService(client, session, entitiesService, blockListService);
return new FeedsService(client, session, entitiesService, blockListService, status, feedsSync);
}
}
......@@ -95,7 +95,7 @@ export class ThemeService {
this.renderer.removeClass(document.body, 'm-theme__dark');
this.renderer.addClass(document.body, 'm-theme__light');
}
this.clearTransitions();
//this.clearTransitions();
}
clearTransitions(){
......@@ -104,4 +104,4 @@ export class ThemeService {
this.renderer.removeClass(document.body, 'm-theme-in-transition');
}, 1000);
}
}
\ No newline at end of file
}
import { BehaviorSubject, Subscription } from "rxjs";
export default class AsyncStatus {
export class AsyncStatus {
protected ready: boolean = false;
protected subject$: BehaviorSubject<boolean> = new BehaviorSubject(false);
......@@ -11,7 +11,7 @@ export default class AsyncStatus {
done(): this {
if (this.ready) {
throw new Error('Already done');
// throw new Error('Already done');
}
this.ready = true;
......@@ -42,4 +42,8 @@ export default class AsyncStatus {
});
});
}
static _() {
return new AsyncStatus();
}
}
......@@ -5,6 +5,6 @@
*/
export default function isMobile() {
let check = false;
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||(<any>window).opera);
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||(<any>window).opera||'');
return check;
};
import { NgModule } from '@angular/core';
import { NgModule, Inject, PLATFORM_ID } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule as NgCommonModule } from '@angular/common';
......@@ -65,7 +65,7 @@ const cryptoRoutes: Routes = [
{
provide: Web3WalletService,
useFactory: Web3WalletService._,
deps: [ LocalWalletService, TransactionOverlayService ]
deps: [ LocalWalletService, TransactionOverlayService, PLATFORM_ID ]
},