|
7 | 7 | // tag XAPPLEPUSHSERVICE aps-version 2 aps-account-id 0715A26B-CA09-4730-A419-793000CA982E aps-device-token 2918390218931890821908309283098109381029309829018310983092892829 aps-subtopic com.apple.mobilemail mailboxes (INBOX Notes)
|
8 | 8 | //
|
9 | 9 |
|
| 10 | +const requiredKeys = ['aps-version', 'aps-account-id', 'aps-device-token', 'aps-subtopic', 'mailboxes']; |
| 11 | + |
10 | 12 | module.exports = {
|
11 | 13 | state: ['Authenticated', 'Selected'],
|
12 | 14 |
|
13 |
| - /* |
14 |
| - Schema: [ |
15 |
| - { |
16 |
| - name: 'aps-version', |
17 |
| - type: 'number' // always 2 |
18 |
| - }, |
19 |
| - { |
20 |
| - name: 'aps-account-id', |
21 |
| - type: 'string' |
22 |
| - }, |
23 |
| - { |
24 |
| - name: 'aps-device-token', |
25 |
| - type: 'string' |
26 |
| - }, |
27 |
| - { |
28 |
| - name: 'aps-subtopic', |
29 |
| - type: 'string' // always "com.apple.mobilemail" |
30 |
| - }, |
31 |
| - // NOTE: this is irrelevant as it won't be used until we figure out how to notify for other than INBOX |
32 |
| - // <https://github.com/nodemailer/wildduck/issues/711#issuecomment-2251643672> |
33 |
| - { |
34 |
| - name: 'mailboxes', |
35 |
| - type: 'string' // e.g. (INBOX Notes) |
| 15 | + // the input is a key-value set which is not supported by the default schema handler |
| 16 | + schema: false, |
| 17 | + |
| 18 | + // [ |
| 19 | + // { type: 'ATOM', value: 'aps-version' }, |
| 20 | + // { type: 'ATOM', value: '2' }, |
| 21 | + // { type: 'ATOM', value: 'aps-account-id' }, |
| 22 | + // { type: 'ATOM', value: 'xxxxxxx' }, |
| 23 | + // { type: 'ATOM', value: 'aps-device-token' }, |
| 24 | + // { |
| 25 | + // type: 'ATOM', |
| 26 | + // value: 'xxxxxx' |
| 27 | + // }, |
| 28 | + // { type: 'ATOM', value: 'aps-subtopic' }, |
| 29 | + // { type: 'ATOM', value: 'com.apple.mobilemail' }, |
| 30 | + // { type: 'ATOM', value: 'mailboxes' }, |
| 31 | + // [ |
| 32 | + // { type: 'STRING', value: 'Sent Mail' }, |
| 33 | + // { type: 'STRING', value: 'INBOX' } |
| 34 | + // ] |
| 35 | + // ] |
| 36 | + |
| 37 | + handler(command, callback) { |
| 38 | + // Command = { |
| 39 | + // tag: 'I5', |
| 40 | + // command: 'XAPPLEPUSHSERVICE', |
| 41 | + // attributes: [ |
| 42 | + // { type: 'ATOM', value: 'aps-version' }, // 0 |
| 43 | + // { type: 'ATOM', value: '2' }, // 1 |
| 44 | + // { type: 'ATOM', value: 'aps-account-id' }, // 2 |
| 45 | + // { type: 'ATOM', value: 'xxxxxx' }, // 3 |
| 46 | + // { type: 'ATOM', value: 'aps-device-token' }, // 4 |
| 47 | + // { // 5 |
| 48 | + // type: 'ATOM', |
| 49 | + // value: 'xxxxxx' |
| 50 | + // }, |
| 51 | + // { type: 'ATOM', value: 'aps-subtopic' }, // 6 |
| 52 | + // { type: 'ATOM', value: 'com.apple.mobilemail' }, // 7 |
| 53 | + // { type: 'ATOM', value: 'mailboxes' }, // 8 |
| 54 | + // [ // 9 |
| 55 | + // { type: 'STRING', value: 'Sent Mail' }, |
| 56 | + // { type: 'STRING', value: 'INBOX' } |
| 57 | + // ] |
| 58 | + // ] |
| 59 | + // } |
| 60 | + |
| 61 | + const apsConfig = this._server.options.aps || {}; |
| 62 | + |
| 63 | + // Reject if not enabled |
| 64 | + if (!apsConfig.enabled) { |
| 65 | + return callback(null, { |
| 66 | + response: 'BAD', |
| 67 | + message: `Unknown command: ${command.command}` |
| 68 | + }); |
| 69 | + } |
| 70 | + |
| 71 | + // Parse input arguments into a structured object: |
| 72 | + |
| 73 | + // { |
| 74 | + // "aps-version": "2", |
| 75 | + // "aps-account-id": "0715A26B-CA09-4730-A419-793000CA982E", |
| 76 | + // "aps-device-token": "2918390218931890821908309283098109381029309829018310983092892829", |
| 77 | + // "aps-subtopic": "com.apple.mobilemail", |
| 78 | + // "mailboxes": [ |
| 79 | + // "INBOX", |
| 80 | + // "Notes" |
| 81 | + // ] |
| 82 | + // } |
| 83 | + |
| 84 | + let data = {}; |
| 85 | + let keyName; |
| 86 | + for (let i = 0, len = (command.attributes || []).length; i < len; i++) { |
| 87 | + let isKey = i % 2 === 0; |
| 88 | + let attr = command.attributes[i]; |
| 89 | + if (isKey && !['ATOM', 'STRING'].includes(attr.type)) { |
| 90 | + return callback(null, { |
| 91 | + response: 'BAD', |
| 92 | + message: `Invalid argument for ${command.command}` |
| 93 | + }); |
| 94 | + } |
| 95 | + if (isKey) { |
| 96 | + keyName = (attr.value || '').toString().toLowerCase(); |
| 97 | + continue; |
| 98 | + } |
| 99 | + |
| 100 | + if (!requiredKeys.includes(keyName)) { |
| 101 | + // skip unknown keys |
| 102 | + } |
| 103 | + |
| 104 | + if (['ATOM', 'STRING'].includes(attr.type)) { |
| 105 | + data[keyName] = (attr.value || '').toString(); |
| 106 | + } else if (Array.isArray(attr) && keyName === 'mailboxes') { |
| 107 | + let mailboxes = attr |
| 108 | + .map(entry => { |
| 109 | + if (['ATOM', 'STRING'].includes(entry.type)) { |
| 110 | + return (entry.value || '').toString(); |
| 111 | + } |
| 112 | + return false; |
| 113 | + }) |
| 114 | + .filter(name => name); |
| 115 | + data[keyName] = mailboxes; |
| 116 | + } |
36 | 117 | }
|
37 |
| - ], |
38 |
| - */ |
39 |
| - |
40 |
| - // it's actually something like this in production |
41 |
| - // [ |
42 |
| - // { type: 'ATOM', value: 'aps-version' }, |
43 |
| - // { type: 'ATOM', value: '2' }, |
44 |
| - // { type: 'ATOM', value: 'aps-account-id' }, |
45 |
| - // { type: 'ATOM', value: 'xxxxxxx' }, |
46 |
| - // { type: 'ATOM', value: 'aps-device-token' }, |
47 |
| - // { |
48 |
| - // type: 'ATOM', |
49 |
| - // value: 'xxxxxx' |
50 |
| - // }, |
51 |
| - // { type: 'ATOM', value: 'aps-subtopic' }, |
52 |
| - // { type: 'ATOM', value: 'com.apple.mobilemail' }, |
53 |
| - // { type: 'ATOM', value: 'mailboxes' }, |
54 |
| - // [ |
55 |
| - // { type: 'STRING', value: 'Sent Mail' }, |
56 |
| - // { type: 'STRING', value: 'INBOX' } |
57 |
| - // ] |
58 |
| - // ] |
59 |
| - |
60 |
| - // disabled for now |
61 |
| - schema: false, |
62 |
| - |
63 |
| - handler(command, callback) { |
64 |
| - // Command = { |
65 |
| - // tag: 'I5', |
66 |
| - // command: 'XAPPLEPUSHSERVICE', |
67 |
| - // attributes: [ |
68 |
| - // { type: 'ATOM', value: 'aps-version' }, // 0 |
69 |
| - // { type: 'ATOM', value: '2' }, // 1 |
70 |
| - // { type: 'ATOM', value: 'aps-account-id' }, // 2 |
71 |
| - // { type: 'ATOM', value: 'xxxxxx' }, // 3 |
72 |
| - // { type: 'ATOM', value: 'aps-device-token' }, // 4 |
73 |
| - // { // 5 |
74 |
| - // type: 'ATOM', |
75 |
| - // value: 'xxxxxx' |
76 |
| - // }, |
77 |
| - // { type: 'ATOM', value: 'aps-subtopic' }, // 6 |
78 |
| - // { type: 'ATOM', value: 'com.apple.mobilemail' }, // 7 |
79 |
| - // { type: 'ATOM', value: 'mailboxes' }, // 8 |
80 |
| - // [ // 9 |
81 |
| - // { type: 'STRING', value: 'Sent Mail' }, |
82 |
| - // { type: 'STRING', value: 'INBOX' } |
83 |
| - // ] |
84 |
| - // ] |
85 |
| - // } |
86 |
| - |
87 |
| - const version = (command.attributes[1] && command.attributes[1].value) || ''; |
88 |
| - if (version !== '2') { |
89 |
| - return callback(null, { |
90 |
| - response: 'NO', |
91 |
| - code: 'CLIENTBUG', |
92 |
| - }); |
93 |
| - } |
94 |
| - |
95 |
| - const accountID = (command.attributes[3] && command.attributes[3].value) || ''; |
96 |
| - const deviceToken = (command.attributes[5] && command.attributes[5].value) || ''; |
97 |
| - const subTopic = (command.attributes[7] && command.attributes[7].value) || ''; |
98 |
| - |
99 |
| - if (subTopic !== 'com.apple.mobilemail') { |
100 |
| - return callback(null, { |
101 |
| - response: 'NO', |
102 |
| - code: 'CLIENTBUG', |
103 |
| - }); |
104 |
| - } |
105 |
| - |
106 |
| - // NOTE: mailboxes param is not used at this time (it's a list anyways too) |
107 |
| - const mailboxes = command.attributes[9] && Array.isArray(command.attributes[9]) && command.attributes[9].length > 0 ? command.attributes[9].map(object => object.value) : []; |
108 |
| - |
109 |
| - if (typeof this._server.onXAPPLEPUSHSERVICE !== 'function') { |
110 |
| - return callback(null, { |
111 |
| - response: 'NO', |
112 |
| - message: command.command + ' not implemented', |
113 |
| - }); |
114 |
| - } |
115 |
| - |
116 |
| - const logdata = { |
117 |
| - short_message: '[XAPPLEPUSHSERVICE]', |
118 |
| - _mail_action: 'xapplepushservice', |
119 |
| - _accountId: accountID, |
120 |
| - _deviceToken: deviceToken, |
121 |
| - _subTopic: subTopic, |
122 |
| - _mailboxes: mailboxes, |
123 |
| - _user: this.session.user.id.toString(), |
124 |
| - _sess: this.id, |
125 |
| - }; |
126 |
| - |
127 |
| - this._server.onXAPPLEPUSHSERVICE(accountID, deviceToken, subTopic, mailboxes, this.session, error => { |
128 |
| - if (error) { |
129 |
| - logdata._error = error.message; |
130 |
| - logdata._code = error.code; |
131 |
| - logdata._response = error.response; |
132 |
| - this._server.loggelf(logdata); |
133 |
| - |
134 |
| - return callback(null, { |
135 |
| - response: 'NO', |
136 |
| - code: 'TEMPFAIL', |
137 |
| - }); |
138 |
| - } |
139 |
| - |
140 |
| - // <https://opensource.apple.com/source/dovecot/dovecot-293/dovecot/src/imap/cmd-x-apple-push-service.c.auto.html> |
141 |
| - // <https://github.com/st3fan/dovecot-xaps-plugin/blob/3d1c71e0c78cc35ca6ead21f49a8e0e35e948a7c/xaps-imap-plugin.c#L158-L166> |
142 |
| - this.send(`* XAPPLEPUSHSERVICE aps-version "${version}" aps-topic "${subTopic}"`); |
143 |
| - callback(null, { |
144 |
| - response: 'OK', |
145 |
| - message: 'XAPPLEPUSHSERVICE Registration successful.' |
146 |
| - }); |
147 |
| - }); |
148 |
| - }, |
| 118 | + |
| 119 | + // Make sure all required keys (exept mailboxes) are present |
| 120 | + for (let requiredKey of requiredKeys) { |
| 121 | + if (!data[requiredKey] && requiredKey !== 'mailboxes') { |
| 122 | + return callback(null, { |
| 123 | + response: 'BAD', |
| 124 | + message: `Missing required arguments for ${command.command}` |
| 125 | + }); |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + const version = data['aps-version']; |
| 130 | + const accountID = data['aps-account-id']; |
| 131 | + const deviceToken = data['aps-device-token']; |
| 132 | + const subTopic = data['aps-subtopic']; |
| 133 | + const mailboxes = data.mailboxes || []; |
| 134 | + |
| 135 | + if (version !== '2') { |
| 136 | + return callback(null, { |
| 137 | + response: 'NO', |
| 138 | + message: 'Unsupported APS version', |
| 139 | + code: 'CLIENTBUG' |
| 140 | + }); |
| 141 | + } |
| 142 | + |
| 143 | + if (subTopic !== 'com.apple.mobilemail') { |
| 144 | + return callback(null, { |
| 145 | + response: 'NO', |
| 146 | + message: `Invalid subtopic for ${command.command}`, |
| 147 | + code: 'CLIENTBUG' |
| 148 | + }); |
| 149 | + } |
| 150 | + |
| 151 | + const logdata = { |
| 152 | + short_message: '[XAPPLEPUSHSERVICE]', |
| 153 | + _mail_action: 'xapplepushservice', |
| 154 | + _accountId: accountID, |
| 155 | + _deviceToken: deviceToken, |
| 156 | + _subTopic: subTopic, |
| 157 | + _mailboxes: mailboxes, |
| 158 | + _user: this.session.user.id.toString(), |
| 159 | + _sess: this.id |
| 160 | + }; |
| 161 | + |
| 162 | + this._server.onXAPPLEPUSHSERVICE(accountID, deviceToken, subTopic, mailboxes, this.session, error => { |
| 163 | + if (error) { |
| 164 | + logdata._error = error.message; |
| 165 | + logdata._code = error.code; |
| 166 | + logdata._response = error.response; |
| 167 | + this._server.loggelf(logdata); |
| 168 | + |
| 169 | + return callback(null, { |
| 170 | + response: 'NO', |
| 171 | + code: 'TEMPFAIL' |
| 172 | + }); |
| 173 | + } |
| 174 | + |
| 175 | + // <https://opensource.apple.com/source/dovecot/dovecot-293/dovecot/src/imap/cmd-x-apple-push-service.c.auto.html> |
| 176 | + // <https://github.com/st3fan/dovecot-xaps-plugin/blob/3d1c71e0c78cc35ca6ead21f49a8e0e35e948a7c/xaps-imap-plugin.c#L158-L166> |
| 177 | + this.send(`* XAPPLEPUSHSERVICE aps-version "${version}" aps-topic "${subTopic}"`); |
| 178 | + callback(null, { |
| 179 | + response: 'OK', |
| 180 | + message: 'XAPPLEPUSHSERVICE Registration successful.' |
| 181 | + }); |
| 182 | + }); |
| 183 | + } |
149 | 184 | };
|
0 commit comments