@vrplatform/log 2.0.0 → 2.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 (61) hide show
  1. package/build/main/log/index.spec.d.ts +0 -0
  2. package/build/main/log/index.spec.js +2 -0
  3. package/build/main/log/index.spec.js.map +1 -0
  4. package/build/main/log/type.d.ts +3 -1
  5. package/build/main/tracking/_intercom.d.ts +109 -0
  6. package/build/main/tracking/_intercom.js +139 -0
  7. package/build/main/tracking/_intercom.js.map +1 -0
  8. package/build/main/tracking/eventTypes.d.ts +16 -0
  9. package/build/main/tracking/eventTypes.js +3 -0
  10. package/build/main/tracking/eventTypes.js.map +1 -0
  11. package/build/main/tracking/index.d.ts +96 -33
  12. package/build/main/tracking/index.js +105 -68
  13. package/build/main/tracking/index.js.map +1 -1
  14. package/build/main/tracking/types.d.ts +508 -15
  15. package/build/main/utils/convertKeysToSnakeCase.d.ts +1 -0
  16. package/build/main/utils/convertKeysToSnakeCase.js +23 -0
  17. package/build/main/utils/convertKeysToSnakeCase.js.map +1 -0
  18. package/build/main/utils/convertValuesToString.d.ts +5 -0
  19. package/build/main/utils/convertValuesToString.js +11 -0
  20. package/build/main/utils/convertValuesToString.js.map +1 -0
  21. package/build/main/utils/index.d.ts +3 -0
  22. package/build/main/utils/index.js +20 -0
  23. package/build/main/utils/index.js.map +1 -0
  24. package/build/main/utils/isTest.d.ts +1 -0
  25. package/build/main/utils/isTest.js +18 -0
  26. package/build/main/utils/isTest.js.map +1 -0
  27. package/build/module/log/index.spec.d.ts +0 -0
  28. package/build/module/log/index.spec.js +2 -0
  29. package/build/module/log/index.spec.js.map +1 -0
  30. package/build/module/log/type.d.ts +3 -1
  31. package/build/module/tracking/_intercom.d.ts +109 -0
  32. package/build/module/tracking/_intercom.js +136 -0
  33. package/build/module/tracking/_intercom.js.map +1 -0
  34. package/build/module/tracking/eventTypes.d.ts +16 -0
  35. package/build/module/tracking/eventTypes.js +2 -0
  36. package/build/module/tracking/eventTypes.js.map +1 -0
  37. package/build/module/tracking/index.d.ts +96 -33
  38. package/build/module/tracking/index.js +96 -57
  39. package/build/module/tracking/index.js.map +1 -1
  40. package/build/module/tracking/types.d.ts +508 -15
  41. package/build/module/utils/convertKeysToSnakeCase.d.ts +1 -0
  42. package/build/module/utils/convertKeysToSnakeCase.js +19 -0
  43. package/build/module/utils/convertKeysToSnakeCase.js.map +1 -0
  44. package/build/module/utils/convertValuesToString.d.ts +5 -0
  45. package/build/module/utils/convertValuesToString.js +8 -0
  46. package/build/module/utils/convertValuesToString.js.map +1 -0
  47. package/build/module/utils/index.d.ts +3 -0
  48. package/build/module/utils/index.js +4 -0
  49. package/build/module/utils/index.js.map +1 -0
  50. package/build/module/utils/isTest.d.ts +1 -0
  51. package/build/module/utils/isTest.js +14 -0
  52. package/build/module/utils/isTest.js.map +1 -0
  53. package/package.json +22 -24
  54. package/src/log/type.ts +5 -1
  55. package/src/tracking/index.ts +230 -97
  56. package/src/tracking/types.ts +622 -97
  57. package/src/utils/convertKeysToSnakeCase.ts +21 -0
  58. package/src/utils/convertValuesToString.ts +10 -0
  59. package/src/utils/index.ts +3 -0
  60. package/src/utils/isTest.ts +16 -0
  61. package/src/tracking/intercom.ts +0 -258
package/src/log/type.ts CHANGED
@@ -25,11 +25,15 @@ export type LogFn = (
25
25
  message: any,
26
26
  additionalContext?: Record<string, any>
27
27
  ) => void;
28
- export type Log = {
28
+
29
+ export type LogFns = {
29
30
  error: LogFn;
30
31
  info: LogFn;
31
32
  debug: LogFn;
32
33
  warn: LogFn;
34
+ };
35
+
36
+ export type Log = LogFns & {
33
37
  child: (data: ChildLogOptions) => Log;
34
38
  addContext: (data: Record<string, any>) => void;
35
39
  };
@@ -1,55 +1,123 @@
1
+ declare module 'intercom-client' {
2
+ export namespace Intercom {
3
+ export interface CreateContactRequestWithExternalId {
4
+ /** A unique identifier for the contact which is given to Intercom */
5
+ external_id: string;
6
+ /** The contacts phone */
7
+ phone?: string;
8
+ /** The contacts name */
9
+ name?: string;
10
+ /** An image URL containing the avatar of a contact */
11
+ avatar?: string;
12
+ /** The time specified for when a contact signed up */
13
+ signed_up_at?: number;
14
+ /** The time when the contact was last seen (either where the Intercom Messenger was installed or when specified manually) */
15
+ last_seen_at?: number;
16
+ /** The id of an admin that has been assigned account ownership of the contact */
17
+ owner_id?: number;
18
+ /** Whether the contact is unsubscribed from emails */
19
+ unsubscribed_from_emails?: boolean;
20
+ /** The custom attributes which are set for the contact */
21
+ custom_attributes?: ContactCustomAttributes;
22
+ }
23
+ interface UpdateContactRequest {
24
+ /** The role of the contact. */
25
+ role?: 'user' | 'lead';
26
+ /** A unique identifier for the contact which is given to Intercom */
27
+ external_id?: string;
28
+ /** The contacts email */
29
+ email?: string;
30
+ /** The contacts phone */
31
+ phone?: string;
32
+ /** The contacts name */
33
+ name?: string;
34
+ /** An image URL containing the avatar of a contact */
35
+ avatar?: string;
36
+ /** The time specified for when a contact signed up */
37
+ signed_up_at?: number;
38
+ /** The time when the contact was last seen (either where the Intercom Messenger was installed or when specified manually) */
39
+ last_seen_at?: number;
40
+ /** The id of an admin that has been assigned account ownership of the contact */
41
+ owner_id?: number;
42
+ /** Whether the contact is unsubscribed from emails */
43
+ unsubscribed_from_emails?: boolean;
44
+ /** The custom attributes which are set for the contact */
45
+ custom_attributes?: ContactCustomAttributes;
46
+ }
47
+ interface ContactCustomAttributes {
48
+ Onboarding_Support?: string; // The customer's preferred onboarding level
49
+ userType?: string; // User Type (user or owner)
50
+ // ARCHIVED
51
+ // article_id?: string; // undefined
52
+ // workflowInstanceId?: number; // undefined
53
+ // Active_Listings?: string; // The number of active listings a user has
54
+ // Accounting_Software?: string; // The client's accounting software
55
+ }
56
+ interface CreateOrUpdateCompanyRequest {
57
+ /** The name of the Company */
58
+ name?: string;
59
+ /** The company id you have defined for the company. Can't be updated */
60
+ company_id?: string;
61
+ /** The name of the plan you have associated with the company. */
62
+ plan?: string;
63
+ /** The number of employees in this company. */
64
+ size?: number;
65
+ /** The URL for this company's website. Please note that the value specified here is not validated. Accepts any string. */
66
+ website?: string;
67
+ /** The industry that this company operates in. */
68
+ industry?: string;
69
+ /** A hash of key/value pairs containing any other data about the company you want Intercom to store. */
70
+ custom_attributes?: CompanyCustomAttributes;
71
+ /** The time the company was created by you. */
72
+ remote_created_at?: number;
73
+ /** How much revenue the company generates for your business. Note that this will truncate floats. i.e. it only allow for whole integers, 155.98 will be truncated to 155. Note that this has an upper limit of 2\*\*31-1 or 2147483647.. */
74
+ monthly_spend?: number;
75
+ }
76
+ interface CompanyCustomAttributes {
77
+ teamId?: string; // undefined
78
+ team?: string; // undefined
79
+ type?: string; // undefined
80
+ status?: string; // undefined
81
+ pms?: string; // undefined
82
+ accountingSoftware?: string; // Accounting software used by the team.
83
+ creation_source?: string; // undefined
84
+ activeListings?: number; // Active Listings
85
+ paymentMethodType?: string; // Payment Method
86
+ partnerName?: string; // Partner Name
87
+ billingPortalUrl?: string; // Billing Portal URL
88
+ billingStatus?: string; // undefined
89
+ billingPartner?: string; // Name of billing partner
90
+ // ARCHIVED
91
+ // firstName?: string; // undefined
92
+ // lastName?: string; // undefined
93
+ // email?: string; // undefined
94
+ // apps?: string; // All active connections besides pms/accounting
95
+ // accountingPartner?: string; // Name of accounting partner
96
+ // 'SaaS User'?: boolean; // undefined
97
+ }
98
+ }
99
+ }
100
+
1
101
  import { Analytics } from '@june-so/analytics-node';
102
+ import { Intercom, IntercomClient } from 'intercom-client';
103
+ import { fetcher } from 'intercom-client/core/fetcher/Fetcher';
2
104
  import { PostHog } from 'posthog-node';
3
105
  import { type LogBindings, type WorkerLog, useLog } from '../log';
4
- import { useIntercom } from './intercom';
5
- import type { TrackingEvent } from './types';
106
+ import { convertKeysToSnakeCase, isTest } from '../utils';
107
+ import { convertValuesToString } from '../utils/convertValuesToString';
108
+ import type {
109
+ GroupProps,
110
+ IdentifyProps,
111
+ TrackProps,
112
+ TrackingEvent,
113
+ } from './types';
6
114
 
7
115
  export * from './types';
116
+ export * from 'intercom-client';
117
+ export * from '../utils';
8
118
 
9
- const E2E_TEST_USERS = {
10
- 'e2e-invite-member@finalytic.io': '113d73d8-ee21-46b7-a12a-a74f632401ca',
11
- 'e2e-invite-owner@finalytic.io': '917b7c4e-cb03-491c-9e94-d33a5bdeca05',
12
- 'e2e-sign-up@finalytic.io': 'ec8e5572-74d0-4ae6-af73-bca8db9a27e9',
13
- 'lars+checkly@finalytic.co': '9e7dfc88-503c-4222-9905-9116169d2203',
14
- };
15
-
16
- const E2E_TEST_TEAMS = {
17
- 'VRP Automated Testing': 'c4fd21b4-8447-43a2-bdcb-f06a85a935ad',
18
- test_company_xxxx: '8f21060e-afe6-410a-b2f4-00a3caa20786',
19
- };
20
-
21
- export const isTest = (userId: string, groupId?: string, userEmail?: string) =>
22
- Object.values(E2E_TEST_USERS).includes(userId) ||
23
- (groupId && Object.values(E2E_TEST_TEAMS).includes(groupId)) ||
24
- (userEmail && Object.keys(E2E_TEST_USERS).includes(userEmail));
25
-
26
- const camelToSnakeCase = (str: string) =>
27
- str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
28
-
29
- // todo: move to utils
30
- export const convertKeysToSnakeCase = (obj?: Record<string, any>) => {
31
- const result: Record<string, any> = {};
32
- const hasOwnProperty = Object.prototype.hasOwnProperty;
33
-
34
- for (const key in obj) {
35
- if (hasOwnProperty.call(obj, key)) {
36
- const snakeKey = camelToSnakeCase(key);
37
- if (
38
- typeof obj[key] === 'object' &&
39
- !Array.isArray(obj[key]) &&
40
- obj[key] !== null
41
- ) {
42
- result[snakeKey] = convertKeysToSnakeCase(obj[key]);
43
- } else {
44
- result[snakeKey] = obj[key];
45
- }
46
- }
47
- }
48
-
49
- return result;
50
- };
51
-
52
- export type TrackingProps = {
119
+ export type Tracking = ReturnType<typeof useTracking>;
120
+ export type UseTracking = {
53
121
  name: string;
54
122
  dataset: string;
55
123
  env: {
@@ -59,51 +127,31 @@ export type TrackingProps = {
59
127
  } & LogBindings;
60
128
  debugging?: boolean;
61
129
  };
62
- export type Tracking = ReturnType<typeof useTracking>;
63
-
64
- export type TrackProps = OptionalUser<{
65
- groupId: string;
66
- event: TrackingEvent;
67
- properties: Record<string, string | number | boolean | null | undefined>;
68
- timestamp?: Date;
69
- }>;
70
- export type IdentifyProps = {
71
- userId: string;
72
- traits: Record<string, string | number | boolean | null | undefined>;
73
- disableGeoip?: boolean;
74
- timestamp?: Date;
75
- };
76
- export type GroupProps = OptionalUser<{
77
- groupId: string;
78
- traits?: Record<string, string | number | boolean | null | undefined>;
79
- timestamp?: Date;
80
- }>;
81
- type OptionalUser<T> = T &
82
- (
83
- | { userId: string; anonymousId?: string }
84
- | {
85
- userId?: string;
86
- anonymousId: string;
87
- }
88
- );
89
130
 
90
131
  export const useTracking = (
91
- { dataset, env, name }: TrackingProps,
132
+ { dataset, env, name }: UseTracking,
92
133
  isDev?: boolean
93
134
  ): {
94
- track: (props: TrackProps) => Promise<void>;
135
+ track: <T extends TrackingEvent>(props: TrackProps<T>) => Promise<void>;
95
136
  identify: (props: IdentifyProps) => Promise<void>;
96
137
  group: (props: GroupProps) => Promise<void>;
97
138
  shutdown: () => Promise<any>;
98
139
  log: WorkerLog;
99
140
  june?: Analytics;
100
- _intercom?: ReturnType<typeof useIntercom>;
141
+ intercom?: IntercomClient;
101
142
  posthog?: PostHog;
102
143
  } => {
103
144
  const log = useLog({ name, dataset, env });
104
145
 
105
- const _intercom = env.INTERCOM_TOKEN
106
- ? useIntercom(env.INTERCOM_TOKEN, log)
146
+ const intercom = env.INTERCOM_TOKEN
147
+ ? new IntercomClient({
148
+ token: env.INTERCOM_TOKEN,
149
+ fetcher: (args) =>
150
+ fetcher({
151
+ ...args,
152
+ headers: { ...args.headers, accept: 'application/json' },
153
+ }),
154
+ })
107
155
  : undefined;
108
156
 
109
157
  const june = env.JUNESO_TOKEN ? new Analytics(env.JUNESO_TOKEN) : undefined;
@@ -116,14 +164,14 @@ export const useTracking = (
116
164
 
117
165
  return {
118
166
  // General
119
- track: async ({
167
+ track: async <T extends TrackingEvent>({
120
168
  userId,
121
169
  anonymousId,
122
170
  groupId,
123
171
  event,
124
172
  properties,
125
173
  timestamp,
126
- }: TrackProps) => {
174
+ }: TrackProps<T>) => {
127
175
  if (isDev || isTest(userId || anonymousId || '', groupId)) return;
128
176
 
129
177
  if (anonymousId) {
@@ -136,6 +184,7 @@ export const useTracking = (
136
184
  timestamp,
137
185
  properties: convertKeysToSnakeCase(properties),
138
186
  });
187
+
139
188
  june?.track(
140
189
  {
141
190
  anonymousId,
@@ -160,6 +209,7 @@ export const useTracking = (
160
209
  timestamp,
161
210
  properties: convertKeysToSnakeCase(properties),
162
211
  });
212
+
163
213
  june?.track(
164
214
  {
165
215
  userId,
@@ -173,15 +223,22 @@ export const useTracking = (
173
223
  }
174
224
  );
175
225
 
176
- try {
177
- await _intercom?.trackEvent({
178
- user_id: userId,
226
+ const intercomUserId = (
227
+ await intercom?.contacts.search({
228
+ query: {
229
+ field: 'external_id',
230
+ operator: Intercom.SingleFilterSearchRequestOperator.Equals,
231
+ value: userId,
232
+ },
233
+ })
234
+ )?.data.at(0)?.id;
235
+ if (intercomUserId)
236
+ await intercom?.events.create({
237
+ user_id: intercomUserId,
179
238
  event_name: event,
180
- metadata: properties,
239
+ metadata: convertValuesToString(properties),
240
+ created_at: timestamp ? timestamp.getTime() : Date.now(),
181
241
  });
182
- } catch (error: any) {
183
- console.error(error);
184
- }
185
242
  }
186
243
  },
187
244
  identify: async ({
@@ -197,16 +254,45 @@ export const useTracking = (
197
254
  properties: convertKeysToSnakeCase(traits),
198
255
  disableGeoip,
199
256
  });
257
+
200
258
  june?.identify(
201
259
  {
202
260
  userId,
203
261
  traits,
204
262
  timestamp,
205
263
  },
206
- (err) => {
207
- if (err) console.error(err);
208
- }
264
+ (err) => (err ? console.error(err) : {})
209
265
  );
266
+
267
+ // upsert intercom user
268
+ const data = {
269
+ external_id: userId,
270
+ email: traits?.email,
271
+ name:
272
+ traits?.name ||
273
+ (traits?.firstName
274
+ ? `${traits.firstName} ${traits.lastName}`
275
+ : traits?.lastName),
276
+ avatar: traits?.avatar,
277
+ signed_up_at: traits?.createdAt
278
+ ? new Date(traits.createdAt).getTime() / 1000
279
+ : undefined,
280
+ last_seen_at: new Date().getTime() / 1000,
281
+ custom_attributes: { userType: traits?.role },
282
+ };
283
+
284
+ const intercomUser = (
285
+ await intercom?.contacts.search({
286
+ query: {
287
+ field: 'external_id',
288
+ operator: Intercom.SingleFilterSearchRequestOperator.Equals,
289
+ value: userId,
290
+ },
291
+ })
292
+ )?.data.at(0);
293
+ if (intercomUser?.id)
294
+ await intercom?.contacts.update(intercomUser?.id, data);
295
+ else await intercom?.contacts.create(data);
210
296
  },
211
297
  group: async ({
212
298
  traits,
@@ -217,6 +303,36 @@ export const useTracking = (
217
303
  }: GroupProps) => {
218
304
  if (isDev || isTest(userId || anonymousId || '', groupId)) return;
219
305
 
306
+ const cid = traits?.billingCustomerId || traits?.hyperlineCustomerId;
307
+ const intercomCompany = await intercom?.companies.create({
308
+ name: traits?.name,
309
+ company_id: groupId,
310
+ plan: traits?.plan,
311
+ remote_created_at: traits?.createdAt
312
+ ? new Date(traits.createdAt).getTime() / 1000
313
+ : undefined,
314
+ monthly_spend: traits?.mrr,
315
+ custom_attributes: {
316
+ teamId: groupId,
317
+ team: traits?.name,
318
+ type: traits?.type,
319
+ status: traits?.status,
320
+ pms: traits?.pms,
321
+ accountingSoftware: traits?.accountingSoftware,
322
+ activeListings: traits?.activeListings,
323
+ paymentMethodType: traits?.paymentMethodType,
324
+ partnerName: traits?.partner || traits?.accountingPartner,
325
+ billingPortalUrl: cid
326
+ ? `https://billing.vrplatform.app/portal/${cid}`
327
+ : undefined,
328
+ billingStatus: traits?.billingSubscriptionStatus,
329
+ billingPartner:
330
+ traits?.billingPartner ||
331
+ traits?.partner ||
332
+ traits?.accountingPartner,
333
+ },
334
+ });
335
+
220
336
  if (anonymousId) {
221
337
  posthog?.groupIdentify({
222
338
  groupKey: groupId,
@@ -224,9 +340,10 @@ export const useTracking = (
224
340
  properties: convertKeysToSnakeCase(traits),
225
341
  distinctId: anonymousId,
226
342
  });
227
- june?.group({ anonymousId, groupId, traits, timestamp }, (err) => {
228
- if (err) console.error(err);
229
- });
343
+
344
+ june?.group({ anonymousId, groupId, traits, timestamp }, (err) =>
345
+ err ? console.error(err) : {}
346
+ );
230
347
  } else if (userId) {
231
348
  posthog?.groupIdentify({
232
349
  groupKey: groupId,
@@ -234,15 +351,31 @@ export const useTracking = (
234
351
  properties: convertKeysToSnakeCase(traits),
235
352
  distinctId: userId,
236
353
  });
237
- june?.group({ userId, groupId, traits, timestamp }, (err) => {
238
- if (err) console.error(err);
239
- });
354
+
355
+ june?.group({ userId, groupId, traits, timestamp }, (err) =>
356
+ err ? console.error(err) : {}
357
+ );
358
+
359
+ // attach intercom user to intercom company
360
+ const intercomUser = (
361
+ await intercom?.contacts.search({
362
+ query: {
363
+ field: 'external_id',
364
+ operator: Intercom.SingleFilterSearchRequestOperator.Equals,
365
+ value: userId,
366
+ },
367
+ })
368
+ )?.data.at(0);
369
+ if (intercomUser?.id && intercomCompany?.id)
370
+ intercom?.companies.attachContact(intercomUser?.id, {
371
+ id: intercomCompany?.id,
372
+ });
240
373
  }
241
374
  },
242
375
  shutdown: async () =>
243
376
  Promise.all([posthog?.shutdown(), june?.closeAndFlush(), log?.flush()]),
244
377
  // export dependencies
245
- _intercom,
378
+ intercom,
246
379
  posthog,
247
380
  june,
248
381
  log,