Skip to content

Commit

Permalink
Merge pull request #27 from oslabs-beta/ctstamper/Fixing-Reconnect
Browse files Browse the repository at this point in the history
Added Error handling for port connections and initialization
  • Loading branch information
ctstamper committed Oct 14, 2023
2 parents ac31f4f + 4d52ba7 commit d563029
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 90 deletions.
2 changes: 1 addition & 1 deletion src/app/FrontendTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export interface InitialStateProps {
tabs: unknown;
currentTabInApp: null | string;
connectionStatus: boolean;
reconnectRequested: boolean;
connectRequested: boolean;
}

export interface DiffProps {
Expand Down
12 changes: 7 additions & 5 deletions src/app/RTKslices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const initialState: InitialStateProps = { // we initialize what our initialState
tabs: {},
currentTabInApp: null,
connectionStatus: true,
reconnectRequested: false,
connectRequested: true,
};

const findName = (index, obj) => {
Expand Down Expand Up @@ -116,7 +116,9 @@ export const mainSlice = createSlice({
},

setPort: (state, action) => {
console.log('port start: ', current(state))
state.port = action.payload;
console.log('port end: ', current(state))
},

setTab: (state, action) => {
Expand Down Expand Up @@ -489,12 +491,12 @@ export const mainSlice = createSlice({
},

startReconnect: (state) => {
state.reconnectRequested = true;
state.connectRequested = true;
state.port = initialState.port;
},

endReconnect: (state) => {
state.reconnectRequested = false;
endConnect: (state) => {
state.connectRequested = false;
state.connectionStatus = true;
}

Expand Down Expand Up @@ -530,5 +532,5 @@ export const {
deleteSeries,
disconnected,
startReconnect,
endReconnect,
endConnect,
} = mainSlice.actions
4 changes: 1 addition & 3 deletions src/app/containers/ButtonsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,15 @@ function ButtonsContainer(): JSX.Element {

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

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

const handleReconnectConfirm = () => {
//reconnection logic here
dispatch(startReconnect());
handleReconnectCancel();
dispatch(startReconnect());
}

const handleReconnectCancel = () => {
Expand Down
181 changes: 117 additions & 64 deletions src/app/containers/MainContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
noDev,
setCurrentLocation,
disconnected,
endReconnect
endConnect,
} from '../RTKslices';
import { useDispatch, useSelector } from 'react-redux';

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

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

Expand All @@ -48,83 +48,136 @@ function MainContainer(): JSX.Element {
}
};




const handleDisconnect = (msg): void => {
if (msg === 'portDisconnect') {
console.log('unexpected port disconnection');
dispatch(disconnected());
}
}

const handleConnect = () => {
const maxRetries = 10;
const retryInterval = 1000;
const maxTimeout = 15000;

return new Promise((resolve) => {
let port: chrome.runtime.Port;
console.log('init port: ', port)

const attemptReconnection = (retries: number, startTime: number) => {
// console.log('WORKING')
if (retries <= maxRetries && Date.now() - startTime < maxTimeout) {
if (retries === 1)
port = chrome.runtime.connect();
// console.log('HITTING IF');
chrome.runtime.sendMessage({ action: 'attemptReconnect' }, (response) => {
if (response && response.success) {
console.log('Reconnect Success: ', response.success);
resolve(port);
} else {
console.log('Reconnect failed: ', !response && response.success);

setTimeout(() => {
console.log('trying!', retries)
attemptReconnection(retries + 1, startTime);
}, retryInterval);
}
});
} else {
console.log('PORT CONNECT FAILED');
resolve(null);
}
};
attemptReconnection(1, Date.now());
});
}

const messageListener = (message: {
action: string;
payload: Record<string, unknown>;
sourceTab: number
}) => {
const { action, payload, sourceTab } = message;
let maxTab: number;

if (!sourceTab && action !== 'keepAlive') { // if the sourceTab doesn't exist or is 0 and it is not a 'keepAlive' action
const tabsArray: Array<string> = Object.keys(payload); // we create a tabsArray of strings composed of keys from our payload object
const numTabsArray: number[] = tabsArray.map((tab) => Number(tab)); // we then map out our tabsArray where we convert each string into a number

maxTab = Math.max(...numTabsArray); // we then get the largest tab number value
}

switch (action) {
case 'deleteTab': {
dispatch(deleteTab(payload));
break;
}
case 'devTools': {
dispatch(noDev(payload));
break;
}
case 'changeTab': {
console.log('received changeTab message')
dispatch(setTab(payload));
break;
}
case 'sendSnapshots': {
dispatch(setTab(sourceTab));
// set state with the information received from the background script
dispatch(addNewSnapshots(payload));
break;
}
case 'initialConnectSnapshots': {
dispatch(initialConnect(payload));
break;
}
case 'setCurrentLocation': {
dispatch(setCurrentLocation(payload));
break;
}
default:
}
}

useEffect(() => {
if (port) return; // only open port once so if it exists, do not run useEffect again
if (!connectRequested) return; // only open port once so if it exists, do not run useEffect again

// 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.
const currentPort = chrome.runtime.connect(); // we connect to our service worker

// listen for a message containing snapshots from the /extension/build/background.js service worker
currentPort.onMessage.addListener(
// parameter message is an object with following type script properties
(message: {
action: string;
payload: Record<string, unknown>;
sourceTab: number
}) => {
const { action, payload, sourceTab } = message;
let maxTab: number;

if (!sourceTab && action !== 'keepAlive') { // if the sourceTab doesn't exist or is 0 and it is not a 'keepAlive' action
const tabsArray: Array<string> = Object.keys(payload); // we create a tabsArray of strings composed of keys from our payload object
const numTabsArray: number[] = tabsArray.map((tab) => Number(tab)); // we then map out our tabsArray where we convert each string into a number

maxTab = Math.max(...numTabsArray); // we then get the largest tab number value
}

switch (action) {
case 'deleteTab': {
dispatch(deleteTab(payload));
break;
}
case 'devTools': {
dispatch(noDev(payload));
break;
}
case 'changeTab': {
dispatch(setTab(payload));
break;
}
case 'sendSnapshots': {
dispatch(setTab(sourceTab));
// set state with the information received from the background script
dispatch(addNewSnapshots(payload));
break;
}
case 'initialConnectSnapshots': {
dispatch(initialConnect(payload));
break;
}
case 'setCurrentLocation': {
dispatch(setCurrentLocation(payload));
break;
}
default:
}
},
);
handleConnect()
.then((port: chrome.runtime.Port | null) => {
if (port) {
console.log('PORT SUCCESS: ', port)

const currentPort = port;

if (chrome.runtime.onMessage.hasListener(messageListener))
chrome.runtime.onMessage.removeListener(messageListener);

// listen for a message containing snapshots from the /extension/build/background.js service worker
currentPort.onMessage.addListener(messageListener);

currentPort.postMessage({ initialized: true });
console.log('posted message');

if (chrome.runtime.onMessage.hasListener(handleDisconnect))
chrome.runtime.onMessage.removeListener(handleDisconnect);

// used to track when the above connection closes unexpectedly. Remember that it should persist throughout the application lifecycle
chrome.runtime.onMessage.addListener(handleDisconnect);
if (chrome.runtime.onMessage.hasListener(handleDisconnect))
chrome.runtime.onMessage.removeListener(handleDisconnect);
// used to track when the above connection closes unexpectedly. Remember that it should persist throughout the application lifecycle
chrome.runtime.onMessage.addListener(handleDisconnect);

// assign port to state so it could be used by other components
if (currentPort)
dispatch(setPort(currentPort));
// assign port to state so it could be used by other components
dispatch(setPort(currentPort));

if (!connectionStatus && reconnectRequested)
dispatch(endReconnect());
});
dispatch(endConnect());
} else {
console.log('PORT FAIL: ', port);
}
});
}, [connectRequested]);

// Error Page launch IF(Content script not launched OR RDT not installed OR Target not React app)
if (
Expand Down
49 changes: 32 additions & 17 deletions src/extension/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ function changeCurrLocation(tabObj, rootNode, index, name) {
This allows us to set up listener's for when we connect, message, and disconnect the script.
*/

// let initialized = false;
let portSuccessfullyConnected = false;

// Establishing incoming connection with Reactime.
chrome.runtime.onConnect.addListener((port) => {
/*
Expand All @@ -158,26 +161,31 @@ chrome.runtime.onConnect.addListener((port) => {
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.
*/

console.log('Port: ', port);
portSuccessfullyConnected = port ? true : false;

portsArr.push(port); // push each Reactime communication channel object to the portsArr

// On Reactime launch: make sure RT's active tab is correct
if (portsArr.length > 0) {
portsArr.forEach((bg) => {// go through each port object (each Reactime instance)
bg.postMessage({ // send passed in action object as a message to the current port
action: 'changeTab',
payload: { tabId: activeTab.id, title: activeTab.title },
})
});
}

// send tabs obj to the connected devtools as soon as connection to devtools is made
if (Object.keys(tabsObj).length > 0) {
port.postMessage({
action: 'initialConnectSnapshots',
payload: tabsObj,
});
}
port.onMessage.addListener((msg) => {
console.log('background message: ', msg);
if (msg.initialized && portsArr.length > 0) {
console.log('sending changeTab message!!!!');
portsArr.forEach((bg) => {// go through each port object (each Reactime instance)
bg.postMessage({ // send passed in action object as a message to the current port
action: 'changeTab',
payload: { tabId: activeTab.id, title: activeTab.title },
})
});
}
if (msg.initialized && Object.keys(tabsObj).length > 0) {
console.log('sending initialConnectSnapshots message!!!!!')
port.postMessage({
action: 'initialConnectSnapshots',
payload: tabsObj,
});
}
});

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

switch (action) {
case 'attemptReconnect': {
console.log('portConnection: ', portSuccessfullyConnected);

const success = portSuccessfullyConnected;
sendResponse({ success });
break;
}
case 'jumpToSnap': {
changeCurrLocation(tabsObj[tabId], tabsObj[tabId].hierarchy, index, name);
if (portsArr.length > 0) {
Expand Down

0 comments on commit d563029

Please sign in to comment.