@fluwa-tool/sdk 1.0.0 → 1.0.26
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/features/network/FetchInterceptor.d.ts +6 -0
- package/dist/features/network/FetchInterceptor.js +33 -6
- package/dist/features/network/XMLHttpRequestInterceptor.d.ts +27 -0
- package/dist/features/network/XMLHttpRequestInterceptor.js +151 -0
- package/dist/index.js +7 -0
- package/package.json +1 -1
|
@@ -21,6 +21,7 @@ export declare class FetchInterceptor implements IFetchInterceptor {
|
|
|
21
21
|
private appName;
|
|
22
22
|
private originalFetch;
|
|
23
23
|
private isInstalled;
|
|
24
|
+
private isIntercepting;
|
|
24
25
|
constructor(logger: ILogger, requestLogger: IRequestLogger, mockResolver: IMockResolver, sessionId: string, appName: string);
|
|
25
26
|
/**
|
|
26
27
|
* Instalar o interceptador
|
|
@@ -48,4 +49,9 @@ export declare class FetchInterceptor implements IFetchInterceptor {
|
|
|
48
49
|
* Sleep (para delays de mock)
|
|
49
50
|
*/
|
|
50
51
|
private delay;
|
|
52
|
+
/**
|
|
53
|
+
* Verificar se requisição é interna do Fluwa
|
|
54
|
+
* Suporta headers como plain object ou Headers instance
|
|
55
|
+
*/
|
|
56
|
+
private isFluwaInternalRequest;
|
|
51
57
|
}
|
|
@@ -19,6 +19,7 @@ class FetchInterceptor {
|
|
|
19
19
|
this.appName = appName;
|
|
20
20
|
this.originalFetch = null;
|
|
21
21
|
this.isInstalled = false;
|
|
22
|
+
this.isIntercepting = false; // Flag para prevenir re-intercepção
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
25
|
* Instalar o interceptador
|
|
@@ -64,13 +65,14 @@ class FetchInterceptor {
|
|
|
64
65
|
return async (...args) => {
|
|
65
66
|
const startTime = performance.now();
|
|
66
67
|
const [resource, init] = args;
|
|
68
|
+
// Evitar re-intercepção: se já estamos interceptando, usar fetch original
|
|
69
|
+
if (this.isIntercepting) {
|
|
70
|
+
return this.originalFetch.apply(globalThis, args);
|
|
71
|
+
}
|
|
67
72
|
// Ignorar requisições internas do Fluwa (header X-Fluwa-Internal)
|
|
68
|
-
if (init
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Usar fetch original para requisições internas
|
|
72
|
-
return this.originalFetch.apply(globalThis, args);
|
|
73
|
-
}
|
|
73
|
+
if (this.isFluwaInternalRequest(init)) {
|
|
74
|
+
// Usar fetch original para requisições internas
|
|
75
|
+
return this.originalFetch.apply(globalThis, args);
|
|
74
76
|
}
|
|
75
77
|
// Extrair informações da requisição
|
|
76
78
|
const method = (init?.method || 'GET').toUpperCase();
|
|
@@ -87,6 +89,8 @@ class FetchInterceptor {
|
|
|
87
89
|
appName: this.appName,
|
|
88
90
|
source: types_1.RequestSource.REAL, // Default, pode mudar se usar mock
|
|
89
91
|
};
|
|
92
|
+
// Marcar que estamos interceptando para evitar re-intercepção
|
|
93
|
+
this.isIntercepting = true;
|
|
90
94
|
try {
|
|
91
95
|
// Registrar que começou
|
|
92
96
|
await this.requestLogger.logStart(requestMetadata);
|
|
@@ -140,6 +144,10 @@ class FetchInterceptor {
|
|
|
140
144
|
await this.requestLogger.logComplete(requestMetadata);
|
|
141
145
|
throw error;
|
|
142
146
|
}
|
|
147
|
+
finally {
|
|
148
|
+
// Sempre restaurar a flag
|
|
149
|
+
this.isIntercepting = false;
|
|
150
|
+
}
|
|
143
151
|
};
|
|
144
152
|
}
|
|
145
153
|
/**
|
|
@@ -168,5 +176,24 @@ class FetchInterceptor {
|
|
|
168
176
|
delay(ms) {
|
|
169
177
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
170
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Verificar se requisição é interna do Fluwa
|
|
181
|
+
* Suporta headers como plain object ou Headers instance
|
|
182
|
+
*/
|
|
183
|
+
isFluwaInternalRequest(init) {
|
|
184
|
+
if (!init || !init.headers) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
const headers = init.headers;
|
|
188
|
+
// Verificar se é plain object
|
|
189
|
+
if (typeof headers === 'object' && headers.constructor === Object) {
|
|
190
|
+
return headers['X-Fluwa-Internal'] === 'true';
|
|
191
|
+
}
|
|
192
|
+
// Verificar se é Headers instance
|
|
193
|
+
if (headers instanceof Headers || (typeof headers?.get === 'function')) {
|
|
194
|
+
return headers.get('X-Fluwa-Internal') === 'true';
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
171
198
|
}
|
|
172
199
|
exports.FetchInterceptor = FetchInterceptor;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interceptador de XMLHttpRequest
|
|
3
|
+
* Intercepta requisições axios, jQuery, XMLHttpRequest nativo
|
|
4
|
+
*/
|
|
5
|
+
import { ILogger } from '../../core/Logger';
|
|
6
|
+
import { IRequestLogger } from './RequestLogger';
|
|
7
|
+
import { IMockResolver } from './MockResolver';
|
|
8
|
+
export interface IXMLHttpRequestInterceptor {
|
|
9
|
+
install(): void;
|
|
10
|
+
uninstall(): void;
|
|
11
|
+
}
|
|
12
|
+
export declare class XMLHttpRequestInterceptor implements IXMLHttpRequestInterceptor {
|
|
13
|
+
private logger;
|
|
14
|
+
private requestLogger;
|
|
15
|
+
private mockResolver;
|
|
16
|
+
private sessionId;
|
|
17
|
+
private appName;
|
|
18
|
+
private originalOpen;
|
|
19
|
+
private originalSend;
|
|
20
|
+
private originalSetRequestHeader;
|
|
21
|
+
private isInstalled;
|
|
22
|
+
constructor(logger: ILogger, requestLogger: IRequestLogger, mockResolver: IMockResolver, sessionId: string, appName: string);
|
|
23
|
+
install(): void;
|
|
24
|
+
uninstall(): void;
|
|
25
|
+
private safeParseBody;
|
|
26
|
+
private generateId;
|
|
27
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Interceptador de XMLHttpRequest
|
|
4
|
+
* Intercepta requisições axios, jQuery, XMLHttpRequest nativo
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.XMLHttpRequestInterceptor = void 0;
|
|
8
|
+
const types_1 = require("../../types");
|
|
9
|
+
class XMLHttpRequestInterceptor {
|
|
10
|
+
constructor(logger, requestLogger, mockResolver, sessionId, appName) {
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
this.requestLogger = requestLogger;
|
|
13
|
+
this.mockResolver = mockResolver;
|
|
14
|
+
this.sessionId = sessionId;
|
|
15
|
+
this.appName = appName;
|
|
16
|
+
this.isInstalled = false;
|
|
17
|
+
}
|
|
18
|
+
install() {
|
|
19
|
+
if (this.isInstalled) {
|
|
20
|
+
this.logger.warn('XMLHttpRequest interceptor já foi instalado');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Verificar se XMLHttpRequest está disponível (apenas em navegadores)
|
|
24
|
+
if (typeof XMLHttpRequest === 'undefined') {
|
|
25
|
+
this.logger.debug('XMLHttpRequest não disponível nesse ambiente (requer navegador)');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const self = this;
|
|
29
|
+
const originalXHR = XMLHttpRequest.prototype;
|
|
30
|
+
// Armazenar referência ao open original
|
|
31
|
+
this.originalOpen = originalXHR.open;
|
|
32
|
+
this.originalSend = originalXHR.send;
|
|
33
|
+
this.originalSetRequestHeader = originalXHR.setRequestHeader;
|
|
34
|
+
// Interceptar open()
|
|
35
|
+
originalXHR.open = function (method, url, ...args) {
|
|
36
|
+
const xhr = this;
|
|
37
|
+
xhr._fluwaMethod = method.toUpperCase();
|
|
38
|
+
xhr._fluwaUrl = url;
|
|
39
|
+
xhr._fluwaRequestId = self.generateId();
|
|
40
|
+
xhr._fluwaHeaders = {};
|
|
41
|
+
xhr._fluwaStartTime = Date.now();
|
|
42
|
+
return self.originalOpen.apply(this, [method, url, ...args]);
|
|
43
|
+
};
|
|
44
|
+
// Interceptar setRequestHeader()
|
|
45
|
+
originalXHR.setRequestHeader = function (header, value) {
|
|
46
|
+
const xhr = this;
|
|
47
|
+
if (!xhr._fluwaHeaders) {
|
|
48
|
+
xhr._fluwaHeaders = {};
|
|
49
|
+
}
|
|
50
|
+
xhr._fluwaHeaders[header] = value;
|
|
51
|
+
return self.originalSetRequestHeader.apply(this, [header, value]);
|
|
52
|
+
};
|
|
53
|
+
// Interceptar send()
|
|
54
|
+
this.originalSend = originalXHR.send;
|
|
55
|
+
originalXHR.send = function (body) {
|
|
56
|
+
const xhr = this;
|
|
57
|
+
const method = (xhr._fluwaMethod || 'GET');
|
|
58
|
+
const url = xhr._fluwaUrl;
|
|
59
|
+
const requestId = xhr._fluwaRequestId;
|
|
60
|
+
const headers = xhr._fluwaHeaders || {};
|
|
61
|
+
const requestMetadata = {
|
|
62
|
+
id: requestId,
|
|
63
|
+
method,
|
|
64
|
+
url,
|
|
65
|
+
headers,
|
|
66
|
+
body: body ? self.safeParseBody(body) : undefined,
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
sessionId: self.sessionId,
|
|
69
|
+
appName: self.appName,
|
|
70
|
+
source: types_1.RequestSource.REAL,
|
|
71
|
+
};
|
|
72
|
+
// Verificar se há mock
|
|
73
|
+
self.mockResolver.resolve(method, url)
|
|
74
|
+
.then((mockResponse) => {
|
|
75
|
+
if (mockResponse) {
|
|
76
|
+
// Há mock disponível
|
|
77
|
+
requestMetadata.source = types_1.RequestSource.MOCK;
|
|
78
|
+
requestMetadata.status = mockResponse.status || 200;
|
|
79
|
+
requestMetadata.response = mockResponse.response;
|
|
80
|
+
// Logar
|
|
81
|
+
self.requestLogger.logStart(requestMetadata).catch(() => { });
|
|
82
|
+
// Aplicar delay se configurado
|
|
83
|
+
if (mockResponse.delay) {
|
|
84
|
+
return new Promise(resolve => setTimeout(resolve, mockResponse.delay));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Sem mock, será requisição real
|
|
89
|
+
self.requestLogger.logStart(requestMetadata).catch(() => { });
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
.catch(() => {
|
|
93
|
+
self.requestLogger.logStart(requestMetadata).catch(() => { });
|
|
94
|
+
});
|
|
95
|
+
// Interceptar mudanças de estado
|
|
96
|
+
const originalOnReadyStateChange = xhr.onreadystatechange;
|
|
97
|
+
xhr.onreadystatechange = function () {
|
|
98
|
+
if (xhr.readyState === 4) {
|
|
99
|
+
// Requisição completou
|
|
100
|
+
requestMetadata.status = xhr.status;
|
|
101
|
+
// Tentar extrair response
|
|
102
|
+
try {
|
|
103
|
+
if (xhr.responseText) {
|
|
104
|
+
requestMetadata.response = self.safeParseBody(xhr.responseText);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
requestMetadata.response = xhr.responseText;
|
|
109
|
+
}
|
|
110
|
+
const duration = Date.now() - (xhr._fluwaStartTime || 0);
|
|
111
|
+
requestMetadata.duration = duration;
|
|
112
|
+
self.requestLogger.logComplete(requestMetadata).catch(() => { });
|
|
113
|
+
}
|
|
114
|
+
// Chamar handler original
|
|
115
|
+
if (originalOnReadyStateChange) {
|
|
116
|
+
originalOnReadyStateChange.call(this);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
// Chamar send original
|
|
120
|
+
return self.originalSend.apply(this, [body]);
|
|
121
|
+
};
|
|
122
|
+
this.isInstalled = true;
|
|
123
|
+
this.logger.success('XMLHttpRequest interceptor instalado');
|
|
124
|
+
}
|
|
125
|
+
uninstall() {
|
|
126
|
+
if (!this.isInstalled || !this.originalOpen) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const originalXHR = XMLHttpRequest.prototype;
|
|
130
|
+
originalXHR.open = this.originalOpen;
|
|
131
|
+
originalXHR.send = this.originalSend;
|
|
132
|
+
originalXHR.setRequestHeader = this.originalSetRequestHeader;
|
|
133
|
+
this.isInstalled = false;
|
|
134
|
+
this.logger.success('XMLHttpRequest interceptor desinstalado');
|
|
135
|
+
}
|
|
136
|
+
safeParseBody(body) {
|
|
137
|
+
if (typeof body === 'string') {
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(body);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return body;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return body;
|
|
146
|
+
}
|
|
147
|
+
generateId() {
|
|
148
|
+
return `${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
exports.XMLHttpRequestInterceptor = XMLHttpRequestInterceptor;
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ const Config_1 = require("./core/Config");
|
|
|
33
33
|
const HttpClient_1 = require("./features/communication/HttpClient");
|
|
34
34
|
const WebSocketClient_1 = require("./features/communication/WebSocketClient");
|
|
35
35
|
const FetchInterceptor_1 = require("./features/network/FetchInterceptor");
|
|
36
|
+
const XMLHttpRequestInterceptor_1 = require("./features/network/XMLHttpRequestInterceptor");
|
|
36
37
|
const RequestLogger_1 = require("./features/network/RequestLogger");
|
|
37
38
|
const MockResolver_1 = require("./features/network/MockResolver");
|
|
38
39
|
const IdGenerator_1 = require("./core/IdGenerator");
|
|
@@ -46,6 +47,7 @@ let sessionId;
|
|
|
46
47
|
let httpClient;
|
|
47
48
|
let wsClient;
|
|
48
49
|
let fetchInterceptor;
|
|
50
|
+
let xmlHttpRequestInterceptor;
|
|
49
51
|
let mockResolver;
|
|
50
52
|
// ============================================
|
|
51
53
|
// INICIALIZAÇÃO
|
|
@@ -82,8 +84,11 @@ async function initFluwaTool(partialConfig = {}) {
|
|
|
82
84
|
const requestLogger = new RequestLogger_1.RequestLogger(httpClient, logger);
|
|
83
85
|
// 6. Criar interceptador de fetch
|
|
84
86
|
fetchInterceptor = new FetchInterceptor_1.FetchInterceptor(logger, requestLogger, mockResolver, sessionId, config.getAppName());
|
|
87
|
+
// 6b. Criar interceptador de XMLHttpRequest (para axios, jQuery, etc)
|
|
88
|
+
xmlHttpRequestInterceptor = new XMLHttpRequestInterceptor_1.XMLHttpRequestInterceptor(logger, requestLogger, mockResolver, sessionId, config.getAppName());
|
|
85
89
|
// 7. Instalar interceptadores
|
|
86
90
|
fetchInterceptor.install();
|
|
91
|
+
xmlHttpRequestInterceptor.install();
|
|
87
92
|
// 8. Conectar WebSocket
|
|
88
93
|
logger.debug('Conectando ao WebSocket...');
|
|
89
94
|
try {
|
|
@@ -139,6 +144,7 @@ function disableFluwaTool() {
|
|
|
139
144
|
return;
|
|
140
145
|
}
|
|
141
146
|
fetchInterceptor.uninstall();
|
|
147
|
+
xmlHttpRequestInterceptor.uninstall();
|
|
142
148
|
wsClient.disconnect();
|
|
143
149
|
isInitialized = false;
|
|
144
150
|
logger.success('Fluwa-Tool desativado');
|
|
@@ -151,6 +157,7 @@ async function enableFluwaTool() {
|
|
|
151
157
|
return;
|
|
152
158
|
}
|
|
153
159
|
fetchInterceptor.install();
|
|
160
|
+
xmlHttpRequestInterceptor.install();
|
|
154
161
|
try {
|
|
155
162
|
await wsClient.connect();
|
|
156
163
|
}
|