grm-shared-library 1.1.245 → 1.1.247

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.
@@ -60,7 +60,16 @@ export interface IncidentAnalyticsTotals {
60
60
  emergencies: number;
61
61
  /** Answered: an action by anybody but the reporter (a status change, an assignment, an edit, a dispatch). */
62
62
  responded: number;
63
- /** Reached Resolved or Closed, at any time since. */
63
+ /** Sent to the control room (not withheld for want of a subscription, nor kept from it by the reporter). */
64
+ reachedControlRoom: number;
65
+ /**
66
+ * Of those sent to the control room: ended before anybody answered - the reporter
67
+ * cancelling their own SOS, or the system closing it. Not a missed answer.
68
+ */
69
+ endedWithoutResponse: number;
70
+ /** Of those sent to the control room: still open, and nobody has answered yet. */
71
+ awaitingResponse: number;
72
+ /** Stand Resolved or Closed now. */
64
73
  resolved: number;
65
74
  closed: number;
66
75
  /** Ended Unresolved. */
@@ -61,7 +61,7 @@ export interface AccountAnalyticsKindRow {
61
61
  /** Of the accounts created in the period, how far each got - as they stand now. */
62
62
  export interface AccountAnalyticsFunnel {
63
63
  created: number;
64
- /** Confirmed their phone or e-mail with a one-time code. */
64
+ /** Proved their phone or e-mail: a sign-in code, Google or Apple (the older app: its one-time code). */
65
65
  verified: number;
66
66
  acceptedTerms: number;
67
67
  /** Signed in at least once. */
@@ -84,7 +84,7 @@ export interface AccountAnalyticsAccounts {
84
84
  /** Active accounts that were created in an earlier period. */
85
85
  returning: number;
86
86
  funnel: AccountAnalyticsFunnel;
87
- /** Now: never confirmed their phone or e-mail with a one-time code. */
87
+ /** Now: never proved their phone or e-mail (see `AccountAnalyticsFunnel.verified`). */
88
88
  pendingNow: number;
89
89
  /** Now: never signed in. */
90
90
  neverSignedIn: number;
@@ -104,12 +104,16 @@ export interface AccountAnalyticsAccounts {
104
104
  recency: AccountAnalyticsCount<AccountRecencyBand>[];
105
105
  }
106
106
  /**
107
- * How somebody signed in:
108
- * - PASSWORD e-mail and password (the admin and the web);
109
- * - OTP phone number and a one-time code (the app);
110
- * - REVIEWER the app-store reviewers' account.
107
+ * How somebody signed in - or, for a sign-up, made their account:
108
+ * - PASSWORD e-mail and password (the admin and the web);
109
+ * - OTP the older app (before 1.0.18): phone number and one code sent by SMS and e-mail together;
110
+ * - PHONE_CODE a code to the phone by SMS or a call, or a WhatsApp message from it - proves the number;
111
+ * - EMAIL_CODE a code to the e-mail - proves the e-mail only;
112
+ * - GOOGLE Google vouches for the e-mail;
113
+ * - APPLE Apple vouches for the e-mail (possibly a private relay address);
114
+ * - REVIEWER the app-store reviewers' account.
111
115
  */
112
- export type AccountSignInMethod = 'PASSWORD' | 'OTP' | 'REVIEWER';
116
+ export type AccountSignInMethod = 'PASSWORD' | 'OTP' | 'PHONE_CODE' | 'EMAIL_CODE' | 'GOOGLE' | 'APPLE' | 'REVIEWER';
113
117
  export interface AccountAnalyticsMethodRow {
114
118
  method: string;
115
119
  succeeded: number;
@@ -127,8 +131,13 @@ export interface AccountAnalyticsSignIns {
127
131
  firstSignIns: number;
128
132
  /** Failed for an e-mail or a code that matches no account (counted, never stored). */
129
133
  unknownAccount: number;
134
+ /**
135
+ * By AccountSignInMethod. A wrong phone or e-mail code is not a failed sign-in - it is
136
+ * counted with the codes (`AccountAnalyticsVerificationCodes`) - so PHONE_CODE and
137
+ * EMAIL_CODE fail only for a switched-off account.
138
+ */
130
139
  byMethod: AccountAnalyticsMethodRow[];
131
- /** INVALID_CREDENTIALS, UNVERIFIED, UNKNOWN_ACCOUNT, INVALID_TOKEN, ERROR... */
140
+ /** INVALID_CREDENTIALS, UNVERIFIED, UNKNOWN_ACCOUNT, INVALID_TOKEN, INVALID_ID_TOKEN, DISABLED, EMAIL_IN_USE, EMAIL_REQUIRED, ERROR... */
132
141
  failureReasons: AccountAnalyticsCount[];
133
142
  averageDailyUsers: number;
134
143
  peakDailyUsers: number;
@@ -141,6 +150,11 @@ export interface AccountAnalyticsOtpActionRow {
141
150
  sent: number;
142
151
  verified: number;
143
152
  }
153
+ /**
154
+ * The older app's one-time codes (before 1.0.18): one code to the e-mail and the phone
155
+ * together, for signing up, in and confirming. The current app's codes are in
156
+ * `AccountAnalyticsSignInOptions.codes`.
157
+ */
144
158
  export interface AccountAnalyticsOtp {
145
159
  /** First sends and resends. */
146
160
  sent: number;
@@ -237,6 +251,166 @@ export interface AccountAnalyticsPrevious {
237
251
  deletionRequests: number;
238
252
  guardianLinks: number;
239
253
  }
254
+ /** How a code travels. SMS and VOICE carry it to the phone; WHATSAPP is the person messaging us FROM the phone; EMAIL. */
255
+ export type AccountVerificationChannel = 'SMS' | 'VOICE' | 'WHATSAPP' | 'EMAIL';
256
+ /** What a code is for: signing in (or up), or proving the phone number of an account already signed in. */
257
+ export type AccountVerificationPurpose = 'SIGN_IN' | 'VERIFY_PHONE';
258
+ /** One channel's codes for one purpose, in the period. */
259
+ export interface AccountAnalyticsCodeChannelRow {
260
+ purpose: AccountVerificationPurpose | string;
261
+ channel: AccountVerificationChannel | string;
262
+ /** Codes sent - for WhatsApp, the message the person was asked to send us. */
263
+ sent: number;
264
+ /** Refused before anything was sent: asked for too often, a number already on another account, a channel that cannot reach it. */
265
+ refused: number;
266
+ /** The SMS or call provider could not take it. The app offers the other channels. */
267
+ notDelivered: number;
268
+ confirmed: number;
269
+ wrongCode: number;
270
+ expired: number;
271
+ /** WhatsApp: the message came from a number other than the one being proven. */
272
+ wrongNumber: number;
273
+ /** confirmed / sent; null with none sent. */
274
+ confirmationRate: number | null;
275
+ }
276
+ /** Codes by the country of the number (or of the app, for an e-mail code) and channel, in the period. */
277
+ export interface AccountAnalyticsCodeCountryRow {
278
+ /** KE, UG, TZ - or UNKNOWN. */
279
+ country: string;
280
+ channel: AccountVerificationChannel | string;
281
+ sent: number;
282
+ notDelivered: number;
283
+ confirmed: number;
284
+ }
285
+ /** The current app's codes (1.0.18 on): to the phone only, or to the e-mail only - never both. */
286
+ export interface AccountAnalyticsVerificationCodes {
287
+ sent: number;
288
+ refused: number;
289
+ notDelivered: number;
290
+ confirmed: number;
291
+ wrongCode: number;
292
+ expired: number;
293
+ wrongNumber: number;
294
+ confirmationRate: number | null;
295
+ /** Biggest first. */
296
+ byChannel: AccountAnalyticsCodeChannelRow[];
297
+ byCountry: AccountAnalyticsCodeCountryRow[];
298
+ /** Why codes were refused: RATE_LIMITED, PHONE_IN_USE, CHANNEL_UNAVAILABLE. */
299
+ refusals: AccountAnalyticsCount[];
300
+ }
301
+ /** App users making their own account, in the period. */
302
+ export interface AccountAnalyticsSignUps {
303
+ /** A phone or e-mail code confirmed that matched no account: the app showed the short sign-up form. */
304
+ formsStarted: number;
305
+ /** Accounts made from that form. Google and Apple make the account straight away, with no form. */
306
+ formsCompleted: number;
307
+ /** formsCompleted / formsStarted; null with none started. */
308
+ completionRate: number | null;
309
+ /** Sign-ups refused - the e-mail or number already has an account, the form came back too late. */
310
+ refused: number;
311
+ /** EMAIL_IN_USE, PHONE_IN_USE, EMAIL_REQUIRED, REGISTRATION_EXPIRED. */
312
+ failureReasons: AccountAnalyticsCount[];
313
+ /**
314
+ * App (Mobile-User) accounts created in the period, by how: PHONE_CODE, EMAIL_CODE,
315
+ * GOOGLE, APPLE, OTP (the older app) - and OTHER: made for the person (a corporate
316
+ * member, a partner's caller) or before sign-ups were recorded.
317
+ */
318
+ byMethod: AccountAnalyticsCount[];
319
+ }
320
+ export interface AccountAnalyticsPhoneCountryRow {
321
+ country: string;
322
+ appUsers: number;
323
+ verified: number;
324
+ unverified: number;
325
+ noNumber: number;
326
+ }
327
+ /**
328
+ * App users' phone numbers - the number a responder calls - as they stand now. A number
329
+ * is verified only when a code to it, or a WhatsApp message from it, proved it; a number
330
+ * saved with "Verify later" is shown to the dispatcher as not verified.
331
+ */
332
+ export interface AccountAnalyticsPhoneNumbers {
333
+ appUsers: number;
334
+ withNumber: number;
335
+ verified: number;
336
+ unverified: number;
337
+ noNumber: number;
338
+ /** verified / appUsers; null with none. */
339
+ verifiedRate: number | null;
340
+ /**
341
+ * Every app user once: how the number was proven - SMS, VOICE, WHATSAPP, LEGACY_OTP (the
342
+ * older app's code, counted where its SMS always arrived) - or UNVERIFIED, or NONE.
343
+ */
344
+ byState: AccountAnalyticsCount[];
345
+ byCountry: AccountAnalyticsPhoneCountryRow[];
346
+ /** In the period: numbers proven by somebody already signed in (after Google, Apple or an e-mail code), by channel. */
347
+ verifiedInPeriod: AccountAnalyticsCount[];
348
+ /** In the period: "Verify later" - a number saved without proving it. */
349
+ deferred: number;
350
+ /** Now: app users whose e-mail is proven (an e-mail code, Google or Apple). */
351
+ emailVerified: number;
352
+ }
353
+ /** Google or Apple. */
354
+ export interface AccountAnalyticsProviderRow {
355
+ provider: 'GOOGLE' | 'APPLE' | string;
356
+ /** Accounts linked to it now. */
357
+ linkedAccounts: number;
358
+ /** In the period. */
359
+ signIns: number;
360
+ /** Accounts it created, in the period. */
361
+ newAccounts: number;
362
+ /** Linked to an account that already had the same proven e-mail, in the period. */
363
+ linkedExisting: number;
364
+ /** The provider's token did not check out, or the account could not be used (switched off, e-mail taken or missing). */
365
+ failed: number;
366
+ }
367
+ /** Sign-ins in one country by one way in, in the period. */
368
+ export interface AccountAnalyticsMethodCountryRow {
369
+ /** The account's country (KE, UG, TZ), or UNKNOWN. */
370
+ country: string;
371
+ method: AccountSignInMethod | string;
372
+ signIns: number;
373
+ }
374
+ export interface AccountAnalyticsSignInOptionsPoint {
375
+ /** As `AccountAnalyticsPoint.bucket`. */
376
+ bucket: string;
377
+ /** Successful sign-ins in the bucket by AccountSignInMethod; methods with none are left out. */
378
+ byMethod: Record<string, number>;
379
+ /** The current app's codes. */
380
+ codesSent: number;
381
+ codesConfirmed: number;
382
+ }
383
+ /** The same figures for the previous period of the same length. */
384
+ export interface AccountAnalyticsSignInOptionsPrevious {
385
+ /** Sign-ins by PHONE_CODE, EMAIL_CODE, GOOGLE or APPLE. */
386
+ currentAppSignIns: number;
387
+ /** Sign-ins by the older app's code (OTP). */
388
+ olderAppSignIns: number;
389
+ codesSent: number;
390
+ codesConfirmed: number;
391
+ phonesVerified: number;
392
+ deferred: number;
393
+ formsCompleted: number;
394
+ }
395
+ /**
396
+ * The ways into the app since 1.0.18 - a code to the phone (SMS, a call, WhatsApp), a code
397
+ * to the e-mail, Google, Apple - next to the older app's SMS-and-e-mail code, and whether
398
+ * the phone numbers responders call are proven.
399
+ */
400
+ export interface AccountAnalyticsSignInOptions {
401
+ /** People who signed in during the period, each by the way they signed in LAST. */
402
+ lastMethodUsed: AccountAnalyticsCount[];
403
+ byCountry: AccountAnalyticsMethodCountryRow[];
404
+ /** One point per bucket, as `AccountAnalytics.timeline`. */
405
+ timeline: AccountAnalyticsSignInOptionsPoint[];
406
+ codes: AccountAnalyticsVerificationCodes;
407
+ signUps: AccountAnalyticsSignUps;
408
+ phones: AccountAnalyticsPhoneNumbers;
409
+ /** GOOGLE and APPLE, always both. */
410
+ providers: AccountAnalyticsProviderRow[];
411
+ /** Absent for "forever". */
412
+ previous: AccountAnalyticsSignInOptionsPrevious | null;
413
+ }
240
414
  /** A Super-Admin's view of every organization: one row each, biggest first. */
241
415
  export interface AccountAnalyticsOrganizationRow {
242
416
  organizationId: number;
@@ -266,6 +440,8 @@ export interface AccountAnalytics {
266
440
  passwords: AccountAnalyticsPasswords;
267
441
  guardians: AccountAnalyticsGuardians;
268
442
  deletions: AccountAnalyticsDeletions;
443
+ /** The ways into the app, codes by channel and proven phone numbers. Absent only from a clarion-authentication older than this field. */
444
+ signInOptions?: AccountAnalyticsSignInOptions;
269
445
  /** A Super-Admin looking at every organization only. */
270
446
  byOrganization?: AccountAnalyticsOrganizationRow[];
271
447
  }
package/package.json CHANGED
@@ -1,72 +1,72 @@
1
- {
2
- "name": "grm-shared-library",
3
- "version": "1.1.245",
4
- "main": "dist/cjs/index.js",
5
- "module": "dist/esm/index.js",
6
- "types": "dist/types/index.d.ts",
7
- "exports": {
8
- ".": {
9
- "types": "./dist/types/index.d.ts",
10
- "import": "./dist/esm/index.js",
11
- "require": "./dist/cjs/index.js"
12
- },
13
- "./dist/modules/*": {
14
- "types": "./dist/types/modules/*.d.ts",
15
- "import": "./dist/esm/modules/*.js",
16
- "require": "./dist/cjs/modules/*.js"
17
- }
18
- },
19
- "files": [
20
- "dist/**/*"
21
- ],
22
- "scripts": {
23
- "clean": "rimraf dist dist-test",
24
- "build:cjs": "tsc -p tsconfig.cjs.json",
25
- "build:esm": "tsc -p tsconfig.esm.json",
26
- "build:types": "tsc -p tsconfig.types.json",
27
- "build": "npm run clean && npm run build:types && npm run build:cjs && npm run build:esm",
28
- "prepublishOnly": "npm run build",
29
- "pretest": "rimraf dist-test",
30
- "test": "tsc -p tsconfig.spec.json && node --test \"dist-test/**/*.spec.js\""
31
- },
32
- "keywords": [],
33
- "author": "",
34
- "license": "ISC",
35
- "description": "",
36
- "publishConfig": {
37
- "access": "public"
38
- },
39
- "devDependencies": {
40
- "@types/express": "^5.0.3",
41
- "@types/geojson": "^7946.0.14",
42
- "@types/node": "^22.10.5",
43
- "rimraf": "^5.0.5",
44
- "typescript": "^5.7.3"
45
- },
46
- "dependencies": {
47
- "@nestjs/common": "^11.0.0",
48
- "@nestjs/core": "^11.0.0",
49
- "@nestjs/mapped-types": "^2.0.6",
50
- "@nestjs/microservices": "^11.0.0",
51
- "@nestjs/swagger": "^11.2.0",
52
- "@sentry/node": "^8.0.0",
53
- "class-transformer": "^0.5.0",
54
- "class-validator": "^0.14.0",
55
- "express": "^5.1.0",
56
- "geojson": "^0.5.0",
57
- "grm-shared-library": "^1.1.101",
58
- "libphonenumber-js": "^1.13.8",
59
- "rxjs": "^7.8.2",
60
- "swagger-ui-express": "^5.0.1",
61
- "@nestjs/config": "^4.0.0"
62
- },
63
- "peerDependencies": {
64
- "@nestjs/common": "^11.0.0",
65
- "@nestjs/core": "^11.0.0",
66
- "@nestjs/microservices": "^11.0.0",
67
- "@sentry/node": "^8.0.0",
68
- "class-transformer": "^0.5.0",
69
- "class-validator": "^0.14.0",
70
- "@nestjs/config": "^4.0.0"
71
- }
72
- }
1
+ {
2
+ "name": "grm-shared-library",
3
+ "version": "1.1.247",
4
+ "main": "dist/cjs/index.js",
5
+ "module": "dist/esm/index.js",
6
+ "types": "dist/types/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/types/index.d.ts",
10
+ "import": "./dist/esm/index.js",
11
+ "require": "./dist/cjs/index.js"
12
+ },
13
+ "./dist/modules/*": {
14
+ "types": "./dist/types/modules/*.d.ts",
15
+ "import": "./dist/esm/modules/*.js",
16
+ "require": "./dist/cjs/modules/*.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist/**/*"
21
+ ],
22
+ "scripts": {
23
+ "clean": "rimraf dist dist-test",
24
+ "build:cjs": "tsc -p tsconfig.cjs.json",
25
+ "build:esm": "tsc -p tsconfig.esm.json",
26
+ "build:types": "tsc -p tsconfig.types.json",
27
+ "build": "npm run clean && npm run build:types && npm run build:cjs && npm run build:esm",
28
+ "prepublishOnly": "npm run build",
29
+ "pretest": "rimraf dist-test",
30
+ "test": "tsc -p tsconfig.spec.json && node --test \"dist-test/**/*.spec.js\""
31
+ },
32
+ "keywords": [],
33
+ "author": "",
34
+ "license": "ISC",
35
+ "description": "",
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "devDependencies": {
40
+ "@types/express": "^5.0.3",
41
+ "@types/geojson": "^7946.0.14",
42
+ "@types/node": "^22.10.5",
43
+ "rimraf": "^5.0.5",
44
+ "typescript": "^5.7.3"
45
+ },
46
+ "dependencies": {
47
+ "@nestjs/common": "^11.0.0",
48
+ "@nestjs/core": "^11.0.0",
49
+ "@nestjs/mapped-types": "^2.0.6",
50
+ "@nestjs/microservices": "^11.0.0",
51
+ "@nestjs/swagger": "^11.2.0",
52
+ "@sentry/node": "^8.0.0",
53
+ "class-transformer": "^0.5.0",
54
+ "class-validator": "^0.14.0",
55
+ "express": "^5.1.0",
56
+ "geojson": "^0.5.0",
57
+ "grm-shared-library": "^1.1.101",
58
+ "libphonenumber-js": "^1.13.8",
59
+ "rxjs": "^7.8.2",
60
+ "swagger-ui-express": "^5.0.1",
61
+ "@nestjs/config": "^4.0.0"
62
+ },
63
+ "peerDependencies": {
64
+ "@nestjs/common": "^11.0.0",
65
+ "@nestjs/core": "^11.0.0",
66
+ "@nestjs/microservices": "^11.0.0",
67
+ "@sentry/node": "^8.0.0",
68
+ "class-transformer": "^0.5.0",
69
+ "class-validator": "^0.14.0",
70
+ "@nestjs/config": "^4.0.0"
71
+ }
72
+ }