PageSourceSearch

https://servicehubcrm.net/app/scripts/otel-init.js

js servicehubcrm.net collected 2026-10-02 01:05:23 UTC 12,676 bytes, 284 lines download raw bytes

1/**
2 * OpenTelemetry Browser — AngularJS 1.x
3 * Self-contained: no depende de bundles CDN externos.
4 * Intercepta XHR ($http de AngularJS), inyecta W3C traceparent,
5 * y envía spans OTLP-JSON a /otel/v1/traces (nginx proxy → collector).
6 *
7 * service.name / deployment.environment se DERIVAN del hostname
8 * (servicehub live/stg, trucare, serveco) — no van hardcodeados.
9 * Además captura errores JS no controlados (window.onerror,
10 * unhandledrejection) y expone shOtelReportError() para el front.
11 */
12(function (global) {
13  'use strict';
14
15  var hostname = global.location ? global.location.hostname : '';
16
17  // ── Resolver servicio/entorno desde el hostname ───────────────────
18  // Misma clasificación de marca/ambiente que usa app.js. Devuelve null
19  // para entornos sin collector (dev directo en EC2) → OTel desactivado.
20  // Sólo se cambian las ETIQUETAS; el destino (/otel/v1/traces) lo decide nginx.
21  function resolveTarget(h) {
22    if (!h || h.indexOf('development.') !== -1) return null;            // dev directo: sin collector
23    if (h.indexOf('trucareprotection') !== -1) return { service: 'live-trucareprotection-web', env: 'live' };
24    if (h.indexOf('servecosoftware')   !== -1) return { service: 'live-serveco-web',           env: 'live' };
25    if (h.indexOf('staging.') !== -1 || h.indexOf('stg.') !== -1 ||
26        h.indexOf('replica')  !== -1 || h.indexOf('it.')  !== -1 ||
27        h.indexOf('dev.')     !== -1)          return { service: 'stg-servicehub-web',   env: 'stg'  };
28    if (h.indexOf('servicehubcrm.net') !== -1 ||
29        h.indexOf('servicehub.live')   !== -1) return { service: 'live-servicehub-web',  env: 'live' };
30    return { service: 'servicehub-web', env: 'unknown' };
31  }
32
33  var _target = resolveTarget(hostname);
34  if (!_target) {
35    console.info('[OTel] Desactivado en entorno de desarrollo (' + hostname + ')');
36    global.shOtelSetUser     = function () {};
37    global.shOtelReportError = function () {};
38    return;
39  }
40
41  var _resourceAttrs = [
42    { key: 'service.name',           value: { stringValue: _target.service } },
43    { key: 'deployment.environment', value: { stringValue: _target.env     } },
44    { key: 'app.framework',          value: { stringValue: 'angularjs-1.x'  } }
45  ];
46
47  // ── Utilidades ────────────────────────────────────────────────────
48  function randHex(len) {
49    var result = '';
50    var chars  = '0123456789abcdef';
51    for (var i = 0; i < len; i++) {
52      result += chars[Math.floor(Math.random() * 16)];
53    }
54    return result;
55  }
56
57  function nowNs() {
58    var ms = (global.performance && global.performance.now)
59      ? global.performance.timeOrigin + global.performance.now()
60      : Date.now();
61    return Math.floor(ms * 1000000);
62  }
63
64  // ── Estado de usuario ─────────────────────────────────────────────
65  var _user = {};
66
67  // ── Atributos base (host + usuario) ───────────────────────────────
68  function baseAttrs() {
69    var attrs = [{ key: 'net.peer.name', value: { stringValue: hostname } }];
70    if (_user.login_id)   attrs.push({ key: 'servicehub.user.login_id', value: { intValue:    _user.login_
70id   } });
71    if (_user.user_id)    attrs.push({ key: 'enduser.id',               value: { intValue:    _user.user_id    } });
72    if (_user.user_id)    attrs.push({ key: 'servicehub.user.id',       value: { intValue:    _user.user_id    } });
73    if (_user.user_name)  attrs.push({ key: 'servicehub.user.name',     value: { stringValue: _user.user_name  } });
74    if (_user.company_id) attrs.push({ key: 'servicehub.company.id',    value: { intValue:    _user.company_id } });
75    return attrs;
76  }
77
78  // ── POST OTLP-JSON (usa XHR nativo — NO el proxy) ─────────────────
79  function postOtlp(payload) {
80    try {
81      var native = new global._OriginalXMLHttpRequest();
82      native.open('POST', '/otel/v1/traces', true);
83      native.setRequestHeader('Content-Type', 'application/json');
84      native.send(JSON.stringify(payload));
85    } catch (e) { /* silencioso */ }
86  }
87
88  // ── Envío de span de petición XHR ─────────────────────────────────
89  function sendSpan(span) {
90    try {
91      var attrs = baseAttrs();
92      attrs.push({ key: 'http.method',      value: { stringValue: span.method } });
93      attrs.push({ key: 'http.url',         value: { stringValue: span.url    } });
94      attrs.push({ key: 'http.status_code', value: { intValue:    span.status } });
95
96      postOtlp({
97        resourceSpans: [{
98          resource: { attributes: _resourceAttrs },
99          scopeSpans: [{
100            scope: { name: 'sh-otel-init', version: '1.1.0' },
101            spans: [{
102              traceId:           span.traceId,
103              spanId:            span.spanId,
104              parentSpanId:      '',
105              name:              span.method + ' ' + span.path,
106              kind:              3,
107              startTimeUnixNano: String(span.startNs),
108              endTimeUnixNano:   String(span.endNs),
109              attributes:        attrs,
110              status: { code: span.status >= 400 ? 2 : 1 }
111            }]
112          }]
113        }]
114      });
115    } catch (e) { /* silencioso */ }
116  }
117
118  // ── Envío de span de error JS (no controlado) ─────────────────────
119  function sendErrorSpan(source, message, stack, info) {
120    try {
121      info = info || {};
122      var t     = nowNs();
123      var attrs = baseAttrs();
124      attrs.push({ key: 'error.source',      value: { stringValue: String(source || 'unknown')        } });
125      attrs.push({ key: 'exception.type',    value: { stringValue: String(info.type || 'Error')       } });
126      attrs.push({ key: 'exception.message', value: { stringValue: String(message || '').slice(0, 2000) } });
127      if (stack)    attrs.push({ key: 'exception.stacktrace', value: { stringValue: String(stack).slice(0, 8000) } });
128      if (info.loc) attrs.push({ key: 'code.filepath',        value: { stringValue: String(info.loc)              } });
129
130      postOtlp({
131        resourceSpans: [{
132          resource: { attributes: _resourceAttrs },
133          scopeSpans: [{
134            scope: { name: 'sh-otel-init', version: '1.1.0' },
135            spans: [{
136              traceId:           randHex(32),
137              spanId:            randHex(16),
138              parentSpanId:      '',
139              name:              'js.error',
140              kind:              1,
141              startTimeUnixNano: String(t),
142              endTimeUnixNano:   String(t),
143              attributes:        attrs,
144              status: { code: 2, message: String(message || '').slice(0, 200) },
145              events: [{
146                timeUnixNano: String(t),
147                name:         'exception',
148                attributes: [
149                  { key: 'exception.type',    value: { stringValue: String(info.type || 'Error') } },
150                  { key: 'exception.message', value: { stringValue: String(message || '')        } }
151                ].concat(stack ? [{ key: 'exception.stacktrace', value: { stringValue: String(stack) } }] : [])
152              }]
153            }]
154          }]
155        }]
156      });
157    } catch (e) { /* silencioso */ }
158  }
159
160  // ── Guardar XHR original ──────────────────────────────────────────
161  global._OriginalXMLHttpRequest = global.XMLHttpRequest;
162
163  // ── Proxy XHR ────────────────────────────────────────────────────
164  global.XMLHttpRequest = function () {
165    var _native = new global._OriginalXMLHttpRequest();
166    var _method = '';
167    var _url    = '';
168    var _path   = '';
169    var _skip   = false;
170    var _traceId, _spanId, _startNs;
171
172    var self = this;
173
174    // Delegar propiedades al nativo
175    var props = [
176      'readyState','response','responseText','responseType','responseURL',
177      'responseXML','status','statusText','timeout','upload','withCredentials'
178    ];
179    props.forEach(function (p) {
180      Object.defineProperty(self, p, {
181        get: function () { return _native[p]; },
182        set: function (v) { _native[p] = v; }
183      });
184    });
185
186    // Delegar event handlers al nativo
187    var handlers = ['onreadystatechange','onload','onerror','ontimeout','onprogress','onabort','onloadend','onloadstart'];
188    handlers.forEach(function (h) {
189      Object.defineProperty(self, h, {
190        get: function () { return _native[h]; },
191        set: function (v) { _native[h] = v; }
192      });
193    });
194
195    self.open = function (method, url) {
196      _method = (method || 'GET').toUpperCase();
197      _url    = url || '';
198      _path   = _url.replace(/\?.*/, '').replace(/^https?:\/\/[^/]+/, '') || '/';
199      _skip   = _url.indexOf('/otel/') !== -1;
200
201      if (!_skip) {
202        _traceId  = randHex(32);
203        _spanId   = randHex(16);
204        _startNs  = nowNs();
205      }
206      return _native.open.apply(_native, arguments);
207    };
208
209    self.setRequestHeader = function (name, value) {
210      return _native.setRequestHeader(name, value);
211    };
212
213    self.send = function (body) {
214      if (!_skip && _traceId) {
215        try {
216          _native.setRequestHeader('traceparent', '00-' + _traceId + '-' + _spanId + '-01');
217        } catch (e) { /* ignorar */ }
218
219        // Usar addEventListener para NO sobreescribir onreadystatechange del caller
220        _native.addEventListener('loadend', function () {
221          sendSpan({
222            traceId:  _traceId,
223            spanId:   _spanId,
224            startNs:  _startNs,
225            endNs:    nowNs(),
226            method:   _method,
227            url:      _url,
228            path:     _path,
229            status:   _native.status || 0
230          });
231        });
232      }
233      return _native.send(body);
234    };
235
236    self.abort              = function () { return _native.abort(); };
237    self.getAllResponseHeaders = function () { return _native.getAllResponseHeaders(); };
238    self.getResponseHeader  = function (h) { return _native.getResponseHeader(h); };
239    self.overrideMimeType   = function (m) { return _native.overrideMimeType(m); };
240    self.addEventListener   = function () { return _native.addEventListener.apply(_native, arguments); };
241    self.removeEventListener = function () { return _native.removeEventListener.apply(_native, arguments); };
242  };
243
244  // ── API pública para setear usuario desde AngularJS ───────────────
245  global.shOtelSetUser = function (user) {
246    if (!user) return;
247    if (user.login_user_id) _user.login_id   = parseInt(user.login_user_id, 10);
248    if (user.user_id)       _user.user_id    = parseInt(user.user_id,       10);
249    if (user.company_id)    _user.company_id = parseInt(user.company_id,    10);
250    if (user.fname || user.lname) {
251      _user.user_name = ((user.fname || '') + ' ' + (user.lname || '')).trim();
252    }
253  };
254
255  // ── Captura de errores JS no controlados ──────────────────────────
256  global.addEventListener('error', function (e) {
257    try {
258      if (!e) return;
259      // Ignorar errores de carga de recursos (img/script/css 404) — no son errores JS
260      if (e.target && e.target !== global && e.target.nodeName) return;
261      var err = e.error || {};
262      var loc = e.filename ? (e.filename + ':' + (e.lineno || 0) + ':' + (e.colno || 0)) : undefined;
263      sendErrorSpan('window.onerror', e.message || err.message, err.stack, { type: err.name, loc: loc });
264    } catch (ex) { /* silencioso */ }
265  });
266
267  global.addEventListener('unhandledrejection', function (e) {
268    try {
269      var r   = e ? e.reason : null;
270      var msg = (r && r.message) ? r.message : String(r);
271      sendErrorSpan('unhandledrejection', msg, r && r.stack, { type: r && r.name });
272    } catch (ex) { /* silencioso */ }
273  });
274
275  // Reporte manual de errores (p.ej. desde $exceptionHandler de AngularJS)
276  global.shOtelReportError = function (err, source) {
277    if (!err) return;
278    var msg = (err && err.message) ? err.message : String(err);
279    sendErrorSpan(source || 'manual', msg, err && err.stack, { type: err && err.name });
280  };
281
282  console.info('[OTel] Browser tracing activo (' + _target.service + ') → /otel/v1/traces');
283
284}(window));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.