├── .eslintignore ├── .prettierignore ├── .gitignore ├── web-src ├── resources.d.ts ├── favicon.ico ├── styles │ ├── OBWhite_Footer.png │ ├── main.css │ ├── loginBackground.svg │ └── forkme_right_green_007200.svg ├── index.html ├── typedarrays.ts ├── printertype.ts ├── epsontmt88v.ts ├── printerchunk.ts ├── btgeneric.ts ├── index.tsx ├── usb.ts ├── bluetooth.ts ├── app.tsx └── webprinter.ts ├── .vscode ├── settings.json └── tasks.json ├── .prettierrc.json ├── .babelrc ├── tsconfig.json ├── README.md ├── .eslintrc.json ├── package.json ├── webpack.config.js └── LICENSE /.eslintignore: -------------------------------------------------------------------------------- 1 | /web -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /web -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules 3 | /web 4 | -------------------------------------------------------------------------------- /web-src/resources.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg'; 2 | declare module '*.png'; 3 | -------------------------------------------------------------------------------- /web-src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrianromeroopenbravo/webhardware/HEAD/web-src/favicon.ico -------------------------------------------------------------------------------- /web-src/styles/OBWhite_Footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrianromeroopenbravo/webhardware/HEAD/web-src/styles/OBWhite_Footer.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.detectIndentation": false, 4 | "editor.formatOnSave": true, 5 | "prettier.requireConfig": true 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "bracketSpacing": true, 7 | "arrowParens": "avoid" 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "build", 7 | "problemMatcher": ["$go"] 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | // "@babel/preset-env", 4 | "@babel/preset-react", 5 | "@babel/typescript" 6 | ], 7 | "plugins": [ 8 | "@babel/plugin-proposal-class-properties", 9 | "@babel/plugin-proposal-object-rest-spread" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "moduleResolution": "node", 7 | "allowJs": true, 8 | "noEmit": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true 13 | }, 14 | "include": ["web-src"] 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Openbravo Web Hardware 2 | 3 | This is a sample project that implement simple receipt printing from a web application using WebUSB and WebBluetooth. It has been tested in Chrome with different platforms: Windows, Linux (Debian) and Android. And using the receipt printers Epson TM-T88V (WebUSB) and the POS-5802DD (WebBluetooth). 4 | 5 | The latest version is deployed here: https://adrianromeroopenbravo.github.io/webhardware/ 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": ["eslint:recommended"], 7 | "parserOptions": { 8 | "ecmaVersion": 9, 9 | "sourceType": "module" 10 | }, 11 | "globals": {}, 12 | "rules": { 13 | "eqeqeq": ["warn", "smart"], 14 | "no-unused-vars": [ 15 | "warn", 16 | { 17 | "args": "none" 18 | } 19 | ], 20 | "no-console": ["warn"], 21 | "no-useless-escape": ["warn"] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web-src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | <%= htmlWebpackPlugin.options.title %> 12 | 13 | 14 | 15 |
16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /web-src/typedarrays.ts: -------------------------------------------------------------------------------- 1 | /* 2 | ************************************************************************************ 3 | * Copyright (C) 2019 Openbravo S.L.U. 4 | * Licensed under the Apache Software License version 2.0 5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed 7 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 9 | * specific language governing permissions and limitations under the License. 10 | ************************************************************************************ 11 | */ 12 | 13 | export function arrays8append(a1: Uint8Array, a2: Uint8Array): Uint8Array { 14 | const tmp = new Uint8Array(a1.length + a2.length); 15 | tmp.set(a1, 0); 16 | tmp.set(a2, a1.byteLength); 17 | return tmp; 18 | } 19 | -------------------------------------------------------------------------------- /web-src/printertype.ts: -------------------------------------------------------------------------------- 1 | /* 2 | ************************************************************************************ 3 | * Copyright (C) 2019 Openbravo S.L.U. 4 | * Licensed under the Apache Software License version 2.0 5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed 7 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 9 | * specific language governing permissions and limitations under the License. 10 | ************************************************************************************ 11 | */ 12 | 13 | export interface WebDevice { 14 | connected(): boolean; 15 | request(): Promise; 16 | sendData(data: Uint8Array): Promise; 17 | } 18 | 19 | export interface PrinterType { 20 | name: string; 21 | createWebDevice: () => WebDevice; 22 | } 23 | -------------------------------------------------------------------------------- /web-src/epsontmt88v.ts: -------------------------------------------------------------------------------- 1 | /* 2 | ************************************************************************************ 3 | * Copyright (C) 2019 Openbravo S.L.U. 4 | * Licensed under the Apache Software License version 2.0 5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed 7 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 9 | * specific language governing permissions and limitations under the License. 10 | ************************************************************************************ 11 | */ 12 | 13 | import { USB } from './usb'; 14 | import { PrinterType } from './printertype'; 15 | 16 | export const EPSONTMT88V: PrinterType = { 17 | name: 'EPSON TM T88V', 18 | createWebDevice: () => 19 | new USB({ 20 | vendorId: 0x04b8, 21 | productId: 0x0202 22 | }) 23 | }; 24 | -------------------------------------------------------------------------------- /web-src/printerchunk.ts: -------------------------------------------------------------------------------- 1 | /* 2 | ************************************************************************************ 3 | * Copyright (C) 2019 Openbravo S.L.U. 4 | * Licensed under the Apache Software License version 2.0 5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed 7 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 9 | * specific language governing permissions and limitations under the License. 10 | ************************************************************************************ 11 | */ 12 | 13 | export type PrintChunk = (data: Uint8Array) => Promise; 14 | 15 | export async function arrays8print( 16 | printChunk: PrintChunk, 17 | size: number, 18 | data: Uint8Array 19 | ) { 20 | for (let i = 0; i < data.length; i += size) { 21 | await printChunk(data.slice(i, i + size)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web-src/btgeneric.ts: -------------------------------------------------------------------------------- 1 | /* 2 | ************************************************************************************ 3 | * Copyright (C) 2019 Openbravo S.L.U. 4 | * Licensed under the Apache Software License version 2.0 5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed 7 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 9 | * specific language governing permissions and limitations under the License. 10 | ************************************************************************************ 11 | */ 12 | 13 | import { Bluetooth } from './bluetooth'; 14 | import { PrinterType } from './printertype'; 15 | 16 | export const BTGENERIC: PrinterType = { 17 | name: 'Generic Bluetooth Receipt Printer', 18 | createWebDevice: () => 19 | new Bluetooth({ 20 | services: ['e7810a71-73ae-499d-8c15-faa9aef0c3f2'], 21 | characteristic: 'bef8d6c9-9c21-4c9e-b632-bd58c1009f9f', 22 | buffersize: 20 23 | }) 24 | }; 25 | -------------------------------------------------------------------------------- /web-src/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | ************************************************************************************ 3 | * Copyright (C) 2019 Openbravo S.L.U. 4 | * Licensed under the Apache Software License version 2.0 5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed 7 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 9 | * specific language governing permissions and limitations under the License. 10 | ************************************************************************************ 11 | */ 12 | 13 | import React from 'react'; 14 | import ReactDOM from 'react-dom'; 15 | import { App } from './app'; 16 | import './styles/main.css'; 17 | 18 | if ('serviceWorker' in navigator) { 19 | window.addEventListener('load', () => { 20 | navigator.serviceWorker 21 | .register('./service-worker.js') 22 | .catch(registrationError => { 23 | console.warn('SW registration failed: ', registrationError); 24 | }); 25 | }); 26 | } 27 | 28 | ReactDOM.render(, document.getElementById('root')); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webhardware", 3 | "version": "1.0.0", 4 | "description": "Openbravo Web Hardware", 5 | "keywords": [], 6 | "author": "Openbravo, S.L.U.", 7 | "license": "Apache-2.0", 8 | "repository": "https://github.com/adrianromeroopenbravo/webhardware", 9 | "main": "index.js", 10 | "scripts": { 11 | "clean": "rm -rf web", 12 | "build": "webpack --config ./webpack.config.js --mode production", 13 | "check-types": "tsc", 14 | "test": "echo \"No test specified\" && exit 0" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.4.3", 18 | "@babel/plugin-proposal-class-properties": "^7.4.0", 19 | "@babel/plugin-proposal-object-rest-spread": "^7.4.3", 20 | "@babel/preset-env": "^7.4.3", 21 | "@babel/preset-react": "^7.0.0", 22 | "@babel/preset-typescript": "^7.3.3", 23 | "@types/react": "^16.8.15", 24 | "@types/react-dom": "^16.8.4", 25 | "@types/w3c-web-usb": "^1.0.3", 26 | "@types/web-bluetooth": "0.0.4", 27 | "babel-loader": "^8.0.5", 28 | "css-loader": "^2.1.1", 29 | "file-loader": "^4.2.0", 30 | "html-loader": "^0.5.5", 31 | "html-webpack-plugin": "^3.2.0", 32 | "mini-css-extract-plugin": "^0.6.0", 33 | "typescript": "^3.4.5", 34 | "webpack": "^4.30.0", 35 | "webpack-cli": "^3.3.1", 36 | "workbox-webpack-plugin": "^4.3.0" 37 | }, 38 | "dependencies": { 39 | "react": "^16.8.6", 40 | "react-dom": "^16.8.6" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /web-src/styles/main.css: -------------------------------------------------------------------------------- 1 | /* 2 | ************************************************************************************ 3 | * Copyright (C) 2019 Openbravo S.L.U. 4 | * Licensed under the Apache Software License version 2.0 5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed 7 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 9 | * specific language governing permissions and limitations under the License. 10 | ************************************************************************************ 11 | */ 12 | 13 | body.display-background { 14 | background: url(./loginBackground.svg); 15 | background-size: cover; 16 | } 17 | 18 | textarea.document-editor { 19 | white-space: nowrap; 20 | font-family: monospace; 21 | } 22 | 23 | h1.title { 24 | font-family: sans-serif; 25 | color: #ffffff; 26 | } 27 | 28 | button.action { 29 | display: inline-block; 30 | padding: 0.7em 1.5em; 31 | border: 0.16em solid #ffffff; 32 | margin: 0 0.3em 0.3em 0; 33 | box-sizing: border-box; 34 | text-decoration: none; 35 | font-family: sans-serif; 36 | font-size: 0.8em; 37 | font-weight: 600; 38 | color: #ffffff; 39 | text-align: center; 40 | transition: all 0.15s; 41 | background-color: transparent; 42 | } 43 | button.action:hover { 44 | color: #dddddd; 45 | border-color: #dddddd; 46 | } 47 | button.action:active { 48 | color: #bbbbbb; 49 | border-color: #bbbbbb; 50 | } 51 | -------------------------------------------------------------------------------- /web-src/styles/loginBackground.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 20 | 25 | 26 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /*global require, module, __dirname */ 2 | 3 | require('webpack'); 4 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 6 | const WorkboxPlugin = require('workbox-webpack-plugin'); 7 | 8 | module.exports = { 9 | entry: './web-src/index.tsx', 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.(ts|tsx|js|jsx)$/, 14 | exclude: /node_modules/, 15 | use: ['babel-loader'] 16 | }, 17 | { 18 | test: /\.css$/, 19 | use: [{ loader: MiniCssExtractPlugin.loader }, { loader: 'css-loader' }] 20 | }, 21 | { 22 | test: /\.(png|svg|jpg|gif)$/, 23 | use: ['file-loader'] 24 | } 25 | ] 26 | }, 27 | resolve: { 28 | extensions: ['*', '.ts', '.tsx', '.js', '.jsx'] 29 | }, 30 | output: { 31 | path: __dirname + '/web', 32 | publicPath: './', 33 | filename: 'bundle.js' 34 | }, 35 | optimization: { 36 | minimize: true, 37 | splitChunks: { 38 | chunks: 'all', 39 | cacheGroups: { 40 | react: { 41 | priority: 100, 42 | test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, 43 | name: 'react', 44 | filename: '[name].bundle.js', 45 | enforce: true 46 | }, 47 | common: { 48 | priority: 1, 49 | test: /[\\/]node_modules[\\/]/, 50 | name: 'common', 51 | filename: '[name].bundle.js', 52 | enforce: true 53 | } 54 | } 55 | } 56 | }, 57 | devtool: 'source-map', 58 | plugins: [ 59 | new MiniCssExtractPlugin({ 60 | filename: 'main.css' 61 | }), 62 | new HtmlWebPackPlugin({ 63 | title: 'Openbravo - Web Hardware', 64 | template: './web-src/index.html', 65 | favicon: './web-src/favicon.ico', 66 | filename: 'index.html' 67 | }), 68 | new WorkboxPlugin.GenerateSW({ 69 | // Progressive web application. PWA. 70 | // these options encourage the ServiceWorkers to get in there fast 71 | // and not allow any straggling "old" SWs to hang around 72 | clientsClaim: true, 73 | skipWaiting: true 74 | }) 75 | ] 76 | }; 77 | -------------------------------------------------------------------------------- /web-src/usb.ts: -------------------------------------------------------------------------------- 1 | /* 2 | ************************************************************************************ 3 | * Copyright (C) 2019 Openbravo S.L.U. 4 | * Licensed under the Apache Software License version 2.0 5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed 7 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 9 | * specific language governing permissions and limitations under the License. 10 | ************************************************************************************ 11 | */ 12 | 13 | import { PrintChunk, arrays8print } from './printerchunk'; 14 | import { WebDevice } from './printertype'; 15 | 16 | export interface USBPrinterType { 17 | vendorId: number; 18 | productId: number; 19 | } 20 | 21 | export class USB implements WebDevice { 22 | printertype: USBPrinterType; 23 | device: USBDevice | null; 24 | 25 | constructor(printertype: USBPrinterType) { 26 | this.printertype = printertype; 27 | this.device = null; 28 | 29 | if (navigator.usb && navigator.usb.addEventListener) { 30 | navigator.usb.addEventListener( 31 | 'disconnect', 32 | (event: USBConnectionEvent) => { 33 | if (event.device === this.device) { 34 | this.onDisconnected(); 35 | } 36 | } 37 | ); 38 | } 39 | } 40 | 41 | connected(): boolean { 42 | return this.device !== null; 43 | } 44 | 45 | async request(): Promise { 46 | if (!navigator.usb || !navigator.usb.requestDevice) { 47 | throw 'USB not supported.'; 48 | } 49 | 50 | this.device = await navigator.usb.requestDevice({ 51 | filters: [ 52 | { 53 | vendorId: this.printertype.vendorId, 54 | productId: this.printertype.productId 55 | } 56 | ] 57 | }); 58 | } 59 | 60 | async sendData(data: Uint8Array): Promise { 61 | if (!this.device) { 62 | throw 'Device is not connected.'; 63 | } 64 | try { 65 | await this.device.open(); 66 | 67 | await this.device.selectConfiguration(1); 68 | await this.device.claimInterface(0); 69 | await arrays8print(this.printChunk(), 64, data); 70 | await this.device.close(); 71 | } finally { 72 | this.onDisconnected(); 73 | } 74 | } 75 | 76 | printChunk(): PrintChunk { 77 | return async (chunk: Uint8Array) => { 78 | if (this.device === null) { 79 | throw 'device is null'; 80 | } 81 | await this.device.transferOut(1, chunk.buffer); 82 | }; 83 | } 84 | 85 | onDisconnected(): void { 86 | this.device = null; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /web-src/bluetooth.ts: -------------------------------------------------------------------------------- 1 | /* 2 | ************************************************************************************ 3 | * Copyright (C) 2019 Openbravo S.L.U. 4 | * Licensed under the Apache Software License version 2.0 5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed 7 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 9 | * specific language governing permissions and limitations under the License. 10 | ************************************************************************************ 11 | */ 12 | 13 | import { PrintChunk, arrays8print } from './printerchunk'; 14 | import { WebDevice } from './printertype'; 15 | 16 | export interface BTPrinterType { 17 | services: string[]; 18 | characteristic: string; 19 | buffersize: number; 20 | } 21 | 22 | export class Bluetooth implements WebDevice { 23 | printertype: BTPrinterType; 24 | size: number; 25 | device: BluetoothDevice | null; 26 | server: BluetoothRemoteGATTServer | null; 27 | service: BluetoothRemoteGATTService | null; 28 | characteristic: BluetoothRemoteGATTCharacteristic | null; 29 | 30 | constructor(printertype: BTPrinterType) { 31 | this.printertype = printertype; 32 | this.size = this.printertype.buffersize; 33 | this.device = null; 34 | this.server = null; 35 | this.service = null; 36 | this.characteristic = null; 37 | } 38 | 39 | connected(): boolean { 40 | return this.device !== null; 41 | } 42 | 43 | async request(): Promise { 44 | if (!navigator.bluetooth || !navigator.bluetooth.requestDevice) { 45 | throw 'Bluetooth not supported.'; 46 | } 47 | 48 | this.device = await navigator.bluetooth.requestDevice({ 49 | filters: [ 50 | { 51 | services: this.printertype.services 52 | } 53 | ] 54 | }); 55 | } 56 | 57 | async sendData(data: Uint8Array): Promise { 58 | if (!this.device || !this.device.gatt) { 59 | throw 'Device is not connected.'; 60 | } 61 | 62 | try { 63 | if (!this.characteristic) { 64 | this.server = await this.device.gatt.connect(); 65 | this.service = await this.server.getPrimaryService( 66 | this.printertype.services[0] 67 | ); 68 | this.characteristic = await this.service.getCharacteristic( 69 | this.printertype.characteristic 70 | ); 71 | } 72 | await arrays8print(this.printChunk(), this.size, data); 73 | } catch (error) { 74 | this.onDisconnected(); 75 | throw error; 76 | } 77 | } 78 | 79 | printChunk(): PrintChunk { 80 | return async (chunk: Uint8Array) => { 81 | if (this.characteristic === null) { 82 | throw 'device is null'; 83 | } 84 | await this.characteristic.writeValue(chunk); 85 | }; 86 | } 87 | 88 | onDisconnected() { 89 | this.device = null; 90 | this.server = null; 91 | this.service = null; 92 | this.characteristic = null; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /web-src/app.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | ************************************************************************************ 3 | * Copyright (C) 2019 Openbravo S.L.U. 4 | * Licensed under the Apache Software License version 2.0 5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed 7 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 9 | * specific language governing permissions and limitations under the License. 10 | ************************************************************************************ 11 | */ 12 | 13 | import React, { Component } from 'react'; 14 | import { WEBPrinter } from './webprinter'; 15 | import { EPSONTMT88V } from './epsontmt88v'; 16 | import { BTGENERIC } from './btgeneric'; 17 | 18 | import githubribbon from './styles/forkme_right_green_007200.svg'; 19 | import openbravologo from './styles/OBWhite_Footer.png'; 20 | 21 | export interface AppProps {} 22 | 23 | export interface AppState { 24 | text: string; 25 | } 26 | 27 | export class App extends Component { 28 | usbwebprinter: WEBPrinter; 29 | btwebprinter: WEBPrinter; 30 | 31 | constructor(props: AppProps) { 32 | super(props); 33 | this.state = { 34 | text: ` 35 | 36 | 37 | 38 | Lorem ipsum 39 | 40 | 41 | dolor sit amet, 42 | 43 | 44 | consectetur adipiscing elit, 45 | 46 | 47 | do eiusmod 48 | tempor incididunt 49 | 50 | 51 | ut 52 | labore et dolore 53 | magna aliqua. 54 | 55 | 56 | ` 57 | }; 58 | 59 | this.usbwebprinter = new WEBPrinter(EPSONTMT88V); 60 | this.btwebprinter = new WEBPrinter(BTGENERIC); 61 | } 62 | 63 | async printText(printer: WEBPrinter) { 64 | try { 65 | if (!printer.connected()) { 66 | await printer.request(); 67 | } 68 | await printer.print(this.state.text); 69 | alert('Success.'); 70 | } catch (error) { 71 | alert('Cannot print.'); 72 | } 73 | } 74 | 75 | async handleClickUSB( 76 | evt: React.MouseEvent 77 | ): Promise { 78 | await this.printText(this.usbwebprinter); 79 | } 80 | 81 | async handleClickBT(evt: React.MouseEvent): Promise { 82 | await this.printText(this.btwebprinter); 83 | } 84 | 85 | updateText(evt: React.ChangeEvent): void { 86 | this.setState({ 87 | text: evt.target.value 88 | }); 89 | } 90 | 91 | render(): JSX.Element { 92 | return ( 93 |
94 | 95 | Fork me on GitHub 100 | 101 |

102 | 103 | Openbravo, S.L.U. 108 | 109 |  Web Hardware 110 |

111 |
112 | 115 | 118 |
119 |
120 | 128 |
129 |
130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /web-src/webprinter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | ************************************************************************************ 3 | * Copyright (C) 2019 Openbravo S.L.U. 4 | * Licensed under the Apache Software License version 2.0 5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed 7 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 9 | * specific language governing permissions and limitations under the License. 10 | ************************************************************************************ 11 | */ 12 | 13 | import { PrinterType, WebDevice } from './printertype'; 14 | import { arrays8append } from './typedarrays'; 15 | 16 | const ESCPOS = { 17 | encoderText: new TextEncoder(), 18 | NEW_LINE: new Uint8Array([0x0d, 0x0a]), 19 | PARTIAL_CUT_1: new Uint8Array([0x1b, 0x69]), 20 | 21 | CHAR_SIZE_0: new Uint8Array([0x1d, 0x21, 0x00]), 22 | CHAR_SIZE_1: new Uint8Array([0x1d, 0x21, 0x01]), 23 | CHAR_SIZE_2: new Uint8Array([0x1d, 0x21, 0x30]), 24 | CHAR_SIZE_3: new Uint8Array([0x1d, 0x21, 0x31]), 25 | 26 | BOLD_SET: new Uint8Array([0x1b, 0x45, 0x01]), 27 | BOLD_RESET: new Uint8Array([0x1b, 0x45, 0x00]), 28 | UNDERLINE_SET: new Uint8Array([0x1b, 0x2d, 0x01]), 29 | UNDERLINE_RESET: new Uint8Array([0x1b, 0x2d, 0x00]), 30 | 31 | CENTER_JUSTIFICATION: new Uint8Array([0x1b, 0x61, 0x01]), 32 | LEFT_JUSTIFICATION: new Uint8Array([0x1b, 0x61, 0x00]), 33 | RIGHT_JUSTIFICATION: new Uint8Array([0x1b, 0x61, 0x02]), 34 | 35 | DRAWER_OPEN: new Uint8Array([0x1b, 0x70, 0x00, 0x32, -0x06]) 36 | }; 37 | 38 | export class WEBPrinter { 39 | webdevice: WebDevice; 40 | 41 | constructor(printertype: PrinterType) { 42 | this.webdevice = printertype.createWebDevice(); 43 | } 44 | 45 | connected() { 46 | return this.webdevice.connected(); 47 | } 48 | 49 | async request() { 50 | await this.webdevice.request(); 51 | } 52 | 53 | async print(doc: string): Promise { 54 | const parser: DOMParser = new DOMParser(); 55 | const dom: Document = parser.parseFromString(doc, 'application/xml'); 56 | 57 | if (dom.documentElement.nodeName === 'parsererror') { 58 | throw 'Error while parsing XML template.'; 59 | } 60 | 61 | const printerdocs: Uint8Array = await this.processDOM(dom); 62 | if (printerdocs.length > 0) { 63 | await this.webdevice.sendData(printerdocs); 64 | } 65 | } 66 | 67 | async processDOM(dom: Document): Promise { 68 | for (let el of dom.children) { 69 | if (el.nodeName === 'output') { 70 | return await this.processOutput(el); 71 | } 72 | } 73 | return new Uint8Array(); 74 | } 75 | 76 | async processOutput(dom: Element): Promise { 77 | let output = new Uint8Array(); 78 | for (let el of dom.children) { 79 | if (el.nodeName === 'ticket') { 80 | output = arrays8append(output, await this.processTicket(el)); 81 | } else if (el.nodeName === 'opendrawer') { 82 | output = arrays8append(output, ESCPOS.DRAWER_OPEN); 83 | } 84 | } 85 | return output; 86 | } 87 | 88 | async processTicket(dom: Element): Promise { 89 | let output = new Uint8Array(); 90 | for (let el of dom.children) { 91 | if (el.nodeName === 'line') { 92 | output = arrays8append(output, await this.processLine(el)); 93 | output = arrays8append(output, ESCPOS.NEW_LINE); 94 | } 95 | } 96 | output = arrays8append(output, ESCPOS.NEW_LINE); 97 | output = arrays8append(output, ESCPOS.NEW_LINE); 98 | output = arrays8append(output, ESCPOS.NEW_LINE); 99 | output = arrays8append(output, ESCPOS.NEW_LINE); 100 | output = arrays8append(output, ESCPOS.PARTIAL_CUT_1); 101 | return output; 102 | } 103 | 104 | async processLine(dom: Element): Promise { 105 | let output: Uint8Array = new Uint8Array(); 106 | const fontsize: string | null = dom.getAttribute('size'); 107 | 108 | if (fontsize === '1') { 109 | output = arrays8append(output, ESCPOS.CHAR_SIZE_1); 110 | } else if (fontsize === '2') { 111 | output = arrays8append(output, ESCPOS.CHAR_SIZE_2); 112 | } else if (fontsize === '3') { 113 | output = arrays8append(output, ESCPOS.CHAR_SIZE_3); 114 | } else { 115 | output = arrays8append(output, ESCPOS.CHAR_SIZE_0); 116 | } 117 | for (let el of dom.children) { 118 | if (el.nodeName === 'text') { 119 | const txt = el.textContent; 120 | 121 | if (txt) { 122 | const bold = el.getAttribute('bold'); 123 | const uderline = el.getAttribute('underline'); 124 | 125 | if (bold === 'true') { 126 | output = arrays8append(output, ESCPOS.BOLD_SET); 127 | } 128 | if (uderline === 'true') { 129 | output = arrays8append(output, ESCPOS.UNDERLINE_SET); 130 | } 131 | output = arrays8append(output, ESCPOS.encoderText.encode(txt)); 132 | if (bold === 'true') { 133 | output = arrays8append(output, ESCPOS.BOLD_RESET); 134 | } 135 | if (uderline === 'true') { 136 | output = arrays8append(output, ESCPOS.UNDERLINE_RESET); 137 | } 138 | } 139 | } 140 | } 141 | return output; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /web-src/styles/forkme_right_green_007200.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 29 | 33 | 37 | 45 | 50 | 56 | 61 | 67 | 72 | 78 | 83 | 88 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [2019] [Openbravo, S.L.U.] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------