1 /*
2  * ShinyDisconnectNotifier.java
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 package org.rstudio.studio.client.shiny;
16 
17 import org.rstudio.core.client.Debug;
18 import org.rstudio.core.client.StringUtil;
19 
20 public class ShinyDisconnectNotifier
21 {
22    public interface ShinyDisconnectSource
23    {
getShinyUrl()24       public String getShinyUrl();
onShinyDisconnect()25       public void onShinyDisconnect();
26    }
27 
ShinyDisconnectNotifier(ShinyDisconnectSource source)28    public ShinyDisconnectNotifier(ShinyDisconnectSource source)
29    {
30       source_ = source;
31       suppressUrl_ = null;
32       initializeEvents();
33    }
34 
35    /**
36     * Begins suppressing disconnect notifications from the current URL.
37     */
suppress()38    public void suppress()
39    {
40       if (!StringUtil.isNullOrEmpty(suppressUrl_))
41       {
42          // should never happen in practice; if it does the safest behavior is
43          // to respect the new suppress URL and discard the old one. warn that
44          // we're doing this.
45          Debug.logWarning("Replacing old Shiny disconnect suppress URL: " + suppressUrl_);
46       }
47       suppressUrl_ = source_.getShinyUrl();
48    }
49 
50    /**
51     * Ends suppression of disconnect notifications.
52     */
unsuppress()53    public void unsuppress()
54    {
55       suppressUrl_ = null;
56    }
57 
initializeEvents()58    private native void initializeEvents() /*-{
59 
60       var self = this;
61       var callback = $entry(function(event) {
62 
63          if (typeof event.data !== "string")
64             return;
65 
66          self.@org.rstudio.studio.client.shiny.ShinyDisconnectNotifier::onMessage(*)(
67             event.data,
68             event.origin,
69             event.target.name
70          );
71 
72       });
73 
74       $wnd.addEventListener("message", callback, true);
75 
76    }-*/;
77 
onMessage(String data, String origin, String name)78    private void onMessage(String data, String origin, String name)
79    {
80       // check to see if this is a 'disconnected' message
81       if (!StringUtil.equals(data, "disconnected"))
82          return;
83 
84       // check to see if the message originated from the same origin
85       String url = source_.getShinyUrl();
86       if (!url.startsWith(origin))
87          return;
88 
89       // if we were suppressing disconnect notifications from this URL,
90       // consume this disconnection and resume
91       if (StringUtil.equals(url, suppressUrl_))
92       {
93          unsuppress();
94          return;
95       }
96 
97       // respond to Shiny disconnect
98       source_.onShinyDisconnect();
99    }
100 
101    private final ShinyDisconnectSource source_;
102    private String suppressUrl_;
103 }
104