Ver Fonte

Replace featureLevel with featureMask (#487)

Check featureMask instead old-school featureLevel
Silly há 7 anos atrás
pai
commit
b23dfc8d5e
6 ficheiros alterados com 164 adições e 148 exclusões
  1. 6 3
      src/directives/member_list_editor.ts
  2. 19 0
      src/helpers.ts
  3. 17 15
      src/services/webclient.ts
  4. 10 2
      src/threema.d.ts
  5. 0 54
      tests/helpers.js
  6. 112 74
      tests/service/string.js

+ 6 - 3
src/directives/member_list_editor.ts

@@ -15,11 +15,12 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {hasFeature} from '../helpers';
 import {WebClientService} from '../services/webclient';
 
 export default [
-    'WebClientService',
-    function(webClientService: WebClientService) {
+    '$log', 'WebClientService',
+    function($log: ng.ILogService, webClientService: WebClientService) {
         return {
             restrict: 'EA',
             scope: {},
@@ -36,7 +37,9 @@ export default [
                 this.allContacts = Array
                     .from(webClientService.contacts.values())
                     .filter((contactReceiver: threema.ContactReceiver) => {
-                        return contactReceiver.featureLevel >= 0;
+                        return hasFeature(contactReceiver,
+                            threema.ContactReceiverFeature.GROUP_CHAT,
+                            $log);
                     }) as threema.ContactReceiver[];
 
                 this.selectedItemChange = (contactReceiver: threema.ContactReceiver) => {

+ 19 - 0
src/helpers.ts

@@ -260,3 +260,22 @@ export function escapeRegExp(str: string) {
 export function msgpackVisualizer(bytes: Uint8Array): string {
     return 'https://msgpack.dbrgn.ch#base64=' + encodeURIComponent(btoa(bytes as any));
 }
+
+/**
+ * Check the featureMask of a contactReceiver
+ */
+export function hasFeature(contactReceiver: threema.ContactReceiver,
+                           feature: threema.ContactReceiverFeature,
+                           $log: ng.ILogService): boolean {
+    if (contactReceiver !== undefined) {
+        if (contactReceiver.featureMask === 0) {
+            $log.warn(contactReceiver.id, 'featureMask', contactReceiver.featureMask);
+            return false;
+        }
+        // tslint:disable:no-bitwise
+        return (contactReceiver.featureMask & feature) !== 0;
+        // tslint:enable:no-bitwise
+    }
+    $log.warn('Cannot check featureMask of a undefined contactReceiver');
+    return false;
+}

+ 17 - 15
src/services/webclient.ts

@@ -19,7 +19,7 @@
 /// <reference types="@saltyrtc/task-relayed-data" />
 
 import * as msgpack from 'msgpack-lite';
-import {hexToU8a, msgpackVisualizer} from '../helpers';
+import {hasFeature, hexToU8a, msgpackVisualizer} from '../helpers';
 import {isContactReceiver, isDistributionListReceiver, isGroupReceiver} from '../typeguards';
 import {BatteryStatusService} from './battery';
 import {BrowserService} from './browser';
@@ -37,6 +37,7 @@ import {VersionService} from './version';
 
 // Aliases
 import InitializationStep = threema.InitializationStep;
+import ContactReceiverFeature = threema.ContactReceiverFeature;
 
 /**
  * This service handles everything related to the communication with the peer.
@@ -943,20 +944,20 @@ export class WebClientService {
                             return reject(this.$translate.instant('error.FILE_TOO_LARGE'));
                         }
 
-                        // Determine required feature level
-                        let requiredFeatureLevel = 3;
-                        let invalidFeatureLevelMessage = 'error.FILE_MESSAGES_NOT_SUPPORTED';
+                        // Determine required feature mask
+                        let requiredFeature: ContactReceiverFeature = ContactReceiverFeature.FILE;
+                        let invalidFeatureMessage = 'error.FILE_MESSAGES_NOT_SUPPORTED';
                         if ((message as threema.FileMessageData).sendAsFile !== true) {
                             // check mime type
                             const mime = (message as threema.FileMessageData).fileType;
 
                             if (this.mimeService.isAudio(mime)) {
-                                requiredFeatureLevel = 1;
-                                invalidFeatureLevelMessage = 'error.AUDIO_MESSAGES_NOT_SUPPORTED';
+                                requiredFeature = ContactReceiverFeature.AUDIO;
+                                invalidFeatureMessage = 'error.AUDIO_MESSAGES_NOT_SUPPORTED';
                             } else if (this.mimeService.isImage(mime)
                                 || this.mimeService.isVideo(mime)) {
-                                requiredFeatureLevel = 0;
-                                invalidFeatureLevelMessage = 'error.MESSAGE_NOT_SUPPORTED';
+                                requiredFeature = ContactReceiverFeature.AUDIO;
+                                invalidFeatureMessage = 'error.MESSAGE_NOT_SUPPORTED';
                             }
                         }
 
@@ -965,7 +966,7 @@ export class WebClientService {
                         // check receiver
                         switch (receiver.type) {
                             case 'distributionList':
-                                return reject(this.$translate.instant(invalidFeatureLevelMessage, {
+                                return reject(this.$translate.instant(invalidFeatureMessage, {
                                     receiverName: receiver.displayName}));
                             case 'group':
                                 const unsupportedMembers = [];
@@ -978,14 +979,15 @@ export class WebClientService {
                                     if (identity !== this.me.id) {
                                         // tslint:disable-next-line: no-shadowed-variable
                                         const contact = this.contacts.get(identity);
-                                        if (contact !== undefined && contact.featureLevel < requiredFeatureLevel) {
+                                        if (contact === undefined
+                                            || !hasFeature(contact, requiredFeature, this.$log)) {
                                             unsupportedMembers.push(contact.displayName);
                                         }
                                     }
                                 });
 
                                 if (unsupportedMembers.length > 0) {
-                                    return reject(this.$translate.instant(invalidFeatureLevelMessage, {
+                                    return reject(this.$translate.instant(invalidFeatureMessage, {
                                         receiverName: unsupportedMembers.join(',')}));
                                 }
                                 break;
@@ -994,10 +996,10 @@ export class WebClientService {
                                 if (contact === undefined) {
                                     this.$log.error('Cannot retrieve contact');
                                     return reject(this.$translate.instant('error.ERROR_OCCURRED'));
-                                } else if (contact.featureLevel < requiredFeatureLevel) {
+                                } else if (!hasFeature(contact, requiredFeature, this.$log)) {
                                     this.$log.debug('Cannot send message: Feature level mismatch:',
-                                        contact.featureLevel, '<', requiredFeatureLevel);
-                                    return reject(this.$translate.instant(invalidFeatureLevelMessage, {
+                                        contact.featureMask, 'does not include', requiredFeature);
+                                    return reject(this.$translate.instant(invalidFeatureMessage, {
                                         receiverName: contact.displayName}));
                                 }
                                 break;
@@ -2182,7 +2184,7 @@ export class WebClientService {
             avatar: {
                 high: data.avatar,
             },
-            featureLevel: 3,
+            featureMask: 0xFF,
             verificationLevel: 3,
             state: 'ACTIVE',
             access: {

+ 10 - 2
src/threema.d.ts

@@ -233,8 +233,8 @@ declare namespace threema {
         // Verification level integer (1-3)
         verificationLevel?: number;
 
-        // Feature level (0-3)
-        featureLevel: number | null;
+        // Feature mask
+        featureMask: number | null;
 
         // The identity state
         state: 'ACTIVE' | 'INACTIVE';
@@ -476,6 +476,14 @@ declare namespace threema {
         CHAT = 'chat',
     }
 
+    const enum ContactReceiverFeature {
+        AUDIO = 0x01,
+        GROUP_CHAT = 0x02,
+        BALLOT = 0x04,
+        FILE = 0x08,
+        VOIP = 0x10,
+    }
+
     interface ControllerModel<T extends BaseReceiver> {
         /**
          * The title shown in the header.

+ 0 - 54
tests/helpers.js

@@ -1,54 +0,0 @@
-describe('Helpers', function () {
-
-    let stringService;
-
-    // Ignoring page reload request
-    beforeAll(() => window.onbeforeunload = () => null);
-
-    beforeEach(function () {
-        // Load 3ema.services
-        module('3ema.services');
-
-        // Inject the $filter function
-        inject(function(StringService) {
-            stringService = StringService;
-        });
-
-    });
-
-    describe('byteChunkSplit', function() {
-        this.testPatterns = (cases, size, offset) => {
-            for (let testcase of cases) {
-                const input = testcase[0];
-                const expected = testcase[1];
-                expect(stringService.byteChunk(input, size, offset)).toEqual(expected);
-            }
-        };
-
-        it('short chunks', () => {
-            this.testPatterns([
-                ['abc',
-                    ['abc',]],
-                ['abcdefghijklmn',
-                    ['abcdef', 'ghijkl', 'mn',]],
-                // four byte emoji
-                ['😅😅',
-                    ['😅', '😅']]
-            ], 6, null);
-        });
-
-
-        it('chunks with offset', () => {
-            this.testPatterns([
-                ['The quick white 🐼. He jumped over the lazy 🐶.',
-                    ['The', 'quick', 'white', '🐼.', 'He', 'jumped', 'over', 'the', 'lazy', '🐶.',]],
-            ], 6, 10);
-
-            this.testPatterns([
-                ['The quick white 🐼. He jumped over the lazy 🐶.',
-                    ['The quick white 🐼', '. He jumped over the', 'lazy 🐶.',]],
-            ], 20, 10);
-        });
-    });
-
-});

+ 112 - 74
tests/service/string.js

@@ -16,87 +16,125 @@ describe('StringService', function() {
 
     });
 
-    it('parse null string', () => {
-        expect($service.getWord(null, 1)).toEqual(jasmine.objectContaining({
-            word: null,
-            realLength: 0
-        }));
-    });
+    describe('getWord', function () {
 
-    it('parse empty string', () => {
-        expect($service.getWord('', 1)).toEqual(jasmine.objectContaining({
-            word: null,
-            realLength: 0
-        }));
-    });
+        it('parse null string', () => {
+            expect($service.getWord(null, 1)).toEqual(jasmine.objectContaining({
+                word: null,
+                realLength: 0
+            }));
+        });
 
-    it('parse string (spaces)', () => {
-        expect($service.getWord('When the man comes around.', 12)).toEqual(jasmine.objectContaining({
-            word: 'man',
-            realLength: 3
-        }));
-        expect($service.getWord('When the man comes around.', 13)).toEqual(jasmine.objectContaining({
-            word: 'man',
-            realLength: 4
-        }));
-        expect($service.getWord('When the man        comes around.', 16)).toEqual(jasmine.objectContaining({
-            word: 'man',
-            realLength: 7
-        }));
-    });
+        it('parse empty string', () => {
+            expect($service.getWord('', 1)).toEqual(jasmine.objectContaining({
+                word: null,
+                realLength: 0
+            }));
+        });
 
-    it('parse string (newline)', () => {
-        expect($service.getWord("When\nthe\nman\ncomes\naround.", 12)).toEqual(jasmine.objectContaining({
-            word: 'man',
-            realLength: 3
-        }));
-        expect($service.getWord("When\nthe\nman\ncomes\naround.", 13)).toEqual(jasmine.objectContaining({
-            word: 'man',
-            realLength: 4
-        }));
-        expect($service.getWord("When\nthe\nman\n\n\n\n\n\n\n\ncomes\naround.", 16)).toEqual(jasmine.objectContaining({
-            word: 'man',
-            realLength: 7
-        }));
-    });
+        it('parse string (spaces)', () => {
+            expect($service.getWord('When the man comes around.', 12)).toEqual(jasmine.objectContaining({
+                word: 'man',
+                realLength: 3
+            }));
+            expect($service.getWord('When the man comes around.', 13)).toEqual(jasmine.objectContaining({
+                word: 'man',
+                realLength: 4
+            }));
+            expect($service.getWord('When the man        comes around.', 16)).toEqual(jasmine.objectContaining({
+                word: 'man',
+                realLength: 7
+            }));
+        });
 
-    it('parse string (newline/spaces)', () => {
-        expect($service.getWord("When the\nman comes around.", 12)).toEqual(jasmine.objectContaining({
-            word: 'man',
-            realLength: 3
-        }));
-        expect($service.getWord("When the\nman \ncomes around.", 13)).toEqual(jasmine.objectContaining({
-            word: 'man',
-            realLength: 4
-        }));
-        expect($service.getWord("When the\nman \n \n \n \ncomes around.", 16)).toEqual(jasmine.objectContaining({
-            word: 'man',
-            realLength: 7
-        }));
-    });
+        it('parse string (newline)', () => {
+            expect($service.getWord("When\nthe\nman\ncomes\naround.", 12)).toEqual(jasmine.objectContaining({
+                word: 'man',
+                realLength: 3
+            }));
+            expect($service.getWord("When\nthe\nman\ncomes\naround.", 13)).toEqual(jasmine.objectContaining({
+                word: 'man',
+                realLength: 4
+            }));
+            expect($service.getWord("When\nthe\nman\n\n\n\n\n\n\n\ncomes\naround.", 16)).toEqual(jasmine.objectContaining({
+                word: 'man',
+                realLength: 7
+            }));
+        });
 
-    it('parse string (special character)', () => {
-        expect($service.getWord('When the :man: comes around.', 15)).toEqual(jasmine.objectContaining({
-            word: ':man:',
-            realLength: 6
-        }));
-        expect($service.getWord('When the :man: comes around.', 14)).toEqual(jasmine.objectContaining({
-            word: ':man:',
-            realLength: 5
-        }));
-    });
+        it('parse string (newline/spaces)', () => {
+            expect($service.getWord("When the\nman comes around.", 12)).toEqual(jasmine.objectContaining({
+                word: 'man',
+                realLength: 3
+            }));
+            expect($service.getWord("When the\nman \ncomes around.", 13)).toEqual(jasmine.objectContaining({
+                word: 'man',
+                realLength: 4
+            }));
+            expect($service.getWord("When the\nman \n \n \n \ncomes around.", 16)).toEqual(jasmine.objectContaining({
+                word: 'man',
+                realLength: 7
+            }));
+        });
 
-    it('parse string (with emoji (2 chars))', () => {
-        expect($service.getWord('this 😄 is a :smile: face', 19)).toEqual(jasmine.objectContaining({
-            word: ':smile:',
-            realLength: 7
-        }));
+        it('parse string (special character)', () => {
+            expect($service.getWord('When the :man: comes around.', 15)).toEqual(jasmine.objectContaining({
+                word: ':man:',
+                realLength: 6
+            }));
+            expect($service.getWord('When the :man: comes around.', 14)).toEqual(jasmine.objectContaining({
+                word: ':man:',
+                realLength: 5
+            }));
+        });
+
+        it('parse string (with emoji (2 chars))', () => {
+            expect($service.getWord('this 😄 is a :smile: face', 19)).toEqual(jasmine.objectContaining({
+                word: ':smile:',
+                realLength: 7
+            }));
+        });
+
+        it('parse string (additional separators)', () => {
+            expect($service.getWord('When the spider:man: comes around.', 20, [':'])).toEqual(jasmine.objectContaining({
+                word: ':man:',
+                realLength: 5
+            }));
+        });
     });
 
-    it('parse string (additional separators)', () => {
-        expect($service.getWord('When the spider:man: comes around.', 20, [':'])).toEqual(jasmine.objectContaining({
-            word: ':man:',
-            realLength: 5
-        }));
+    describe('byteChunkSplit', function() {
+        this.testPatterns = (cases, size, offset) => {
+            for (let testcase of cases) {
+                const input = testcase[0];
+                const expected = testcase[1];
+                expect($service.byteChunk(input, size, offset)).toEqual(expected);
+            }
+        };
+
+        it('short chunks', () => {
+            this.testPatterns([
+                ['abc',
+                    ['abc',]],
+                ['abcdefghijklmn',
+                    ['abcdef', 'ghijkl', 'mn',]],
+                // four byte emoji
+                ['😅😅',
+                    ['😅', '😅']]
+            ], 6, null);
+        });
+
+
+        it('chunks with offset', () => {
+            this.testPatterns([
+                ['The quick white 🐼. He jumped over the lazy 🐶.',
+                    ['The', 'quick', 'white', '🐼.', 'He', 'jumped', 'over', 'the', 'lazy', '🐶.',]],
+            ], 6, 10);
+
+            this.testPatterns([
+                ['The quick white 🐼. He jumped over the lazy 🐶.',
+                    ['The quick white 🐼', '. He jumped over the', 'lazy 🐶.',]],
+            ], 20, 10);
+        });
     });
 });