@central-ticket/queue-sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/centralq-I9tdL_Xr.d.mts +133 -0
- package/dist/centralq-I9tdL_Xr.d.ts +133 -0
- package/dist/chunk-42RGFZKP.mjs +99 -0
- package/dist/chunk-A4HITWM4.mjs +235 -0
- package/dist/chunk-JR7BCYB5.mjs +31 -0
- package/dist/chunk-XRJFNASX.mjs +14 -0
- package/dist/index.d.mts +64 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +509 -0
- package/dist/index.mjs +21 -0
- package/dist/queue-element-DXBW64U2.mjs +111 -0
- package/dist/react.d.mts +65 -0
- package/dist/react.d.ts +65 -0
- package/dist/react.js +624 -0
- package/dist/react.mjs +185 -0
- package/dist/server.d.mts +21 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.js +56 -0
- package/dist/server.mjs +8 -0
- package/dist/svelte.d.mts +79 -0
- package/dist/svelte.d.ts +79 -0
- package/dist/svelte.js +628 -0
- package/dist/svelte.mjs +189 -0
- package/dist/vue.d.mts +31 -0
- package/dist/vue.d.ts +31 -0
- package/dist/vue.js +152 -0
- package/dist/vue.mjs +136 -0
- package/package.json +90 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export { C as CentralQ, a as CentralQClient, b as CentralQClientOptions, c as CentralQCreateOptions, d as CentralQOptions, E as ErrorDetail, e as ExpiredDetail, P as PassedDetail, f as PositionDetail, Q as QueueStatus, g as createCentralQClient } from './centralq-I9tdL_Xr.mjs';
|
|
2
|
+
export { QueueTokenValidationResult, ValidateQueueTokenServerOptions, validateQueueTokenServer } from './server.mjs';
|
|
3
|
+
import * as lit from 'lit';
|
|
4
|
+
import { LitElement } from 'lit';
|
|
5
|
+
|
|
6
|
+
type QueueJoinResponse = {
|
|
7
|
+
status: "ACTIVE";
|
|
8
|
+
token: string;
|
|
9
|
+
expiresAt: number;
|
|
10
|
+
} | {
|
|
11
|
+
status: "WAITING";
|
|
12
|
+
position: number;
|
|
13
|
+
ahead: number;
|
|
14
|
+
} | {
|
|
15
|
+
status: "ERROR";
|
|
16
|
+
message: string;
|
|
17
|
+
};
|
|
18
|
+
interface QueueInitResponse {
|
|
19
|
+
userId: string;
|
|
20
|
+
queueInitToken: string;
|
|
21
|
+
expiresAt: number;
|
|
22
|
+
}
|
|
23
|
+
declare const CENTRALQ_DEFAULT_API_URL = "http://localhost:3001";
|
|
24
|
+
/** @internal — HTTP client usado internamente por CentralQ */
|
|
25
|
+
declare class QueueHttpClient {
|
|
26
|
+
apiUrl: string;
|
|
27
|
+
private apiKey?;
|
|
28
|
+
private queueInitToken?;
|
|
29
|
+
constructor(apiUrl?: string, apiKey?: string | undefined, queueInitToken?: string | undefined);
|
|
30
|
+
private get headers();
|
|
31
|
+
/**
|
|
32
|
+
* Intenta unir a un usuario a la cola de un evento.
|
|
33
|
+
* Llama a POST /api/queue/:eventId/join en el worker.
|
|
34
|
+
*/
|
|
35
|
+
joinQueue(eventId: string, userId: string): Promise<QueueJoinResponse>;
|
|
36
|
+
/**
|
|
37
|
+
* Envía un heartbeat para mantener vivo el slot (waiting o active).
|
|
38
|
+
* El SDK lo llama cada 10s automáticamente.
|
|
39
|
+
*/
|
|
40
|
+
sendHeartbeat(eventId: string, userId: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Libera el slot del usuario inmediatamente.
|
|
43
|
+
* Llamar al completar la compra o al salir voluntariamente.
|
|
44
|
+
*/
|
|
45
|
+
leaveQueue(eventId: string, userId: string): Promise<void>;
|
|
46
|
+
issueQueueInitToken(eventId: string, userId?: string): Promise<QueueInitResponse>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* <central-q> — Web Component de UI puro (internal).
|
|
51
|
+
* Solo renderiza el overlay de la fila. Toda la lógica la maneja CentralQ.
|
|
52
|
+
* No usar directamente — instanciar CentralQ en su lugar.
|
|
53
|
+
*/
|
|
54
|
+
declare class CentralQElement extends LitElement {
|
|
55
|
+
status: "LOADING" | "WAITING" | "ACTIVE" | "EXPIRED" | "ERROR";
|
|
56
|
+
position: number;
|
|
57
|
+
ahead: number;
|
|
58
|
+
/** Callback que CentralQ inyecta para manejar "Volver a la fila" */
|
|
59
|
+
onRetry?: () => void;
|
|
60
|
+
static styles: lit.CSSResult;
|
|
61
|
+
render(): lit.TemplateResult<1>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { CENTRALQ_DEFAULT_API_URL, CentralQElement, QueueHttpClient, type QueueJoinResponse };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export { C as CentralQ, a as CentralQClient, b as CentralQClientOptions, c as CentralQCreateOptions, d as CentralQOptions, E as ErrorDetail, e as ExpiredDetail, P as PassedDetail, f as PositionDetail, Q as QueueStatus, g as createCentralQClient } from './centralq-I9tdL_Xr.js';
|
|
2
|
+
export { QueueTokenValidationResult, ValidateQueueTokenServerOptions, validateQueueTokenServer } from './server.js';
|
|
3
|
+
import * as lit from 'lit';
|
|
4
|
+
import { LitElement } from 'lit';
|
|
5
|
+
|
|
6
|
+
type QueueJoinResponse = {
|
|
7
|
+
status: "ACTIVE";
|
|
8
|
+
token: string;
|
|
9
|
+
expiresAt: number;
|
|
10
|
+
} | {
|
|
11
|
+
status: "WAITING";
|
|
12
|
+
position: number;
|
|
13
|
+
ahead: number;
|
|
14
|
+
} | {
|
|
15
|
+
status: "ERROR";
|
|
16
|
+
message: string;
|
|
17
|
+
};
|
|
18
|
+
interface QueueInitResponse {
|
|
19
|
+
userId: string;
|
|
20
|
+
queueInitToken: string;
|
|
21
|
+
expiresAt: number;
|
|
22
|
+
}
|
|
23
|
+
declare const CENTRALQ_DEFAULT_API_URL = "http://localhost:3001";
|
|
24
|
+
/** @internal — HTTP client usado internamente por CentralQ */
|
|
25
|
+
declare class QueueHttpClient {
|
|
26
|
+
apiUrl: string;
|
|
27
|
+
private apiKey?;
|
|
28
|
+
private queueInitToken?;
|
|
29
|
+
constructor(apiUrl?: string, apiKey?: string | undefined, queueInitToken?: string | undefined);
|
|
30
|
+
private get headers();
|
|
31
|
+
/**
|
|
32
|
+
* Intenta unir a un usuario a la cola de un evento.
|
|
33
|
+
* Llama a POST /api/queue/:eventId/join en el worker.
|
|
34
|
+
*/
|
|
35
|
+
joinQueue(eventId: string, userId: string): Promise<QueueJoinResponse>;
|
|
36
|
+
/**
|
|
37
|
+
* Envía un heartbeat para mantener vivo el slot (waiting o active).
|
|
38
|
+
* El SDK lo llama cada 10s automáticamente.
|
|
39
|
+
*/
|
|
40
|
+
sendHeartbeat(eventId: string, userId: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Libera el slot del usuario inmediatamente.
|
|
43
|
+
* Llamar al completar la compra o al salir voluntariamente.
|
|
44
|
+
*/
|
|
45
|
+
leaveQueue(eventId: string, userId: string): Promise<void>;
|
|
46
|
+
issueQueueInitToken(eventId: string, userId?: string): Promise<QueueInitResponse>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* <central-q> — Web Component de UI puro (internal).
|
|
51
|
+
* Solo renderiza el overlay de la fila. Toda la lógica la maneja CentralQ.
|
|
52
|
+
* No usar directamente — instanciar CentralQ en su lugar.
|
|
53
|
+
*/
|
|
54
|
+
declare class CentralQElement extends LitElement {
|
|
55
|
+
status: "LOADING" | "WAITING" | "ACTIVE" | "EXPIRED" | "ERROR";
|
|
56
|
+
position: number;
|
|
57
|
+
ahead: number;
|
|
58
|
+
/** Callback que CentralQ inyecta para manejar "Volver a la fila" */
|
|
59
|
+
onRetry?: () => void;
|
|
60
|
+
static styles: lit.CSSResult;
|
|
61
|
+
render(): lit.TemplateResult<1>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { CENTRALQ_DEFAULT_API_URL, CentralQElement, QueueHttpClient, type QueueJoinResponse };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __esm = (fn, res) => function __init() {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
};
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
23
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
24
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
25
|
+
if (decorator = decorators[i])
|
|
26
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
27
|
+
if (kind && result) __defProp(target, key, result);
|
|
28
|
+
return result;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/queue-element.ts
|
|
32
|
+
var queue_element_exports = {};
|
|
33
|
+
__export(queue_element_exports, {
|
|
34
|
+
CentralQElement: () => CentralQElement
|
|
35
|
+
});
|
|
36
|
+
var import_lit, import_decorators, CentralQElement;
|
|
37
|
+
var init_queue_element = __esm({
|
|
38
|
+
"src/queue-element.ts"() {
|
|
39
|
+
"use strict";
|
|
40
|
+
import_lit = require("lit");
|
|
41
|
+
import_decorators = require("lit/decorators.js");
|
|
42
|
+
CentralQElement = class extends import_lit.LitElement {
|
|
43
|
+
constructor() {
|
|
44
|
+
super(...arguments);
|
|
45
|
+
this.status = "LOADING";
|
|
46
|
+
this.position = 0;
|
|
47
|
+
this.ahead = 0;
|
|
48
|
+
}
|
|
49
|
+
render() {
|
|
50
|
+
if (this.status === "ACTIVE") return import_lit.html``;
|
|
51
|
+
return import_lit.html`
|
|
52
|
+
<div class="overlay">
|
|
53
|
+
<div class="modal">
|
|
54
|
+
${this.status === "LOADING" ? import_lit.html`
|
|
55
|
+
<div class="spinner"></div>
|
|
56
|
+
<h2>Conectando a la fila...</h2>
|
|
57
|
+
` : ""}
|
|
58
|
+
${this.status === "WAITING" ? import_lit.html`
|
|
59
|
+
<div class="spinner"></div>
|
|
60
|
+
<h2>Estás en la fila virtual</h2>
|
|
61
|
+
<p>Tu posición: <strong>#${this.position}</strong></p>
|
|
62
|
+
${this.ahead > 0 ? import_lit.html`<p>
|
|
63
|
+
Hay <strong>${this.ahead}</strong>
|
|
64
|
+
${this.ahead === 1 ? "persona" : "personas"} delante de
|
|
65
|
+
ti.
|
|
66
|
+
</p>` : import_lit.html`<p>¡Eres el siguiente!</p>`}
|
|
67
|
+
<p style="font-size: 0.85rem; color: #aaa;">
|
|
68
|
+
Por favor, no cierres esta ventana.
|
|
69
|
+
</p>
|
|
70
|
+
` : ""}
|
|
71
|
+
${this.status === "EXPIRED" ? import_lit.html`
|
|
72
|
+
<h2 style="color: #f59e0b;">Tu sesión ha expirado</h2>
|
|
73
|
+
<p>El tiempo para completar la compra terminó.</p>
|
|
74
|
+
<button
|
|
75
|
+
style="margin-top: 1rem; padding: 0.75rem 2rem; font-size: 1rem;
|
|
76
|
+
cursor: pointer; border-radius: 8px; border: none;
|
|
77
|
+
background: #3b82f6; color: white;"
|
|
78
|
+
@click=${() => this.onRetry?.()}
|
|
79
|
+
>
|
|
80
|
+
Volver a la fila
|
|
81
|
+
</button>
|
|
82
|
+
` : ""}
|
|
83
|
+
${this.status === "ERROR" ? import_lit.html`
|
|
84
|
+
<h2 style="color: #ef4444;">Ocurrió un error de conexión</h2>
|
|
85
|
+
<p>Por favor, recarga la página.</p>
|
|
86
|
+
` : ""}
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
CentralQElement.styles = import_lit.css`
|
|
93
|
+
:host {
|
|
94
|
+
display: contents;
|
|
95
|
+
}
|
|
96
|
+
.overlay {
|
|
97
|
+
position: fixed;
|
|
98
|
+
inset: 0;
|
|
99
|
+
background: rgba(0, 0, 0, 0.8);
|
|
100
|
+
backdrop-filter: blur(5px);
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
justify-content: center;
|
|
104
|
+
z-index: 9999;
|
|
105
|
+
font-family: system-ui, sans-serif;
|
|
106
|
+
}
|
|
107
|
+
.modal {
|
|
108
|
+
background: #1e1e1e;
|
|
109
|
+
color: white;
|
|
110
|
+
padding: 2rem;
|
|
111
|
+
border-radius: 12px;
|
|
112
|
+
text-align: center;
|
|
113
|
+
max-width: 400px;
|
|
114
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
|
|
115
|
+
}
|
|
116
|
+
.spinner {
|
|
117
|
+
margin: 1rem auto;
|
|
118
|
+
width: 40px;
|
|
119
|
+
height: 40px;
|
|
120
|
+
border: 4px solid #333;
|
|
121
|
+
border-top-color: #3b82f6;
|
|
122
|
+
border-radius: 50%;
|
|
123
|
+
animation: spin 1s linear infinite;
|
|
124
|
+
}
|
|
125
|
+
@keyframes spin {
|
|
126
|
+
to {
|
|
127
|
+
transform: rotate(360deg);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
`;
|
|
131
|
+
__decorateClass([
|
|
132
|
+
(0, import_decorators.state)()
|
|
133
|
+
], CentralQElement.prototype, "status", 2);
|
|
134
|
+
__decorateClass([
|
|
135
|
+
(0, import_decorators.state)()
|
|
136
|
+
], CentralQElement.prototype, "position", 2);
|
|
137
|
+
__decorateClass([
|
|
138
|
+
(0, import_decorators.state)()
|
|
139
|
+
], CentralQElement.prototype, "ahead", 2);
|
|
140
|
+
CentralQElement = __decorateClass([
|
|
141
|
+
(0, import_decorators.customElement)("central-q")
|
|
142
|
+
], CentralQElement);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// src/index.ts
|
|
147
|
+
var index_exports = {};
|
|
148
|
+
__export(index_exports, {
|
|
149
|
+
CENTRALQ_DEFAULT_API_URL: () => CENTRALQ_DEFAULT_API_URL,
|
|
150
|
+
CentralQ: () => CentralQ,
|
|
151
|
+
CentralQClient: () => CentralQClient,
|
|
152
|
+
QueueHttpClient: () => QueueHttpClient,
|
|
153
|
+
createCentralQClient: () => createCentralQClient,
|
|
154
|
+
validateQueueTokenServer: () => validateQueueTokenServer
|
|
155
|
+
});
|
|
156
|
+
module.exports = __toCommonJS(index_exports);
|
|
157
|
+
|
|
158
|
+
// src/queue-client.ts
|
|
159
|
+
var CENTRALQ_DEFAULT_API_URL = "http://localhost:3001";
|
|
160
|
+
function normalizeApiUrl(apiUrl) {
|
|
161
|
+
const trimmed = apiUrl.replace(/\/+$/, "");
|
|
162
|
+
return trimmed.endsWith("/api") ? trimmed.slice(0, -4) : trimmed;
|
|
163
|
+
}
|
|
164
|
+
var QueueHttpClient = class {
|
|
165
|
+
constructor(apiUrl = CENTRALQ_DEFAULT_API_URL, apiKey, queueInitToken) {
|
|
166
|
+
this.apiUrl = apiUrl;
|
|
167
|
+
this.apiKey = apiKey;
|
|
168
|
+
this.queueInitToken = queueInitToken;
|
|
169
|
+
this.apiUrl = normalizeApiUrl(this.apiUrl);
|
|
170
|
+
}
|
|
171
|
+
get headers() {
|
|
172
|
+
const h = { "Content-Type": "application/json" };
|
|
173
|
+
if (this.apiKey) h["x-api-key"] = this.apiKey;
|
|
174
|
+
if (this.queueInitToken) h["x-queue-init-token"] = this.queueInitToken;
|
|
175
|
+
return h;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Intenta unir a un usuario a la cola de un evento.
|
|
179
|
+
* Llama a POST /api/queue/:eventId/join en el worker.
|
|
180
|
+
*/
|
|
181
|
+
async joinQueue(eventId, userId) {
|
|
182
|
+
const res = await fetch(
|
|
183
|
+
`${this.apiUrl}/api/queue/${encodeURIComponent(eventId)}/join`,
|
|
184
|
+
{
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: this.headers,
|
|
187
|
+
body: JSON.stringify({ userId })
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
if (!res.ok) {
|
|
191
|
+
const body = await res.json().catch(() => ({}));
|
|
192
|
+
throw new Error(
|
|
193
|
+
body.message ?? `Error ${res.status} al conectar con el sistema de colas`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return res.json();
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Envía un heartbeat para mantener vivo el slot (waiting o active).
|
|
200
|
+
* El SDK lo llama cada 10s automáticamente.
|
|
201
|
+
*/
|
|
202
|
+
async sendHeartbeat(eventId, userId) {
|
|
203
|
+
await fetch(
|
|
204
|
+
`${this.apiUrl}/api/queue/${encodeURIComponent(eventId)}/heartbeat`,
|
|
205
|
+
{
|
|
206
|
+
method: "POST",
|
|
207
|
+
headers: this.headers,
|
|
208
|
+
body: JSON.stringify({ userId })
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Libera el slot del usuario inmediatamente.
|
|
214
|
+
* Llamar al completar la compra o al salir voluntariamente.
|
|
215
|
+
*/
|
|
216
|
+
async leaveQueue(eventId, userId) {
|
|
217
|
+
await fetch(
|
|
218
|
+
`${this.apiUrl}/api/queue/${encodeURIComponent(eventId)}/leave`,
|
|
219
|
+
{
|
|
220
|
+
method: "POST",
|
|
221
|
+
headers: this.headers,
|
|
222
|
+
body: JSON.stringify({ userId })
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
async issueQueueInitToken(eventId, userId) {
|
|
227
|
+
const res = await fetch(
|
|
228
|
+
`${this.apiUrl}/api/queue/init/${encodeURIComponent(eventId)}`,
|
|
229
|
+
{
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: this.headers,
|
|
232
|
+
body: JSON.stringify({ userId })
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
const body = await res.json().catch(() => ({}));
|
|
236
|
+
if (!res.ok) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
body.message ?? `Error ${res.status} al inicializar sesi\xF3n de cola`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const token = body.queueInitToken ?? body.initToken;
|
|
242
|
+
if (!body.userId || !token || !body.expiresAt) {
|
|
243
|
+
throw new Error("Respuesta inv\xE1lida al inicializar sesi\xF3n de cola");
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
userId: body.userId,
|
|
247
|
+
queueInitToken: token,
|
|
248
|
+
expiresAt: body.expiresAt
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// src/centralq.ts
|
|
254
|
+
var CentralQ = class _CentralQ {
|
|
255
|
+
constructor(options) {
|
|
256
|
+
this.element = null;
|
|
257
|
+
this.destroyed = false;
|
|
258
|
+
// biome-ignore lint: allow explicit any for generic event map
|
|
259
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
260
|
+
this.client = new QueueHttpClient(
|
|
261
|
+
options.apiUrl,
|
|
262
|
+
options.apiKey,
|
|
263
|
+
options.queueInitToken
|
|
264
|
+
);
|
|
265
|
+
this.eventId = options.eventId;
|
|
266
|
+
this.pollInterval = options.pollInterval ?? 1e4;
|
|
267
|
+
this.container = options.container ?? document.body;
|
|
268
|
+
this.userId = this.resolveUserId(options.userId);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Inicializa CentralQ y comienza el flujo de cola automáticamente.
|
|
272
|
+
* Monta el overlay de UI y empieza a hacer polling.
|
|
273
|
+
*/
|
|
274
|
+
static init(options) {
|
|
275
|
+
const instance = new _CentralQ(options);
|
|
276
|
+
instance.mount();
|
|
277
|
+
instance.checkQueue();
|
|
278
|
+
return instance;
|
|
279
|
+
}
|
|
280
|
+
// Event API
|
|
281
|
+
/** Suscribirse a un evento del ciclo de vida de la cola */
|
|
282
|
+
on(event, listener) {
|
|
283
|
+
if (!this.listeners.has(event)) {
|
|
284
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
285
|
+
}
|
|
286
|
+
this.listeners.get(event).add(listener);
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
/** Desuscribirse de un evento */
|
|
290
|
+
off(event, listener) {
|
|
291
|
+
this.listeners.get(event)?.delete(listener);
|
|
292
|
+
return this;
|
|
293
|
+
}
|
|
294
|
+
emit(event, detail) {
|
|
295
|
+
this.listeners.get(event)?.forEach((fn) => fn(detail));
|
|
296
|
+
}
|
|
297
|
+
// Métodos públicos
|
|
298
|
+
/**
|
|
299
|
+
* Libera el slot del usuario inmediatamente.
|
|
300
|
+
* Llamar cuando el usuario completa la compra.
|
|
301
|
+
*/
|
|
302
|
+
leave() {
|
|
303
|
+
this.stopTimers();
|
|
304
|
+
this.updateUI("ACTIVE");
|
|
305
|
+
this.client.leaveQueue(this.eventId, this.userId).catch(() => {
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Destruye la instancia: para todos los timers, desmonta el overlay,
|
|
310
|
+
* y libera el slot en el servidor.
|
|
311
|
+
*/
|
|
312
|
+
destroy() {
|
|
313
|
+
this.destroyed = true;
|
|
314
|
+
this.stopTimers();
|
|
315
|
+
this.client.leaveQueue(this.eventId, this.userId).catch(() => {
|
|
316
|
+
});
|
|
317
|
+
this.unmount();
|
|
318
|
+
this.listeners.clear();
|
|
319
|
+
}
|
|
320
|
+
/** Retorna el userId resuelto (externo o autogenerado) */
|
|
321
|
+
getUserId() {
|
|
322
|
+
return this.userId;
|
|
323
|
+
}
|
|
324
|
+
// userId: genera y persiste automáticamente
|
|
325
|
+
resolveUserId(externalId) {
|
|
326
|
+
if (externalId) return externalId;
|
|
327
|
+
const key = `ctq_anon_${this.eventId}`;
|
|
328
|
+
try {
|
|
329
|
+
const stored = sessionStorage.getItem(key);
|
|
330
|
+
if (stored) return stored;
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
const generated = `anon_${crypto.randomUUID()}`;
|
|
334
|
+
try {
|
|
335
|
+
sessionStorage.setItem(key, generated);
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
return generated;
|
|
339
|
+
}
|
|
340
|
+
// UI: montar/desmontar el Web Component
|
|
341
|
+
mount() {
|
|
342
|
+
Promise.resolve().then(() => init_queue_element());
|
|
343
|
+
this.element = document.createElement("central-q");
|
|
344
|
+
this.element.onRetry = () => {
|
|
345
|
+
this.updateUI("LOADING");
|
|
346
|
+
this.checkQueue();
|
|
347
|
+
};
|
|
348
|
+
this.container.appendChild(this.element);
|
|
349
|
+
}
|
|
350
|
+
unmount() {
|
|
351
|
+
if (this.element) {
|
|
352
|
+
this.element.remove();
|
|
353
|
+
this.element = null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
updateUI(status, data) {
|
|
357
|
+
if (!this.element) return;
|
|
358
|
+
this.element.status = status;
|
|
359
|
+
if (data?.position !== void 0) this.element.position = data.position;
|
|
360
|
+
if (data?.ahead !== void 0) this.element.ahead = data.ahead;
|
|
361
|
+
}
|
|
362
|
+
// Timers
|
|
363
|
+
stopTimers() {
|
|
364
|
+
if (this.pollingTimer) {
|
|
365
|
+
clearInterval(this.pollingTimer);
|
|
366
|
+
this.pollingTimer = void 0;
|
|
367
|
+
}
|
|
368
|
+
if (this.heartbeatTimer) {
|
|
369
|
+
clearInterval(this.heartbeatTimer);
|
|
370
|
+
this.heartbeatTimer = void 0;
|
|
371
|
+
}
|
|
372
|
+
if (this.expiryTimer) {
|
|
373
|
+
clearTimeout(this.expiryTimer);
|
|
374
|
+
this.expiryTimer = void 0;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
startHeartbeat() {
|
|
378
|
+
if (this.heartbeatTimer) return;
|
|
379
|
+
this.heartbeatTimer = setInterval(() => {
|
|
380
|
+
this.client.sendHeartbeat(this.eventId, this.userId).catch(() => {
|
|
381
|
+
});
|
|
382
|
+
}, this.pollInterval);
|
|
383
|
+
}
|
|
384
|
+
scheduleExpiry(expiresAt) {
|
|
385
|
+
if (this.expiryTimer) clearTimeout(this.expiryTimer);
|
|
386
|
+
const msLeft = expiresAt * 1e3 - Date.now();
|
|
387
|
+
if (msLeft <= 0) {
|
|
388
|
+
this.handleExpired();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
this.expiryTimer = setTimeout(() => this.handleExpired(), msLeft);
|
|
392
|
+
}
|
|
393
|
+
handleExpired() {
|
|
394
|
+
this.stopTimers();
|
|
395
|
+
this.updateUI("EXPIRED");
|
|
396
|
+
this.emit("expired", {
|
|
397
|
+
userId: this.userId,
|
|
398
|
+
eventId: this.eventId
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
// Core: polling + join
|
|
402
|
+
async checkQueue() {
|
|
403
|
+
if (this.destroyed) return;
|
|
404
|
+
try {
|
|
405
|
+
const res = await this.client.joinQueue(this.eventId, this.userId);
|
|
406
|
+
if (res.status === "ACTIVE") {
|
|
407
|
+
if (this.pollingTimer) {
|
|
408
|
+
clearInterval(this.pollingTimer);
|
|
409
|
+
this.pollingTimer = void 0;
|
|
410
|
+
}
|
|
411
|
+
this.startHeartbeat();
|
|
412
|
+
this.scheduleExpiry(res.expiresAt);
|
|
413
|
+
this.updateUI("ACTIVE");
|
|
414
|
+
this.emit("passed", {
|
|
415
|
+
token: res.token,
|
|
416
|
+
expiresAt: res.expiresAt,
|
|
417
|
+
userId: this.userId,
|
|
418
|
+
eventId: this.eventId
|
|
419
|
+
});
|
|
420
|
+
} else if (res.status === "WAITING") {
|
|
421
|
+
this.updateUI("WAITING", {
|
|
422
|
+
position: res.position,
|
|
423
|
+
ahead: res.ahead
|
|
424
|
+
});
|
|
425
|
+
this.emit("position", {
|
|
426
|
+
position: res.position,
|
|
427
|
+
ahead: res.ahead,
|
|
428
|
+
userId: this.userId,
|
|
429
|
+
eventId: this.eventId
|
|
430
|
+
});
|
|
431
|
+
if (!this.pollingTimer) {
|
|
432
|
+
this.pollingTimer = setInterval(
|
|
433
|
+
() => this.checkQueue(),
|
|
434
|
+
this.pollInterval
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
} catch {
|
|
439
|
+
this.updateUI("ERROR");
|
|
440
|
+
this.stopTimers();
|
|
441
|
+
this.emit("error", {
|
|
442
|
+
userId: this.userId,
|
|
443
|
+
eventId: this.eventId
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
var CentralQClient = class {
|
|
449
|
+
constructor(defaults) {
|
|
450
|
+
this.defaults = defaults;
|
|
451
|
+
}
|
|
452
|
+
issueQueueInitToken(eventId, userId) {
|
|
453
|
+
const client = new QueueHttpClient(
|
|
454
|
+
this.defaults.apiUrl,
|
|
455
|
+
this.defaults.apiKey
|
|
456
|
+
);
|
|
457
|
+
return client.issueQueueInitToken(eventId, userId);
|
|
458
|
+
}
|
|
459
|
+
createQueue(options) {
|
|
460
|
+
return CentralQ.init({
|
|
461
|
+
apiUrl: options.apiUrl ?? this.defaults.apiUrl,
|
|
462
|
+
apiKey: options.apiKey ?? this.defaults.apiKey,
|
|
463
|
+
eventId: options.eventId,
|
|
464
|
+
userId: options.userId ?? this.defaults.userId,
|
|
465
|
+
queueInitToken: options.queueInitToken ?? this.defaults.queueInitToken,
|
|
466
|
+
pollInterval: options.pollInterval ?? this.defaults.pollInterval,
|
|
467
|
+
container: options.container ?? this.defaults.container
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
function createCentralQClient(options) {
|
|
472
|
+
return new CentralQClient({
|
|
473
|
+
...options,
|
|
474
|
+
apiUrl: options.apiUrl ?? CENTRALQ_DEFAULT_API_URL
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/server.ts
|
|
479
|
+
function normalizeApiUrl2(apiUrl) {
|
|
480
|
+
return apiUrl.replace(/\/+$/, "");
|
|
481
|
+
}
|
|
482
|
+
async function validateQueueTokenServer(options) {
|
|
483
|
+
const apiUrl = options.apiUrl ?? CENTRALQ_DEFAULT_API_URL;
|
|
484
|
+
const response = await fetch(
|
|
485
|
+
`${normalizeApiUrl2(apiUrl)}/api/verify/${encodeURIComponent(options.eventId)}`,
|
|
486
|
+
{
|
|
487
|
+
method: "POST",
|
|
488
|
+
headers: {
|
|
489
|
+
"Content-Type": "application/json",
|
|
490
|
+
"x-api-key": options.secretKey
|
|
491
|
+
},
|
|
492
|
+
body: JSON.stringify({ token: options.token })
|
|
493
|
+
}
|
|
494
|
+
);
|
|
495
|
+
const data = await response.json().catch(() => ({
|
|
496
|
+
valid: false,
|
|
497
|
+
reason: "Respuesta inv\xE1lida"
|
|
498
|
+
}));
|
|
499
|
+
return { status: response.status, data };
|
|
500
|
+
}
|
|
501
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
502
|
+
0 && (module.exports = {
|
|
503
|
+
CENTRALQ_DEFAULT_API_URL,
|
|
504
|
+
CentralQ,
|
|
505
|
+
CentralQClient,
|
|
506
|
+
QueueHttpClient,
|
|
507
|
+
createCentralQClient,
|
|
508
|
+
validateQueueTokenServer
|
|
509
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateQueueTokenServer
|
|
3
|
+
} from "./chunk-JR7BCYB5.mjs";
|
|
4
|
+
import {
|
|
5
|
+
CentralQ,
|
|
6
|
+
CentralQClient,
|
|
7
|
+
createCentralQClient
|
|
8
|
+
} from "./chunk-A4HITWM4.mjs";
|
|
9
|
+
import {
|
|
10
|
+
CENTRALQ_DEFAULT_API_URL,
|
|
11
|
+
QueueHttpClient
|
|
12
|
+
} from "./chunk-42RGFZKP.mjs";
|
|
13
|
+
import "./chunk-XRJFNASX.mjs";
|
|
14
|
+
export {
|
|
15
|
+
CENTRALQ_DEFAULT_API_URL,
|
|
16
|
+
CentralQ,
|
|
17
|
+
CentralQClient,
|
|
18
|
+
QueueHttpClient,
|
|
19
|
+
createCentralQClient,
|
|
20
|
+
validateQueueTokenServer
|
|
21
|
+
};
|