Преглед на файлове

Improve null correctness in controller models

Danilo Bargen преди 6 години
родител
ревизия
e9b245391c
променени са 6 файла, в които са добавени 146 реда и са изтрити 84 реда
  1. 5 4
      src/controller_model/avatar.ts
  2. 28 17
      src/controller_model/contact.ts
  3. 35 21
      src/controller_model/distributionList.ts
  4. 59 31
      src/controller_model/group.ts
  5. 12 4
      src/controller_model/me.ts
  6. 7 7
      src/services/webclient.ts

+ 5 - 4
src/controller_model/avatar.ts

@@ -15,27 +15,28 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {hasValue} from '../helpers';
 import {WebClientService} from '../services/webclient';
 
 export class AvatarControllerModel {
     private logTag: string = '[AvatarControllerModel]';
 
     private $log: ng.ILogService;
-    private avatar: ArrayBuffer = null;
+    private avatar: ArrayBuffer | null = null;
     private loadAvatar: Promise<ArrayBuffer | null>;
     private onChangeAvatar: (image: ArrayBuffer) => void;
     private _avatarChanged: boolean = false;
 
     constructor($log: ng.ILogService,
                 webClientService: WebClientService,
-                receiver: threema.Receiver) {
+                receiver: threema.Receiver | null) {
         this.$log = $log;
         this.loadAvatar = new Promise((resolve, reject) => {
-            if (receiver === null) {
+            if (!hasValue(receiver)) {
                 $log.debug(this.logTag, 'loadAvatar: No receiver defined, no avatar');
                 resolve(null);
                 return;
-            } else if (receiver.avatar.high === undefined || receiver.avatar.high === null) {
+            } else if (!hasValue(receiver.avatar) || !hasValue(receiver.avatar.high)) {
                 $log.debug(this.logTag, 'loadAvatar: Requesting high res avatar from app');
                 webClientService.requestAvatar(receiver, true)
                     .then((data: ArrayBuffer) => resolve(data))

+ 28 - 17
src/controller_model/contact.ts

@@ -22,6 +22,7 @@ import {AvatarControllerModel} from './avatar';
 import ControllerModelMode = threema.ControllerModelMode;
 
 export class ContactControllerModel implements threema.ControllerModel<threema.ContactReceiver> {
+    private logTag = '[ContactControllerModel]';
 
     // Angular services
     private $log: ng.ILogService;
@@ -29,14 +30,14 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
     private $mdDialog: ng.material.IDialogService;
 
     private onRemovedCallback: threema.OnRemovedCallback;
-    public firstName: string;
-    public lastName: string;
+    public firstName?: string;
+    public lastName?: string;
     public identity: string;
     public subject: string;
     public access: threema.ContactReceiverAccess;
     public isLoading = false;
 
-    private contact: threema.ContactReceiver;
+    private contact: threema.ContactReceiver | null;
     private webClientService: WebClientService;
     private firstNameLabel: string;
     private avatarController: AvatarControllerModel;
@@ -49,20 +50,26 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
         this.$log = $log;
         this.$translate = $translate;
         this.$mdDialog = $mdDialog;
-        this.contact = contact;
+        if (contact === undefined) {
+            if (mode !== ControllerModelMode.NEW) {
+                throw new Error('ContactControllerModel: Contact may not be undefined for mode ' + mode);
+            }
+        } else {
+            this.contact = contact;
+        }
         this.webClientService = webClientService;
         this.mode = mode;
 
         switch (this.getMode()) {
             case ControllerModelMode.EDIT:
                 this.subject = $translate.instant('messenger.EDIT_RECEIVER');
-                this.firstName = this.contact.firstName;
-                this.lastName = this.contact.lastName;
+                this.firstName = this.contact!.firstName;
+                this.lastName = this.contact!.lastName;
                 this.avatarController = new AvatarControllerModel(
                     this.$log, this.webClientService, this.contact,
                 );
 
-                this.access = this.contact.access;
+                this.access = this.contact!.access;
                 this.firstNameLabel = this.access.canChangeLastName ?
                     $translate.instant('messenger.FIRST_NAME') :
                     $translate.instant('messenger.NAME');
@@ -70,8 +77,8 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
 
             case ControllerModelMode.VIEW:
             case ControllerModelMode.CHAT:
-                this.subject = this.contact.displayName;
-                this.access = this.contact.access;
+                this.subject = this.contact!.displayName;
+                this.access = this.contact!.access;
                 break;
 
             case ControllerModelMode.NEW:
@@ -79,7 +86,7 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
                 break;
 
             default:
-                $log.error('Invalid controller model mode: ', this.getMode());
+                $log.error(this.logTag, 'Invalid controller model mode: ', this.getMode());
         }
     }
 
@@ -100,7 +107,7 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
     }
 
     public canChat(): boolean {
-        return this.contact.id !== this.webClientService.me.id;
+        return this.contact !== null && this.contact.id !== this.webClientService.me.id;
     }
 
     public canEdit(): boolean {
@@ -126,13 +133,17 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
         this.$mdDialog.show(confirm).then(() => {
             this.reallyClean();
         }, () => {
-            this.$log.debug('clean canceled');
+            this.$log.debug(this.logTag, 'Clean canceled');
         });
     }
 
     private reallyClean(): any {
+        if (!this.contact) {
+            this.$log.error(this.logTag, 'reallyClean: Contact is null');
+            return;
+        }
         if (!this.canClean()) {
-            this.$log.error('not allowed to clean this contact');
+            this.$log.error(this.logTag, 'Not allowed to clean this contact');
             return;
         }
 
@@ -143,7 +154,7 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
             })
             .catch((error) => {
                 // TODO: Handle this properly / show an error message
-                this.$log.error(`Cleaning receiver conversation failed: ${error}`);
+                this.$log.error(this.logTag, `Cleaning receiver conversation failed: ${error}`);
                 this.isLoading = false;
             });
     }
@@ -156,7 +167,7 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
         switch (this.getMode()) {
             case ControllerModelMode.EDIT:
                 return this.webClientService.modifyContact(
-                    this.contact.id,
+                    this.contact!.id,
                     this.firstName,
                     this.lastName,
                     this.avatarController.avatarChanged ? this.avatarController.getAvatar() : undefined,
@@ -164,8 +175,8 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
             case ControllerModelMode.NEW:
                 return this.webClientService.addContact(this.identity);
             default:
-                this.$log.error('not allowed to save contact');
-
+                this.$log.error(this.logTag, 'Cannot save contact, invalid mode');
+                return Promise.reject('Cannot save contact, invalid mode');
         }
     }
 

+ 35 - 21
src/controller_model/distributionList.ts

@@ -21,6 +21,7 @@ import {WebClientService} from '../services/webclient';
 import ControllerModelMode = threema.ControllerModelMode;
 
 export class DistributionListControllerModel implements threema.ControllerModel<threema.DistributionListReceiver> {
+    private logTag = '[DistributionListControllerModel]';
 
     private $log: ng.ILogService;
     private $translate: ng.translate.ITranslateService;
@@ -31,7 +32,7 @@ export class DistributionListControllerModel implements threema.ControllerModel<
     public isLoading = false;
 
     private addContactPlaceholder: string;
-    private distributionList: threema.DistributionListReceiver;
+    private distributionList: threema.DistributionListReceiver | null;
     private webClientService: WebClientService;
     private mode: ControllerModelMode;
     private onRemovedCallback: threema.OnRemovedCallback;
@@ -44,7 +45,14 @@ export class DistributionListControllerModel implements threema.ControllerModel<
         this.$translate = $translate;
         this.$mdDialog = $mdDialog;
 
-        this.distributionList = distributionList;
+        if (distributionList === undefined) {
+            if (mode !== ControllerModelMode.NEW) {
+                throw new Error('DistributionListControllerModel: Distribution list ' +
+                                'may not be undefined for mode ' + mode);
+            }
+        } else {
+            this.distributionList = distributionList;
+        }
         this.mode = mode;
         this.webClientService = webClientService;
         this.addContactPlaceholder = $translate.instant('messenger.DISTRIBUTION_LIST_SELECT_MEMBERS');
@@ -52,14 +60,14 @@ export class DistributionListControllerModel implements threema.ControllerModel<
         switch (this.getMode()) {
             case ControllerModelMode.EDIT:
                 this.subject = $translate.instant('messenger.EDIT_RECEIVER');
-                this.name = this.distributionList.displayName;
-                this.members = this.distributionList.members;
+                this.name = this.distributionList!.displayName;
+                this.members = this.distributionList!.members;
                 break;
 
             case ControllerModelMode.VIEW:
             case ControllerModelMode.CHAT:
-                this.subject = this.distributionList.displayName;
-                this.members = this.distributionList.members;
+                this.subject = this.distributionList!.displayName;
+                this.members = this.distributionList!.members;
                 break;
 
             case ControllerModelMode.NEW:
@@ -68,7 +76,7 @@ export class DistributionListControllerModel implements threema.ControllerModel<
                 break;
 
             default:
-                $log.error('Invalid controller model mode: ', this.getMode());
+                $log.error(this.logTag, 'Invalid controller model mode: ', this.getMode());
         }
     }
 
@@ -110,13 +118,17 @@ export class DistributionListControllerModel implements threema.ControllerModel<
         this.$mdDialog.show(confirm).then(() => {
             this.reallyClean();
         }, () => {
-            this.$log.debug('clean canceled');
+            this.$log.debug(this.logTag, 'Clean canceled');
         });
     }
 
     private reallyClean(): any {
+        if (!this.distributionList) {
+            this.$log.error(this.logTag, 'reallyClean: Distribution list is null');
+            return;
+        }
         if (!this.canClean()) {
-            this.$log.error('not allowed to clean this contact');
+            this.$log.error(this.logTag, 'Not allowed to clean this distribution list');
             return;
         }
 
@@ -127,7 +139,7 @@ export class DistributionListControllerModel implements threema.ControllerModel<
             })
             .catch((error) => {
                 // TODO: Handle this properly / show an error message
-                this.$log.error(`Cleaning receiver conversation failed: ${error}`);
+                this.$log.error(this.logTag, `Cleaning receiver conversation failed: ${error}`);
                 this.isLoading = false;
             });
     }
@@ -137,7 +149,6 @@ export class DistributionListControllerModel implements threema.ControllerModel<
     }
 
     public delete(ev): void {
-
         const confirm = this.$mdDialog.confirm()
             .title(this.$translate.instant('messenger.DISTRIBUTION_LIST_DELETE'))
             .textContent(this.$translate.instant('messenger.DISTRIBUTION_LIST_DELETE_REALLY'))
@@ -147,27 +158,30 @@ export class DistributionListControllerModel implements threema.ControllerModel<
 
         this.$mdDialog.show(confirm).then(() => {
             this.reallyDelete();
-
-            if (this.onRemovedCallback) {
-                this.onRemovedCallback(this.distributionList.id);
-            }
         }, () => {
-            this.$log.debug('delete canceled');
+            this.$log.debug(this.logTag, 'Delete canceled');
         });
     }
 
     private reallyDelete(): void {
+        if (!this.distributionList) {
+            this.$log.error(this.logTag, 'reallyDelete: Distribution list is null');
+            return;
+        }
         if (!this.distributionList.access.canDelete) {
-            this.$log.error('cannot delete distribution list');
+            this.$log.error(this.logTag, 'Not allowed to delete this distribution list');
             return;
         }
 
         this.isLoading = true;
         this.webClientService.deleteDistributionList(this.distributionList).then(() => {
             this.isLoading = false;
+            if (this.onRemovedCallback && this.distributionList !== null) {
+                this.onRemovedCallback(this.distributionList.id);
+            }
         }).catch((error) => {
             // TODO: Handle this properly / show an error message
-            this.$log.error(`Deleting distribution list failed: ${error}`);
+            this.$log.error(this.logTag, `Deleting distribution list failed: ${error}`);
             this.isLoading = false;
         });
     }
@@ -176,7 +190,7 @@ export class DistributionListControllerModel implements threema.ControllerModel<
         switch (this.getMode()) {
             case ControllerModelMode.EDIT:
                 return this.webClientService.modifyDistributionList(
-                    this.distributionList.id,
+                    this.distributionList!.id,
                     this.members,
                     this.name,
                 );
@@ -185,8 +199,8 @@ export class DistributionListControllerModel implements threema.ControllerModel<
                     this.members,
                     this.name);
             default:
-                this.$log.error('not allowed to save distribution list');
-
+                this.$log.error(this.logTag, 'Cannot save distribution list, invalid mode');
+                return Promise.reject('Cannot save distribution list, invalid mode');
         }
     }
 

+ 59 - 31
src/controller_model/group.ts

@@ -22,17 +22,20 @@ import {AvatarControllerModel} from './avatar';
 import ControllerModelMode = threema.ControllerModelMode;
 
 export class GroupControllerModel implements threema.ControllerModel<threema.GroupReceiver> {
+    private logTag = '[GroupControllerModel]';
 
     private $log: ng.ILogService;
     private $translate: ng.translate.ITranslateService;
     private $mdDialog: ng.material.IDialogService;
     public members: string[];
     public name: string;
+    public access: threema.GroupReceiverAccess;
     public subject: string;
+
     public isLoading = false; // TODO: Show loading indicator
 
     private addContactPlaceholder: string;
-    private group: threema.GroupReceiver;
+    private group: threema.GroupReceiver | null;
     private webClientService: WebClientService;
     private avatarController: AvatarControllerModel;
     private mode: ControllerModelMode;
@@ -46,7 +49,13 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
         this.$translate = $translate;
         this.$mdDialog = $mdDialog;
 
-        this.group = group;
+        if (group === undefined) {
+            if (mode !== ControllerModelMode.NEW) {
+                throw new Error('GroupControllerModel: Group may not be undefined for mode ' + mode);
+            }
+        } else {
+            this.group = group;
+        }
         this.mode = mode;
         this.webClientService = webClientService;
         this.addContactPlaceholder = $translate.instant('messenger.GROUP_SELECT_CONTACTS');
@@ -54,17 +63,19 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
         switch (this.getMode()) {
             case ControllerModelMode.EDIT:
                 this.subject = $translate.instant('messenger.EDIT_RECEIVER');
-                this.name = this.group.displayName;
-                this.members = this.group.members;
+                this.name = this.group!.displayName;
+                this.members = this.group!.members;
                 this.avatarController = new AvatarControllerModel(
-                    this.$log, this.webClientService, this.group,
+                    this.$log, this.webClientService, this.group!,
                 );
+                this.access = this.group!.access;
                 break;
 
             case ControllerModelMode.VIEW:
             case ControllerModelMode.CHAT:
-                this.subject = this.group.displayName;
-                this.members = this.group.members;
+                this.subject = this.group!.displayName;
+                this.members = this.group!.members;
+                this.access = this.group!.access;
                 break;
 
             case ControllerModelMode.NEW:
@@ -76,7 +87,7 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
                 break;
 
             default:
-                $log.error('Invalid controller model mode: ', this.getMode());
+                $log.error(this.logTag, 'Invalid controller model mode: ', this.getMode());
         }
     }
 
@@ -103,10 +114,10 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
     }
 
     public canEdit(): boolean {
-        return this.group.access !== undefined && (
-                this.group.access.canChangeAvatar === true
-                || this.group.access.canChangeName === true
-                || this.group.access.canChangeMembers === true
+        return this.access !== undefined && (
+                this.access.canChangeAvatar === true
+                || this.access.canChangeName === true
+                || this.access.canChangeMembers === true
             );
     }
 
@@ -130,8 +141,12 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
     }
 
     private reallyClean(): any {
+        if (!this.group) {
+            this.$log.error(this.logTag, 'reallyClean: Group is null');
+            return;
+        }
         if (!this.canClean()) {
-            this.$log.error('not allowed to clean this contact');
+            this.$log.error(this.logTag, 'Not allowed to clean this group');
             return;
         }
 
@@ -142,7 +157,7 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
             })
             .catch((error) => {
                 // TODO: Handle this properly / show an error message
-                this.$log.error(`Cleaning receiver conversation failed: ${error}`);
+                this.$log.error(this.logTag, `Cleaning receiver conversation failed: ${error}`);
                 this.isLoading = false;
             });
     }
@@ -152,6 +167,10 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
     }
 
     public leave(ev): void {
+        if (!this.group) {
+            this.$log.error(this.logTag, 'leave: Group is null');
+            return;
+        }
         const confirm = this.$mdDialog.confirm()
             .title(this.$translate.instant('messenger.GROUP_LEAVE'))
             .textContent(this.$translate.instant(
@@ -163,20 +182,20 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
             .cancel(this.$translate.instant('common.CANCEL'));
 
         this.$mdDialog.show(confirm).then(() => {
-            this.reallyLeave();
+            this.reallyLeave(this.group!);
         }, () => {
-            this.$log.debug('leave canceled');
+            this.$log.debug(this.logTag, 'Leave canceled');
         });
     }
 
-    private reallyLeave(): void {
-        if (!this.group.access.canLeave) {
-            this.$log.error('cannot leave group');
+    private reallyLeave(group: threema.GroupReceiver): void {
+        if (!group.access.canLeave) {
+            this.$log.error(this.logTag, 'Cannot leave group');
             return;
         }
 
         this.isLoading = true;
-        this.webClientService.leaveGroup(this.group)
+        this.webClientService.leaveGroup(group)
             .then(() => {
                 this.isLoading = false;
             })
@@ -188,6 +207,11 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
     }
 
     public delete(ev): void {
+        if (!this.group) {
+            this.$log.error(this.logTag, 'delete: Group is null');
+            return;
+        }
+
         const confirm = this.$mdDialog.confirm()
             .title(this.$translate.instant('messenger.GROUP_DELETE'))
             .textContent(this.$translate.instant('messenger.GROUP_DELETE_REALLY'))
@@ -196,24 +220,24 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
             .cancel(this.$translate.instant('common.CANCEL'));
 
         this.$mdDialog.show(confirm).then(() => {
-            this.reallyDelete();
+            this.reallyDelete(this.group!);
         }, () => {
             this.$log.debug('delete canceled');
         });
     }
 
-    private reallyDelete(): void {
-        if (!this.group.access.canDelete) {
+    private reallyDelete(group: threema.GroupReceiver): void {
+        if (!this.access.canDelete) {
             this.$log.error('can not delete group');
             return;
         }
 
         this.isLoading = true;
-        this.webClientService.deleteGroup(this.group)
+        this.webClientService.deleteGroup(group)
             .then(() => {
                 this.isLoading = false;
                 if (this.onRemovedCallback) {
-                    this.onRemovedCallback(this.group.id);
+                    this.onRemovedCallback(group.id);
                 }
             })
             .catch((error) => {
@@ -224,8 +248,12 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
     }
 
     public sync(ev): void {
-        if (!this.group.access.canSync) {
-            this.$log.error('cannot sync group');
+        if (!this.group) {
+            this.$log.error(this.logTag, 'sync: Group is null');
+            return;
+        }
+        if (!this.access.canSync) {
+            this.$log.error(this.logTag, 'Cannot sync group');
             return;
         }
 
@@ -244,7 +272,7 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
         switch (this.getMode()) {
             case ControllerModelMode.EDIT:
                 return this.webClientService.modifyGroup(
-                    this.group.id,
+                    this.group!.id,
                     this.members,
                     this.name,
                     this.avatarController.avatarChanged ? this.avatarController.getAvatar() : undefined,
@@ -256,8 +284,8 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
                     this.avatarController.avatarChanged ? this.avatarController.getAvatar() : undefined,
                 );
             default:
-                this.$log.error('not allowed to save group');
-
+                this.$log.error(this.logTag, 'Cannot save group, invalid mode');
+                return Promise.reject('Cannot save group, invalid mode');
         }
     }
 
@@ -279,7 +307,7 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
         this.$mdDialog.show(
             this.$mdDialog.alert()
                 .clickOutsideToClose(true)
-                .title(this.group.displayName)
+                .title(this.group ? this.group.displayName : 'Error')
                 .textContent(this.$translate.instant('validationError.modifyReceiver.' + errorCode))
                 .ok(this.$translate.instant('common.OK')),
         );

+ 12 - 4
src/controller_model/me.ts

@@ -15,6 +15,7 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {hasValue} from '../helpers';
 import {WebClientService} from '../services/webclient';
 import {AvatarControllerModel} from './avatar';
 
@@ -52,7 +53,7 @@ export class MeControllerModel implements threema.ControllerModel<threema.MeRece
                 $mdDialog: ng.material.IDialogService,
                 webClientService: WebClientService,
                 mode: ControllerModelMode,
-                me?: threema.MeReceiver) {
+                me: threema.MeReceiver) {
         this.$log = $log;
         this.$translate = $translate;
         this.$mdDialog = $mdDialog;
@@ -60,7 +61,7 @@ export class MeControllerModel implements threema.ControllerModel<threema.MeRece
         this.webClientService = webClientService;
         this.mode = mode;
 
-        this.nickname = webClientService.me.publicNickname;
+        this.nickname = webClientService.me.publicNickname || '';  // TODO: Make ARP publicNickname non-optional
         switch (mode) {
             case ControllerModelMode.EDIT:
                 this.subject = $translate.instant('messenger.EDIT_RECEIVER');
@@ -139,7 +140,7 @@ export class MeControllerModel implements threema.ControllerModel<threema.MeRece
      * The profile can be edited if there are no MDM restrictions.
      */
     public canEdit(): boolean {
-        const mdm: threema.MdmRestrictions = this.webClientService.appCapabilities.mdm;
+        const mdm = this.webClientService.appCapabilities.mdm;
         return mdm === undefined || !mdm.readonlyProfile;
     }
 
@@ -160,7 +161,14 @@ export class MeControllerModel implements threema.ControllerModel<threema.MeRece
                     // Profile was successfully updated. Update local data.
                     this.webClientService.me.publicNickname = this.nickname;
                     if (this.avatarController.avatarChanged) {
-                        this.webClientService.me.avatar.high = this.avatarController.getAvatar();
+                        const avatar = this.avatarController.getAvatar();
+                        if (avatar) {
+                            if (!hasValue(this.webClientService.me.avatar)) {
+                                this.webClientService.me.avatar = {high: avatar};
+                            } else {
+                                this.webClientService.me.avatar.high = avatar;
+                            }
+                        }
                     }
                     return this.me;
                 });

+ 7 - 7
src/services/webclient.ts

@@ -1775,7 +1775,7 @@ export class WebClientService {
         threemaId: string,
         firstName?: string,
         lastName?: string,
-        avatar?: ArrayBuffer,
+        avatar?: ArrayBuffer | null,
     ): Promise<threema.ContactReceiver> {
         // Prepare payload data
         const data = {};
@@ -1847,15 +1847,15 @@ export class WebClientService {
      */
     public createGroup(
         members: string[],
-        name: string = null,
-        avatar?: ArrayBuffer,
+        name: string | null = null,
+        avatar?: ArrayBuffer | null,
     ): Promise<threema.GroupReceiver> {
         const data = {
             [WebClientService.ARGUMENT_MEMBERS]: members,
             [WebClientService.ARGUMENT_NAME]: name,
         } as object;
 
-        if (avatar !== undefined) {
+        if (hasValue(avatar)) {
             data[WebClientService.ARGUMENT_AVATAR] = avatar;
         }
 
@@ -1870,7 +1870,7 @@ export class WebClientService {
         id: string,
         members: string[],
         name?: string,
-        avatar?: ArrayBuffer,
+        avatar?: ArrayBuffer | null,
     ): Promise<threema.GroupReceiver> {
         // Prepare payload data
         const data = {
@@ -2006,7 +2006,7 @@ export class WebClientService {
     /**
      * Modify own profile.
      */
-    public modifyProfile(nickname?: string, avatar?: ArrayBuffer): Promise<null> {
+    public modifyProfile(nickname?: string, avatar?: ArrayBuffer | null): Promise<null> {
         // Prepare payload data
         const data = {};
         if (nickname !== undefined && nickname !== null) {
@@ -2586,7 +2586,7 @@ export class WebClientService {
         // Set avatar for receiver according to resolution
         const field: string = highResolution ? 'high' : 'low';
         const receiverData = this.receivers.getData(args);
-        if (receiverData.avatar === null || receiverData.avatar === undefined) {
+        if (!hasValue(receiverData.avatar)) {
             receiverData.avatar = {};
         }
         receiverData.avatar[field] = avatar;