@ohbug/core 1.1.7 → 2.0.0

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/src/client.ts ADDED
@@ -0,0 +1,210 @@
1
+ import type {
2
+ OhbugAction,
3
+ OhbugClient,
4
+ OhbugClientConstructor,
5
+ OhbugClientConstructorValues,
6
+ OhbugConfig,
7
+ OhbugCreateEvent,
8
+ OhbugEventWithMethods,
9
+ OhbugExtension,
10
+ OhbugGetDevice,
11
+ OhbugLoggerConfig,
12
+ OhbugMetaData,
13
+ OhbugNotifier,
14
+ OhbugSDK,
15
+ OhbugUser,
16
+ } from '@ohbug/types'
17
+ import { isObject, isString } from '@ohbug/utils'
18
+
19
+ import { schema as baseSchema } from './config'
20
+ import { createEvent, handleEventCreated, isEvent } from './event'
21
+ import { notify } from './notify'
22
+ import { Action } from './action'
23
+ import { verifyConfig } from './lib/verifyConfig'
24
+ import { getConfigErrorMessage, getErrorMessage } from './lib/getErrorMessage'
25
+ import { addMetaData, deleteMetaData, getMetaData } from './lib/metaData'
26
+
27
+ export const Client: OhbugClientConstructor = class Client
28
+ implements OhbugClient {
29
+ readonly __sdk: OhbugSDK
30
+
31
+ readonly __config: OhbugConfig
32
+
33
+ readonly __logger: OhbugLoggerConfig
34
+
35
+ readonly __device: OhbugGetDevice
36
+
37
+ readonly __notifier: OhbugNotifier
38
+
39
+ readonly __extensions: OhbugExtension[]
40
+
41
+ readonly __actions: OhbugAction[]
42
+
43
+ __user: OhbugUser
44
+
45
+ readonly __metaData: OhbugMetaData
46
+
47
+ constructor({
48
+ sdk,
49
+ config: baseConfig,
50
+ schema = baseSchema,
51
+ device,
52
+ notifier,
53
+ }: OhbugClientConstructorValues) {
54
+ const { config, errors } = verifyConfig(baseConfig, schema)
55
+
56
+ // initialization
57
+ this.__sdk = sdk
58
+ this.__config = config
59
+ this.__logger = config.logger
60
+ this.__device = device
61
+ this.__notifier = notifier
62
+
63
+ this.__extensions = []
64
+
65
+ this.__actions = []
66
+ this.__user = config.user
67
+ this.__metaData = {}
68
+ if (isObject(config.metaData)) {
69
+ Object.keys(config.metaData).forEach((key) => {
70
+ this.addMetaData(key, config.metaData[key])
71
+ })
72
+ }
73
+
74
+ if (Object.keys(errors).length)
75
+ this.__logger.warn(getConfigErrorMessage(errors, baseConfig))
76
+ }
77
+
78
+ /**
79
+ * Load extension
80
+ * 加载扩展
81
+ *
82
+ * @param extension
83
+ */
84
+ use(extension: OhbugExtension): OhbugClient {
85
+ this.__extensions.push(extension)
86
+ extension.setup?.(this)
87
+ return this
88
+ }
89
+
90
+ /**
91
+ * Create an event, you will get a data body containing device actions and other information
92
+ * 创建事件,将会得到一个含有 device actions 等信息的数据体
93
+ *
94
+ * @param value
95
+ */
96
+ createEvent<D = any>(value: OhbugCreateEvent<D>): OhbugEventWithMethods<D> | null {
97
+ const event = createEvent(value, this)
98
+
99
+ return handleEventCreated(event, this)
100
+ }
101
+
102
+ /**
103
+ * Used to trigger the reporting interface
104
+ * 用于触发上报接口
105
+ *
106
+ * @param eventLike
107
+ * @param beforeNotify
108
+ */
109
+ notify<D = any>(
110
+ eventLike: any,
111
+ beforeNotify?: (
112
+ event: OhbugEventWithMethods<D> | null
113
+ ) => OhbugEventWithMethods<D> | null,
114
+ ): Promise<any | null> {
115
+ let event: OhbugEventWithMethods<D> | null
116
+ if (Boolean(eventLike) && !isEvent(eventLike))
117
+ event = this.createEvent(eventLike)
118
+ else
119
+ event = eventLike
120
+
121
+ if (beforeNotify) event = beforeNotify(event)
122
+
123
+ return notify<D>(event, this)
124
+ }
125
+
126
+ /**
127
+ * Add an action.
128
+ * Once the threshold is reached, the oldest breadcrumbs will be deleted.
129
+ * 新增一个动作。
130
+ * 一旦达到阈值,最老的 Action 将被删除。
131
+ *
132
+ * @param message
133
+ * @param data
134
+ * @param type
135
+ * @param timestamp
136
+ */
137
+ addAction(
138
+ message: string,
139
+ data: Record<string, any>,
140
+ type: string,
141
+ timestamp?: string,
142
+ ): void {
143
+ const actions = this.__actions
144
+
145
+ const targetMessage = isString(message) ? message : ''
146
+ const targetData = data || {}
147
+ const targetType = isString(type) ? type : ''
148
+
149
+ const action = new Action(targetMessage, targetData, targetType, timestamp)
150
+ if (actions.length >= this.__config.maxActions!)
151
+ actions.shift()
152
+
153
+ actions.push(action)
154
+ }
155
+
156
+ /**
157
+ * Get current user information
158
+ * 获取当前的用户信息
159
+ */
160
+ getUser(): OhbugUser | undefined {
161
+ return this.__user
162
+ }
163
+
164
+ /**
165
+ * Set current user information
166
+ * 设置当前的用户信息
167
+ */
168
+ setUser(user: OhbugUser): OhbugUser | undefined {
169
+ if (isObject(user) && Object.keys(user).length <= 6) {
170
+ this.__user = { ...this.__user, ...user }
171
+ return this.getUser()
172
+ }
173
+ this.__logger.warn(getErrorMessage(
174
+ 'setUser should be an object and have up to 6 attributes',
175
+ user,
176
+ ))
177
+ return undefined
178
+ }
179
+
180
+ /**
181
+ * Add metaData
182
+ * 新增 metaData
183
+ *
184
+ * @param section
185
+ * @param data
186
+ */
187
+ addMetaData(section: string, data: any) {
188
+ return addMetaData(this.__metaData, section, data)
189
+ }
190
+
191
+ /**
192
+ * Get metaData
193
+ * 获取 metaData
194
+ *
195
+ * @param section
196
+ */
197
+ getMetaData(section: string) {
198
+ return getMetaData(this.__metaData, section)
199
+ }
200
+
201
+ /**
202
+ * Delete metaData
203
+ * 删除 metaData
204
+ *
205
+ * @param section
206
+ */
207
+ deleteMetaData(section: string) {
208
+ return deleteMetaData(this.__metaData, section)
209
+ }
210
+ }
package/src/config.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { isFunction, isNumber, isObject, isString, logger } from '@ohbug/utils'
2
+ import type { OhbugEventWithMethods, OhbugSchema } from '@ohbug/types'
3
+
4
+ export const schema: OhbugSchema = {
5
+ // base
6
+ apiKey: {
7
+ defaultValue: undefined,
8
+ message: 'is required',
9
+ validate: value => Boolean(value) && isString(value),
10
+ },
11
+ appVersion: {
12
+ defaultValue: undefined,
13
+ message: 'should be a string',
14
+ validate: value => value === undefined || isString(value),
15
+ },
16
+ appType: {
17
+ defaultValue: undefined,
18
+ message: 'should be a string',
19
+ validate: value => value === undefined || isString(value),
20
+ },
21
+ releaseStage: {
22
+ defaultValue: 'production',
23
+ message: 'should be a string',
24
+ validate: value => value === undefined || isString(value),
25
+ },
26
+ endpoint: {
27
+ defaultValue: 'http://localhost:6660',
28
+ message: 'should be a string',
29
+ validate: value => value === undefined || isString(value),
30
+ },
31
+ maxActions: {
32
+ defaultValue: 30,
33
+ message: 'should be a number between 0 and 100',
34
+ validate: value =>
35
+ value === undefined || (isNumber(value) && value >= 1 && value <= 100),
36
+ },
37
+ // hooks
38
+ created: {
39
+ defaultValue: (event: OhbugEventWithMethods<any>) => event,
40
+ message: 'should be a function',
41
+ validate: value => value === undefined || isFunction(value),
42
+ },
43
+ notified: {
44
+ defaultValue: () => {},
45
+ message: 'should be a function',
46
+ validate: value => value === undefined || isFunction(value),
47
+ },
48
+ // utils
49
+ logger: {
50
+ defaultValue: logger,
51
+ message:
52
+ 'should be null or an object with methods { log, info, warn, error }',
53
+ validate: value =>
54
+ value === undefined
55
+ || (value
56
+ && ['log', 'info', 'warn', 'error'].reduce<boolean>(
57
+ (accumulator, method) =>
58
+ accumulator && typeof value[method] === 'function',
59
+ true,
60
+ )),
61
+ },
62
+ // data
63
+ user: {
64
+ defaultValue: undefined,
65
+ message: 'should be an object and have up to 6 attributes',
66
+ validate: value =>
67
+ value === undefined
68
+ || (isObject(value) && Object.keys(value).length <= 6),
69
+ },
70
+ metaData: {
71
+ defaultValue: undefined,
72
+ message: 'should be an object',
73
+ validate: value => value === undefined || isObject(value),
74
+ },
75
+ }
package/src/event.ts ADDED
@@ -0,0 +1,276 @@
1
+ import type {
2
+ OhbugAction,
3
+ OhbugCategory,
4
+ OhbugClient,
5
+ OhbugCreateEvent,
6
+ OhbugDevice,
7
+ OhbugEvent,
8
+ OhbugEventWithMethods,
9
+ OhbugMetaData,
10
+ OhbugReleaseStage,
11
+ OhbugSDK,
12
+ OhbugUser,
13
+ } from '@ohbug/types'
14
+
15
+ import { isFunction, isObject, isString } from '@ohbug/utils'
16
+ import { Action } from './action'
17
+ import { getErrorMessage } from './lib/getErrorMessage'
18
+ import { addMetaData, deleteMetaData, getMetaData } from './lib/metaData'
19
+
20
+ export class Event<D> implements OhbugEventWithMethods<D> {
21
+ readonly apiKey: string
22
+
23
+ readonly appVersion?: string
24
+
25
+ readonly appType?: string
26
+
27
+ readonly timestamp: string
28
+
29
+ readonly category?: OhbugCategory
30
+
31
+ readonly type: string
32
+
33
+ readonly sdk: OhbugSDK
34
+
35
+ readonly device: OhbugDevice
36
+
37
+ readonly detail: D
38
+
39
+ user?: OhbugUser
40
+
41
+ readonly actions?: OhbugAction[]
42
+
43
+ readonly metaData: OhbugMetaData
44
+
45
+ readonly releaseStage?: OhbugReleaseStage
46
+
47
+ readonly __client?: OhbugClient
48
+
49
+ constructor(values: OhbugEvent<D>, client?: OhbugClient) {
50
+ const {
51
+ apiKey,
52
+ appVersion,
53
+ appType,
54
+ releaseStage,
55
+ timestamp,
56
+ category,
57
+ type,
58
+ sdk,
59
+
60
+ detail,
61
+ device,
62
+ user,
63
+ actions,
64
+ metaData,
65
+ } = values
66
+ this.apiKey = apiKey
67
+ this.appVersion = appVersion
68
+ this.appType = appType
69
+ this.releaseStage = releaseStage
70
+ this.timestamp = timestamp
71
+ this.category = category
72
+ this.type = type
73
+ this.sdk = sdk
74
+
75
+ this.detail = detail
76
+ this.device = device
77
+ this.user = user
78
+ this.actions = actions
79
+ this.metaData = metaData ?? {}
80
+
81
+ this.__client = client
82
+ }
83
+
84
+ get __isOhbugEvent() {
85
+ return true
86
+ }
87
+
88
+ /**
89
+ * Add an action.
90
+ * Once the threshold is reached, the oldest breadcrumbs will be deleted.
91
+ * 新增一个动作。
92
+ * 一旦达到阈值,最老的 Action 将被删除。
93
+ *
94
+ * @param message
95
+ * @param data
96
+ * @param type
97
+ * @param timestamp
98
+ */
99
+ addAction(
100
+ message: string,
101
+ data: Record<string, any>,
102
+ type: string,
103
+ timestamp?: string,
104
+ ): void {
105
+ const actions = this.actions!
106
+
107
+ const targetMessage = isString(message) ? message : ''
108
+ const targetData = data || {}
109
+ const targetType = isString(type) ? type : ''
110
+
111
+ const action = new Action(targetMessage, targetData, targetType, timestamp)
112
+ if (actions.length >= (this.__client?.__config.maxActions ?? 30))
113
+ actions.shift()
114
+
115
+ actions.push(action)
116
+ }
117
+
118
+ /**
119
+ * Get current user information
120
+ * 获取当前的用户信息
121
+ */
122
+ getUser(): OhbugUser | undefined {
123
+ return this.user
124
+ }
125
+
126
+ /**
127
+ * Set current user information
128
+ * 设置当前的用户信息
129
+ */
130
+ setUser(user: OhbugUser): OhbugUser | undefined {
131
+ if (isObject(user) && Object.keys(user).length <= 6) {
132
+ this.user = { ...this.user, ...user }
133
+ return this.getUser()
134
+ }
135
+ this.__client?.__logger.error(getErrorMessage(
136
+ 'setUser should be an object and have up to 6 attributes',
137
+ user,
138
+ ))
139
+ return undefined
140
+ }
141
+
142
+ /**
143
+ * Add metaData
144
+ * 新增 metaData
145
+ *
146
+ * @param section
147
+ * @param data
148
+ */
149
+ addMetaData(section: string, data: any) {
150
+ return addMetaData(this.metaData, section, data)
151
+ }
152
+
153
+ /**
154
+ * Get metaData
155
+ * 获取 metaData
156
+ *
157
+ * @param section
158
+ */
159
+ getMetaData(section: string) {
160
+ return getMetaData(this.metaData, section)
161
+ }
162
+
163
+ /**
164
+ * Delete metaData
165
+ * 删除 metaData
166
+ *
167
+ * @param section
168
+ */
169
+ deleteMetaData(section: string) {
170
+ return deleteMetaData(this.metaData, section)
171
+ }
172
+
173
+ toJSON() {
174
+ const {
175
+ apiKey,
176
+ appVersion,
177
+ appType,
178
+ timestamp,
179
+ category,
180
+ type,
181
+ sdk,
182
+ device,
183
+ detail,
184
+ user,
185
+ actions,
186
+ metaData,
187
+ releaseStage,
188
+ } = this
189
+ return {
190
+ apiKey,
191
+ appVersion,
192
+ appType,
193
+ timestamp,
194
+ category,
195
+ type,
196
+ sdk,
197
+ device,
198
+ detail,
199
+ user,
200
+ actions,
201
+ metaData,
202
+ releaseStage,
203
+ }
204
+ }
205
+ }
206
+
207
+ export function createEvent<D>(
208
+ values: OhbugCreateEvent<D>,
209
+ client: OhbugClient,
210
+ ): OhbugEventWithMethods<D> {
211
+ const { apiKey, appVersion, appType, releaseStage } = client.__config
212
+ const timestamp = new Date().toISOString()
213
+ const device = client.__device(client)
214
+ let category: OhbugCategory
215
+ let type: string
216
+ let detail: D
217
+ if (
218
+ isObject(values)
219
+ && Object.prototype.hasOwnProperty.call(values, 'type')
220
+ && Object.prototype.hasOwnProperty.call(values, 'detail')
221
+ ) {
222
+ category = values.category || 'error'
223
+ type = values.type
224
+ detail = values.detail
225
+ }
226
+ else {
227
+ category = 'error'
228
+ type = 'unknownError'
229
+ detail = values as unknown as D
230
+ }
231
+
232
+ return new Event(
233
+ {
234
+ apiKey,
235
+ appVersion,
236
+ appType,
237
+ timestamp,
238
+ category,
239
+ type,
240
+ sdk: client.__sdk,
241
+ device,
242
+ user: client.__user,
243
+ detail,
244
+ actions: client.__actions,
245
+ metaData: client.__metaData,
246
+ releaseStage,
247
+ },
248
+ client,
249
+ )
250
+ }
251
+
252
+ export function handleEventCreated<D = any>(
253
+ event: OhbugEventWithMethods<D>,
254
+ client: OhbugClient,
255
+ ): OhbugEventWithMethods<D> | null {
256
+ const funcs = [
257
+ client.__config.created,
258
+ ...client.__extensions.map(({ created }) => created),
259
+ ].filter(v => isFunction(v)) as ((
260
+ event: OhbugEventWithMethods<D>,
261
+ client: OhbugClient
262
+ ) => OhbugEventWithMethods<D> | null)[]
263
+ if (funcs.length === 0)
264
+ return event
265
+
266
+ return funcs.reduce<OhbugEventWithMethods<D> | null>((previous, current) => {
267
+ if (previous && isFunction(current))
268
+ return current(previous, client)
269
+
270
+ return null
271
+ }, event)
272
+ }
273
+
274
+ export function isEvent(eventLike: any): eventLike is OhbugEventWithMethods<any> {
275
+ return Boolean(eventLike?.__isOhbugEvent)
276
+ }
@@ -0,0 +1,8 @@
1
+ import type { OhbugExtension, OhbugExtensionUI } from '@ohbug/types'
2
+
3
+ export function defineExtension(extension: OhbugExtension) {
4
+ return extension
5
+ }
6
+ export function createExtensionUI(extensionUI: OhbugExtensionUI) {
7
+ return extensionUI
8
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './client'
2
+ export { isEvent } from './event'
3
+ export * from './extension'
4
+ export * from './types'
5
+ export * as types from './types'
@@ -0,0 +1,17 @@
1
+ import type { OhbugConfig } from '@ohbug/types'
2
+
3
+ export function getConfigErrorMessage(
4
+ errors: Record<keyof OhbugConfig, string>,
5
+ config: OhbugConfig,
6
+ ) {
7
+ return new Error(`Invalid configuration\n${Object.keys(errors)
8
+ .map((key) => {
9
+ return `- ${key} ${errors[key as keyof OhbugConfig]}, got ${JSON.stringify(config[key as keyof OhbugConfig])}`
10
+ })
11
+ .join('\n')}
12
+ `)
13
+ }
14
+
15
+ export function getErrorMessage(message: string, data: any) {
16
+ return new Error(`Invalid data\n- ${message}, got ${JSON.stringify(data)}`)
17
+ }
@@ -0,0 +1,21 @@
1
+ import type { OhbugMetaData } from '@ohbug/types'
2
+
3
+ export function addMetaData(map: OhbugMetaData, section: string, data: any) {
4
+ if (!section) return
5
+
6
+ map[section] = data
7
+ }
8
+
9
+ export function getMetaData(map: OhbugMetaData, section: string) {
10
+ if (map[section])
11
+ return map[section]
12
+
13
+ return undefined
14
+ }
15
+
16
+ export function deleteMetaData(map: OhbugMetaData, section: string) {
17
+ if (map[section])
18
+ return delete map[section]
19
+
20
+ return undefined
21
+ }
@@ -0,0 +1,37 @@
1
+ import type { OhbugConfig, OhbugSchema } from '@ohbug/types'
2
+
3
+ interface ConfigAndErrors {
4
+ config: Record<keyof OhbugConfig, any>
5
+ errors: Record<keyof OhbugConfig, string>
6
+ }
7
+ export function verifyConfig(config: OhbugConfig, schema: OhbugSchema) {
8
+ const keys = Object.keys(schema) as (keyof OhbugConfig)[]
9
+ return keys.reduce<ConfigAndErrors>(
10
+ (accumulator, key) => {
11
+ const configValue = config[key]
12
+ const { defaultValue, message, validate } = schema[key]
13
+
14
+ if (configValue !== undefined) {
15
+ const valid = validate(configValue)
16
+ if (valid) {
17
+ accumulator.config[key] = configValue
18
+ }
19
+ else {
20
+ accumulator.config[key] = defaultValue
21
+ accumulator.errors[key] = message
22
+ }
23
+ }
24
+ else {
25
+ // if there is no corresponding configuration, use the default value
26
+ // 如果没有传入相应配置 使用默认值
27
+ accumulator.config[key] = defaultValue
28
+ }
29
+
30
+ return accumulator
31
+ },
32
+ {
33
+ config: {} as ConfigAndErrors['config'],
34
+ errors: {} as ConfigAndErrors['errors'],
35
+ },
36
+ )
37
+ }
package/src/notify.ts ADDED
@@ -0,0 +1,38 @@
1
+ import type { OhbugClient, OhbugEventWithMethods } from '@ohbug/types'
2
+ import { isFunction } from '@ohbug/utils'
3
+
4
+ function handleNotified<D>(
5
+ event: OhbugEventWithMethods<D>,
6
+ client: OhbugClient,
7
+ ) {
8
+ const funcs = [
9
+ client.__config.notified,
10
+ ...client.__extensions
11
+ .filter(({ notified }) => isFunction(notified))
12
+ .map(({ notified }) => notified),
13
+ ]
14
+ funcs.forEach(func => func?.(event, client))
15
+ }
16
+
17
+ /**
18
+ * Used to control the timing of reporting events and the related life cycle.
19
+ *
20
+ * @param event
21
+ * @param client
22
+ */
23
+ export async function notify<D>(
24
+ event: OhbugEventWithMethods<D> | null,
25
+ client: OhbugClient,
26
+ ): Promise<any> {
27
+ try {
28
+ let result = null
29
+ if (event) {
30
+ result = await client.__notifier(event)
31
+ handleNotified(event, client)
32
+ }
33
+ return result
34
+ }
35
+ catch (e) {
36
+ client.__logger.error(e)
37
+ }
38
+ }