@customerio/cdp-analytics-node 0.0.1

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.
Files changed (171) hide show
  1. package/LICENSE.MD +22 -0
  2. package/README.md +20 -0
  3. package/dist/cjs/app/analytics-node.js +170 -0
  4. package/dist/cjs/app/analytics-node.js.map +1 -0
  5. package/dist/cjs/app/context.js +13 -0
  6. package/dist/cjs/app/context.js.map +1 -0
  7. package/dist/cjs/app/dispatch-emit.js +37 -0
  8. package/dist/cjs/app/dispatch-emit.js.map +1 -0
  9. package/dist/cjs/app/emitter.js +8 -0
  10. package/dist/cjs/app/emitter.js.map +1 -0
  11. package/dist/cjs/app/event-factory.js +12 -0
  12. package/dist/cjs/app/event-factory.js.map +1 -0
  13. package/dist/cjs/app/event-queue.js +24 -0
  14. package/dist/cjs/app/event-queue.js.map +1 -0
  15. package/dist/cjs/app/settings.js +11 -0
  16. package/dist/cjs/app/settings.js.map +1 -0
  17. package/dist/cjs/app/types/event.js +3 -0
  18. package/dist/cjs/app/types/event.js.map +1 -0
  19. package/dist/cjs/app/types/index.js +7 -0
  20. package/dist/cjs/app/types/index.js.map +1 -0
  21. package/dist/cjs/app/types/params.js +3 -0
  22. package/dist/cjs/app/types/params.js.map +1 -0
  23. package/dist/cjs/app/types/plugin.js +3 -0
  24. package/dist/cjs/app/types/plugin.js.map +1 -0
  25. package/dist/cjs/generated/version.js +6 -0
  26. package/dist/cjs/generated/version.js.map +1 -0
  27. package/dist/cjs/index.js +11 -0
  28. package/dist/cjs/index.js.map +1 -0
  29. package/dist/cjs/lib/abort.js +77 -0
  30. package/dist/cjs/lib/abort.js.map +1 -0
  31. package/dist/cjs/lib/base-64-encode.js +19 -0
  32. package/dist/cjs/lib/base-64-encode.js.map +1 -0
  33. package/dist/cjs/lib/create-url.js +15 -0
  34. package/dist/cjs/lib/create-url.js.map +1 -0
  35. package/dist/cjs/lib/env.js +25 -0
  36. package/dist/cjs/lib/env.js.map +1 -0
  37. package/dist/cjs/lib/extract-promise-parts.js +21 -0
  38. package/dist/cjs/lib/extract-promise-parts.js.map +1 -0
  39. package/dist/cjs/lib/fetch.js +7 -0
  40. package/dist/cjs/lib/fetch.js.map +1 -0
  41. package/dist/cjs/lib/get-message-id.js +14 -0
  42. package/dist/cjs/lib/get-message-id.js.map +1 -0
  43. package/dist/cjs/lib/uuid.js +6 -0
  44. package/dist/cjs/lib/uuid.js.map +1 -0
  45. package/dist/cjs/plugins/customerio/context-batch.js +55 -0
  46. package/dist/cjs/plugins/customerio/context-batch.js.map +1 -0
  47. package/dist/cjs/plugins/customerio/index.js +44 -0
  48. package/dist/cjs/plugins/customerio/index.js.map +1 -0
  49. package/dist/cjs/plugins/customerio/publisher.js +189 -0
  50. package/dist/cjs/plugins/customerio/publisher.js.map +1 -0
  51. package/dist/esm/app/analytics-node.js +166 -0
  52. package/dist/esm/app/analytics-node.js.map +1 -0
  53. package/dist/esm/app/context.js +9 -0
  54. package/dist/esm/app/context.js.map +1 -0
  55. package/dist/esm/app/dispatch-emit.js +33 -0
  56. package/dist/esm/app/dispatch-emit.js.map +1 -0
  57. package/dist/esm/app/emitter.js +4 -0
  58. package/dist/esm/app/emitter.js.map +1 -0
  59. package/dist/esm/app/event-factory.js +8 -0
  60. package/dist/esm/app/event-factory.js.map +1 -0
  61. package/dist/esm/app/event-queue.js +20 -0
  62. package/dist/esm/app/event-queue.js.map +1 -0
  63. package/dist/esm/app/settings.js +7 -0
  64. package/dist/esm/app/settings.js.map +1 -0
  65. package/dist/esm/app/types/event.js +2 -0
  66. package/dist/esm/app/types/event.js.map +1 -0
  67. package/dist/esm/app/types/index.js +4 -0
  68. package/dist/esm/app/types/index.js.map +1 -0
  69. package/dist/esm/app/types/params.js +2 -0
  70. package/dist/esm/app/types/params.js.map +1 -0
  71. package/dist/esm/app/types/plugin.js +2 -0
  72. package/dist/esm/app/types/plugin.js.map +1 -0
  73. package/dist/esm/generated/version.js +3 -0
  74. package/dist/esm/generated/version.js.map +1 -0
  75. package/dist/esm/index.js +6 -0
  76. package/dist/esm/index.js.map +1 -0
  77. package/dist/esm/lib/abort.js +73 -0
  78. package/dist/esm/lib/abort.js.map +1 -0
  79. package/dist/esm/lib/base-64-encode.js +15 -0
  80. package/dist/esm/lib/base-64-encode.js.map +1 -0
  81. package/dist/esm/lib/create-url.js +11 -0
  82. package/dist/esm/lib/create-url.js.map +1 -0
  83. package/dist/esm/lib/env.js +21 -0
  84. package/dist/esm/lib/env.js.map +1 -0
  85. package/dist/esm/lib/extract-promise-parts.js +17 -0
  86. package/dist/esm/lib/extract-promise-parts.js.map +1 -0
  87. package/dist/esm/lib/fetch.js +3 -0
  88. package/dist/esm/lib/fetch.js.map +1 -0
  89. package/dist/esm/lib/get-message-id.js +10 -0
  90. package/dist/esm/lib/get-message-id.js.map +1 -0
  91. package/dist/esm/lib/uuid.js +2 -0
  92. package/dist/esm/lib/uuid.js.map +1 -0
  93. package/dist/esm/plugins/customerio/context-batch.js +51 -0
  94. package/dist/esm/plugins/customerio/context-batch.js.map +1 -0
  95. package/dist/esm/plugins/customerio/index.js +39 -0
  96. package/dist/esm/plugins/customerio/index.js.map +1 -0
  97. package/dist/esm/plugins/customerio/publisher.js +185 -0
  98. package/dist/esm/plugins/customerio/publisher.js.map +1 -0
  99. package/dist/types/app/analytics-node.d.ts +62 -0
  100. package/dist/types/app/analytics-node.d.ts.map +1 -0
  101. package/dist/types/app/context.d.ts +6 -0
  102. package/dist/types/app/context.d.ts.map +1 -0
  103. package/dist/types/app/dispatch-emit.d.ts +7 -0
  104. package/dist/types/app/dispatch-emit.d.ts.map +1 -0
  105. package/dist/types/app/emitter.d.ts +23 -0
  106. package/dist/types/app/emitter.d.ts.map +1 -0
  107. package/dist/types/app/event-factory.d.ts +14 -0
  108. package/dist/types/app/event-factory.d.ts.map +1 -0
  109. package/dist/types/app/event-queue.d.ts +7 -0
  110. package/dist/types/app/event-queue.d.ts.map +1 -0
  111. package/dist/types/app/settings.d.ts +33 -0
  112. package/dist/types/app/settings.d.ts.map +1 -0
  113. package/dist/types/app/types/event.d.ts +7 -0
  114. package/dist/types/app/types/event.d.ts.map +1 -0
  115. package/dist/types/app/types/index.d.ts +4 -0
  116. package/dist/types/app/types/index.d.ts.map +1 -0
  117. package/dist/types/app/types/params.d.ts +60 -0
  118. package/dist/types/app/types/params.d.ts.map +1 -0
  119. package/dist/types/app/types/plugin.d.ts +6 -0
  120. package/dist/types/app/types/plugin.d.ts.map +1 -0
  121. package/dist/types/generated/version.d.ts +2 -0
  122. package/dist/types/generated/version.d.ts.map +1 -0
  123. package/dist/types/index.d.ts +7 -0
  124. package/dist/types/index.d.ts.map +1 -0
  125. package/dist/types/lib/abort.d.ts +6 -0
  126. package/dist/types/lib/abort.d.ts.map +1 -0
  127. package/dist/types/lib/base-64-encode.d.ts +5 -0
  128. package/dist/types/lib/base-64-encode.d.ts.map +1 -0
  129. package/dist/types/lib/create-url.d.ts +8 -0
  130. package/dist/types/lib/create-url.d.ts.map +1 -0
  131. package/dist/types/lib/env.d.ts +3 -0
  132. package/dist/types/lib/env.d.ts.map +1 -0
  133. package/dist/types/lib/extract-promise-parts.d.ts +9 -0
  134. package/dist/types/lib/extract-promise-parts.d.ts.map +1 -0
  135. package/dist/types/lib/fetch.d.ts +2 -0
  136. package/dist/types/lib/fetch.d.ts.map +1 -0
  137. package/dist/types/lib/get-message-id.d.ts +7 -0
  138. package/dist/types/lib/get-message-id.d.ts.map +1 -0
  139. package/dist/types/lib/uuid.d.ts +2 -0
  140. package/dist/types/lib/uuid.d.ts.map +1 -0
  141. package/dist/types/plugins/customerio/context-batch.d.ts +26 -0
  142. package/dist/types/plugins/customerio/context-batch.d.ts.map +1 -0
  143. package/dist/types/plugins/customerio/index.d.ts +13 -0
  144. package/dist/types/plugins/customerio/index.d.ts.map +1 -0
  145. package/dist/types/plugins/customerio/publisher.d.ts +38 -0
  146. package/dist/types/plugins/customerio/publisher.d.ts.map +1 -0
  147. package/package.json +43 -0
  148. package/src/app/analytics-node.ts +295 -0
  149. package/src/app/context.ts +11 -0
  150. package/src/app/dispatch-emit.ts +42 -0
  151. package/src/app/emitter.ts +23 -0
  152. package/src/app/event-factory.ts +20 -0
  153. package/src/app/event-queue.ts +23 -0
  154. package/src/app/settings.ts +39 -0
  155. package/src/app/types/event.ts +7 -0
  156. package/src/app/types/index.ts +3 -0
  157. package/src/app/types/params.ts +74 -0
  158. package/src/app/types/plugin.ts +5 -0
  159. package/src/generated/version.ts +2 -0
  160. package/src/index.ts +17 -0
  161. package/src/lib/abort.ts +77 -0
  162. package/src/lib/base-64-encode.ts +14 -0
  163. package/src/lib/create-url.ts +11 -0
  164. package/src/lib/env.ts +32 -0
  165. package/src/lib/extract-promise-parts.ts +21 -0
  166. package/src/lib/fetch.ts +3 -0
  167. package/src/lib/get-message-id.ts +10 -0
  168. package/src/lib/uuid.ts +1 -0
  169. package/src/plugins/customerio/context-batch.ts +71 -0
  170. package/src/plugins/customerio/index.ts +65 -0
  171. package/src/plugins/customerio/publisher.ts +258 -0
@@ -0,0 +1,295 @@
1
+ import {
2
+ CoreAnalytics,
3
+ bindAll,
4
+ pTimeout,
5
+ } from '@customerio/cdp-analytics-core'
6
+ import { AnalyticsSettings, validateSettings } from './settings'
7
+ import { version } from '../generated/version'
8
+ import { createConfiguredNodePlugin } from '../plugins/customerio'
9
+ import { NodeEventFactory } from './event-factory'
10
+ import { Callback, dispatchAndEmit } from './dispatch-emit'
11
+ import { NodeEmitter } from './emitter'
12
+ import {
13
+ AliasParams,
14
+ GroupParams,
15
+ IdentifyParams,
16
+ PageParams,
17
+ TrackParams,
18
+ Plugin,
19
+ CustomerioEvent,
20
+ } from './types'
21
+ import { Context } from './context'
22
+ import { NodeEventQueue } from './event-queue'
23
+
24
+ export class Analytics extends NodeEmitter implements CoreAnalytics {
25
+ private readonly _eventFactory: NodeEventFactory
26
+ private _isClosed = false
27
+ private _pendingEvents = 0
28
+ private readonly _closeAndFlushDefaultTimeout: number
29
+ private readonly _publisher: ReturnType<
30
+ typeof createConfiguredNodePlugin
31
+ >['publisher']
32
+
33
+ private readonly _queue: NodeEventQueue
34
+
35
+ ready: Promise<void>
36
+
37
+ constructor(settings: AnalyticsSettings) {
38
+ super()
39
+ validateSettings(settings)
40
+
41
+ this._eventFactory = new NodeEventFactory()
42
+ this._queue = new NodeEventQueue()
43
+
44
+ const flushInterval = settings.flushInterval ?? 10000
45
+
46
+ this._closeAndFlushDefaultTimeout = flushInterval * 1.25 // add arbitrary multiplier in case an event is in a plugin.
47
+
48
+ const { plugin, publisher } = createConfiguredNodePlugin(
49
+ {
50
+ writeKey: settings.writeKey,
51
+ host: settings.host,
52
+ path: settings.path,
53
+ maxRetries: settings.maxRetries ?? 3,
54
+ maxEventsInBatch: settings.maxEventsInBatch ?? 15,
55
+ httpRequestTimeout: settings.httpRequestTimeout,
56
+ flushInterval,
57
+ },
58
+ this as NodeEmitter
59
+ )
60
+ this._publisher = publisher
61
+ this.ready = this.register(plugin).then(() => undefined)
62
+
63
+ this.emit('initialize', settings)
64
+
65
+ bindAll(this)
66
+ }
67
+
68
+ get VERSION() {
69
+ return version
70
+ }
71
+
72
+ /**
73
+ * Call this method to stop collecting new events and flush all existing events.
74
+ * This method also waits for any event method-specific callbacks to be triggered,
75
+ * and any of their subsequent promises to be resolved/rejected.
76
+ */
77
+ public closeAndFlush({
78
+ timeout = this._closeAndFlushDefaultTimeout,
79
+ }: {
80
+ /** Set a maximum time permitted to wait before resolving. */
81
+ timeout?: number
82
+ } = {}): Promise<void> {
83
+ this._publisher.flushAfterClose(this._pendingEvents)
84
+ this._isClosed = true
85
+ const promise = new Promise<void>((resolve) => {
86
+ if (!this._pendingEvents) {
87
+ resolve()
88
+ } else {
89
+ this.once('drained', () => resolve())
90
+ }
91
+ })
92
+ return timeout ? pTimeout(promise, timeout).catch(() => undefined) : promise
93
+ }
94
+
95
+ private _dispatch(CustomerioEvent: CustomerioEvent, callback?: Callback) {
96
+ if (this._isClosed) {
97
+ this.emit('call_after_close', CustomerioEvent as CustomerioEvent)
98
+ return undefined
99
+ }
100
+
101
+ this._pendingEvents++
102
+
103
+ dispatchAndEmit(CustomerioEvent, this._queue, this, callback)
104
+ .catch((ctx) => ctx)
105
+ .finally(() => {
106
+ this._pendingEvents--
107
+
108
+ if (!this._pendingEvents) {
109
+ this.emit('drained')
110
+ }
111
+ })
112
+ }
113
+
114
+ /**
115
+ * Combines two unassociated user identities.
116
+ */
117
+ alias(
118
+ { userId, previousId, context, timestamp, integrations }: AliasParams,
119
+ callback?: Callback
120
+ ): void {
121
+ const CustomerioEvent = this._eventFactory.alias(userId, previousId, {
122
+ context,
123
+ integrations,
124
+ timestamp,
125
+ })
126
+ this._dispatch(CustomerioEvent, callback)
127
+ }
128
+
129
+ /**
130
+ * Associates an identified user with a collective.
131
+ */
132
+ group(
133
+ {
134
+ timestamp,
135
+ groupId,
136
+ userId,
137
+ anonymousId,
138
+ traits = {},
139
+ context,
140
+ integrations,
141
+ }: GroupParams,
142
+ callback?: Callback
143
+ ): void {
144
+ const CustomerioEvent = this._eventFactory.group(groupId, traits, {
145
+ context,
146
+ anonymousId,
147
+ userId,
148
+ timestamp,
149
+ integrations,
150
+ })
151
+
152
+ this._dispatch(CustomerioEvent, callback)
153
+ }
154
+
155
+ /**
156
+ * Includes a unique userId and (maybe anonymousId) and any optional traits you know about them.
157
+ */
158
+ identify(
159
+ {
160
+ userId,
161
+ anonymousId,
162
+ traits = {},
163
+ context,
164
+ timestamp,
165
+ integrations,
166
+ }: IdentifyParams,
167
+ callback?: Callback
168
+ ): void {
169
+ const CustomerioEvent = this._eventFactory.identify(userId, traits, {
170
+ context,
171
+ anonymousId,
172
+ userId,
173
+ timestamp,
174
+ integrations,
175
+ })
176
+ this._dispatch(CustomerioEvent, callback)
177
+ }
178
+
179
+ /**
180
+ * The page method lets you record page views on your website, along with optional extra information about the page being viewed.
181
+ */
182
+ page(
183
+ {
184
+ userId,
185
+ anonymousId,
186
+ category,
187
+ name,
188
+ properties,
189
+ context,
190
+ timestamp,
191
+ integrations,
192
+ }: PageParams,
193
+ callback?: Callback
194
+ ): void {
195
+ const CustomerioEvent = this._eventFactory.page(
196
+ category ?? null,
197
+ name ?? null,
198
+ properties,
199
+ { context, anonymousId, userId, timestamp, integrations }
200
+ )
201
+ this._dispatch(CustomerioEvent, callback)
202
+ }
203
+
204
+ /**
205
+ * Records screen views on your app, along with optional extra information
206
+ * about the screen viewed by the user.
207
+ */
208
+ screen(
209
+ {
210
+ userId,
211
+ anonymousId,
212
+ category,
213
+ name,
214
+ properties,
215
+ context,
216
+ timestamp,
217
+ integrations,
218
+ }: PageParams,
219
+ callback?: Callback
220
+ ): void {
221
+ const CustomerioEvent = this._eventFactory.screen(
222
+ category ?? null,
223
+ name ?? null,
224
+ properties,
225
+ { context, anonymousId, userId, timestamp, integrations }
226
+ )
227
+
228
+ this._dispatch(CustomerioEvent, callback)
229
+ }
230
+
231
+ /**
232
+ * Records actions your users perform.
233
+ */
234
+ track(
235
+ {
236
+ userId,
237
+ anonymousId,
238
+ event,
239
+ properties,
240
+ context,
241
+ timestamp,
242
+ integrations,
243
+ }: TrackParams,
244
+ callback?: Callback
245
+ ): void {
246
+ const CustomerioEvent = this._eventFactory.track(event, properties, {
247
+ context,
248
+ userId,
249
+ anonymousId,
250
+ timestamp,
251
+ integrations,
252
+ })
253
+
254
+ this._dispatch(CustomerioEvent, callback)
255
+ }
256
+
257
+ /**
258
+ * Registers one or more plugins to augment Analytics functionality.
259
+ * @param plugins
260
+ */
261
+ register(...plugins: Plugin[]): Promise<void> {
262
+ return this._queue.criticalTasks.run(async () => {
263
+ const ctx = Context.system()
264
+
265
+ const registrations = plugins.map((xt) =>
266
+ this._queue.register(ctx, xt, this)
267
+ )
268
+ await Promise.all(registrations)
269
+ this.emit(
270
+ 'register',
271
+ plugins.map((el) => el.name)
272
+ )
273
+ })
274
+ }
275
+
276
+ /**
277
+ * Deregisters one or more plugins based on their names.
278
+ * @param pluginNames - The names of one or more plugins to deregister.
279
+ */
280
+ async deregister(...pluginNames: string[]): Promise<void> {
281
+ const ctx = Context.system()
282
+
283
+ const deregistrations = pluginNames.map((pl) => {
284
+ const plugin = this._queue.plugins.find((p) => p.name === pl)
285
+ if (plugin) {
286
+ return this._queue.deregister(ctx, plugin, this)
287
+ } else {
288
+ ctx.log('warn', `plugin ${pl} not found`)
289
+ }
290
+ })
291
+
292
+ await Promise.all(deregistrations)
293
+ this.emit('deregister', pluginNames)
294
+ }
295
+ }
@@ -0,0 +1,11 @@
1
+ // create a derived class since we may want to add node specific things to Context later
2
+
3
+ import { CoreContext } from '@customerio/cdp-analytics-core'
4
+ import { CustomerioEvent } from './types'
5
+
6
+ // While this is not a type, it is a definition
7
+ export class Context extends CoreContext<CustomerioEvent> {
8
+ static override system() {
9
+ return new this({ type: 'track', event: 'system' })
10
+ }
11
+ }
@@ -0,0 +1,42 @@
1
+ import { dispatch } from '@customerio/cdp-analytics-core'
2
+ import type { NodeEmitter } from './emitter'
3
+ import { Context } from './context'
4
+ import { NodeEventQueue } from './event-queue'
5
+ import { CustomerioEvent } from './types'
6
+
7
+ export type Callback = (err?: unknown, ctx?: Context) => void
8
+
9
+ const normalizeDispatchCb = (cb: Callback) => (ctx: Context) => {
10
+ const failedDelivery = ctx.failedDelivery()
11
+ return failedDelivery ? cb(failedDelivery.reason, ctx) : cb(undefined, ctx)
12
+ }
13
+
14
+ /* Dispatch function, but swallow promise rejections and use event emitter instead */
15
+ export const dispatchAndEmit = async (
16
+ event: CustomerioEvent,
17
+ queue: NodeEventQueue,
18
+ emitter: NodeEmitter,
19
+ callback?: Callback
20
+ ): Promise<void> => {
21
+ try {
22
+ const context = new Context(event)
23
+ const ctx = await dispatch(context, queue, emitter, {
24
+ ...(callback ? { callback: normalizeDispatchCb(callback) } : {}),
25
+ })
26
+ const failedDelivery = ctx.failedDelivery()
27
+ if (failedDelivery) {
28
+ emitter.emit('error', {
29
+ code: 'delivery_failure',
30
+ reason: failedDelivery.reason,
31
+ ctx: ctx,
32
+ })
33
+ } else {
34
+ emitter.emit(event.type, ctx)
35
+ }
36
+ } catch (err) {
37
+ emitter.emit('error', {
38
+ code: 'unknown',
39
+ reason: err,
40
+ })
41
+ }
42
+ }
@@ -0,0 +1,23 @@
1
+ import { CoreEmitterContract, Emitter } from '@customerio/cdp-analytics-core'
2
+ import { Context } from './context'
3
+ import type { AnalyticsSettings } from './settings'
4
+ import { CustomerioEvent } from './types'
5
+
6
+ /**
7
+ * Map of emitter event names to method args.
8
+ */
9
+ export type NodeEmitterEvents = CoreEmitterContract<Context> & {
10
+ initialize: [AnalyticsSettings]
11
+ call_after_close: [CustomerioEvent] // any event that did not get dispatched due to close
12
+ http_request: [
13
+ {
14
+ url: string
15
+ method: string
16
+ headers: Record<string, string>
17
+ body: string
18
+ }
19
+ ]
20
+ drained: []
21
+ }
22
+
23
+ export class NodeEmitter extends Emitter<NodeEmitterEvents> { }
@@ -0,0 +1,20 @@
1
+ import { EventFactory } from '@customerio/cdp-analytics-core'
2
+ import { createMessageId } from '../lib/get-message-id'
3
+ import { CustomerioEvent } from './types'
4
+
5
+ // use declaration merging to downcast CoreCustomerioEvent without adding any runtime code.
6
+ // if/when we decide to add an actual implementation to NodeEventFactory that actually changes the event shape, we can remove this.
7
+ export interface NodeEventFactory {
8
+ alias(...args: Parameters<EventFactory['alias']>): CustomerioEvent
9
+ group(...args: Parameters<EventFactory['group']>): CustomerioEvent
10
+ identify(...args: Parameters<EventFactory['identify']>): CustomerioEvent
11
+ track(...args: Parameters<EventFactory['track']>): CustomerioEvent
12
+ page(...args: Parameters<EventFactory['page']>): CustomerioEvent
13
+ screen(...args: Parameters<EventFactory['screen']>): CustomerioEvent
14
+ }
15
+
16
+ export class NodeEventFactory extends EventFactory {
17
+ constructor() {
18
+ super({ createMessageId })
19
+ }
20
+ }
@@ -0,0 +1,23 @@
1
+ import { CoreEventQueue, PriorityQueue } from '@customerio/cdp-analytics-core'
2
+ import type { Plugin } from '../app/types'
3
+ import type { Context } from './context'
4
+
5
+ class NodePriorityQueue extends PriorityQueue<Context> {
6
+ constructor() {
7
+ super(1, [])
8
+ }
9
+ // do not use an internal "seen" map
10
+ getAttempts(ctx: Context): number {
11
+ return ctx.attempts ?? 0
12
+ }
13
+ updateAttempts(ctx: Context): number {
14
+ ctx.attempts = this.getAttempts(ctx) + 1
15
+ return this.getAttempts(ctx)
16
+ }
17
+ }
18
+
19
+ export class NodeEventQueue extends CoreEventQueue<Context, Plugin> {
20
+ constructor() {
21
+ super(new NodePriorityQueue())
22
+ }
23
+ }
@@ -0,0 +1,39 @@
1
+ import { ValidationError } from '@customerio/cdp-analytics-core'
2
+
3
+ export interface AnalyticsSettings {
4
+ /**
5
+ * Key for your workspace
6
+ */
7
+ writeKey: string
8
+ /**
9
+ /**
10
+ * The base URL of the API. Default: "https://cdp.customer.io"
11
+ */
12
+ host?: string
13
+ /**
14
+ * The API path route. Default: "/v1/batch"
15
+ */
16
+ path?: string
17
+ /**
18
+ * The number of times to retry flushing a batch. Default: 3
19
+ */
20
+ maxRetries?: number
21
+ /**
22
+ * The number of messages to enqueue before flushing. Default: 15
23
+ */
24
+ maxEventsInBatch?: number
25
+ /**
26
+ * The number of milliseconds to wait before flushing the queue automatically. Default: 10000
27
+ */
28
+ flushInterval?: number
29
+ /**
30
+ * The maximum number of milliseconds to wait for an http request. Default: 10000
31
+ */
32
+ httpRequestTimeout?: number
33
+ }
34
+
35
+ export const validateSettings = (settings: AnalyticsSettings) => {
36
+ if (!settings.writeKey) {
37
+ throw new ValidationError('writeKey', 'writeKey is missing.')
38
+ }
39
+ }
@@ -0,0 +1,7 @@
1
+ import type { CoreCustomerioEvent } from '@customerio/cdp-analytics-core'
2
+
3
+ type CustomerioEventType = 'track' | 'page' | 'identify' | 'alias' | 'screen'
4
+
5
+ export interface CustomerioEvent extends CoreCustomerioEvent {
6
+ type: CustomerioEventType
7
+ }
@@ -0,0 +1,3 @@
1
+ export * from './params'
2
+ export * from './event'
3
+ export * from './plugin'
@@ -0,0 +1,74 @@
1
+ import type {
2
+ GroupTraits,
3
+ UserTraits,
4
+ CoreExtraContext,
5
+ EventProperties,
6
+ Integrations,
7
+ Timestamp,
8
+ } from '@customerio/cdp-analytics-core'
9
+
10
+ export type { GroupTraits, UserTraits }
11
+
12
+ /**
13
+ * A dictionary of extra context to attach to the call.
14
+ * Note: context differs from traits because it is not attributes of the user itself.
15
+ */
16
+ export interface ExtraContext extends CoreExtraContext { }
17
+
18
+ /**
19
+ * An ID associated with the user. Note: at least one of userId or anonymousId must be included.
20
+ **/
21
+ type IdentityOptions =
22
+ | { userId: string; anonymousId?: string }
23
+ | { userId?: string; anonymousId: string }
24
+
25
+ export type AliasParams = {
26
+ /* The new user id you want to associate with the user. */
27
+ userId: string
28
+ /* The previous id that the user was recognized by (this can be either a userId or an anonymousId). */
29
+ previousId: string
30
+ context?: ExtraContext
31
+ timestamp?: Timestamp
32
+ integrations?: Integrations
33
+ }
34
+
35
+ export type GroupParams = {
36
+ groupId: string
37
+ /**
38
+ * Traits are pieces of information you know about a group.
39
+ */
40
+ traits?: GroupTraits
41
+ context?: ExtraContext
42
+ timestamp?: Timestamp
43
+ integrations?: Integrations
44
+ } & IdentityOptions
45
+
46
+ export type IdentifyParams = {
47
+ /**
48
+ * Traits are pieces of information you know about a group.
49
+ */
50
+ traits?: UserTraits
51
+ context?: ExtraContext
52
+ timestamp?: Timestamp
53
+ integrations?: Integrations
54
+ } & IdentityOptions
55
+
56
+ export type PageParams = {
57
+ /* The category of the page. Useful for cases like ecommerce where many pages might live under a single category. */
58
+ category?: string
59
+ /* The name of the page.*/
60
+ name?: string
61
+ /* A dictionary of properties of the page. */
62
+ properties?: EventProperties
63
+ timestamp?: Timestamp
64
+ context?: ExtraContext
65
+ integrations?: Integrations
66
+ } & IdentityOptions
67
+
68
+ export type TrackParams = {
69
+ event: string
70
+ properties?: EventProperties
71
+ context?: ExtraContext
72
+ timestamp?: Timestamp
73
+ integrations?: Integrations
74
+ } & IdentityOptions
@@ -0,0 +1,5 @@
1
+ import type { CorePlugin } from '@customerio/cdp-analytics-core'
2
+ import type { Analytics } from '../analytics-node'
3
+ import type { Context } from '../context'
4
+
5
+ export interface Plugin extends CorePlugin<Context, Analytics> { }
@@ -0,0 +1,2 @@
1
+ // This file is generated.
2
+ export const version = '1.0.0-beta.22'
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ export { Analytics } from './app/analytics-node'
2
+ export { Context } from './app/context'
3
+ export type {
4
+ Plugin,
5
+ GroupTraits,
6
+ UserTraits,
7
+ TrackParams,
8
+ IdentifyParams,
9
+ AliasParams,
10
+ GroupParams,
11
+ PageParams,
12
+ } from './app/types'
13
+ export type { AnalyticsSettings } from './app/settings'
14
+
15
+ // export Analytics as both a named export and a default export (for backwards-compat. reasons)
16
+ import { Analytics } from './app/analytics-node'
17
+ export default Analytics
@@ -0,0 +1,77 @@
1
+ /**
2
+ * use non-native event emitter for the benefit of non-node runtimes like CF workers.
3
+ */
4
+ import { Emitter } from '@customerio/cdp-analytics-core'
5
+ import { detectRuntime } from './env'
6
+
7
+ /**
8
+ * adapted from: https://www.npmjs.com/package/node-abort-controller
9
+ */
10
+ class AbortSignal {
11
+ onabort: globalThis.AbortSignal['onabort'] = null
12
+ aborted = false
13
+ eventEmitter = new Emitter()
14
+
15
+ toString() {
16
+ return '[object AbortSignal]'
17
+ }
18
+ get [Symbol.toStringTag]() {
19
+ return 'AbortSignal'
20
+ }
21
+ removeEventListener(...args: Parameters<Emitter['off']>) {
22
+ this.eventEmitter.off(...args)
23
+ }
24
+ addEventListener(...args: Parameters<Emitter['on']>) {
25
+ this.eventEmitter.on(...args)
26
+ }
27
+ dispatchEvent(type: string) {
28
+ const event = { type, target: this }
29
+
30
+ const handlerName = `on${type}`
31
+
32
+ if (typeof (this as any)[handlerName] === 'function') {
33
+ ; (this as any)[handlerName](event)
34
+ }
35
+
36
+ this.eventEmitter.emit(type, event)
37
+ }
38
+ }
39
+
40
+ /**
41
+ * This polyfill is only neccessary to support versions of node < 14.17.
42
+ * Can be removed once node 14 support is dropped.
43
+ */
44
+ class AbortController {
45
+ signal = new AbortSignal()
46
+ abort() {
47
+ if (this.signal.aborted) return
48
+
49
+ this.signal.aborted = true
50
+ this.signal.dispatchEvent('abort')
51
+ }
52
+ toString() {
53
+ return '[object AbortController]'
54
+ }
55
+ get [Symbol.toStringTag]() {
56
+ return 'AbortController'
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @param timeoutMs - Set a request timeout, after which the request is cancelled.
62
+ */
63
+ export const abortSignalAfterTimeout = (timeoutMs: number) => {
64
+ if (detectRuntime() === 'cloudflare-worker') {
65
+ return [] // TODO: this is broken in cloudflare workers, otherwise results in "A hanging Promise was canceled..." error.
66
+ }
67
+ const ac = new (global.AbortController || AbortController)()
68
+
69
+ const timeoutId = setTimeout(() => {
70
+ ac.abort()
71
+ }, timeoutMs)
72
+
73
+ // Allow Node.js processes to exit early if only the timeout is running
74
+ timeoutId?.unref?.()
75
+
76
+ return [ac.signal, timeoutId] as const
77
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Base64 encoder that works in browser, worker, node runtimes.
3
+ */
4
+ export const b64encode = (str: string): string => {
5
+ if (
6
+ // in node env
7
+ typeof Buffer !== 'undefined'
8
+ ) {
9
+ return Buffer.from(str).toString('base64')
10
+ } else {
11
+ // in worker env
12
+ return btoa(str)
13
+ }
14
+ }
@@ -0,0 +1,11 @@
1
+ const stripTrailingSlash = (str: string) => str.replace(/\/$/, '')
2
+
3
+ /**
4
+ *
5
+ * @param host e.g. "http://foo.com"
6
+ * @param path e.g. "/bar"
7
+ * @returns "e.g." "http://foo.com/bar"
8
+ */
9
+ export const tryCreateFormattedUrl = (host: string, path?: string) => {
10
+ return stripTrailingSlash(new URL(path || '', host).href)
11
+ }