1- import type { IAppsTokens } from '@rocket.chat/core-typings' ;
2- import { Messages , AppsTokens , Users , Rooms , Settings } from '@rocket.chat/models' ;
3- import { Random } from '@rocket.chat/random' ;
4- import { ajv , validateBadRequestErrorResponse , validateUnauthorizedErrorResponse } from '@rocket.chat/rest-typings' ;
5- import { Match , check } from 'meteor/check' ;
1+ import { Push } from '@rocket.chat/core-services' ;
2+ import type { IPushToken } from '@rocket.chat/core-typings' ;
3+ import { Messages , PushToken , Users , Rooms , Settings } from '@rocket.chat/models' ;
4+ import {
5+ ajv ,
6+ isPushGetProps ,
7+ validateNotFoundErrorResponse ,
8+ validateBadRequestErrorResponse ,
9+ validateUnauthorizedErrorResponse ,
10+ validateForbiddenErrorResponse ,
11+ } from '@rocket.chat/rest-typings' ;
12+ import type { JSONSchemaType } from 'ajv' ;
613import { Meteor } from 'meteor/meteor' ;
714
815import { executePushTest } from '../../../../server/lib/pushConfig' ;
916import { canAccessRoomAsync } from '../../../authorization/server/functions/canAccessRoom' ;
10- import { pushUpdate } from '../../../push/server/methods' ;
1117import PushNotification from '../../../push-notifications/server/lib/PushNotification' ;
1218import { settings } from '../../../settings/server' ;
1319import type { ExtractRoutesFromAPI } from '../ApiClass' ;
1420import { API } from '../api' ;
21+ import type { SuccessResult } from '../definition' ;
1522
16- API . v1 . addRoute (
17- 'push.token' ,
18- { authRequired : true } ,
19- {
20- async post ( ) {
21- const { id , type , value , appName } = this . bodyParams ;
23+ type PushTokenPOST = {
24+ id ?: string ;
25+ type : 'apn' | 'gcm' ;
26+ value : string ;
27+ appName : string ;
28+ } ;
2229
23- if ( id && typeof id !== 'string' ) {
24- throw new Meteor . Error ( 'error-id-param-not-valid' , 'The required "id" body param is invalid.' ) ;
25- }
30+ const PushTokenPOSTSchema : JSONSchemaType < PushTokenPOST > = {
31+ type : 'object' ,
32+ properties : {
33+ id : {
34+ type : 'string' ,
35+ nullable : true ,
36+ } ,
37+ type : {
38+ type : 'string' ,
39+ enum : [ 'apn' , 'gcm' ] ,
40+ } ,
41+ value : {
42+ type : 'string' ,
43+ minLength : 1 ,
44+ } ,
45+ appName : {
46+ type : 'string' ,
47+ minLength : 1 ,
48+ } ,
49+ } ,
50+ required : [ 'type' , 'value' , 'appName' ] ,
51+ additionalProperties : false ,
52+ } ;
2653
27- const deviceId = id || Random . id ( ) ;
54+ export const isPushTokenPOSTProps = ajv . compile < PushTokenPOST > ( PushTokenPOSTSchema ) ;
2855
29- if ( ! type || ( type !== 'apn' && type !== 'gcm' ) ) {
30- throw new Meteor . Error ( 'error-type-param-not-valid' , 'The required "type" body param is missing or invalid.' ) ;
31- }
56+ type PushTokenDELETE = {
57+ token : string ;
58+ } ;
3259
33- if ( ! value || typeof value !== 'string' ) {
34- throw new Meteor . Error ( 'error-token-param-not-valid' , 'The required "value" body param is missing or invalid.' ) ;
35- }
60+ const PushTokenDELETESchema : JSONSchemaType < PushTokenDELETE > = {
61+ type : 'object' ,
62+ properties : {
63+ token : {
64+ type : 'string' ,
65+ minLength : 1 ,
66+ } ,
67+ } ,
68+ required : [ 'token' ] ,
69+ additionalProperties : false ,
70+ } ;
3671
37- if ( ! appName || typeof appName !== 'string' ) {
38- throw new Meteor . Error ( 'error-appName-param-not-valid' , 'The required "appName" body param is missing or invalid.' ) ;
39- }
72+ export const isPushTokenDELETEProps = ajv . compile < PushTokenDELETE > ( PushTokenDELETESchema ) ;
4073
41- const authToken = this . request . headers . get ( 'x-auth-token' ) ;
42- if ( ! authToken ) {
74+ type PushTokenResult = Pick < IPushToken , '_id' | 'token' | 'appName' | 'userId' | 'enabled' | 'createdAt' | '_updatedAt' > ;
75+
76+ /**
77+ * Pick only the attributes we actually want to return on the endpoint, ensuring nothing from older schemas get mixed in
78+ */
79+ function cleanTokenResult ( result : Omit < IPushToken , 'authToken' > ) : PushTokenResult {
80+ const { _id, token, appName, userId, enabled, createdAt, _updatedAt } = result ;
81+
82+ return {
83+ _id,
84+ token,
85+ appName,
86+ userId,
87+ enabled,
88+ createdAt,
89+ _updatedAt,
90+ } ;
91+ }
92+
93+ const pushEndpoints = API . v1
94+ . post (
95+ 'push.token' ,
96+ {
97+ response : {
98+ 200 : ajv . compile < SuccessResult < { result : PushTokenResult } > [ 'body' ] > ( {
99+ additionalProperties : false ,
100+ type : 'object' ,
101+ properties : {
102+ success : {
103+ type : 'boolean' ,
104+ description : 'Indicates if the request was successful.' ,
105+ } ,
106+ result : {
107+ type : 'object' ,
108+ description : 'The updated token data for this device' ,
109+ properties : {
110+ _id : {
111+ type : 'string' ,
112+ } ,
113+ token : {
114+ type : 'object' ,
115+ properties : {
116+ apn : {
117+ type : 'string' ,
118+ } ,
119+ gcm : {
120+ type : 'string' ,
121+ } ,
122+ } ,
123+ required : [ ] ,
124+ additionalProperties : false ,
125+ } ,
126+ appName : {
127+ type : 'string' ,
128+ } ,
129+ userId : {
130+ type : 'string' ,
131+ nullable : true ,
132+ } ,
133+ enabled : {
134+ type : 'boolean' ,
135+ } ,
136+ createdAt : {
137+ type : 'string' ,
138+ } ,
139+ _updatedAt : {
140+ type : 'string' ,
141+ } ,
142+ } ,
143+ additionalProperties : false ,
144+ } ,
145+ } ,
146+ required : [ 'success' , 'result' ] ,
147+ } ) ,
148+ 400 : validateBadRequestErrorResponse ,
149+ 401 : validateUnauthorizedErrorResponse ,
150+ 403 : validateForbiddenErrorResponse ,
151+ } ,
152+ body : isPushTokenPOSTProps ,
153+ authRequired : true ,
154+ } ,
155+ async function action ( ) {
156+ const { id, type, value, appName } = this . bodyParams ;
157+
158+ const rawToken = this . request . headers . get ( 'x-auth-token' ) ;
159+ if ( ! rawToken ) {
43160 throw new Meteor . Error ( 'error-authToken-param-not-valid' , 'The required "authToken" header param is missing or invalid.' ) ;
44161 }
162+ const authToken = Accounts . _hashLoginToken ( rawToken ) ;
45163
46- const result = await pushUpdate ( {
47- id : deviceId ,
48- token : { [ type ] : value } as IAppsTokens [ 'token' ] ,
164+ const result = await Push . registerPushToken ( {
165+ ... ( id && { _id : id } ) ,
166+ token : { [ type ] : value } as IPushToken [ 'token' ] ,
49167 authToken,
50168 appName,
51169 userId : this . userId ,
52170 } ) ;
53171
54- return API . v1 . success ( { result } ) ;
172+ return API . v1 . success ( { result : cleanTokenResult ( result ) } ) ;
173+ } ,
174+ )
175+ . delete (
176+ 'push.token' ,
177+ {
178+ response : {
179+ 200 : ajv . compile < void > ( {
180+ additionalProperties : false ,
181+ type : 'object' ,
182+ properties : {
183+ success : {
184+ type : 'boolean' ,
185+ } ,
186+ } ,
187+ required : [ 'success' ] ,
188+ } ) ,
189+ 400 : validateBadRequestErrorResponse ,
190+ 401 : validateUnauthorizedErrorResponse ,
191+ 403 : validateForbiddenErrorResponse ,
192+ 404 : validateNotFoundErrorResponse ,
193+ } ,
194+ body : isPushTokenDELETEProps ,
195+ authRequired : true ,
55196 } ,
56- async delete ( ) {
197+ async function action ( ) {
57198 const { token } = this . bodyParams ;
58199
59- if ( ! token || typeof token !== 'string' ) {
60- throw new Meteor . Error ( 'error-token-param-not-valid' , 'The required "token" body param is missing or invalid.' ) ;
61- }
62-
63- const affectedRecords = (
64- await AppsTokens . deleteMany ( {
65- $or : [
66- {
67- 'token.apn' : token ,
68- } ,
69- {
70- 'token.gcm' : token ,
71- } ,
72- ] ,
73- userId : this . userId ,
74- } )
75- ) . deletedCount ;
200+ const removeResult = await PushToken . removeAllByTokenStringAndUserId ( token , this . userId ) ;
76201
77- if ( affectedRecords === 0 ) {
202+ if ( removeResult . deletedCount === 0 ) {
78203 return API . v1 . notFound ( ) ;
79204 }
80205
81206 return API . v1 . success ( ) ;
82207 } ,
83- } ,
84- ) ;
85-
86- API . v1 . addRoute (
87- 'push.get' ,
88- { authRequired : true } ,
89- {
90- async get ( ) {
91- const params = this . queryParams ;
92- check (
93- params ,
94- Match . ObjectIncluding ( {
95- id : String ,
208+ )
209+ . get (
210+ 'push.get' ,
211+ {
212+ authRequired : true ,
213+ query : isPushGetProps ,
214+ response : {
215+ 200 : ajv . compile < { data : { message : object ; notification : object } ; success : true } > ( {
216+ type : 'object' ,
217+ properties : {
218+ data : {
219+ type : 'object' ,
220+ properties : {
221+ message : { type : 'object' , additionalProperties : true } ,
222+ notification : { type : 'object' , additionalProperties : true } ,
223+ } ,
224+ required : [ 'message' , 'notification' ] ,
225+ additionalProperties : false ,
226+ } ,
227+ success : { type : 'boolean' , enum : [ true ] } ,
228+ } ,
229+ required : [ 'data' , 'success' ] ,
230+ additionalProperties : false ,
96231 } ) ,
97- ) ;
232+ 400 : validateBadRequestErrorResponse ,
233+ 401 : validateUnauthorizedErrorResponse ,
234+ } ,
235+ } ,
236+ async function action ( ) {
237+ const { id } = this . queryParams ;
98238
99239 const receiver = await Users . findOneById ( this . userId ) ;
100240 if ( ! receiver ) {
101241 throw new Error ( 'error-user-not-found' ) ;
102242 }
103243
104- const message = await Messages . findOneById ( params . id ) ;
244+ const message = await Messages . findOneById ( id ) ;
105245 if ( ! message ) {
106246 throw new Error ( 'error-message-not-found' ) ;
107247 }
@@ -119,25 +259,36 @@ API.v1.addRoute(
119259
120260 return API . v1 . success ( { data } ) ;
121261 } ,
122- } ,
123- ) ;
124-
125- API . v1 . addRoute (
126- 'push.info' ,
127- { authRequired : true } ,
128- {
129- async get ( ) {
262+ )
263+ . get (
264+ 'push.info' ,
265+ {
266+ authRequired : true ,
267+ response : {
268+ 200 : ajv . compile < { pushGatewayEnabled : boolean ; defaultPushGateway : boolean ; success : true } > ( {
269+ type : 'object' ,
270+ properties : {
271+ pushGatewayEnabled : { type : 'boolean' } ,
272+ defaultPushGateway : { type : 'boolean' } ,
273+ success : { type : 'boolean' , enum : [ true ] } ,
274+ } ,
275+ required : [ 'pushGatewayEnabled' , 'defaultPushGateway' , 'success' ] ,
276+ additionalProperties : false ,
277+ } ) ,
278+ 401 : validateUnauthorizedErrorResponse ,
279+ } ,
280+ } ,
281+ async function action ( ) {
130282 const defaultGateway = ( await Settings . findOneById ( 'Push_gateway' , { projection : { packageValue : 1 } } ) ) ?. packageValue ;
131283 const defaultPushGateway = settings . get ( 'Push_gateway' ) === defaultGateway ;
132284 return API . v1 . success ( {
133- pushGatewayEnabled : settings . get ( 'Push_enable' ) ,
285+ pushGatewayEnabled : settings . get < boolean > ( 'Push_enable' ) ,
134286 defaultPushGateway,
135287 } ) ;
136288 } ,
137- } ,
138- ) ;
289+ ) ;
139290
140- const pushEndpoints = API . v1 . post (
291+ const pushTestEndpoints = API . v1 . post (
141292 'push.test' ,
142293 {
143294 authRequired : true ,
@@ -177,9 +328,13 @@ const pushEndpoints = API.v1.post(
177328 } ,
178329) ;
179330
180- export type PushEndpoints = ExtractRoutesFromAPI < typeof pushEndpoints > ;
331+ type PushTestEndpoints = ExtractRoutesFromAPI < typeof pushTestEndpoints > ;
332+
333+ type PushTokenEndpoints = ExtractRoutesFromAPI < typeof pushEndpoints > ;
334+
335+ type PushAllEndpoints = PushTestEndpoints & PushTokenEndpoints ;
181336
182337declare module '@rocket.chat/rest-typings' {
183338 // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
184- interface Endpoints extends PushEndpoints { }
339+ interface Endpoints extends PushAllEndpoints { }
185340}
0 commit comments