@oondemand/oon-core-back 0.3.57 → 0.3.58

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.
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+
3
+ const registry = require("../../core/registry");
4
+ const GenericError = require("../../core/errors/GenericError");
5
+ const { normalizeKey } = require("../integrations/registry");
6
+
7
+ const LIST_MODES = new Set(["import", "full", "incremental", "individual"]);
8
+ const DIRECTIONS = new Set(["inbound", "outbound", "bidirectional"]);
9
+ const CONFLICT_POLICIES = new Set(["remote-wins", "local-wins", "skip", "error"]);
10
+
11
+ function cloneDeclarative(value) {
12
+ if (Array.isArray(value)) return value.map(cloneDeclarative);
13
+ if (!value || typeof value !== "object") return value;
14
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneDeclarative(item)]));
15
+ }
16
+
17
+ function normalizeCall(key, definition = {}) {
18
+ const callKey = normalizeKey(key, "identificador da chamada Omie");
19
+ if (!definition.endpoint || !definition.call) {
20
+ throw new GenericError(
21
+ `A chamada Omie ${callKey} deve declarar endpoint e call.`,
22
+ { statusCode: 422 },
23
+ );
24
+ }
25
+ return {
26
+ ...cloneDeclarative(definition),
27
+ key: callKey,
28
+ label: String(definition.label || callKey),
29
+ connectionTest: definition.connectionTest === true,
30
+ };
31
+ }
32
+
33
+ function normalizeList(definition = {}, index = 0) {
34
+ const key = normalizeKey(definition.key, `identificador da lista Omie ${index + 1}`);
35
+ const mode = String(definition.mode || "full").toLowerCase();
36
+ const direction = String(definition.direction || "inbound").toLowerCase();
37
+ const conflict = String(definition.policies?.conflict || "remote-wins").toLowerCase();
38
+ if (!LIST_MODES.has(mode)) {
39
+ throw new GenericError(`Modo inválido na lista Omie ${key}: ${mode}.`, { statusCode: 422 });
40
+ }
41
+ if (!DIRECTIONS.has(direction)) {
42
+ throw new GenericError(`Direção inválida na lista Omie ${key}: ${direction}.`, { statusCode: 422 });
43
+ }
44
+ if (!CONFLICT_POLICIES.has(conflict)) {
45
+ throw new GenericError(`Política de conflito inválida na lista Omie ${key}: ${conflict}.`, {
46
+ statusCode: 422,
47
+ });
48
+ }
49
+ if (!definition.call) {
50
+ throw new GenericError(`A lista Omie ${key} deve referenciar uma chamada declarada.`, {
51
+ statusCode: 422,
52
+ });
53
+ }
54
+ if (!definition.target?.model || !definition.target?.externalKey) {
55
+ throw new GenericError(
56
+ `A lista Omie ${key} deve declarar target.model e target.externalKey.`,
57
+ { statusCode: 422 },
58
+ );
59
+ }
60
+ return {
61
+ ...cloneDeclarative(definition),
62
+ key,
63
+ label: String(definition.label || key),
64
+ description: String(definition.description || ""),
65
+ call: normalizeKey(definition.call, `chamada da lista ${key}`),
66
+ mode,
67
+ direction,
68
+ order: Number(definition.order ?? (index + 1) * 10),
69
+ includeInFullSync: definition.includeInFullSync !== false,
70
+ policies: {
71
+ create: definition.policies?.create !== false,
72
+ update: definition.policies?.update !== false,
73
+ inactivate: definition.policies?.inactivate === true,
74
+ conflict,
75
+ ...(definition.policies || {}),
76
+ },
77
+ };
78
+ }
79
+
80
+ function normalizeWebhook(definition = {}, index = 0) {
81
+ const eventType = String(definition.eventType || "*").trim();
82
+ if (!eventType) {
83
+ throw new GenericError(`Webhook Omie ${index + 1} sem eventType.`, { statusCode: 422 });
84
+ }
85
+ const actions = Array.isArray(definition.actions) ? definition.actions : [];
86
+ actions.forEach((action, actionIndex) => {
87
+ if (!action?.handler) {
88
+ throw new GenericError(
89
+ `A ação ${actionIndex + 1} do webhook ${eventType} deve declarar handler.`,
90
+ { statusCode: 422 },
91
+ );
92
+ }
93
+ });
94
+ return {
95
+ ...cloneDeclarative(definition),
96
+ eventType,
97
+ resource: String(definition.resource || ""),
98
+ actions,
99
+ };
100
+ }
101
+
102
+ function collectOmieDefinitions() {
103
+ const result = {
104
+ calls: {},
105
+ lists: [],
106
+ webhooks: [],
107
+ handlers: {},
108
+ instances: [],
109
+ legacy: {},
110
+ };
111
+ for (const declaration of registry.listOmieMappings()) {
112
+ if (typeof declaration.toOmie === "function") {
113
+ const legacyKey = normalizeKey(declaration.name, "mapping Omie legado");
114
+ result.legacy[legacyKey] = {
115
+ key: legacyKey,
116
+ label: String(declaration.label || declaration.name || legacyKey),
117
+ toOmie: declaration.toOmie,
118
+ };
119
+ }
120
+ for (const [key, call] of Object.entries(declaration.calls || {})) {
121
+ const normalized = normalizeCall(key, call);
122
+ if (result.calls[normalized.key]) {
123
+ throw new GenericError(`Chamada Omie duplicada: ${normalized.key}.`, { statusCode: 422 });
124
+ }
125
+ result.calls[normalized.key] = normalized;
126
+ }
127
+ for (const list of declaration.lists || []) result.lists.push(normalizeList(list, result.lists.length));
128
+ for (const webhook of declaration.webhooks || []) {
129
+ result.webhooks.push(normalizeWebhook(webhook, result.webhooks.length));
130
+ }
131
+ for (const [handler, implementation] of Object.entries(declaration.handlers || {})) {
132
+ if (typeof implementation !== "function") {
133
+ throw new GenericError(`Handler Omie ${handler} deve ser uma função.`, { statusCode: 422 });
134
+ }
135
+ if (result.handlers[handler]) {
136
+ throw new GenericError(`Handler Omie duplicado: ${handler}.`, { statusCode: 422 });
137
+ }
138
+ result.handlers[handler] = implementation;
139
+ }
140
+ for (const instance of declaration.instances || []) {
141
+ result.instances.push({
142
+ id: normalizeKey(instance.id || instance.instanceId || "default", "instância Omie"),
143
+ label: String(instance.label || instance.id || "Omie"),
144
+ });
145
+ }
146
+ }
147
+
148
+ const listKeys = new Set();
149
+ for (const list of result.lists) {
150
+ if (listKeys.has(list.key)) {
151
+ throw new GenericError(`Lista Omie duplicada: ${list.key}.`, { statusCode: 422 });
152
+ }
153
+ listKeys.add(list.key);
154
+ if (!result.calls[list.call]) {
155
+ throw new GenericError(
156
+ `A lista Omie ${list.key} referencia a chamada inexistente ${list.call}.`,
157
+ { statusCode: 422 },
158
+ );
159
+ }
160
+ }
161
+ return result;
162
+ }
163
+
164
+ function describeOmieDefinitions(definitions = collectOmieDefinitions()) {
165
+ return {
166
+ calls: Object.values(definitions.calls).map((call) => ({
167
+ key: call.key,
168
+ label: call.label,
169
+ endpoint: call.endpoint,
170
+ call: call.call,
171
+ paginated: Boolean(call.pagination),
172
+ connectionTest: call.connectionTest,
173
+ })),
174
+ lists: definitions.lists.map((list) => ({
175
+ key: list.key,
176
+ label: list.label,
177
+ description: list.description,
178
+ call: list.call,
179
+ mode: list.mode,
180
+ direction: list.direction,
181
+ targetModel: list.target.model,
182
+ externalKey: list.target.externalKey,
183
+ policies: list.policies,
184
+ schedule: list.schedule || null,
185
+ order: list.order,
186
+ })),
187
+ webhooks: definitions.webhooks.map((webhook) => ({
188
+ eventType: webhook.eventType,
189
+ resource: webhook.resource,
190
+ actions: webhook.actions.map((action) => ({ handler: action.handler })),
191
+ })),
192
+ instances: definitions.instances,
193
+ legacyMappings: Object.values(definitions.legacy || {}).map((mapping) => ({
194
+ key: mapping.key,
195
+ label: mapping.label,
196
+ })),
197
+ };
198
+ }
199
+
200
+ module.exports = {
201
+ collectOmieDefinitions,
202
+ describeOmieDefinitions,
203
+ normalizeCall,
204
+ normalizeList,
205
+ normalizeWebhook,
206
+ };
@@ -1,69 +1,33 @@
1
- const axios = require("axios");
2
- const registry = require("../../core/registry");
1
+ "use strict";
3
2
 
4
- /**
5
- * Módulo Omie (Seção 12) — engine + fila + retry. O dev declara
6
- * `defineOmieMapping(...)` ou usa `omie.sync(...)` em triggers; toda a
7
- * mecânica de chamada/retentativa fica aqui.
8
- */
9
- const queue = [];
10
- let draining = false;
3
+ const client = require("./client");
4
+ const definitions = require("./definitions");
5
+ const models = require("./models");
6
+ const provider = require("./provider");
11
7
 
12
- async function callOmie({ endpoint, call, param }, { retries = 3 } = {}) {
13
- const base = process.env.API_OMIE || "https://app.omie.com.br/api/v1";
14
- const body = {
15
- call,
16
- app_key: process.env.OMIE_APP_KEY,
17
- app_secret: process.env.OMIE_APP_SECRET,
18
- param: Array.isArray(param) ? param : [param],
19
- };
20
-
21
- let lastErr;
22
- for (let attempt = 1; attempt <= retries; attempt++) {
23
- try {
24
- const { data } = await axios.post(`${base}/${endpoint}/`, body);
25
- return data;
26
- } catch (err) {
27
- lastErr = err;
28
- await new Promise((r) => setTimeout(r, attempt * 500));
29
- }
30
- }
31
- throw lastErr;
32
- }
33
-
34
- function enqueue(job) {
35
- queue.push(job);
36
- drain();
37
- }
38
-
39
- async function drain() {
40
- if (draining) return;
41
- draining = true;
42
- while (queue.length) {
43
- const job = queue.shift();
44
- try {
45
- await job();
46
- } catch (err) {
47
- console.log("[omie] job falhou:", err?.message);
48
- }
49
- }
50
- draining = false;
51
- }
52
-
53
- /**
54
- * Helper usado em triggers: resolve o mapping registrado e enfileira o
55
- * sync. Se não houver app_key configurada, vira no-op seguro.
56
- */
57
8
  const omie = {
58
- async sync({ mapping, payload, ...rest }) {
59
- if (!process.env.OMIE_APP_KEY) return { skipped: true };
60
- const def = mapping ? registry.listOmieMappings().find((m) => m.name === mapping) : null;
61
- const request = def?.toOmie ? def.toOmie(payload) : { ...rest, param: payload };
62
- return new Promise((resolve, reject) => {
63
- enqueue(() => callOmie(request).then(resolve).catch(reject));
64
- });
9
+ sync(input = {}) {
10
+ return provider.enqueueOmieCall(input);
11
+ },
12
+ enqueue(input = {}) {
13
+ return provider.enqueueOmieCall(input);
14
+ },
15
+ async call(input = {}, options = {}) {
16
+ const declaredCall = input.callKey || input.mapping || input.name;
17
+ const normalized = definitions.collectOmieDefinitions();
18
+ return provider.executeConfiguredCall(
19
+ normalized,
20
+ declaredCall,
21
+ input.payload || input.input || {},
22
+ { ...options, instanceId: input.instanceId || options.instanceId },
23
+ );
65
24
  },
66
- call: callOmie,
67
25
  };
68
26
 
69
- module.exports = { omie };
27
+ module.exports = {
28
+ ...client,
29
+ ...definitions,
30
+ ...models,
31
+ ...provider,
32
+ omie,
33
+ };
@@ -0,0 +1,308 @@
1
+ "use strict";
2
+
3
+ const crypto = require("node:crypto");
4
+ const mongoose = require("mongoose");
5
+ const GenericError = require("../../core/errors/GenericError");
6
+
7
+ const SECRET_PREFIX = "oon:v1";
8
+ const DEFAULT_BASE_URL = "https://app.omie.com.br/api/v1";
9
+
10
+ function encryptionSecret() {
11
+ return String(
12
+ process.env.OON_CREDENTIALS_ENCRYPTION_KEY
13
+ || process.env.OMIE_CREDENTIALS_ENCRYPTION_KEY
14
+ || "",
15
+ ).trim();
16
+ }
17
+
18
+ function encryptionKey() {
19
+ const secret = encryptionSecret();
20
+ if (!secret) {
21
+ throw new GenericError(
22
+ "Configure OON_CREDENTIALS_ENCRYPTION_KEY para persistir credenciais Omie.",
23
+ { statusCode: 503 },
24
+ );
25
+ }
26
+ return crypto.createHash("sha256").update(secret).digest();
27
+ }
28
+
29
+ function encryptSecret(value) {
30
+ const clear = String(value || "");
31
+ if (!clear) return "";
32
+ const iv = crypto.randomBytes(12);
33
+ const cipher = crypto.createCipheriv("aes-256-gcm", encryptionKey(), iv);
34
+ const encrypted = Buffer.concat([cipher.update(clear, "utf8"), cipher.final()]);
35
+ const tag = cipher.getAuthTag();
36
+ return [SECRET_PREFIX, iv.toString("base64url"), tag.toString("base64url"), encrypted.toString("base64url")].join(":");
37
+ }
38
+
39
+ function decryptSecret(value) {
40
+ const encrypted = String(value || "");
41
+ if (!encrypted) return "";
42
+ const [namespace, version, ivValue, tagValue, payloadValue] = encrypted.split(":");
43
+ if (`${namespace}:${version}` !== SECRET_PREFIX || !ivValue || !tagValue || !payloadValue) {
44
+ throw new GenericError("Credencial Omie armazenada em formato inválido.", { statusCode: 500 });
45
+ }
46
+ const decipher = crypto.createDecipheriv(
47
+ "aes-256-gcm",
48
+ encryptionKey(),
49
+ Buffer.from(ivValue, "base64url"),
50
+ );
51
+ decipher.setAuthTag(Buffer.from(tagValue, "base64url"));
52
+ return Buffer.concat([
53
+ decipher.update(Buffer.from(payloadValue, "base64url")),
54
+ decipher.final(),
55
+ ]).toString("utf8");
56
+ }
57
+
58
+ function maskSecret(value) {
59
+ const clear = String(value || "");
60
+ if (!clear) return "";
61
+ if (clear.length <= 4) return "••••";
62
+ return `${clear.slice(0, 2)}${"•".repeat(Math.min(8, clear.length - 4))}${clear.slice(-2)}`;
63
+ }
64
+
65
+ function normalizeInstanceId(value) {
66
+ const instanceId = String(value || "default").trim().toLowerCase();
67
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(instanceId)) {
68
+ throw new GenericError("Identificador da instância Omie inválido.", { statusCode: 422 });
69
+ }
70
+ return instanceId;
71
+ }
72
+
73
+ function configurationSchema() {
74
+ const schema = new mongoose.Schema({
75
+ instanceId: { type: String, required: true, unique: true, index: true },
76
+ label: { type: String, required: true, default: "Omie" },
77
+ active: { type: Boolean, default: true, index: true },
78
+ environment: { type: String, enum: ["homologation", "production"], default: "production" },
79
+ baseUrl: { type: String, required: true, default: DEFAULT_BASE_URL },
80
+ appKeyEncrypted: { type: String, default: "", select: false },
81
+ appSecretEncrypted: { type: String, default: "", select: false },
82
+ webhookTokenEncrypted: { type: String, default: "", select: false },
83
+ appKeyMasked: { type: String, default: "" },
84
+ credentialsConfigured: { type: Boolean, default: false },
85
+ connectionStatus: { type: String, enum: ["not-tested", "ok", "error"], default: "not-tested" },
86
+ lastConnectionError: { type: String, default: "" },
87
+ lastConnectionTestAt: { type: Date, default: null },
88
+ }, { timestamps: true, minimize: false, strict: true });
89
+ return schema;
90
+ }
91
+
92
+ function listStateSchema() {
93
+ const schema = new mongoose.Schema({
94
+ provider: { type: String, default: "omie", index: true },
95
+ instanceId: { type: String, required: true, index: true },
96
+ listKey: { type: String, required: true, index: true },
97
+ status: { type: String, default: "not-run", index: true },
98
+ cursor: { type: mongoose.Schema.Types.Mixed, default: null },
99
+ lastStartedAt: { type: Date, default: null },
100
+ lastCompletedAt: { type: Date, default: null },
101
+ summary: { type: mongoose.Schema.Types.Mixed, default: {} },
102
+ diagnostic: { type: mongoose.Schema.Types.Mixed, default: {} },
103
+ }, { timestamps: true, minimize: false, strict: true });
104
+ schema.index({ provider: 1, instanceId: 1, listKey: 1 }, { unique: true });
105
+ return schema;
106
+ }
107
+
108
+ function getOmieModels() {
109
+ return {
110
+ Configuration: mongoose.models.OonOmieConfiguration
111
+ || mongoose.model("OonOmieConfiguration", configurationSchema(), "oon_omie_configurations"),
112
+ ListState: mongoose.models.OonOmieListState
113
+ || mongoose.model("OonOmieListState", listStateSchema(), "oon_omie_list_states"),
114
+ };
115
+ }
116
+
117
+ function instanceEnvPrefix(instanceId) {
118
+ return normalizeInstanceId(instanceId).replace(/[^a-z0-9]/g, "_").toUpperCase();
119
+ }
120
+
121
+ function environmentCredentials(instanceId) {
122
+ const normalized = normalizeInstanceId(instanceId);
123
+ const prefix = instanceEnvPrefix(normalized);
124
+ const specific = normalized === "default" ? "OMIE" : `OMIE_${prefix}`;
125
+ return {
126
+ appKey: String(process.env[`${specific}_APP_KEY`] || "").trim(),
127
+ appSecret: String(process.env[`${specific}_APP_SECRET`] || "").trim(),
128
+ webhookToken: String(process.env[`${specific}_WEBHOOK_TOKEN`] || "").trim(),
129
+ baseUrl: String(process.env[`${specific}_BASE_URL`] || process.env.API_OMIE || DEFAULT_BASE_URL).trim(),
130
+ };
131
+ }
132
+
133
+ function publicConfiguration(document) {
134
+ if (!document) return null;
135
+ const value = typeof document.toObject === "function" ? document.toObject() : { ...document };
136
+ delete value.appKeyEncrypted;
137
+ delete value.appSecretEncrypted;
138
+ delete value.webhookTokenEncrypted;
139
+ return value;
140
+ }
141
+
142
+ async function listOmieConfigurations() {
143
+ const { Configuration } = getOmieModels();
144
+ const persisted = await Configuration.find({}).sort({ label: 1, instanceId: 1 }).lean();
145
+ if (persisted.length) return persisted.map(publicConfiguration);
146
+ const env = environmentCredentials("default");
147
+ return [{
148
+ instanceId: "default",
149
+ label: "Omie",
150
+ active: true,
151
+ environment: "production",
152
+ baseUrl: env.baseUrl,
153
+ appKeyMasked: maskSecret(env.appKey),
154
+ credentialsConfigured: Boolean(env.appKey && env.appSecret),
155
+ connectionStatus: "not-tested",
156
+ lastConnectionError: "",
157
+ lastConnectionTestAt: null,
158
+ source: "environment",
159
+ }];
160
+ }
161
+
162
+ async function saveOmieConfiguration(instanceId, input = {}) {
163
+ const normalized = normalizeInstanceId(instanceId);
164
+ const { Configuration } = getOmieModels();
165
+ const current = await Configuration.findOne({ instanceId: normalized })
166
+ .select("+appKeyEncrypted +appSecretEncrypted +webhookTokenEncrypted");
167
+ const update = {
168
+ instanceId: normalized,
169
+ label: String(input.label || current?.label || (normalized === "default" ? "Omie" : normalized)).trim(),
170
+ active: input.active !== undefined ? Boolean(input.active) : (current?.active ?? true),
171
+ environment: ["homologation", "production"].includes(input.environment)
172
+ ? input.environment
173
+ : (current?.environment || "production"),
174
+ baseUrl: String(input.baseUrl || current?.baseUrl || environmentCredentials(normalized).baseUrl || DEFAULT_BASE_URL)
175
+ .trim()
176
+ .replace(/\/+$/, ""),
177
+ };
178
+
179
+ const appKey = String(input.appKey || "").trim();
180
+ const appSecret = String(input.appSecret || "").trim();
181
+ const webhookToken = String(input.webhookToken || "").trim();
182
+ if (appKey) {
183
+ update.appKeyEncrypted = encryptSecret(appKey);
184
+ update.appKeyMasked = maskSecret(appKey);
185
+ } else if (current?.appKeyEncrypted) {
186
+ update.appKeyEncrypted = current.appKeyEncrypted;
187
+ update.appKeyMasked = current.appKeyMasked;
188
+ }
189
+ if (appSecret) update.appSecretEncrypted = encryptSecret(appSecret);
190
+ else if (current?.appSecretEncrypted) update.appSecretEncrypted = current.appSecretEncrypted;
191
+ if (webhookToken) update.webhookTokenEncrypted = encryptSecret(webhookToken);
192
+ else if (current?.webhookTokenEncrypted) update.webhookTokenEncrypted = current.webhookTokenEncrypted;
193
+
194
+ const env = environmentCredentials(normalized);
195
+ const effectiveKey = appKey || (current?.appKeyEncrypted ? decryptSecret(current.appKeyEncrypted) : env.appKey);
196
+ const effectiveSecret = appSecret
197
+ || (current?.appSecretEncrypted ? decryptSecret(current.appSecretEncrypted) : env.appSecret);
198
+ update.credentialsConfigured = Boolean(effectiveKey && effectiveSecret);
199
+ if (update.active && !update.credentialsConfigured) {
200
+ throw new GenericError(
201
+ "Configure App Key e App Secret antes de ativar a integração Omie.",
202
+ { statusCode: 422 },
203
+ );
204
+ }
205
+ if (appKey || appSecret || input.baseUrl) {
206
+ update.connectionStatus = "not-tested";
207
+ update.lastConnectionError = "";
208
+ update.lastConnectionTestAt = null;
209
+ }
210
+
211
+ const saved = await Configuration.findOneAndUpdate(
212
+ { instanceId: normalized },
213
+ { $set: update },
214
+ { upsert: true, new: true, setDefaultsOnInsert: true, runValidators: true },
215
+ ).lean();
216
+ return publicConfiguration(saved);
217
+ }
218
+
219
+ async function resolveOmieConfiguration(instanceId = "default", options = {}) {
220
+ const normalized = normalizeInstanceId(instanceId);
221
+ const { Configuration } = getOmieModels();
222
+ let query = Configuration.findOne({ instanceId: normalized });
223
+ if (options.includeSecrets) {
224
+ query = query.select("+appKeyEncrypted +appSecretEncrypted +webhookTokenEncrypted");
225
+ }
226
+ const persisted = await query.lean();
227
+ const env = environmentCredentials(normalized);
228
+ if (!persisted) {
229
+ return {
230
+ instanceId: normalized,
231
+ label: normalized === "default" ? "Omie" : normalized,
232
+ active: true,
233
+ environment: "production",
234
+ baseUrl: env.baseUrl,
235
+ appKey: options.includeSecrets ? env.appKey : undefined,
236
+ appSecret: options.includeSecrets ? env.appSecret : undefined,
237
+ webhookToken: options.includeSecrets ? env.webhookToken : undefined,
238
+ appKeyMasked: maskSecret(env.appKey),
239
+ credentialsConfigured: Boolean(env.appKey && env.appSecret),
240
+ connectionStatus: "not-tested",
241
+ source: "environment",
242
+ };
243
+ }
244
+ const result = publicConfiguration(persisted);
245
+ if (options.includeSecrets) {
246
+ result.appKey = persisted.appKeyEncrypted ? decryptSecret(persisted.appKeyEncrypted) : env.appKey;
247
+ result.appSecret = persisted.appSecretEncrypted ? decryptSecret(persisted.appSecretEncrypted) : env.appSecret;
248
+ result.webhookToken = persisted.webhookTokenEncrypted
249
+ ? decryptSecret(persisted.webhookTokenEncrypted)
250
+ : env.webhookToken;
251
+ }
252
+ return result;
253
+ }
254
+
255
+ async function updateConnectionStatus(instanceId, result = {}) {
256
+ const normalized = normalizeInstanceId(instanceId);
257
+ const { Configuration } = getOmieModels();
258
+ await Configuration.updateOne(
259
+ { instanceId: normalized },
260
+ {
261
+ $set: {
262
+ connectionStatus: result.ok ? "ok" : "error",
263
+ lastConnectionError: result.ok ? "" : String(result.error || "Falha na conexão."),
264
+ lastConnectionTestAt: new Date(),
265
+ },
266
+ },
267
+ );
268
+ }
269
+
270
+ async function resolveInstanceByWebhookToken(token) {
271
+ const candidate = String(token || "");
272
+ if (!candidate) return null;
273
+ const { Configuration } = getOmieModels();
274
+ const configurations = await Configuration.find({ active: true })
275
+ .select("+webhookTokenEncrypted")
276
+ .lean();
277
+ for (const configuration of configurations) {
278
+ if (!configuration.webhookTokenEncrypted) continue;
279
+ const expected = decryptSecret(configuration.webhookTokenEncrypted);
280
+ const left = Buffer.from(candidate);
281
+ const right = Buffer.from(expected);
282
+ if (left.length === right.length && crypto.timingSafeEqual(left, right)) {
283
+ return configuration.instanceId;
284
+ }
285
+ }
286
+ const env = environmentCredentials("default");
287
+ if (env.webhookToken) {
288
+ const left = Buffer.from(candidate);
289
+ const right = Buffer.from(env.webhookToken);
290
+ if (left.length === right.length && crypto.timingSafeEqual(left, right)) return "default";
291
+ }
292
+ return null;
293
+ }
294
+
295
+ module.exports = {
296
+ DEFAULT_BASE_URL,
297
+ decryptSecret,
298
+ encryptSecret,
299
+ getOmieModels,
300
+ listOmieConfigurations,
301
+ maskSecret,
302
+ normalizeInstanceId,
303
+ publicConfiguration,
304
+ resolveInstanceByWebhookToken,
305
+ resolveOmieConfiguration,
306
+ saveOmieConfiguration,
307
+ updateConnectionStatus,
308
+ };