Skip to content

Commit d563029

Browse files
authored
Merge pull request #27 from oslabs-beta/ctstamper/Fixing-Reconnect
Added Error handling for port connections and initialization
2 parents ac31f4f + 4d52ba7 commit d563029

File tree

5 files changed

+158
-90
lines changed

5 files changed

+158
-90
lines changed

src/app/FrontendTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export interface InitialStateProps {
143143
tabs: unknown;
144144
currentTabInApp: null | string;
145145
connectionStatus: boolean;
146-
reconnectRequested: boolean;
146+
connectRequested: boolean;
147147
}
148148

149149
export interface DiffProps {

src/app/RTKslices.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const initialState: InitialStateProps = { // we initialize what our initialState
99
tabs: {},
1010
currentTabInApp: null,
1111
connectionStatus: true,
12-
reconnectRequested: false,
12+
connectRequested: true,
1313
};
1414

1515
const findName = (index, obj) => {
@@ -116,7 +116,9 @@ export const mainSlice = createSlice({
116116
},
117117

118118
setPort: (state, action) => {
119+
console.log('port start: ', current(state))
119120
state.port = action.payload;
121+
console.log('port end: ', current(state))
120122
},
121123

122124
setTab: (state, action) => {
@@ -489,12 +491,12 @@ export const mainSlice = createSlice({
489491
},
490492

491493
startReconnect: (state) => {
492-
state.reconnectRequested = true;
494+
state.connectRequested = true;
493495
state.port = initialState.port;
494496
},
495497

496-
endReconnect: (state) => {
497-
state.reconnectRequested = false;
498+
endConnect: (state) => {
499+
state.connectRequested = false;
498500
state.connectionStatus = true;
499501
}
500502

@@ -530,5 +532,5 @@ export const {
530532
deleteSeries,
531533
disconnected,
532534
startReconnect,
533-
endReconnect,
535+
endConnect,
534536
} = mainSlice.actions

src/app/containers/ButtonsContainer.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,15 @@ function ButtonsContainer(): JSX.Element {
5858

5959
//adding a local state using useState for the reconnect button functionality
6060
const [reconnectDialogOpen, setReconnectDialogOpen] = useState(false);
61-
const [disconnectedDialogOpen, setDisconnectedDialogOpen] = useState(false);
6261

6362
//logic for handling dialog box opening and closing
6463
const handleReconnectClick = () => {
6564
setReconnectDialogOpen(true);
6665
}
6766

6867
const handleReconnectConfirm = () => {
69-
//reconnection logic here
70-
dispatch(startReconnect());
7168
handleReconnectCancel();
69+
dispatch(startReconnect());
7270
}
7371

7472
const handleReconnectCancel = () => {

src/app/containers/MainContainer.tsx

Lines changed: 117 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
noDev,
1515
setCurrentLocation,
1616
disconnected,
17-
endReconnect
17+
endConnect,
1818
} from '../RTKslices';
1919
import { useDispatch, useSelector } from 'react-redux';
2020

@@ -28,7 +28,7 @@ function MainContainer(): JSX.Element {
2828
const currentTab = useSelector((state: any) => state.main.currentTab);
2929
const tabs = useSelector((state: any) => state.main.tabs);
3030
const port = useSelector((state: any) => state.main.port);
31-
const { connectionStatus, reconnectRequested } = useSelector((state: any) => state.main);
31+
const { connectRequested } = useSelector((state: any) => state.main);
3232

3333
const [actionView, setActionView] = useState(true); // We create a local state 'actionView' and set it to true
3434

@@ -48,83 +48,136 @@ function MainContainer(): JSX.Element {
4848
}
4949
};
5050

51+
52+
53+
5154
const handleDisconnect = (msg): void => {
5255
if (msg === 'portDisconnect') {
5356
console.log('unexpected port disconnection');
5457
dispatch(disconnected());
5558
}
5659
}
5760

61+
const handleConnect = () => {
62+
const maxRetries = 10;
63+
const retryInterval = 1000;
64+
const maxTimeout = 15000;
65+
66+
return new Promise((resolve) => {
67+
let port: chrome.runtime.Port;
68+
console.log('init port: ', port)
69+
70+
const attemptReconnection = (retries: number, startTime: number) => {
71+
// console.log('WORKING')
72+
if (retries <= maxRetries && Date.now() - startTime < maxTimeout) {
73+
if (retries === 1)
74+
port = chrome.runtime.connect();
75+
// console.log('HITTING IF');
76+
chrome.runtime.sendMessage({ action: 'attemptReconnect' }, (response) => {
77+
if (response && response.success) {
78+
console.log('Reconnect Success: ', response.success);
79+
resolve(port);
80+
} else {
81+
console.log('Reconnect failed: ', !response && response.success);
82+
83+
setTimeout(() => {
84+
console.log('trying!', retries)
85+
attemptReconnection(retries + 1, startTime);
86+
}, retryInterval);
87+
}
88+
});
89+
} else {
90+
console.log('PORT CONNECT FAILED');
91+
resolve(null);
92+
}
93+
};
94+
attemptReconnection(1, Date.now());
95+
});
96+
}
97+
98+
const messageListener = (message: {
99+
action: string;
100+
payload: Record<string, unknown>;
101+
sourceTab: number
102+
}) => {
103+
const { action, payload, sourceTab } = message;
104+
let maxTab: number;
105+
106+
if (!sourceTab && action !== 'keepAlive') { // if the sourceTab doesn't exist or is 0 and it is not a 'keepAlive' action
107+
const tabsArray: Array<string> = Object.keys(payload); // we create a tabsArray of strings composed of keys from our payload object
108+
const numTabsArray: number[] = tabsArray.map((tab) => Number(tab)); // we then map out our tabsArray where we convert each string into a number
109+
110+
maxTab = Math.max(...numTabsArray); // we then get the largest tab number value
111+
}
112+
113+
switch (action) {
114+
case 'deleteTab': {
115+
dispatch(deleteTab(payload));
116+
break;
117+
}
118+
case 'devTools': {
119+
dispatch(noDev(payload));
120+
break;
121+
}
122+
case 'changeTab': {
123+
console.log('received changeTab message')
124+
dispatch(setTab(payload));
125+
break;
126+
}
127+
case 'sendSnapshots': {
128+
dispatch(setTab(sourceTab));
129+
// set state with the information received from the background script
130+
dispatch(addNewSnapshots(payload));
131+
break;
132+
}
133+
case 'initialConnectSnapshots': {
134+
dispatch(initialConnect(payload));
135+
break;
136+
}
137+
case 'setCurrentLocation': {
138+
dispatch(setCurrentLocation(payload));
139+
break;
140+
}
141+
default:
142+
}
143+
}
144+
58145
useEffect(() => {
59-
if (port) return; // only open port once so if it exists, do not run useEffect again
146+
if (!connectRequested) return; // only open port once so if it exists, do not run useEffect again
60147

61148
// chrome.runtime allows our application to retrieve our service worker (our eventual bundles/background.bundle.js after running npm run build), details about the manifest, and allows us to listen and respond to events in our application lifecycle.
62-
const currentPort = chrome.runtime.connect(); // we connect to our service worker
63-
64-
// listen for a message containing snapshots from the /extension/build/background.js service worker
65-
currentPort.onMessage.addListener(
66-
// parameter message is an object with following type script properties
67-
(message: {
68-
action: string;
69-
payload: Record<string, unknown>;
70-
sourceTab: number
71-
}) => {
72-
const { action, payload, sourceTab } = message;
73-
let maxTab: number;
74-
75-
if (!sourceTab && action !== 'keepAlive') { // if the sourceTab doesn't exist or is 0 and it is not a 'keepAlive' action
76-
const tabsArray: Array<string> = Object.keys(payload); // we create a tabsArray of strings composed of keys from our payload object
77-
const numTabsArray: number[] = tabsArray.map((tab) => Number(tab)); // we then map out our tabsArray where we convert each string into a number
78-
79-
maxTab = Math.max(...numTabsArray); // we then get the largest tab number value
80-
}
81149

82-
switch (action) {
83-
case 'deleteTab': {
84-
dispatch(deleteTab(payload));
85-
break;
86-
}
87-
case 'devTools': {
88-
dispatch(noDev(payload));
89-
break;
90-
}
91-
case 'changeTab': {
92-
dispatch(setTab(payload));
93-
break;
94-
}
95-
case 'sendSnapshots': {
96-
dispatch(setTab(sourceTab));
97-
// set state with the information received from the background script
98-
dispatch(addNewSnapshots(payload));
99-
break;
100-
}
101-
case 'initialConnectSnapshots': {
102-
dispatch(initialConnect(payload));
103-
break;
104-
}
105-
case 'setCurrentLocation': {
106-
dispatch(setCurrentLocation(payload));
107-
break;
108-
}
109-
default:
110-
}
111-
},
112-
);
150+
handleConnect()
151+
.then((port: chrome.runtime.Port | null) => {
152+
if (port) {
153+
console.log('PORT SUCCESS: ', port)
154+
155+
const currentPort = port;
156+
157+
if (chrome.runtime.onMessage.hasListener(messageListener))
158+
chrome.runtime.onMessage.removeListener(messageListener);
113159

160+
// listen for a message containing snapshots from the /extension/build/background.js service worker
161+
currentPort.onMessage.addListener(messageListener);
162+
163+
currentPort.postMessage({ initialized: true });
164+
console.log('posted message');
114165

115-
if (chrome.runtime.onMessage.hasListener(handleDisconnect))
116-
chrome.runtime.onMessage.removeListener(handleDisconnect);
117-
118-
// used to track when the above connection closes unexpectedly. Remember that it should persist throughout the application lifecycle
119-
chrome.runtime.onMessage.addListener(handleDisconnect);
166+
if (chrome.runtime.onMessage.hasListener(handleDisconnect))
167+
chrome.runtime.onMessage.removeListener(handleDisconnect);
168+
169+
// used to track when the above connection closes unexpectedly. Remember that it should persist throughout the application lifecycle
170+
chrome.runtime.onMessage.addListener(handleDisconnect);
120171

121-
// assign port to state so it could be used by other components
122-
if (currentPort)
123-
dispatch(setPort(currentPort));
172+
// assign port to state so it could be used by other components
173+
dispatch(setPort(currentPort));
124174

125-
if (!connectionStatus && reconnectRequested)
126-
dispatch(endReconnect());
127-
});
175+
dispatch(endConnect());
176+
} else {
177+
console.log('PORT FAIL: ', port);
178+
}
179+
});
180+
}, [connectRequested]);
128181

129182
// Error Page launch IF(Content script not launched OR RDT not installed OR Target not React app)
130183
if (

src/extension/background.js

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ function changeCurrLocation(tabObj, rootNode, index, name) {
137137
This allows us to set up listener's for when we connect, message, and disconnect the script.
138138
*/
139139

140+
// let initialized = false;
141+
let portSuccessfullyConnected = false;
142+
140143
// Establishing incoming connection with Reactime.
141144
chrome.runtime.onConnect.addListener((port) => {
142145
/*
@@ -158,26 +161,31 @@ chrome.runtime.onConnect.addListener((port) => {
158161
159162
Again, this port object is used for communication within your extension, not for communication with external ports or tabs in the Chrome browser. If you need to interact with specific tabs or external ports, you would use other APIs or methods, such as chrome.tabs or other Chrome Extension APIs.
160163
*/
164+
165+
console.log('Port: ', port);
166+
portSuccessfullyConnected = port ? true : false;
161167

162168
portsArr.push(port); // push each Reactime communication channel object to the portsArr
163-
164-
// On Reactime launch: make sure RT's active tab is correct
165-
if (portsArr.length > 0) {
166-
portsArr.forEach((bg) => {// go through each port object (each Reactime instance)
167-
bg.postMessage({ // send passed in action object as a message to the current port
168-
action: 'changeTab',
169-
payload: { tabId: activeTab.id, title: activeTab.title },
170-
})
171-
});
172-
}
173169

174-
// send tabs obj to the connected devtools as soon as connection to devtools is made
175-
if (Object.keys(tabsObj).length > 0) {
176-
port.postMessage({
177-
action: 'initialConnectSnapshots',
178-
payload: tabsObj,
179-
});
180-
}
170+
port.onMessage.addListener((msg) => {
171+
console.log('background message: ', msg);
172+
if (msg.initialized && portsArr.length > 0) {
173+
console.log('sending changeTab message!!!!');
174+
portsArr.forEach((bg) => {// go through each port object (each Reactime instance)
175+
bg.postMessage({ // send passed in action object as a message to the current port
176+
action: 'changeTab',
177+
payload: { tabId: activeTab.id, title: activeTab.title },
178+
})
179+
});
180+
}
181+
if (msg.initialized && Object.keys(tabsObj).length > 0) {
182+
console.log('sending initialConnectSnapshots message!!!!!')
183+
port.postMessage({
184+
action: 'initialConnectSnapshots',
185+
payload: tabsObj,
186+
});
187+
}
188+
});
181189

182190
// every time devtool is closed, remove the port from portsArr
183191
port.onDisconnect.addListener((e) => {
@@ -290,6 +298,13 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
290298
}
291299

292300
switch (action) {
301+
case 'attemptReconnect': {
302+
console.log('portConnection: ', portSuccessfullyConnected);
303+
304+
const success = portSuccessfullyConnected;
305+
sendResponse({ success });
306+
break;
307+
}
293308
case 'jumpToSnap': {
294309
changeCurrLocation(tabsObj[tabId], tabsObj[tabId].hierarchy, index, name);
295310
if (portsArr.length > 0) {

0 commit comments

Comments
 (0)