From ea1495f4ff7906ddb5058479eb13582225ee27b2 Mon Sep 17 00:00:00 2001
From: Eric Dahlvang <ericdahlvang@gmail.com>
Date: Wed, 22 Jul 2020 15:51:42 -0700
Subject: [PATCH] Add installation update activity handler

---
 .../etc/botbuilder-core.api.md                |  3 ++
 .../botbuilder-core/src/activityHandler.ts    | 31 +++++++++++++++++++
 .../src/activityHandlerBase.ts                | 16 ++++++++++
 .../tests/ActivityHandler.test.js             | 13 ++++++++
 .../tests/activityHandlerBase.test.js         |  9 ++++++
 5 files changed, 72 insertions(+)

diff --git a/libraries/botbuilder-core/etc/botbuilder-core.api.md b/libraries/botbuilder-core/etc/botbuilder-core.api.md
index 68f990428b..bd6c24f96c 100644
--- a/libraries/botbuilder-core/etc/botbuilder-core.api.md
+++ b/libraries/botbuilder-core/etc/botbuilder-core.api.md
@@ -76,6 +76,8 @@ export class ActivityHandler extends ActivityHandlerBase {
     protected onTurnActivity(context: TurnContext): Promise<void>;
     onTyping(handler: BotHandler): this;
     protected onTypingActivity(context: TurnContext): Promise<void>;
+    onInstallationUpdate(handler: BotHandler): this;
+    protected onInstallationUpdateActivity(context: TurnContext): Promise<void>;
     protected onUnrecognizedActivity(context: TurnContext): Promise<void>;
     onUnrecognizedActivityType(handler: BotHandler): this;
     run(context: TurnContext): Promise<void>;
@@ -95,6 +97,7 @@ export class ActivityHandlerBase {
     protected onReactionsRemovedActivity(reactionsRemoved: MessageReaction[], context: TurnContext): Promise<void>;
     protected onTurnActivity(context: TurnContext): Promise<void>;
     protected onTypingActivity(context: TurnContext): Promise<void>;
+    protected onInstallationUpdateActivity(context: TurnContext): Promise<void>;
     protected onUnrecognizedActivity(context: TurnContext): Promise<void>;
     run(context: TurnContext): Promise<void>;
 }
diff --git a/libraries/botbuilder-core/src/activityHandler.ts b/libraries/botbuilder-core/src/activityHandler.ts
index a88a93b262..3dd9c6efdd 100644
--- a/libraries/botbuilder-core/src/activityHandler.ts
+++ b/libraries/botbuilder-core/src/activityHandler.ts
@@ -287,6 +287,21 @@ export class ActivityHandler extends ActivityHandlerBase {
         return this.on('Typing', handler);
     }
 
+    /**
+     * Registers an activity event handler for the _installationupdate_ activity.
+     * 
+     * @param handler The event handler.
+     * 
+     * @remarks
+     * Returns a reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object.
+     * 
+     * To handle a InstallationUpdate event, use the
+     * [onInstallationUpdate](xref:botbuilder-core.ActivityHandler.onInstallationUpdate) type-specific event handler.
+     */
+    public onInstallationUpdate(handler: BotHandler): this {
+        return this.on('InstallationUpdate', handler);
+    }
+
     /**
      * Registers an activity event handler for the _tokens-response_ event, emitted for any incoming
      * `tokens/response` event activity. These are generated as part of the OAuth authentication flow.
@@ -498,6 +513,22 @@ export class ActivityHandler extends ActivityHandlerBase {
         await this.handle(context, 'Typing', this.defaultNextEvent(context));
     }
 
+    /**
+     * Runs all registered _instllationupdate_ handlers and then continues the event emission process.
+     * 
+     * @param context The context object for the current turn.
+     * 
+     * @remarks
+     * Overwrite this method to support channel-specific behavior across multiple channels.
+     * 
+     * The default logic is to call any handlers registered via
+     * [onInstallationUpdateActivity](xref:botbuilder-core.ActivityHandler.onInstallationUpdateActivity),
+     * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent).
+     */
+    protected async onInstallationUpdateActivity(context: TurnContext): Promise<void> {
+        await this.handle(context, 'InstallationUpdate', this.defaultNextEvent(context));
+    }
+
     /**
      * Runs all registered _unrecognized activity type_ handlers and then continues the event emission process.
      * 
diff --git a/libraries/botbuilder-core/src/activityHandlerBase.ts b/libraries/botbuilder-core/src/activityHandlerBase.ts
index 10f4a19c5d..fddc4b53a1 100644
--- a/libraries/botbuilder-core/src/activityHandlerBase.ts
+++ b/libraries/botbuilder-core/src/activityHandlerBase.ts
@@ -79,6 +79,9 @@ export class ActivityHandlerBase {
             case ActivityTypes.Typing:
                 await this.onTypingActivity(context);
                 break;
+            case ActivityTypes.InstallationUpdate:
+                await this.onInstallationUpdateActivity(context);
+                break;
             default:
             // handler for unknown or unhandled types
                 await this.onUnrecognizedActivity(context);
@@ -200,6 +203,19 @@ export class ActivityHandlerBase {
         return;
     }
 
+    /**
+     * Provides a hook for emitting the _installationupdate_ event.
+     * 
+     * @param context The context object for the current turn.
+     * 
+     * @remarks
+     * Overwrite this method to run registered _installationupdate_ handlers and then continue the event
+     * emission process.
+     */
+    protected async onInstallationUpdateActivity(context: TurnContext): Promise<void> {
+        return;
+    }
+
     /**
      * Provides a hook for emitting the _unrecognized_ event.
      * 
diff --git a/libraries/botbuilder-core/tests/ActivityHandler.test.js b/libraries/botbuilder-core/tests/ActivityHandler.test.js
index f94078a0c9..2a5b6dc89e 100644
--- a/libraries/botbuilder-core/tests/ActivityHandler.test.js
+++ b/libraries/botbuilder-core/tests/ActivityHandler.test.js
@@ -223,6 +223,19 @@ describe('ActivityHandler', function() {
         processActivity({type: ActivityTypes.Typing}, bot, done);
     });
 
+    it(`should fire onInstallationUpdate`, async function(done) {
+
+        const bot = new ActivityHandler();
+
+        bot.onInstallationUpdate(async (context, next) => {
+            assert(true, 'onInstallationUpdate not called');
+            done();
+            await next();
+        });
+
+        processActivity({type: ActivityTypes.InstallationUpdate}, bot, done);
+    });
+
     it(`should fire onUnrecognizedActivityType`, async function(done) {
 
         const bot = new ActivityHandler();
diff --git a/libraries/botbuilder-core/tests/activityHandlerBase.test.js b/libraries/botbuilder-core/tests/activityHandlerBase.test.js
index 4f073e8f6a..d3515c735f 100644
--- a/libraries/botbuilder-core/tests/activityHandlerBase.test.js
+++ b/libraries/botbuilder-core/tests/activityHandlerBase.test.js
@@ -29,6 +29,7 @@ describe('ActivityHandlerBase', function() {
     let onEventCalled = false;
     let onEndOfConversationCalled = false;
     let onTypingCalled = false;
+    let onInstallationUpdateCalled = false;
     let onUnrecognizedActivity = false;
 
     afterEach(function() {
@@ -39,6 +40,7 @@ describe('ActivityHandlerBase', function() {
         onEventCalled = false;
         onEndOfConversationCalled = false;
         onTypingCalled = false;
+        onInstallationUpdateCalled = false;
         onUnrecognizedActivity = false;
     });
 
@@ -127,6 +129,11 @@ describe('ActivityHandlerBase', function() {
             onTypingCalled = true;
         }
 
+        async onInstallationUpdateActivity(context) {
+            assert(context, 'context not found');
+            onInstallationUpdateCalled = true;
+        }
+
         async onUnrecognizedActivity(context) {
             assert(context, 'context not found');
             onUnrecognizedActivity = true;
@@ -142,6 +149,7 @@ describe('ActivityHandlerBase', function() {
         processActivity({type: ActivityTypes.Event}, bot, done);
         processActivity({type: ActivityTypes.EndOfConversation}, bot, done);
         processActivity({type: ActivityTypes.Typing}, bot, done);
+        processActivity({type: ActivityTypes.InstallationUpdate}, bot, done);
         processActivity({ type: 'unrecognized' }, bot, done);
 
         assert(onTurnActivityCalled, 'onTurnActivity was not called');
@@ -151,6 +159,7 @@ describe('ActivityHandlerBase', function() {
         assert(onEventCalled, 'onEventActivity was not called');
         assert(onEndOfConversationCalled, 'onEndOfConversationCalled was not called');
         assert(onTypingCalled, 'onTypingCalled was not called');
+        assert(onInstallationUpdateCalled, 'onInstallationUpdateCalled was not called');
         assert(onUnrecognizedActivity, 'onUnrecognizedActivity was not called');
         done();
     });