/** * 模仿thinkingdata-node直接写一个log的写入,而日志直接用pinus-logger实现 */ import { taLogger } from "../../util/logger"; const KEY_NAME_MATCH_REGEX = /^[a-zA-Z#][a-zA-Z0-9_]+$/; let util: any = {}; util.version = '1.2.2'; util.each = function (obj, iterator, context) { if (obj === null) { return; } if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (let i = 0, l = obj.length; i < l; i++) { if (i in obj && iterator.call(context, obj[i], i, obj) === {}) { return; } } } else { for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { if (iterator.call(context, obj[key], key, obj) === {}) { return; } } } } }; util.formatDate = function (d) { function pad(n) { return n < 10 ? '0' + n : n; } function padMilliseconds(n) { if (n < 10) { return '00' + n; } else if (n < 100) { return '0' + n; } else { return n; } } return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()) + '.' + padMilliseconds(d.getMilliseconds()); }; util.searchObjDate = function (o) { if (util.check.isObject(o) || util.check.isArray(o)) { util.each(o, function (a, b) { if (util.check.isObject(a) || util.check.isArray(a)) { util.searchObjDate(o[b]); } else { if (util.check.isDate(a)) { o[b] = util.formatDate(a); } } }); } }; util.check = { isUndefined: function (obj) { return obj === void 0; }, isObject: function (obj) { return (toString.call(obj) === '[object Object]') && (obj !== null); }, isEmptyObject: function (obj) { if (util.check.isObject(obj)) { for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { return false; } } return true; } return false; }, isArray: function (obj) { return toString.call(obj) === '[object Array]'; }, isString: function (obj) { return toString.call(obj) === '[object String]'; }, isDate: function (obj) { return toString.call(obj) === '[object Date]'; }, isNumber: function (obj) { return toString.call(obj) === '[object Number]'; }, isBoolean: function (obj) { return toString.call(obj) === '[object Boolean]'; }, isJSONString: function (str) { try { JSON.parse(str); } catch (e) { return false; } return true; } }; util.properties = { _strip: function (prop) { if (!util.check.isObject(prop)) { return prop; } util.each(prop, function (v, k) { if (!(util.check.isString(v) || util.check.isNumber(v) || util.check.isDate(v) || util.check.isBoolean(v) || util.check.isArray(v))) { util.log('您的数据-', k, v, '-格式不满足要求,我们已经将其删除. 属性值只支持 String, Number, Date, Boolean, Array'); delete prop[k]; } }); return prop; }, _checkPropertiesKey: function (obj) { let flag = true; util.each(obj, (v, k) => { if (!KEY_NAME_MATCH_REGEX.test(k)) { util.log('不合法的 KEY 值: ' + k); flag = false; } }); return flag; }, name: function (s) { if (!util.check.isString(s) || !KEY_NAME_MATCH_REGEX.test(s)) { util.log('请检查参数格式, 事件或属性名称必须是英文字母、 \'util\'或\'#\' 开头, 包含字母和数字的字符串: ' + s); return false; } else { return true; } }, properties: function (p) { this._strip(p); if (p) { if (util.check.isObject(p)) { if (this._checkPropertiesKey(p)) { return true; } else { util.log('请检查参数格式, properties 的 key 只能以字母或\'#\'开头,包含数字、字母和下划线 util'); return false; } } else { util.log('properties 可以没有,但有的话必须是对象'); return false; } } else { return true; } }, propertiesMust: function (p) { this._strip(p); if (p === undefined || !util.check.isObject(p) || util.check.isEmptyObject(p)) { util.log('properties 必须是对象且有值'); return false; } else { if (this._checkPropertiesKey(p)) { return true; } else { util.log('请检查参数格式, properties 的 key 只能以字母或\'#\'开头,包含数字、字母和下划线 util'); return false; } } }, userId: function (id) { if (util.check.isString(id) && /^.+$/.test(id)) { return true; } else { util.log('用户 id 必须是不能为空'); return false; } } } const DEFAULT_PROPERTIES = { "#lib": "node", "#lib_version": util.version, }; /** * 内部函数,创建 ta 实例 */ let _createClient = function (consumer) { let ta: any = {}; ta.consumer = consumer; ta.superProperties = {}; ta._basicCheck = function (params) { if (!util.check.isObject(params)) { return new Error("params for track must be an object"); } if (!util.properties.userId(params.accountId) && !util.properties.userId(params.distinctId)) { return new Error("account ID and distinct ID cannot be empty at the same time"); } return undefined; }; ta.track = function (params, skipLocalCheck = false) { let callback = params.callback || function () { }; if (!ta._trackCheck(params, skipLocalCheck, callback)) { return; } ta._sendRequest("track", params, callback); }; ta._trackCheck = function (params, skipLocalCheck, callback) { let err = this._basicCheck(params); if (err) { callback(err); return false; } if (!util.properties.name(params.event)) { callback(new Error("invalid event name")); return false; } if (!skipLocalCheck && !util.properties.properties(params.properties)) { callback(new Error("invalid properties")); return false; } return true; }; ta.userSet = function (params) { let callback = params.callback || function () { }; let err = this._basicCheck(params); if (err) { callback(err); return; } if (!util.properties.propertiesMust(params.properties)) { callback(new Error("invalid properties")); return; } ta._sendRequest("user_set", params, callback); }; ta.userSetOnce = function (params) { let callback = params.callback || function () { }; let err = this._basicCheck(params); if (err) { callback(err); return; } if (!util.properties.propertiesMust(params.properties)) { callback(new Error("invalid properties")); return; } ta._sendRequest("user_setOnce", params, callback); }; ta.userAdd = function (params) { let callback = params.callback || function () { }; let err = this._basicCheck(params); if (err) { callback(err); return; } if (!util.properties.propertiesMust(params.properties)) { callback(new Error("invalid properties")); return; } ta._sendRequest("user_add", params, callback); }; ta.setDynamicSuperProperties = function (getDynamicProperties, callback) { let err; if (typeof getDynamicProperties === "function") { if (util.properties.properties(getDynamicProperties())) { ta.getDynamicProperties = getDynamicProperties; } else { err = new Error("Invalid return type of getDynamicProperties"); } } else { err = new Error("getDynamicProperties must be a funciton"); } if (callback && err) { callback(err); } }; // 内部函数,组织数据格式,并发送给对应的 consumer 去处理 ta._sendRequest = function (type, eventData, callback) { let time = util.check.isUndefined(eventData.time) || !util.check.isDate(eventData.time) ? new Date() : eventData.time; let data = { "#type": type, "#time": util.formatDate(time), }; if (eventData.distinctId) { data["#distinct_id"] = eventData.distinctId; } if (eventData.accountId) { data["#account_id"] = eventData.accountId; } if (eventData.ip) { data["#ip"] = eventData.ip; } if (eventData.event) { data["#event_name"] = eventData.event; } if (eventData.eventId) { data["#event_id"] = eventData.eventId; } if (eventData.firstCheckId) { data["#first_check_id"] = eventData.firstCheckId; } if (eventData.uuid) { data["#uuid"] = eventData.uuid; } if (eventData.appId) { data["#app_id"] = eventData.appId; } if (type === "track" || type === "track_update" || type === "track_overwrite") { data["properties"] = { ...DEFAULT_PROPERTIES, ...ta.superProperties, ...(ta.getDynamicProperties ? ta.getDynamicProperties() : {}) }; } else { data["properties"] = {}; } if ( util.check.isObject(eventData.properties) && !util.check.isEmptyObject(eventData.properties) ) { data['properties'] = { ...data['properties'], ...eventData.properties || {} } } util.searchObjDate(data); this.consumer.add(data, callback); }; return ta; }; export function initTaLoggingMode() { return _createClient({ add: (msg: string) => { taLogger.info(JSON.stringify(msg)); } }) }