initial implementation

This commit is contained in:
Phil Kunz 2018-10-06 13:25:45 +00:00
commit 665bcba84e
13 changed files with 1916 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.nogit/
node_modules/
assets/pdfdir/
coverage/
public/
pages/
.yarn/

147
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,147 @@
# gitzone standard
image: hosttoday/ht-docker-node:npmci
cache:
paths:
- .npmci_cache/
key: "$CI_BUILD_STAGE"
stages:
- security
- test
- release
- metadata
# ====================
# security stage
# ====================
mirror:
stage: security
script:
- npmci git mirror
tags:
- docker
- notpriv
snyk:
stage: security
script:
- npmci npm prepare
- npmci command npm install -g snyk
- npmci command npm install --ignore-scripts
- npmci command snyk test
tags:
- docker
- notpriv
# ====================
# test stage
# ====================
testLEGACY:
stage: test
script:
- npmci npm prepare
- npmci node install legacy
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
- notpriv
allow_failure: true
testLTS:
stage: test
script:
- npmci npm prepare
- npmci node install lts
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
- notpriv
testSTABLE:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
- notpriv
release:
stage: release
script:
- npmci node install stable
- npmci npm publish
only:
- tags
tags:
- docker
- notpriv
# ====================
# metadata stage
# ====================
codequality:
stage: metadata
image: docker:stable
allow_failure: true
services:
- docker:stable-dind
script:
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run
--env SOURCE_CODE="$PWD"
--volume "$PWD":/code
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
artifacts:
paths: [codeclimate.json]
tags:
- docker
- priv
trigger:
stage: metadata
script:
- npmci trigger
only:
- tags
tags:
- docker
- notpriv
pages:
image: hosttoday/ht-docker-node:npmci
stage: metadata
script:
- npmci command npm install -g typedoc typescript
- npmci npm prepare
- npmci npm install
- npmci command typedoc --module "commonjs" --target "ES2016" --out public/ ts/
tags:
- docker
- notpriv
only:
- tags
artifacts:
expire_in: 1 week
paths:
- public
allow_failure: true
windowsCompatibility:
image: stefanscherer/node-windows:10-build-tools
stage: metadata
script:
- npm install & npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- windows
allow_failure: true

8
npmextra.json Normal file
View File

@ -0,0 +1,8 @@
{
"npmci": {
"npmGlobalTools": [
"@gitzone/npmts",
"ts-node"
]
}
}

1578
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "smartpdf",
"version": "1.0.1",
"description": "create pdfs on the fly",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"author": "Lossless GmbH",
"license": "MIT",
"scripts": {
"test": "tstest test/",
"format": "(gitzone format)"
},
"devDependencies": {
"@gitzone/tsbuild": "^2.0.22",
"@gitzone/tsrun": "^1.1.12",
"@gitzone/tstest": "^1.0.15",
"@pushrocks/tapbundle": "^3.0.7",
"@types/node": "^10.11.4"
},
"dependencies": {
"@pushrocks/smartfile": "^6.0.8",
"@pushrocks/smartnetwork": "^1.1.0",
"@pushrocks/smartpromise": "^2.0.5",
"@types/express": "^4.16.0",
"@types/puppeteer": "^1.8.0",
"express": "^4.16.3",
"puppeteer": "^1.8.0",
"smartunique": "^2.0.0"
}
}

19
test/test.ts Normal file
View File

@ -0,0 +1,19 @@
import { expect, tap } from '@pushrocks/tapbundle';
import * as smartpdf from '../ts/index'
let testSmartPdf: smartpdf.SmartPdf;
tap.test('should create a valid instance of smartpdf', async () => {
testSmartPdf = new smartpdf.SmartPdf();
expect(testSmartPdf).to.be.instanceof(smartpdf.SmartPdf);
})
tap.test('should create a pdf from html string', async () => {
await testSmartPdf.getPdfForHtmlString('hi');
});
tap.test('should create a pdf from website', async () => {
await testSmartPdf.getPdfForWebsite('https://wikipedia.org');
});
tap.start()

1
ts/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './smartpdf.classes.smartpdf';

View File

@ -0,0 +1,8 @@
import * as plugins from './smartpdf.plugins';
export class PdfCandidate {
pdfId = plugins.smartunique.shortId();
doneDeferred = plugins.smartpromise.defer();
constructor(public htmlString) {}
}

View File

@ -0,0 +1 @@
import * as plugins from './smartpdf.plugins';

View File

@ -0,0 +1,74 @@
import * as plugins from './smartpdf.plugins';
import * as paths from './smartpdf.paths';
import { Server } from 'http';
import { PdfCandidate } from './smartpdf.classes.pdfcandidate';
export class SmartPdf {
htmlServerInstance: Server;
serverPort: number;
headlessBrowser: plugins.puppeteer.Browser;
private _readyDeferred: plugins.smartpromise.Deferred<void>;
private _candidates: {[key: string]: PdfCandidate} = {};
constructor() {
this._readyDeferred = new plugins.smartpromise.Deferred();
this.init();
}
async init() {
// setup puppeteer
this.headlessBrowser = await plugins.puppeteer.launch();
// setup server
const app = plugins.express();
app.get('/:pdfId', (req, res) => {
res.setHeader('PDF-ID', this._candidates[req.params.pdfId].pdfId);
res.send(this._candidates[req.params.pdfId].htmlString);
});
this.htmlServerInstance = plugins.http.createServer(app);
const smartnetworkInstance = new plugins.smartnetwork.SmartNetwork();
const portAvailable = smartnetworkInstance.isLocalPortAvailable(3210);
this.htmlServerInstance.listen(3210, 'localhost');
this.htmlServerInstance.on('listening', () => {
this._readyDeferred.resolve();
});
}
/**
* returns a pdf for a given html string;
*/
async getPdfForHtmlString(htmlStringArg: string) {
await this._readyDeferred.promise;
const pdfCandidate = new PdfCandidate(htmlStringArg);
this._candidates[pdfCandidate.pdfId] = pdfCandidate;
const page = await this.headlessBrowser.newPage();
const response = await page.goto(`http://localhost:3210/${pdfCandidate.pdfId}`, { waitUntil: 'networkidle2' });
const headers = response.headers();
if(headers['pdf-id'] !== pdfCandidate.pdfId) {
console.log('Error! Headers do not match. For security reasons no pdf is being emitted!');
return
} else {
console.log(`id security check passed for ${pdfCandidate.pdfId}`);
}
await page.pdf({
path: plugins.path.join(paths.pdfDir, `${pdfCandidate.pdfId}.pdf`),
format: 'A4'
});
await page.close();
delete this._candidates[pdfCandidate.pdfId];
pdfCandidate.doneDeferred.resolve();
await pdfCandidate.doneDeferred.promise;
}
async getPdfForWebsite(websiteUrl: string) {
const page = await this.headlessBrowser.newPage();
const response = await page.goto(websiteUrl, { waitUntil: 'networkidle2' });
const pdfId = plugins.smartunique.shortId();
await page.pdf({
path: plugins.path.join(paths.pdfDir, `${pdfId}.pdf`),
format: 'A4'
});
await page.close();
}
}

6
ts/smartpdf.paths.ts Normal file
View File

@ -0,0 +1,6 @@
import * as plugins from './smartpdf.plugins';
export const packageDir = plugins.path.join(__dirname, '../');
export const pdfDir = plugins.path.join(packageDir, 'assets/pdfdir');
plugins.smartfile.fs.ensureDirSync(pdfDir);

30
ts/smartpdf.plugins.ts Normal file
View File

@ -0,0 +1,30 @@
// native
import * as http from 'http';
import * as path from 'path';
export {
http,
path
}
// @pushrocks
import * as smartfile from '@pushrocks/smartfile';
import * as smartpromise from '@pushrocks/smartpromise';
import * as smartnetwork from '@pushrocks/smartnetwork';
import * as smartunique from 'smartunique';
export {
smartfile,
smartpromise,
smartunique,
smartnetwork
}
// thirdparty
import * as express from 'express';
import * as puppeteer from 'puppeteer';
export {
express,
puppeteer
}

7
tslint.json Normal file
View File

@ -0,0 +1,7 @@
{
"extends": ["tslint:latest", "tslint-config-prettier"],
"rules": {
"semicolon": [true, "always"]
}
}