Lightning コンポーネントの実行環境や実行ユーザ種別を取得する

Lightning コンポーネントを開発していると、実行環境や実行ユーザの種類によって動作を変更させたい場合があります。
そこで Lightning コンポーネントで使える簡単なツールコンポーネントを作ってみました。


##検知可能なコンテキスト
このコンポーネントは、自分が現在実行されている環境について以下の情報を取得出来ます。

- 実行環境
    - コミュニティ
    - Lightning Experience
    - Salesforce モバイルアプリ
    - Lightning Experience ビルダー 
    - コミュニティビルダー
    - コミュニティプレビュー
- 実行ユーザ
    - ゲストユーザ
    - 外部ユーザ
    - 内部ユーザ

## 使い方
`LightningContext` のインスタンスを作成し `getContext()`　を呼び出す事で、Thenable なコンテキストオブジェクトが取得できます。

```html:HowToUseLightningContext.cmp
<aura:component access="global">
    <!-- Event Handler -->
    <aura:handler name="init" value="{!this}" action="{!c.onInit}" />

    <!-- Lightning Context -->
    <c:LightningContext aura:id="context" />
</aura:component>
```

```js:HowToUseLightningContextController.js
({
    onInit: function(c, e, h) {        
        c.find("context").getContext()
        .then($A.getCallback(function(ctx) {
            
            // 実行環境を検知
            if (ctx.isCommunity) {
                // コミュニティ
            } else if (ctx.isLex) {
                // Lightning Experience
            } else if (ctx.isMobile) {
                // Salesforce モバイルアプリ
            } else if (ctx.isLexBuilder) {
                // Lightning Experience ビルダー 
            } else if (ctx.isCommunityBuilder) {
                // コミュニティビルダー
            } else if (ctx.isCommunityPreview) {
                // コミュニティプレビュー
            }
            
            // 実行ユーザを検知
            if (ctx.isGuestUser) {
                // ゲストユーザ
            } else if (ctx.isInternalUser) {
                // 内部ユーザ
            } else if (ctx.isExternalUser) {
                // 外部ユーザ
            }
        }));
    }
})

```



##LightningContext　の実装
LightningContext　バンドルの実装例になります。
主に Apex で取得できる情報とクライアント上で取得できる情報を加味した上で、最終的なコンテキストの判断を行っています。
コンポーネントメソッドとして `getContext`　を持っており、このメソッドが呼び出された際にコンテキストを検出し返します。ただし、パフォーマンス向上のため ２ 度目以降の `getContext` 呼び出し時には、属性に保存されたコンテキストを直接返します。

```html:LightningContext.cmp
<aura:component access="global" controller="LightningContextController">
    <!-- Private Attributes -->
    <aura:attribute access="private" type="Map"  name="context" />

    <!-- Global Method -->
    <aura:method access="global" name="getContext" action="{!c.onGetContext}" /> 
</aura:component>
```


```js:LightningContextController.js
({
    onGetContext: function(c, e, h) {
        return c.get("v.context") ? Promise.resolve(c.get("v.context")) : h.getContext(c, h)
        .then($A.getCallback(function(ctx) {
            const containsLexBuilderURL = document.location.href.toLowerCase().indexOf("flexipageeditor") >= 0;
            const containsCommunityBuilderURL = document.location.href.toLowerCase().indexOf(".livepreview.salesforce-communities.com") >= 0;
            const containsCommunityPreviewURL = document.location.href.toLowerCase().indexOf(".preview.salesforce-communities.com") >= 0;
            
            const context = {
                isCommunity: ctx.isSite,
                isLex:       !ctx.isSite && ctx.uiThemeDisplayed === "Theme4d",
                isMobile:    ctx.uiThemeDisplayed === "Theme4t",
                
                isLexBuilder:       !ctx.isSite && ctx.uiThemeDisplayed === "Theme4d" && containsLexBuilderURL,
                isCommunityBuilder: ctx.isSite && containsCommunityBuilderURL,
                isCommunityPreview: ctx.isSite && containsCommunityPreviewURL, 
                
                isGuestUser:    ctx.isGuestUser,
                isInternalUser: ctx.isInternalUser,
                isExternalUser: !ctx.isInternalUser && !ctx.isGuestUser, 
            };
            c.set("v.context", context);
            return context;
        }))
        .catch($A.getCallback(function(reason) {
            console.error(reason);
        })); 
    },
})
```

```js:LightningContextHelper.js
({
    getContext: function(c, h) {
        const action = c.get("c.getContext");
        return new Promise(function (resolve, reject) {
            action.setCallback(this, function(response) {
                const ret = response.getReturnValue();
                if (response.getState() === 'SUCCESS') ret.hasError ? reject(ret.message) : resolve(ret);
                else if (response.getState() === 'ERROR') reject(response.getError());
            });
            $A.enqueueAction(action);
        });
    },
})
```

```java:LightningContextController.apxc
public class LightningContextController {
    public class Error {
        @AuraEnabled public String message;
        @AuraEnabled public Boolean hasError = true;
        public Error(String message){
            this.message = message;
        }
    } 
    
    @AuraEnabled
    public static Object getContext() {
        try {
            Map<String, Object> ctx = new Map<String, Object>();
            ctx.put('isSite', String.isNotBlank(Site.getSiteId()));
            ctx.put('uiThemeDisplayed', UserInfo.getUiThemeDisplayed());
            ctx.put('isInternalUser', Auth.CommunitiesUtil.isInternalUser());
            ctx.put('isGuestUser', Auth.CommunitiesUtil.isGuestUser());
            return ctx;
        } catch (Exception e) {
            return new Error(e.getMessage());
        }
    }  
}
```