ab-ecosystem-cpass 1.0.5 → 1.0.7

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 (42) hide show
  1. package/README.md +62 -1
  2. package/dist/client.d.ts +24 -0
  3. package/dist/client.js +32 -5
  4. package/dist/client.js.map +1 -1
  5. package/dist/index.d.ts +9 -0
  6. package/dist/index.js +20 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/internal/bytes.d.ts +38 -0
  9. package/dist/internal/bytes.js +84 -0
  10. package/dist/internal/bytes.js.map +1 -0
  11. package/dist/internal/device.d.ts +24 -0
  12. package/dist/internal/device.js +91 -0
  13. package/dist/internal/device.js.map +1 -0
  14. package/dist/internal/md5.d.ts +4 -0
  15. package/dist/internal/md5.js +109 -0
  16. package/dist/internal/md5.js.map +1 -0
  17. package/dist/internal/receiver-file.d.ts +106 -0
  18. package/dist/internal/receiver-file.js +128 -0
  19. package/dist/internal/receiver-file.js.map +1 -0
  20. package/dist/resources/audience/notifications.d.ts +171 -0
  21. package/dist/resources/audience/notifications.js +215 -0
  22. package/dist/resources/audience/notifications.js.map +1 -0
  23. package/dist/resources/campaign-audience.d.ts +132 -0
  24. package/dist/resources/campaign-audience.js +224 -0
  25. package/dist/resources/campaign-audience.js.map +1 -0
  26. package/dist/resources/campaign-messages.d.ts +150 -0
  27. package/dist/resources/campaign-messages.js +172 -0
  28. package/dist/resources/campaign-messages.js.map +1 -0
  29. package/dist/resources/campaigns.d.ts +239 -0
  30. package/dist/resources/campaigns.js +300 -0
  31. package/dist/resources/campaigns.js.map +1 -0
  32. package/dist/types/audience.d.ts +82 -0
  33. package/dist/types/campaign.d.ts +319 -0
  34. package/dist/types/campaign.js +3 -0
  35. package/dist/types/campaign.js.map +1 -0
  36. package/dist/types/index.d.ts +1 -0
  37. package/dist/types/index.js +2 -0
  38. package/dist/types/index.js.map +1 -1
  39. package/docs/audience/notifications.md +263 -0
  40. package/docs/campaign/campaign-messages.md +273 -0
  41. package/docs/campaign/campaigns.md +449 -0
  42. package/package.json +1 -1
package/README.md CHANGED
@@ -179,6 +179,7 @@ same pages ship inside the package, so they are readable offline at
179
179
  | `crm.exports` | [export history](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/audience/exports.md) | `list` `get` `create` `delete` |
180
180
  | `crm.webhooks` | [audience webhooks](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/audience/webhooks.md) | `list` `get` `create` `update` `delete` |
181
181
  | `crm.scheduledMessages` | [scheduled messages](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/audience/scheduled-messages.md) | `list` `get` `create` `createSeries` `createRepeating` `reschedule` `cancel` `cancelSeries` `sendNow` |
182
+ | `crm.notifications` | [push notifications](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/audience/notifications.md) | `subscribe` `unsubscribe` `devices` — the devices a user wants notified |
182
183
 
183
184
  ### Channels — `/v1/channels`
184
185
 
@@ -225,4 +226,64 @@ Requests are promises; pushes are what other agents and contacts did. The same
225
226
  event name can be both — an archive you performed and one another agent performed
226
227
  arrive identically, so one handler per kind of change is enough.
227
228
 
228
- The campaign module is not in this release.
229
+ ## Campaigns
230
+
231
+ One message sent to many people. The audience is a single `filters` field with
232
+ five modes, chosen by the **shape** of the array: `[]` for every contact, a list
233
+ of filter conditions for a segment, `[{numbers, hash}]` for a bulk list,
234
+ `[{type: 'channel'|'template', ...}]` for a group campaign split into buckets, or
235
+ `[{type: 'status'|'selection', ...}]` to re-target an earlier campaign.
236
+
237
+ `createBulk` is the one worth knowing about. Hand it numbers or a CSV and it does
238
+ the whole bulk path — the recipient list does not travel inside the campaign,
239
+ because a million numbers is not a request body:
240
+
241
+ ```ts
242
+ const { campaign, receivers, skipped, duplicates } = await crm.campaigns.createBulk({
243
+ name: 'Diwali offer',
244
+ audience: { csv: await file.text(), recipientColumn: 'phone' },
245
+ messages: [{ channel: { id: channelId }, message: template }],
246
+ });
247
+ ```
248
+
249
+ It parses the input, encrypts the list under a per-file secret, uploads it
250
+ through a presigned URL, and creates the campaign pointing at the file. Every
251
+ CSV column becomes a per-recipient template variable. Rows with no usable number
252
+ and repeated numbers are dropped and **counted** — `skipped` and `duplicates` —
253
+ rather than passed over silently.
254
+
255
+ `createGroup` is the bucketed form: it splits recipients across channels or
256
+ templates, uploading one file per bucket.
257
+
258
+ ```ts
259
+ await crm.campaigns.createGroup({
260
+ name: 'Diwali offer',
261
+ distributeBy: 'channel',
262
+ buckets: [
263
+ { channel: channelA, audience: { rows: north, recipientColumn: 'phone' } },
264
+ { channel: channelB, audience: { rows: south, recipientColumn: 'phone' } },
265
+ ],
266
+ messages: [{ channel: { id: channelA }, message: template }],
267
+ });
268
+ ```
269
+
270
+ Note that `campaign.receivers` is `0` on a campaign you just created: the
271
+ audience is resolved when the campaign sends, not when it is built. The
272
+ `receivers` on the result is the count written into the file, and it is the one
273
+ to show a user.
274
+
275
+ | Resource | Page | Actions |
276
+ | --- | --- | --- |
277
+ | `crm.campaigns` | [campaigns](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/campaign/campaigns.md) | `list` `get` `create` `createBulk` `createGroup` `uploadReceivers` `update` `schedule` `cancel` `delete` |
278
+ | `crm.campaignMessages` | [campaign messages](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/campaign/campaign-messages.md) | `stats` `list` `iterate` `replies` — reporting, counted off the per-recipient rows |
279
+
280
+ The audience helpers are exported too, so a UI can show "12,480 recipients, 3
281
+ duplicates dropped" before anything is uploaded:
282
+
283
+ ```ts
284
+ import { parseDelimited, resolveAudience } from 'ab-ecosystem-cpass';
285
+
286
+ const { headers } = parseDelimited(text);
287
+ const { receivers, skipped, duplicates } = resolveAudience({ csv: text, recipientColumn });
288
+ ```
289
+
package/dist/client.d.ts CHANGED
@@ -10,12 +10,15 @@ import { Exports } from './resources/audience/export-history';
10
10
  import { Webhooks } from './resources/audience/webhooks';
11
11
  import { ScheduledMessages } from './resources/audience/scheduled-messages';
12
12
  import { InboxMedia } from './resources/audience/inbox-media';
13
+ import { Notifications } from './resources/audience/notifications';
13
14
  import { Channels } from './resources/channels/channels';
14
15
  import { Templates } from './resources/channels/templates';
15
16
  import { ChannelWebhooks } from './resources/channels/webhooks';
16
17
  import { Billing } from './resources/channels/billing';
17
18
  import { Wallet } from './resources/channels/wallet';
18
19
  import { Activity } from './resources/channels/activity';
20
+ import { Campaigns } from './resources/campaigns';
21
+ import { CampaignMessages } from './resources/campaign-messages';
19
22
  /**
20
23
  * One client for the whole gateway. Construct it once and share it: it holds no
21
24
  * per-request state, and every resource below runs over the same HTTP client,
@@ -60,6 +63,12 @@ export declare class CrmClient {
60
63
  * pass to `inbox.messages.sendMedia()`.
61
64
  */
62
65
  readonly inboxMedia: InboxMedia;
66
+ /**
67
+ * Push notification subscriptions: the browsers and apps a user wants
68
+ * notified. `subscribe()` after the browser hands you a Firebase registration
69
+ * token, `unsubscribe()` on logout.
70
+ */
71
+ readonly notifications: Notifications;
63
72
  /** Channels, and the provider properties that hang off them. */
64
73
  readonly channels: Channels;
65
74
  /** WhatsApp message templates, per channel. */
@@ -72,5 +81,20 @@ export declare class CrmClient {
72
81
  readonly wallet: Wallet;
73
82
  /** Channel activity feed. */
74
83
  readonly activity: Activity;
84
+ /**
85
+ * Campaigns: a message sent to many people at once.
86
+ *
87
+ * `createBulk()` is the one to reach for when you hold the recipients
88
+ * yourself -- numbers or a CSV. It encrypts the list, uploads it, and creates
89
+ * the campaign pointing at the file, which is otherwise several steps a caller
90
+ * has to get exactly right.
91
+ */
92
+ readonly campaigns: Campaigns;
93
+ /**
94
+ * Campaign reporting: the funnel, the per-recipient rows behind it, and what
95
+ * people replied -- all counted off the message rows when asked for, so there
96
+ * is no roll-up to wait for and no export job to poll.
97
+ */
98
+ readonly campaignMessages: CampaignMessages;
75
99
  constructor(options: ClientOptions);
76
100
  }
package/dist/client.js CHANGED
@@ -14,6 +14,7 @@ const export_history_1 = require("./resources/audience/export-history");
14
14
  const webhooks_1 = require("./resources/audience/webhooks");
15
15
  const scheduled_messages_1 = require("./resources/audience/scheduled-messages");
16
16
  const inbox_media_1 = require("./resources/audience/inbox-media");
17
+ const notifications_1 = require("./resources/audience/notifications");
17
18
  // Channels
18
19
  const channels_1 = require("./resources/channels/channels");
19
20
  const templates_1 = require("./resources/channels/templates");
@@ -21,11 +22,9 @@ const webhooks_2 = require("./resources/channels/webhooks");
21
22
  const billing_1 = require("./resources/channels/billing");
22
23
  const wallet_1 = require("./resources/channels/wallet");
23
24
  const activity_1 = require("./resources/channels/activity");
24
- // The campaign module is not wired up yet; its resources are still excluded
25
- // from the build.
26
- // import { Campaigns } from './resources/campaigns';
27
- // import { CampaignReports } from './resources/campaign-reports';
28
- // import { CampaignReportExports } from './resources/campaign-report-exports';
25
+ // Campaign
26
+ const campaigns_1 = require("./resources/campaigns");
27
+ const campaign_messages_1 = require("./resources/campaign-messages");
29
28
  /**
30
29
  * One client for the whole gateway. Construct it once and share it: it holds no
31
30
  * per-request state, and every resource below runs over the same HTTP client,
@@ -71,6 +70,12 @@ class CrmClient {
71
70
  * pass to `inbox.messages.sendMedia()`.
72
71
  */
73
72
  inboxMedia;
73
+ /**
74
+ * Push notification subscriptions: the browsers and apps a user wants
75
+ * notified. `subscribe()` after the browser hands you a Firebase registration
76
+ * token, `unsubscribe()` on logout.
77
+ */
78
+ notifications;
74
79
  // -- Channels -------------------------------------------------------------
75
80
  /** Channels, and the provider properties that hang off them. */
76
81
  channels;
@@ -84,6 +89,22 @@ class CrmClient {
84
89
  wallet;
85
90
  /** Channel activity feed. */
86
91
  activity;
92
+ // -- Campaign -------------------------------------------------------------
93
+ /**
94
+ * Campaigns: a message sent to many people at once.
95
+ *
96
+ * `createBulk()` is the one to reach for when you hold the recipients
97
+ * yourself -- numbers or a CSV. It encrypts the list, uploads it, and creates
98
+ * the campaign pointing at the file, which is otherwise several steps a caller
99
+ * has to get exactly right.
100
+ */
101
+ campaigns;
102
+ /**
103
+ * Campaign reporting: the funnel, the per-recipient rows behind it, and what
104
+ * people replied -- all counted off the message rows when asked for, so there
105
+ * is no roll-up to wait for and no export job to poll.
106
+ */
107
+ campaignMessages;
87
108
  constructor(options) {
88
109
  const http = new http_1.HttpClient(options);
89
110
  this.contacts = new contacts_1.Contacts(http);
@@ -97,12 +118,18 @@ class CrmClient {
97
118
  this.webhooks = new webhooks_1.Webhooks(http);
98
119
  this.scheduledMessages = new scheduled_messages_1.ScheduledMessages(http);
99
120
  this.inboxMedia = new inbox_media_1.InboxMedia(http);
121
+ this.notifications = new notifications_1.Notifications(http);
100
122
  this.channels = new channels_1.Channels(http);
101
123
  this.templates = new templates_1.Templates(http);
102
124
  this.channelWebhooks = new webhooks_2.ChannelWebhooks(http);
103
125
  this.billing = new billing_1.Billing(http);
104
126
  this.wallet = new wallet_1.Wallet(http);
105
127
  this.activity = new activity_1.Activity(http);
128
+ // Campaigns build their own uploader over the same HTTP client: a bulk
129
+ // audience is uploaded through the media endpoint before the campaign is
130
+ // created.
131
+ this.campaigns = new campaigns_1.Campaigns(http);
132
+ this.campaignMessages = new campaign_messages_1.CampaignMessages(http);
106
133
  }
107
134
  }
108
135
  exports.CrmClient = CrmClient;
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;AAAA,iCAAwD;AAExD,WAAW;AACX,4DAAyD;AACzD,oEAAgE;AAChE,sEAAkE;AAClE,sEAAkE;AAClE,4DAAyD;AACzD,oEAAgE;AAChE,wEAA8D;AAC9D,wEAA8D;AAC9D,4DAAyD;AACzD,gFAA4E;AAC5E,kEAA8D;AAE9D,WAAW;AACX,4DAAyD;AACzD,8DAA2D;AAC3D,4DAAgE;AAChE,0DAAuD;AACvD,wDAAqD;AACrD,4DAAyD;AAEzD,4EAA4E;AAC5E,kBAAkB;AAClB,qDAAqD;AACrD,kEAAkE;AAClE,+EAA+E;AAE/E;;;;;;;;;;;GAWG;AACH,MAAa,SAAS;IACpB,4EAA4E;IAE5E,gDAAgD;IACvC,QAAQ,CAAW;IAC5B,oDAAoD;IAC3C,WAAW,CAAc;IAClC,kEAAkE;IACzD,YAAY,CAAe;IACpC,qDAAqD;IAC5C,YAAY,CAAe;IACpC,uCAAuC;IAC9B,QAAQ,CAAW;IAC5B,qFAAqF;IAC5E,WAAW,CAAc;IAClC,kEAAkE;IACzD,OAAO,CAAU;IAC1B,yDAAyD;IAChD,OAAO,CAAU;IAC1B,0DAA0D;IACjD,QAAQ,CAAW;IAC5B;;;OAGG;IACM,iBAAiB,CAAoB;IAE9C;;;;;;OAMG;IACM,UAAU,CAAa;IAEhC,4EAA4E;IAE5E,gEAAgE;IACvD,QAAQ,CAAW;IAC5B,+CAA+C;IACtC,SAAS,CAAY;IAC9B,wEAAwE;IAC/D,eAAe,CAAkB;IAC1C,yDAAyD;IAChD,OAAO,CAAU;IAC1B,6BAA6B;IACpB,MAAM,CAAS;IACxB,6BAA6B;IACpB,QAAQ,CAAW;IAE5B,YAAY,OAAsB;QAChC,MAAM,IAAI,GAAG,IAAI,iBAAU,CAAC,OAAO,CAAC,CAAC;QAErC,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,WAAW,GAAG,IAAI,0BAAW,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,WAAW,GAAG,IAAI,0BAAW,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,IAAI,wBAAO,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,IAAI,wBAAO,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,iBAAiB,GAAG,IAAI,sCAAiB,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,CAAC,UAAU,GAAG,IAAI,wBAAU,CAAC,IAAI,CAAC,CAAC;QAEvC,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,SAAS,GAAG,IAAI,qBAAS,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,eAAe,GAAG,IAAI,0BAAe,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,OAAO,GAAG,IAAI,iBAAO,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,IAAI,eAAM,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAQ,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;CACF;AAzED,8BAyEC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -12,7 +12,16 @@ export { CrmTriggers } from './resources/audience/crm-triggers';
12
12
  export { Imports } from './resources/audience/import-history';
13
13
  export { Exports } from './resources/audience/export-history';
14
14
  export { Webhooks } from './resources/audience/webhooks';
15
+ export { Notifications } from './resources/audience/notifications';
15
16
  export { InboxMedia, type UploadMediaOptions, type UploadMediaSource, } from './resources/audience/inbox-media';
17
+ export { Campaigns, type CreateBulkCampaignInput, type BulkCampaignResult, type CreateGroupCampaignInput, type GroupBucket, type GroupBucketResult, type GroupCampaignResult, type ReceiverUploader, } from './resources/campaigns';
18
+ export { CampaignMessages } from './resources/campaign-messages';
19
+ /**
20
+ * The audience helpers, exported because they are useful before a campaign
21
+ * exists: a UI can parse a CSV, show "12,480 recipients, 3 duplicates dropped",
22
+ * and let someone confirm before anything is uploaded.
23
+ */
24
+ export { MAX_BULK_RECEIVERS, buildReceivers, normaliseColumn, normaliseNumbers, parseDelimited, resolveAudience, type BulkAudienceInput, type CampaignReceiver, type ResolvedAudience, } from './resources/campaign-audience';
16
25
  export { Channels, type SetPropOptions } from './resources/channels/channels';
17
26
  export { Templates } from './resources/channels/templates';
18
27
  export { ChannelWebhooks } from './resources/channels/webhooks';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.Activity = exports.Wallet = exports.Billing = exports.ChannelWebhooks = exports.Templates = exports.Channels = exports.InboxMedia = exports.Webhooks = exports.Exports = exports.Imports = exports.CrmTriggers = exports.Segments = exports.CustomFields = exports.ContactNotes = exports.ContactTags = exports.Contacts = exports.CHANNELS = exports.AUDIENCE = void 0;
17
+ exports.Activity = exports.Wallet = exports.Billing = exports.ChannelWebhooks = exports.Templates = exports.Channels = exports.resolveAudience = exports.parseDelimited = exports.normaliseNumbers = exports.normaliseColumn = exports.buildReceivers = exports.MAX_BULK_RECEIVERS = exports.CampaignMessages = exports.Campaigns = exports.InboxMedia = exports.Notifications = exports.Webhooks = exports.Exports = exports.Imports = exports.CrmTriggers = exports.Segments = exports.CustomFields = exports.ContactNotes = exports.ContactTags = exports.Contacts = exports.CHANNELS = exports.AUDIENCE = void 0;
18
18
  __exportStar(require("./client"), exports);
19
19
  __exportStar(require("./http"), exports);
20
20
  __exportStar(require("./errors"), exports);
@@ -41,8 +41,27 @@ var export_history_1 = require("./resources/audience/export-history");
41
41
  Object.defineProperty(exports, "Exports", { enumerable: true, get: function () { return export_history_1.Exports; } });
42
42
  var webhooks_1 = require("./resources/audience/webhooks");
43
43
  Object.defineProperty(exports, "Webhooks", { enumerable: true, get: function () { return webhooks_1.Webhooks; } });
44
+ var notifications_1 = require("./resources/audience/notifications");
45
+ Object.defineProperty(exports, "Notifications", { enumerable: true, get: function () { return notifications_1.Notifications; } });
44
46
  var inbox_media_1 = require("./resources/audience/inbox-media");
45
47
  Object.defineProperty(exports, "InboxMedia", { enumerable: true, get: function () { return inbox_media_1.InboxMedia; } });
48
+ // Campaign
49
+ var campaigns_1 = require("./resources/campaigns");
50
+ Object.defineProperty(exports, "Campaigns", { enumerable: true, get: function () { return campaigns_1.Campaigns; } });
51
+ var campaign_messages_1 = require("./resources/campaign-messages");
52
+ Object.defineProperty(exports, "CampaignMessages", { enumerable: true, get: function () { return campaign_messages_1.CampaignMessages; } });
53
+ /**
54
+ * The audience helpers, exported because they are useful before a campaign
55
+ * exists: a UI can parse a CSV, show "12,480 recipients, 3 duplicates dropped",
56
+ * and let someone confirm before anything is uploaded.
57
+ */
58
+ var campaign_audience_1 = require("./resources/campaign-audience");
59
+ Object.defineProperty(exports, "MAX_BULK_RECEIVERS", { enumerable: true, get: function () { return campaign_audience_1.MAX_BULK_RECEIVERS; } });
60
+ Object.defineProperty(exports, "buildReceivers", { enumerable: true, get: function () { return campaign_audience_1.buildReceivers; } });
61
+ Object.defineProperty(exports, "normaliseColumn", { enumerable: true, get: function () { return campaign_audience_1.normaliseColumn; } });
62
+ Object.defineProperty(exports, "normaliseNumbers", { enumerable: true, get: function () { return campaign_audience_1.normaliseNumbers; } });
63
+ Object.defineProperty(exports, "parseDelimited", { enumerable: true, get: function () { return campaign_audience_1.parseDelimited; } });
64
+ Object.defineProperty(exports, "resolveAudience", { enumerable: true, get: function () { return campaign_audience_1.resolveAudience; } });
46
65
  // Channels
47
66
  var channels_1 = require("./resources/channels/channels");
48
67
  Object.defineProperty(exports, "Channels", { enumerable: true, get: function () { return channels_1.Channels; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,yCAAuB;AACvB,2CAAyB;AACzB,0CAAwB;AAExB,2CAAuD;AAA9C,iGAAA,QAAQ,OAAA;AAAE,iGAAA,QAAQ,OAAA;AAE3B,WAAW;AACX,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,kEAAgE;AAAvD,2GAAA,WAAW,OAAA;AACpB,oEAAkE;AAAzD,6GAAA,YAAY,OAAA;AACrB,oEAAkE;AAAzD,6GAAA,YAAY,OAAA;AACrB,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,kEAAgE;AAAvD,2GAAA,WAAW,OAAA;AACpB,sEAA8D;AAArD,yGAAA,OAAO,OAAA;AAChB,sEAA8D;AAArD,yGAAA,OAAO,OAAA;AAChB,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,gEAI0C;AAHxC,yGAAA,UAAU,OAAA;AAKZ,WAAW;AACX,0DAA8E;AAArE,oGAAA,QAAQ,OAAA;AACjB,4DAA2D;AAAlD,sGAAA,SAAS,OAAA;AAClB,0DAAgE;AAAvD,2GAAA,eAAe,OAAA;AACxB,wDAAuD;AAA9C,kGAAA,OAAO,OAAA;AAChB,sDAAqD;AAA5C,gGAAA,MAAM,OAAA;AACf,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,yCAAuB;AACvB,2CAAyB;AACzB,0CAAwB;AAExB,2CAAuD;AAA9C,iGAAA,QAAQ,OAAA;AAAE,iGAAA,QAAQ,OAAA;AAE3B,WAAW;AACX,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,kEAAgE;AAAvD,2GAAA,WAAW,OAAA;AACpB,oEAAkE;AAAzD,6GAAA,YAAY,OAAA;AACrB,oEAAkE;AAAzD,6GAAA,YAAY,OAAA;AACrB,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,kEAAgE;AAAvD,2GAAA,WAAW,OAAA;AACpB,sEAA8D;AAArD,yGAAA,OAAO,OAAA;AAChB,sEAA8D;AAArD,yGAAA,OAAO,OAAA;AAChB,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,oEAAmE;AAA1D,8GAAA,aAAa,OAAA;AACtB,gEAI0C;AAHxC,yGAAA,UAAU,OAAA;AAKZ,WAAW;AACX,mDAS+B;AAR7B,sGAAA,SAAS,OAAA;AASX,mEAAiE;AAAxD,qHAAA,gBAAgB,OAAA;AACzB;;;;GAIG;AACH,mEAUuC;AATrC,uHAAA,kBAAkB,OAAA;AAClB,mHAAA,cAAc,OAAA;AACd,oHAAA,eAAe,OAAA;AACf,qHAAA,gBAAgB,OAAA;AAChB,mHAAA,cAAc,OAAA;AACd,oHAAA,eAAe,OAAA;AAMjB,WAAW;AACX,0DAA8E;AAArE,oGAAA,QAAQ,OAAA;AACjB,4DAA2D;AAAlD,sGAAA,SAAS,OAAA;AAClB,0DAAgE;AAAvD,2GAAA,eAAe,OAAA;AACxB,wDAAuD;AAA9C,kGAAA,OAAO,OAAA;AAChB,sDAAqD;AAA5C,gGAAA,MAAM,OAAA;AACf,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Byte and text conversions, written out rather than taken from a platform.
3
+ *
4
+ * This SDK runs in a browser and in Node, and the obvious helpers differ between
5
+ * them: `btoa` is browser-first and deprecated in Node, `Buffer` does not exist
6
+ * in a browser, and feature-detecting between the two puts an environment branch
7
+ * in the middle of something that has to produce identical bytes everywhere.
8
+ * These are a few lines each and have no such branch.
9
+ */
10
+ /** UTF-8 bytes of a string. `TextEncoder` is global in Node 18+ and every browser. */
11
+ export declare const utf8: (value: string) => Uint8Array;
12
+ /**
13
+ * A standalone `ArrayBuffer` holding a copy of these bytes.
14
+ *
15
+ * Web Crypto takes `BufferSource`, and a `Uint8Array` is not always one as far as
16
+ * the type system is concerned: since TypeScript 5.7 the array is generic in its
17
+ * backing buffer, and the `ArrayBufferLike` that `TextEncoder` hands back could
18
+ * in principle be a `SharedArrayBuffer`, which `BufferSource` excludes. Copying
19
+ * into a buffer of known provenance settles it without an assertion, and without
20
+ * putting a generic type parameter into the emitted declarations -- which would
21
+ * make this package fail to typecheck for anyone on an older compiler.
22
+ */
23
+ export declare const toArrayBuffer: (bytes: Uint8Array) => ArrayBuffer;
24
+ /** Standard base64, with padding -- the form `CryptoJS`'s default formatter emits. */
25
+ export declare const toBase64: (bytes: Uint8Array) => string;
26
+ /** Lower-case hex, two characters per byte. */
27
+ export declare const toHex: (bytes: Uint8Array) => string;
28
+ /**
29
+ * Cryptographically random bytes.
30
+ *
31
+ * `crypto.getRandomValues` is the one API that is present and seeded properly in
32
+ * both environments -- global in Node 18+ and in browsers. `Math.random` is not
33
+ * an acceptable substitute here: the value it would produce is the key under
34
+ * which a customer's phone numbers are encrypted.
35
+ */
36
+ export declare const randomBytes: (length: number) => Uint8Array;
37
+ /** Web Crypto, or a clear error naming what is missing rather than a `TypeError`. */
38
+ export declare const subtle: () => SubtleCrypto;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ /**
3
+ * Byte and text conversions, written out rather than taken from a platform.
4
+ *
5
+ * This SDK runs in a browser and in Node, and the obvious helpers differ between
6
+ * them: `btoa` is browser-first and deprecated in Node, `Buffer` does not exist
7
+ * in a browser, and feature-detecting between the two puts an environment branch
8
+ * in the middle of something that has to produce identical bytes everywhere.
9
+ * These are a few lines each and have no such branch.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.subtle = exports.randomBytes = exports.toHex = exports.toBase64 = exports.toArrayBuffer = exports.utf8 = void 0;
13
+ /** UTF-8 bytes of a string. `TextEncoder` is global in Node 18+ and every browser. */
14
+ const utf8 = (value) => new TextEncoder().encode(value);
15
+ exports.utf8 = utf8;
16
+ /**
17
+ * A standalone `ArrayBuffer` holding a copy of these bytes.
18
+ *
19
+ * Web Crypto takes `BufferSource`, and a `Uint8Array` is not always one as far as
20
+ * the type system is concerned: since TypeScript 5.7 the array is generic in its
21
+ * backing buffer, and the `ArrayBufferLike` that `TextEncoder` hands back could
22
+ * in principle be a `SharedArrayBuffer`, which `BufferSource` excludes. Copying
23
+ * into a buffer of known provenance settles it without an assertion, and without
24
+ * putting a generic type parameter into the emitted declarations -- which would
25
+ * make this package fail to typecheck for anyone on an older compiler.
26
+ */
27
+ const toArrayBuffer = (bytes) => {
28
+ const buffer = new ArrayBuffer(bytes.length);
29
+ new Uint8Array(buffer).set(bytes);
30
+ return buffer;
31
+ };
32
+ exports.toArrayBuffer = toArrayBuffer;
33
+ const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
34
+ /** Standard base64, with padding -- the form `CryptoJS`'s default formatter emits. */
35
+ const toBase64 = (bytes) => {
36
+ let out = '';
37
+ for (let i = 0; i < bytes.length; i += 3) {
38
+ const a = bytes[i];
39
+ const b = bytes[i + 1];
40
+ const c = bytes[i + 2];
41
+ out += BASE64_ALPHABET[a >> 2];
42
+ out += BASE64_ALPHABET[((a & 0x03) << 4) | ((b ?? 0) >> 4)];
43
+ out += b === undefined ? '=' : BASE64_ALPHABET[((b & 0x0f) << 2) | ((c ?? 0) >> 6)];
44
+ out += c === undefined ? '=' : BASE64_ALPHABET[c & 0x3f];
45
+ }
46
+ return out;
47
+ };
48
+ exports.toBase64 = toBase64;
49
+ /** Lower-case hex, two characters per byte. */
50
+ const toHex = (bytes) => {
51
+ let out = '';
52
+ for (const byte of bytes)
53
+ out += byte.toString(16).padStart(2, '0');
54
+ return out;
55
+ };
56
+ exports.toHex = toHex;
57
+ /**
58
+ * Cryptographically random bytes.
59
+ *
60
+ * `crypto.getRandomValues` is the one API that is present and seeded properly in
61
+ * both environments -- global in Node 18+ and in browsers. `Math.random` is not
62
+ * an acceptable substitute here: the value it would produce is the key under
63
+ * which a customer's phone numbers are encrypted.
64
+ */
65
+ const randomBytes = (length) => {
66
+ const source = globalThis.crypto;
67
+ if (!source?.getRandomValues) {
68
+ throw new Error('No cryptographic random source: globalThis.crypto.getRandomValues is unavailable. ' +
69
+ 'This SDK needs Node 18+ or a browser with Web Crypto.');
70
+ }
71
+ return source.getRandomValues(new Uint8Array(length));
72
+ };
73
+ exports.randomBytes = randomBytes;
74
+ /** Web Crypto, or a clear error naming what is missing rather than a `TypeError`. */
75
+ const subtle = () => {
76
+ const source = globalThis.crypto;
77
+ if (!source?.subtle) {
78
+ throw new Error('Web Crypto is unavailable: globalThis.crypto.subtle is missing. ' +
79
+ 'Encrypting campaign receivers needs Node 18+ or a secure browser context (https or localhost).');
80
+ }
81
+ return source.subtle;
82
+ };
83
+ exports.subtle = subtle;
84
+ //# sourceMappingURL=bytes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.js","sourceRoot":"","sources":["../../src/internal/bytes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { DevicePlatform } from '../types';
2
+ /**
3
+ * A label a person would recognise their own browser by, e.g. `Chrome on Windows`.
4
+ *
5
+ * `undefined` rather than a placeholder when there is nothing to go on -- the
6
+ * field is optional, and an omitted label reads back as `''`, which a UI can
7
+ * render as "this device" far better than a string like `Unknown on Unknown`
8
+ * can.
9
+ *
10
+ * Only the family and the system, never the version. A version would change the
11
+ * label every few weeks on a device that has not changed, and the label exists
12
+ * to be recognised rather than to be precise.
13
+ */
14
+ export declare const describeDevice: () => string | undefined;
15
+ /**
16
+ * `'web'` when there is a DOM, `undefined` otherwise.
17
+ *
18
+ * Keyed on `document` and not on `navigator`, which is the distinction that
19
+ * matters: React Native defines a `navigator` but no `document`, so a
20
+ * navigator-based check would quietly register every mobile device as a browser
21
+ * and the send path would fill in the `webpush` block instead of `android`/`apns`
22
+ * -- a notification that is accepted by FCM and never shown.
23
+ */
24
+ export declare const detectPlatform: () => DevicePlatform | undefined;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectPlatform = exports.describeDevice = void 0;
4
+ /**
5
+ * What the current runtime is, for the two fields a caller should not have to
6
+ * fill in by hand.
7
+ *
8
+ * Both functions here are best-effort and both are cosmetic-or-defaultable:
9
+ * nothing in the send path depends on getting the label right, and a platform
10
+ * that cannot be detected is asked for rather than guessed.
11
+ *
12
+ * ## Why this is not the `user-agents` package
13
+ *
14
+ * The legacy panel sent its `device` field as `new UserAgent().data` from the
15
+ * `user-agents` npm package. That package **generates a random plausible user
16
+ * agent**; it does not read the browser's own. So every device row ever written
17
+ * by the old panel describes a machine that does not exist, which is why the
18
+ * replacement stores a caller-supplied label instead of a parsed object, and why
19
+ * the default below reads `navigator.userAgent` directly.
20
+ */
21
+ /** The browser's own user agent, or `undefined` outside a browser. */
22
+ const userAgent = () => {
23
+ const nav = globalThis.navigator;
24
+ return typeof nav?.userAgent === 'string' && nav.userAgent ? nav.userAgent : undefined;
25
+ };
26
+ /**
27
+ * Browser families, **most specific first**.
28
+ *
29
+ * The order is the whole correctness of this table: Edge and Opera both carry
30
+ * `Chrome/` in their user agent, and every Chromium browser carries `Safari/`.
31
+ * Matching in declaration order is what keeps Edge from reading as Chrome and
32
+ * Chrome from reading as Safari.
33
+ */
34
+ const BROWSERS = [
35
+ [/\bEdg(?:e|A|iOS)?\//, 'Edge'],
36
+ [/\bOPR\/|\bOpera\//, 'Opera'],
37
+ [/\bSamsungBrowser\//, 'Samsung Internet'],
38
+ [/\bFirefox\/|\bFxiOS\//, 'Firefox'],
39
+ // Chrome on iOS is `CriOS`: it is Safari's engine wearing Chrome's name, and
40
+ // the name is what a user would recognise in a device list.
41
+ [/\bChrome\/|\bCriOS\//, 'Chrome'],
42
+ [/\bSafari\//, 'Safari'],
43
+ ];
44
+ /** Operating systems, also most specific first -- an Android UA also says `Linux`. */
45
+ const SYSTEMS = [
46
+ [/\bWindows\b/, 'Windows'],
47
+ [/\bAndroid\b/, 'Android'],
48
+ [/\b(?:iPhone|iPad|iPod)\b/, 'iOS'],
49
+ [/\bCrOS\b/, 'ChromeOS'],
50
+ [/\bMac OS X\b|\bMacintosh\b/, 'macOS'],
51
+ [/\bLinux\b/, 'Linux'],
52
+ ];
53
+ const match = (table, ua) => table.find(([pattern]) => pattern.test(ua))?.[1];
54
+ /**
55
+ * A label a person would recognise their own browser by, e.g. `Chrome on Windows`.
56
+ *
57
+ * `undefined` rather than a placeholder when there is nothing to go on -- the
58
+ * field is optional, and an omitted label reads back as `''`, which a UI can
59
+ * render as "this device" far better than a string like `Unknown on Unknown`
60
+ * can.
61
+ *
62
+ * Only the family and the system, never the version. A version would change the
63
+ * label every few weeks on a device that has not changed, and the label exists
64
+ * to be recognised rather than to be precise.
65
+ */
66
+ const describeDevice = () => {
67
+ const ua = userAgent();
68
+ if (!ua)
69
+ return undefined;
70
+ const browser = match(BROWSERS, ua);
71
+ const system = match(SYSTEMS, ua);
72
+ if (browser && system)
73
+ return `${browser} on ${system}`;
74
+ return browser ?? system;
75
+ };
76
+ exports.describeDevice = describeDevice;
77
+ /**
78
+ * `'web'` when there is a DOM, `undefined` otherwise.
79
+ *
80
+ * Keyed on `document` and not on `navigator`, which is the distinction that
81
+ * matters: React Native defines a `navigator` but no `document`, so a
82
+ * navigator-based check would quietly register every mobile device as a browser
83
+ * and the send path would fill in the `webpush` block instead of `android`/`apns`
84
+ * -- a notification that is accepted by FCM and never shown.
85
+ */
86
+ const detectPlatform = () => typeof globalThis.document === 'object' &&
87
+ globalThis.document !== null
88
+ ? 'web'
89
+ : undefined;
90
+ exports.detectPlatform = detectPlatform;
91
+ //# sourceMappingURL=device.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.js","sourceRoot":"","sources":["../../src/internal/device.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,sEAAsE;AACtE,MAAM,SAAS,GAAG,GAAuB,EAAE;IACzC,MAAM,GAAG,GAAI,UAAsD,CAAC,SAAS,CAAC;IAC9E,OAAO,OAAO,GAAG,EAAE,SAAS,KAAK,QAAQ,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;AACzF,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,QAAQ,GAAgC;IAC5C,CAAC,qBAAqB,EAAE,MAAM,CAAC;IAC/B,CAAC,mBAAmB,EAAE,OAAO,CAAC;IAC9B,CAAC,oBAAoB,EAAE,kBAAkB,CAAC;IAC1C,CAAC,uBAAuB,EAAE,SAAS,CAAC;IACpC,6EAA6E;IAC7E,4DAA4D;IAC5D,CAAC,sBAAsB,EAAE,QAAQ,CAAC;IAClC,CAAC,YAAY,EAAE,QAAQ,CAAC;CACzB,CAAC;AAEF,sFAAsF;AACtF,MAAM,OAAO,GAAgC;IAC3C,CAAC,aAAa,EAAE,SAAS,CAAC;IAC1B,CAAC,aAAa,EAAE,SAAS,CAAC;IAC1B,CAAC,0BAA0B,EAAE,KAAK,CAAC;IACnC,CAAC,UAAU,EAAE,UAAU,CAAC;IACxB,CAAC,4BAA4B,EAAE,OAAO,CAAC;IACvC,CAAC,WAAW,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,KAAK,GAAG,CAAC,KAAkC,EAAE,EAAU,EAAsB,EAAE,CACnF,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAEnD;;;;;;;;;;;GAWG;AACI,MAAM,cAAc,GAAG,GAAuB,EAAE;IACrD,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAE1B,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAElC,IAAI,OAAO,IAAI,MAAM;QAAE,OAAO,GAAG,OAAO,OAAO,MAAM,EAAE,CAAC;IACxD,OAAO,OAAO,IAAI,MAAM,CAAC;AAC3B,CAAC,CAAC;AATW,QAAA,cAAc,kBASzB;AAEF;;;;;;;;GAQG;AACI,MAAM,cAAc,GAAG,GAA+B,EAAE,CAC7D,OAAQ,UAAqC,CAAC,QAAQ,KAAK,QAAQ;IAClE,UAAqC,CAAC,QAAQ,KAAK,IAAI;IACtD,CAAC,CAAC,KAAK;IACP,CAAC,CAAC,SAAS,CAAC;AAJH,QAAA,cAAc,kBAIX"}
@@ -0,0 +1,4 @@
1
+ /** MD5 of raw bytes, as lower-case hex. */
2
+ export declare const md5Bytes: (input: Uint8Array) => string;
3
+ /** MD5 of a string's UTF-8 bytes, as lower-case hex -- what `md5(value)` on npm returns. */
4
+ export declare const md5: (value: string) => string;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.md5 = exports.md5Bytes = void 0;
4
+ const bytes_1 = require("./bytes");
5
+ /**
6
+ * MD5, because the receiver file's key derivation uses it and no platform offers
7
+ * it.
8
+ *
9
+ * Web Crypto deliberately does not implement MD5 -- it is broken for every
10
+ * purpose it was designed for -- and Node's `crypto.createHash('md5')` does not
11
+ * exist in a browser. So it is written out here.
12
+ *
13
+ * **This is not a security choice, it is a compatibility requirement.** The
14
+ * campaign manager decrypts these files with `md5(secret)` as the AES key, and
15
+ * that algorithm is fixed on the reading side. Changing it here would produce
16
+ * files nothing can read. See `receiver-file.ts`, which documents the whole
17
+ * scheme and its weaknesses in one place.
18
+ *
19
+ * Not exported from the package: nothing outside the receiver file should reach
20
+ * for an MD5.
21
+ */
22
+ /** Per-round left-rotation amounts. */
23
+ const SHIFTS = [
24
+ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
25
+ 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
26
+ 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
27
+ 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
28
+ ];
29
+ /**
30
+ * `floor(abs(sin(i + 1)) * 2^32)`, the constants from RFC 1321.
31
+ *
32
+ * Computed rather than transcribed: sixty-four hand-typed 32-bit hex literals is
33
+ * a place to make a silent mistake, and the derivation is exact in IEEE-754 for
34
+ * every one of these values. The test vectors in RFC 1321 are what confirm it.
35
+ */
36
+ const K = (() => {
37
+ const table = new Uint32Array(64);
38
+ for (let i = 0; i < 64; i += 1)
39
+ table[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 2 ** 32);
40
+ return table;
41
+ })();
42
+ const rotl = (value, bits) => (value << bits) | (value >>> (32 - bits));
43
+ /** MD5 of raw bytes, as lower-case hex. */
44
+ const md5Bytes = (input) => {
45
+ const bitLength = input.length * 8;
46
+ // One 0x80 byte, then zeroes, then the bit length as a little-endian u64 --
47
+ // padded so the total is a whole number of 64-byte blocks.
48
+ const blocks = Math.floor((input.length + 8) / 64) + 1;
49
+ const padded = new Uint8Array(blocks * 64);
50
+ padded.set(input);
51
+ padded[input.length] = 0x80;
52
+ const view = new DataView(padded.buffer);
53
+ view.setUint32(padded.length - 8, bitLength >>> 0, true);
54
+ view.setUint32(padded.length - 4, Math.floor(bitLength / 0x1_0000_0000), true);
55
+ let h0 = 0x67452301;
56
+ let h1 = 0xefcdab89;
57
+ let h2 = 0x98badcfe;
58
+ let h3 = 0x10325476;
59
+ const words = new Uint32Array(16);
60
+ for (let block = 0; block < blocks; block += 1) {
61
+ const offset = block * 64;
62
+ for (let i = 0; i < 16; i += 1)
63
+ words[i] = view.getUint32(offset + i * 4, true);
64
+ let a = h0;
65
+ let b = h1;
66
+ let c = h2;
67
+ let d = h3;
68
+ for (let i = 0; i < 64; i += 1) {
69
+ let f;
70
+ let g;
71
+ if (i < 16) {
72
+ f = (b & c) | (~b & d);
73
+ g = i;
74
+ }
75
+ else if (i < 32) {
76
+ f = (d & b) | (~d & c);
77
+ g = (5 * i + 1) % 16;
78
+ }
79
+ else if (i < 48) {
80
+ f = b ^ c ^ d;
81
+ g = (3 * i + 5) % 16;
82
+ }
83
+ else {
84
+ f = c ^ (b | ~d);
85
+ g = (7 * i) % 16;
86
+ }
87
+ const rotated = rotl((a + f + K[i] + words[g]) | 0, SHIFTS[i]);
88
+ a = d;
89
+ d = c;
90
+ c = b;
91
+ b = (b + rotated) | 0;
92
+ }
93
+ h0 = (h0 + a) | 0;
94
+ h1 = (h1 + b) | 0;
95
+ h2 = (h2 + c) | 0;
96
+ h3 = (h3 + d) | 0;
97
+ }
98
+ const digest = new Uint8Array(16);
99
+ new DataView(digest.buffer).setUint32(0, h0 >>> 0, true);
100
+ new DataView(digest.buffer).setUint32(4, h1 >>> 0, true);
101
+ new DataView(digest.buffer).setUint32(8, h2 >>> 0, true);
102
+ new DataView(digest.buffer).setUint32(12, h3 >>> 0, true);
103
+ return (0, bytes_1.toHex)(digest);
104
+ };
105
+ exports.md5Bytes = md5Bytes;
106
+ /** MD5 of a string's UTF-8 bytes, as lower-case hex -- what `md5(value)` on npm returns. */
107
+ const md5 = (value) => (0, exports.md5Bytes)((0, bytes_1.utf8)(value));
108
+ exports.md5 = md5;
109
+ //# sourceMappingURL=md5.js.map