Compare commits
3 Commits
03c5ee1244
...
4c722e2144
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c722e2144 | |||
| 63343aae16 | |||
| a665703f46 |
@@ -14,11 +14,11 @@ Objetivos:
|
||||
- [x] Subscripción a topics por API REST
|
||||
- [x] Documentación de la API REST con Bruno
|
||||
- [x] Firma de mesajes con HMAC sha256
|
||||
- [ ] Generar eventos periodicos
|
||||
- [x] Generar eventos periodicos
|
||||
- [ ] Generar ordenes de shopify proceduralmente
|
||||
- [ ] Generar ciclos de vida realistas create -> update -> delete
|
||||
- [ ] Dockerizar
|
||||
- [ ] Añadir clientes iniciales
|
||||
- [x] Añadir clientes iniciales
|
||||
|
||||
Webhooks Soportados:
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ client.use(express.json({
|
||||
client.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Webhook reception
|
||||
client.post('/order/create', verifyWebhookSignature, handleWebhook);
|
||||
client.post('/order/create', handleWebhook);
|
||||
|
||||
// Health check
|
||||
client.get('/health', (req, res) => {
|
||||
res.status(200).send({ resp: 'OK' });
|
||||
res.status(200).send({ resp: 'OK - Client' });
|
||||
});
|
||||
|
||||
export default client;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Configuracion de los host iniciales y a que topics escuchan
|
||||
*/
|
||||
|
||||
import { SetupSubscription, SubscriptionData } from "#controllers/subscriptions.types.js"
|
||||
import { ScheduledEvent, SetupSubscription, SubscriptionData } from "#controllers/subscriptions.types.js"
|
||||
import { webhooksConfig } from "."
|
||||
|
||||
|
||||
@@ -22,12 +22,19 @@ export function generateSubscriptions(initialHosts?: SetupSubscription[]) {
|
||||
...setup
|
||||
})
|
||||
}
|
||||
|
||||
return topicSubscriberMap
|
||||
}
|
||||
|
||||
type InitialHost = {
|
||||
topic: string,
|
||||
host: string,
|
||||
port: string,
|
||||
endpoint: string,
|
||||
method: "POST" | "PUT" | "DELETE",
|
||||
secretkey: string,
|
||||
}
|
||||
|
||||
export const INITIAL_HOSTS: SetupSubscription[] = [
|
||||
export const INITIAL_HOSTS: InitialHost[] = [
|
||||
{
|
||||
topic: "order/create",
|
||||
host: "localhost",
|
||||
@@ -35,11 +42,13 @@ export const INITIAL_HOSTS: SetupSubscription[] = [
|
||||
endpoint: "/order/create",
|
||||
method: "POST",
|
||||
secretkey: webhooksConfig.apiSecret || "1234",
|
||||
open: new Date,
|
||||
options: {
|
||||
period: 1000
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
export const INITIAL_EVENTS: ScheduledEvent[] = [
|
||||
{
|
||||
topic: "order/create",
|
||||
numberOfMessages: 5,
|
||||
periodMs: 1000
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { generateHMACSignature } from "#middleware/hmac.js";
|
||||
import { requestBuilder, shopifyHeaderBuilder } from "#shared/requests.js";
|
||||
import { ShopifyEvent, SubscriptionData, SubscriptorData, Topic } from "./subscriptions.types";
|
||||
import { ScheduledEvent, ShopifyEvent, SubscriptionData, SubscriptorData, Topic } from "./subscriptions.types";
|
||||
|
||||
/**
|
||||
* De scheduler tiene poco
|
||||
* TODO:
|
||||
* - No se pueden crear eventos nuevos
|
||||
*/
|
||||
export class EventScheduler {
|
||||
public eventList: SubscriptorData[] = []
|
||||
public eventList: ScheduledEvent[] = []
|
||||
public activeIntervals: NodeJS.Timeout[] = []
|
||||
|
||||
public subscriptionManager: SubscriptionManager
|
||||
constructor(args: {
|
||||
events?: SubscriptorData[],
|
||||
events?: ScheduledEvent[],
|
||||
subscriptionManager: SubscriptionManager
|
||||
}) {
|
||||
this.subscriptionManager = args.subscriptionManager
|
||||
|
||||
if (args.events != undefined) {
|
||||
this.eventList = args.events
|
||||
}
|
||||
@@ -20,16 +25,25 @@ export class EventScheduler {
|
||||
|
||||
private start() {
|
||||
for (const event of this.eventList) {
|
||||
console.log("Evento", event)
|
||||
let sentMesages = 0
|
||||
const interval = setInterval(() => {
|
||||
console.log("[Server] Lanzado evento ", event)
|
||||
if (event.options?.mesages == undefined || event.options?.mesages < 1)
|
||||
if (event.numberOfMessages == undefined || event.numberOfMessages < 1) {
|
||||
console.log("xx")
|
||||
return; // Se lannza de continuo
|
||||
}
|
||||
|
||||
this.subscriptionManager.poll(<ShopifyEvent>{
|
||||
topic: event.topic,
|
||||
data: { id: 123 }
|
||||
})
|
||||
|
||||
sentMesages++;
|
||||
if (sentMesages > event.options.mesages) {
|
||||
if (sentMesages > event.numberOfMessages) {
|
||||
clearInterval(interval)
|
||||
}
|
||||
}, event.options?.period ?? 1000)
|
||||
}, event.periodMs ?? 1000)
|
||||
this.activeIntervals.push(interval)
|
||||
}
|
||||
}
|
||||
@@ -38,13 +52,10 @@ export class EventScheduler {
|
||||
|
||||
export class SubscriptionManager {
|
||||
private subscriptions: Map<Topic, SubscriptionData> = new Map<Topic, SubscriptionData>()
|
||||
public scheduler: EventScheduler
|
||||
|
||||
constructor(
|
||||
scheduler: EventScheduler,
|
||||
subs?: typeof this.subscriptions,
|
||||
) {
|
||||
this.scheduler = scheduler
|
||||
if (subs != undefined) {
|
||||
this.subscriptions = subs
|
||||
}
|
||||
@@ -67,12 +78,14 @@ export class SubscriptionManager {
|
||||
const topic = event.topic
|
||||
|
||||
if (!this.subscriptions.has(topic)) {
|
||||
console.error("Topic desconocido: " + topic)
|
||||
console.error("[Server] Topic desconocido: " + topic)
|
||||
return;
|
||||
}
|
||||
|
||||
const subscriptors = this.subscriptions.get(topic)
|
||||
|
||||
console.log("[Server] Enviando evento a ", subscriptors)
|
||||
|
||||
for (const sub of subscriptors!.subscriptors) {
|
||||
const body = {
|
||||
id: 1234
|
||||
@@ -90,13 +103,14 @@ export class SubscriptionManager {
|
||||
endpoint: sub.endpoint
|
||||
})
|
||||
request.write(parsedBody)
|
||||
|
||||
// Data puede venir en chunks!
|
||||
request.on("data", () => console.log)
|
||||
request.on("end", () => console.log)
|
||||
request.on("error", () => console.error)
|
||||
|
||||
request.end()
|
||||
console.debug("Enviado evento a ", sub.host, ":", sub.port, sub.endpoint)
|
||||
console.log("[Server] Enviado evento a ", sub.host, ":", sub.port, sub.endpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,7 @@ export type SubscriptorData = {
|
||||
port: string,
|
||||
endpoint: string,
|
||||
secretkey: string,
|
||||
method: "POST" | "GET" | "PUT" | "DELETE"
|
||||
open: Date,
|
||||
options?: {
|
||||
/* En ms. Cada cuanto se envia un mensaje nuevo, para solo se envia un mensaje */
|
||||
period?: number,
|
||||
/* Numero de mensajes, para undefiend solo se manda uno, para <= 0 seran infinitos */
|
||||
mesages?: number,
|
||||
}
|
||||
method: "POST" | "PUT" | "DELETE"
|
||||
}
|
||||
|
||||
export type ShopifyEvent = {
|
||||
@@ -33,3 +26,9 @@ export type Topic = string
|
||||
export type SetupSubscription = SubscriptorData & {
|
||||
topic: string
|
||||
}
|
||||
|
||||
export type ScheduledEvent = {
|
||||
topic: string // Meter una lista de topics/plantilla
|
||||
numberOfMessages?: number,
|
||||
periodMs?: number
|
||||
}
|
||||
|
||||
106
src/impresoras/printer-ejemplo-brother.ts
Normal file
106
src/impresoras/printer-ejemplo-brother.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import net from "net"
|
||||
|
||||
const PRINTER_IP = "192.168.1.254"
|
||||
const PRINTER_PORT = 9100
|
||||
|
||||
// Definición de comandos especiales en Hexadecimal
|
||||
const ESC = 0x1B;
|
||||
const LF = 0x0A;
|
||||
const FF = 0x0C;
|
||||
|
||||
function generateQRCode(data: string) {
|
||||
const dataBuffer = Buffer.from(data);
|
||||
const length = dataBuffer.length;
|
||||
const dL = length & 0xFF; // Byte bajo de la longitud
|
||||
const dH = (length >> 8) & 0xFF; // Byte alto de la longitud
|
||||
|
||||
return Buffer.concat([
|
||||
Buffer.from([ESC, 0x69, 0x51]), // Comando QR Code (ESC i Q)
|
||||
Buffer.from([0x02]), // n1: Tamaño de la celda (0x03 a 0x0B)
|
||||
Buffer.from([0x00]), // n2: Símbolo (0x00 = Standard)
|
||||
Buffer.from([0x00]), // n3: Structured Append (0x00 = No)
|
||||
Buffer.from([0x02]), // n4: Error Correction (0x02 = M)
|
||||
Buffer.from([0x00]), // n5: Data kind (0x00 = Manual)
|
||||
Buffer.from([dL, dH]), // Longitud de los datos
|
||||
dataBuffer // El contenido (google.com)
|
||||
]);
|
||||
}
|
||||
|
||||
const qrdata = "google.com"
|
||||
function sendPTouchTemplate() {
|
||||
const client = new net.Socket();
|
||||
|
||||
client.connect(PRINTER_PORT, PRINTER_IP, () => {
|
||||
// Comandos P-Touch Template
|
||||
const commands = Buffer.concat([
|
||||
Buffer.from([0x1b, 0x69, 0x61, 0x03]), // 1. Entrar en modo P-touch Template
|
||||
Buffer.from('^II'), // 2. Inicializar etiqueta
|
||||
|
||||
// Configuración de texto
|
||||
Buffer.from('^ONProduct: ABC123^FS'), // Texto 1
|
||||
Buffer.from('^ONPrice: $19.99^FS'), // Texto 2
|
||||
Buffer.from('^ONDate: 2025-12-29^FS'),// Texto 3
|
||||
|
||||
// Código QR (Comando ^BQ)
|
||||
// Parámetros: ^BQ, modelo, tamaño, error, datos
|
||||
Buffer.from('^BQ,2,4,M,https://google.com^FS'),
|
||||
|
||||
Buffer.from('^FF'), // 3. Imprimir y Cortar (Form Feed)
|
||||
]);
|
||||
|
||||
client.write(commands, () => {
|
||||
console.log('Enviado en modo P-touch Template');
|
||||
client.destroy();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sendLabel() {
|
||||
const client = new net.Socket();
|
||||
|
||||
client.connect(PRINTER_PORT, PRINTER_IP, () => {
|
||||
console.log(`Conectado a la impresora en ${PRINTER_IP}`);
|
||||
|
||||
// Creamos un array de buffers para enviar los comandos crudos
|
||||
const commands = Buffer.concat([
|
||||
Buffer.from([ESC, 0x40]), // Inicializar (ESC @)
|
||||
Buffer.from([ESC, 0x69, 0x61, 0x00]), // Modo ESC/P (ESC i a 0)
|
||||
|
||||
// Línea 1
|
||||
Buffer.from([ESC, 0x24, 0x20, 0x00]), // Posición horizontal 32 dots (ESC $ nL nH)
|
||||
Buffer.from('Product: ABC123\n'),
|
||||
|
||||
Buffer.from([LF]), // Salto de línea extra
|
||||
|
||||
generateQRCode(qrdata),
|
||||
|
||||
// Línea 2
|
||||
Buffer.from([ESC, 0x24, 0x20, 0x00]),
|
||||
Buffer.from('Price: $19.99\n'),
|
||||
|
||||
Buffer.from([LF]),
|
||||
|
||||
// Línea 3
|
||||
Buffer.from([ESC, 0x24, 0x20, 0x00]),
|
||||
Buffer.from('Date: 2025-12-29\n'),
|
||||
|
||||
Buffer.from([FF]) // Imprimir y expulsar (Form Feed)
|
||||
]);
|
||||
|
||||
client.write(commands, () => {
|
||||
console.log('Datos enviados correctamente.');
|
||||
client.destroy(); // Cerramos la conexión después de enviar
|
||||
});
|
||||
});
|
||||
|
||||
client.on('error', (err) => {
|
||||
console.error(`Error de conexión: ${err.message}`);
|
||||
});
|
||||
|
||||
client.on('close', () => {
|
||||
console.log('Conexión cerrada.');
|
||||
});
|
||||
}
|
||||
|
||||
sendLabel();
|
||||
//sendPTouchTemplate()
|
||||
@@ -6,10 +6,15 @@ const PRINTER_PORT = 9100; // Standard raw printing port
|
||||
|
||||
// Simple ZPL label example
|
||||
const zplData = `
|
||||
^XA
|
||||
^FO50,50^A0N,50,50^FDZSim Node Server Test^FS
|
||||
^FO50,120^B3N,N,100,Y,N^FD123456^FS
|
||||
^XZ
|
||||
<ESC>@
|
||||
<ESC>(U<01><00><0A>
|
||||
<ESC>$<32><00>
|
||||
Product: ABC123<LF>
|
||||
<ESC>$<32><00>
|
||||
Price: $19.99<LF>
|
||||
<ESC>$<32><00>
|
||||
Date: 2025-12-29<LF>
|
||||
<FF>
|
||||
`;
|
||||
|
||||
const client = new net.Socket();
|
||||
@@ -1,21 +1,25 @@
|
||||
import { Router } from 'express';
|
||||
import { ordersHandlerBuilder } from '#controllers/orders.webhook.js';
|
||||
import { subscriptonHandlerBuilder } from '#controllers/subscriptions.js';
|
||||
import { generateSubscriptions } from '#config/initial_hosts.js';
|
||||
import { generateSubscriptions, INITIAL_EVENTS, INITIAL_HOSTS } from '#config/initial_hosts.js';
|
||||
import { EventScheduler, SubscriptionManager } from '#controllers/subscription_manager.js';
|
||||
|
||||
const webhookRouter = Router();
|
||||
|
||||
const baseSubscriptions = generateSubscriptions()
|
||||
const subscriptionList = [...baseSubscriptions.values()].map(e => e.subscriptors).flat()
|
||||
const scheduler = new EventScheduler({ events: subscriptionList })
|
||||
const subscriptions = new SubscriptionManager(scheduler, baseSubscriptions)
|
||||
const initialSubscriptions = generateSubscriptions()
|
||||
const eventosIniciales = INITIAL_EVENTS
|
||||
|
||||
const subscriptionsManager = new SubscriptionManager(initialSubscriptions)
|
||||
const scheduler = new EventScheduler({
|
||||
events: eventosIniciales,
|
||||
subscriptionManager: subscriptionsManager
|
||||
})
|
||||
|
||||
// subto
|
||||
webhookRouter.post('/subto', subscriptonHandlerBuilder(subscriptions));
|
||||
webhookRouter.post('/subto', subscriptonHandlerBuilder(subscriptionsManager));
|
||||
|
||||
// Simulacion de los webhook de shopify
|
||||
// Al llamar se supone que se genera el evento
|
||||
webhookRouter.post('/shopify/orders', ordersHandlerBuilder(subscriptions));
|
||||
webhookRouter.post('/shopify/orders', ordersHandlerBuilder(subscriptionsManager));
|
||||
|
||||
export default webhookRouter;
|
||||
|
||||
@@ -11,7 +11,8 @@ export function requestBuilder(args: {
|
||||
const request = http.request({
|
||||
host: args.host,
|
||||
port: args.port,
|
||||
path: args.endpoint
|
||||
path: args.endpoint,
|
||||
method: args.method
|
||||
})
|
||||
|
||||
Object.entries(args.headers).forEach(([name, value]) => {
|
||||
|
||||
Reference in New Issue
Block a user