Skip to content

Commit 80717cd

Browse files
authored
feat: Add support for Live Activity with ActivityKit push notifications (#130)
1 parent 650993d commit 80717cd

File tree

7 files changed

+212
-1
lines changed

7 files changed

+212
-1
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
NODE_VERSION: 14
1818
- name: Node.js 16
1919
NODE_VERSION: 16
20-
runs-on: ubuntu-18.04
20+
runs-on: ubuntu-latest
2121
timeout-minutes: 30
2222
steps:
2323
- uses: actions/checkout@v2

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,40 @@ This will result in the the following notification payload being sent to the dev
148148
{"messageFrom":"John Appelseed","aps":{"badge":3,"sound":"ping.aiff","alert":"\uD83D\uDCE7 \u2709 You have a new message"}}
149149
```
150150

151+
Create a Live Activity notification object, configuring it with the relevant parameters (See the [notification documentation](doc/notification.markdown) for more details.)
152+
153+
```javascript
154+
var note = new apn.Notification();
155+
156+
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
157+
note.badge = 3;
158+
note.sound = "ping.aiff";
159+
note.alert = "\uD83D\uDCE7 \u2709 You have a new message";
160+
note.payload = {'messageFrom': 'John Appleseed'};
161+
note.topic = "<your-app-bundle-id>";
162+
note.pushType = "liveactivity",
163+
note.relevanceScore = 75,
164+
note.timestamp = Math.floor(Date.now() / 1000); // Current time
165+
note.staleDate = Math.floor(Date.now() / 1000) + (8 * 3600); // Expires 8 hour from now.
166+
note.event = "update"
167+
note.contentState = {}
168+
```
169+
170+
Send the notification to the API with `send`, which returns a promise.
171+
172+
```javascript
173+
apnProvider.send(note, deviceToken).then( (result) => {
174+
// see documentation for an explanation of result
175+
});
176+
```
177+
178+
This will result in the the following notification payload being sent to the device
179+
180+
181+
```json
182+
{"messageFrom":"John Appleseed","aps":{"badge":3,"sound":"ping.aiff","alert":"\uD83D\uDCE7 \u2709 You have a new message", "relevance-score":75,"timestamp":1683129662,"stale-date":1683216062,"event":"update","content-state":{}}}
183+
```
184+
151185
You should only create one `Provider` per-process for each certificate/key pair you have. You do not need to create a new `Provider` for each notification. If you are only sending notifications to one app then there is no need for more than one `Provider`.
152186

153187
If you are constantly creating `Provider` instances in your app, make sure to call `Provider.shutdown()` when you are done with each provider to release its resources and memory.

doc/notification.markdown

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ This table shows the name of the setter, with the key-path of the underlying pro
107107
| `targetContentIdentifier` | `aps.target-content-id` | `String` |
108108
| `threadId` | `aps.thread-id` | `String` |
109109
| `interruptionLevel` | `aps.interruption-level` | `String` |
110+
| `relevanceScore` | `aps.relevance-score` | `Number` |
111+
| `timestamp` | `aps.timestamp` | `Number` |
112+
| `staleDate` | `aps.staleDate` | `Number` |
113+
| `event` | `aps.event` | `String` |
114+
| `contentState` | `aps.content-state` | `Object` |
110115
| `mdm` | `mdm` | `String` |
111116

112117
When the notification is transmitted these properties will be added to the output before encoding.

lib/notification/apsProperties.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,36 @@ module.exports = {
8383
}
8484
},
8585

86+
set relevanceScore(value) {
87+
if (typeof value === 'number' || value === undefined) {
88+
this.aps['relevance-score'] = value;
89+
}
90+
},
91+
92+
set timestamp(value) {
93+
if (typeof value === 'number' || value === undefined) {
94+
this.aps.timestamp = value;
95+
}
96+
},
97+
98+
set staleDate(value) {
99+
if (typeof value === 'number' || value === undefined) {
100+
this.aps['stale-date'] = value;
101+
}
102+
},
103+
104+
set event(value) {
105+
if (typeof value === 'string' || value === undefined) {
106+
this.aps.event = value;
107+
}
108+
},
109+
110+
set contentState(value) {
111+
if (typeof value === 'object' || value === undefined) {
112+
this.aps['content-state'] = value;
113+
}
114+
},
115+
86116
set contentAvailable(value) {
87117
if (value === true || value === 1) {
88118
this.aps['content-available'] = 1;

lib/notification/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ Notification.prototype = require('./apsProperties');
4949
'threadId',
5050
'interruptionLevel',
5151
'targetContentIdentifier',
52+
'relevanceScore',
53+
'timestamp',
54+
'staleDate',
55+
'event',
56+
'contentState',
5257
].forEach(propName => {
5358
const methodName = 'set' + propName[0].toUpperCase() + propName.slice(1);
5459
Notification.prototype[methodName] = function (value) {

test/client.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ describe('Client', () => {
521521
}, 1);
522522
});
523523
});
524+
clientSocket.on('error', () => {});
524525
});
525526
await new Promise(resolve => proxy.listen(3128, resolve));
526527

test/notification/apsProperties.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,142 @@ describe('Notification', function () {
832832
});
833833
});
834834

835+
describe('event', function () {
836+
it('defaults to undefined', function () {
837+
expect(compiledOutput()).to.not.have.nested.property('aps.event');
838+
});
839+
840+
it('can be set to a string', function () {
841+
note.event = 'the-event';
842+
843+
expect(compiledOutput()).to.have.nested.property('aps.event', 'the-event');
844+
});
845+
846+
it('can be set to undefined', function () {
847+
note.event = 'the-event';
848+
note.event = undefined;
849+
850+
expect(compiledOutput()).to.not.have.nested.property('aps.event');
851+
});
852+
853+
describe('setEvent', function () {
854+
it('is chainable', function () {
855+
expect(note.setEvent('the-event')).to.equal(note);
856+
expect(compiledOutput()).to.have.nested.property('aps.event', 'the-event');
857+
});
858+
});
859+
});
860+
861+
describe('timestamp', function () {
862+
it('defaults to undefined', function () {
863+
expect(compiledOutput()).to.not.have.nested.property('aps.timestamp');
864+
});
865+
866+
it('can be set to a number', function () {
867+
note.timestamp = 1234;
868+
869+
expect(compiledOutput()).to.have.nested.property('aps.timestamp', 1234);
870+
});
871+
872+
it('can be set to undefined', function () {
873+
note.timestamp = 1234;
874+
note.timestamp = undefined;
875+
876+
expect(compiledOutput()).to.not.have.nested.property('aps.timestamp');
877+
});
878+
879+
describe('setTimestamp', function () {
880+
it('is chainable', function () {
881+
expect(note.setTimestamp(1234)).to.equal(note);
882+
expect(compiledOutput()).to.have.nested.property('aps.timestamp', 1234);
883+
});
884+
});
885+
});
886+
887+
describe('relevance-score', function () {
888+
it('defaults to undefined', function () {
889+
expect(compiledOutput()).to.not.have.nested.property('aps.relevance-score');
890+
});
891+
892+
it('can be set to a number', function () {
893+
note.relevanceScore = 1234;
894+
895+
expect(compiledOutput()).to.have.nested.property('aps.relevance-score', 1234);
896+
});
897+
898+
it('can be set to undefined', function () {
899+
note.relevanceScore = 1234;
900+
note.relevanceScore = undefined;
901+
902+
expect(compiledOutput()).to.not.have.nested.property('aps.relevance-score');
903+
});
904+
905+
describe('setRelevanceScore', function () {
906+
it('is chainable', function () {
907+
expect(note.setRelevanceScore(1234)).to.equal(note);
908+
expect(compiledOutput()).to.have.nested.property('aps.relevance-score', 1234);
909+
});
910+
});
911+
});
912+
913+
describe('stale-date', function () {
914+
it('defaults to undefined', function () {
915+
expect(compiledOutput()).to.not.have.nested.property('aps.stale-date');
916+
});
917+
918+
it('can be set to a number', function () {
919+
note.staleDate = 1234;
920+
921+
expect(compiledOutput()).to.have.nested.property('aps.stale-date', 1234);
922+
});
923+
924+
it('can be set to undefined', function () {
925+
note.staleDate = 1234;
926+
note.staleDate = undefined;
927+
928+
expect(compiledOutput()).to.not.have.nested.property('aps.stale-date');
929+
});
930+
931+
describe('setStaleDate', function () {
932+
it('is chainable', function () {
933+
expect(note.setStaleDate(1234)).to.equal(note);
934+
expect(compiledOutput()).to.have.nested.property('aps.stale-date', 1234);
935+
});
936+
});
937+
});
938+
939+
describe('content-state', function () {
940+
const payload = { foo: 'bar' };
941+
it('defaults to undefined', function () {
942+
expect(compiledOutput()).to.not.have.nested.property('aps.content-state');
943+
});
944+
945+
it('can be set to a object', function () {
946+
note.contentState = payload;
947+
948+
expect(compiledOutput())
949+
.to.have.nested.property('aps.content-state')
950+
.that.deep.equals(payload);
951+
});
952+
953+
it('can be set to undefined', function () {
954+
note.contentState = payload;
955+
note.contentState = undefined;
956+
957+
expect(compiledOutput()).to.not.have.nested.property('aps.content-state');
958+
});
959+
960+
describe('setContentState', function () {
961+
it('is chainable', function () {
962+
expect(note.setContentState(payload)).to.equal(note);
963+
console.log(compiledOutput());
964+
expect(compiledOutput())
965+
.to.have.nested.property('aps.content-state')
966+
.that.deep.equals(payload);
967+
});
968+
});
969+
});
970+
835971
context('when no aps properties are set', function () {
836972
it('is not present', function () {
837973
expect(compiledOutput().aps).to.be.undefined;

0 commit comments

Comments
 (0)