diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8718b41 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +/dist +/lib +/components + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..82a944c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "ms-vscode.vscode-typescript-next" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3501f16 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "editor.tabSize": 2, + "editor.detectIndentation": false, + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "prettier.configPath": ".prettierrc.json", + "files.trimTrailingWhitespace": true, + "typescript.preferences.quoteStyle": "double" +} diff --git a/src/common/types.ts b/src/common/types.ts index 1c87c15..231d0a1 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -11,10 +11,8 @@ export interface Call { export enum MessageEvent { // Request - Login, Call, OpenPhoneWindow, - Ping, } export interface EmptyData {} diff --git a/src/content/index.tsx b/src/content/index.tsx index adf9e8c..07d99e9 100644 --- a/src/content/index.tsx +++ b/src/content/index.tsx @@ -1,66 +1,105 @@ import React, { useEffect, useState } from "react"; import ReactDOM from "react-dom"; -import { - ChakraProvider, - Text, - IconButton, - Flex, - CSSReset, -} from "@chakra-ui/react"; -import { Phone } from "react-feather"; +import { Call, CallAction, Message, MessageEvent } from "./../common/types"; +import { openPhonePopup } from "./../utils"; export const ContentApp = () => { - const [isOpen, setIsOpen] = useState(false); - const [position, setPosition] = useState({ left: 0, top: 0 }); const [selectedText, setSelectedText] = useState(""); + const [counting, setCounting] = useState(0); + const [position, setPosition] = useState({ x: 0, y: 0 }); - // Add event listener for text selection useEffect(() => { - function handleMouseUp() { - const selectedText = window.getSelection()?.toString(); - if (selectedText) { - setSelectedText(selectedText); - const range = window.getSelection()?.getRangeAt(0); - const rect = range?.getBoundingClientRect(); - setIsOpen(true); - setPosition({ left: rect?.left || 0, top: rect?.bottom || 0 }); + function mouseUpListener(e: any) { + const text = window.getSelection()?.toString().trim(); + if (text && text.length > 0) { + const number = validateAndFormatNumber(text); + setSelectedText(number || ""); + setPosition({ x: e.pageX, y: e.pageY }); } else { - setIsOpen(false); + setSelectedText(""); } } - document.addEventListener("mouseup", handleMouseUp); + document.addEventListener("mouseup", mouseUpListener); return () => { - document.removeEventListener("mouseup", handleMouseUp); + document.removeEventListener("mouseup", mouseUpListener); }; }, []); + const validateAndFormatNumber = (input: string): string | null => { + const strippedInput = input.replace(/\s+|\.|-|\+|\(|\)/g, ""); // remove all spaces + + // check if the final string contains only numbers + if (/^\d+$/.test(strippedInput)) { + return strippedInput; + } else { + return null; + } + }; + + const onClick = () => { + setSelectedText(""); + const msg: Message = { + event: MessageEvent.Call, + data: { + action: CallAction.OUTBOUND, + number: selectedText || "", + }, + }; + chrome.runtime.sendMessage(msg); + }; + + if (!selectedText) { + return null; + } + return ( - - - {isOpen && ( - +
+

- {selectedText} - } - /> - - )} - + Number:{" "} +

+ {selectedText} +
+ + ); }; diff --git a/src/utils/index.ts b/src/utils/index.ts index a1f82f2..684e1fb 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,4 @@ -import { SipConstants } from "src/lib"; +import { SipConstants } from "./../lib"; import { deleteWindowIdKey, getWindowIdKey, saveWindowIdKey } from "./storage"; import { PhoneNumberFormat, PhoneNumberUtil } from "google-libphonenumber"; @@ -13,20 +13,23 @@ export const formatPhoneNumber = (number: string) => { }; export const openPhonePopup = () => { - const runningPhoneWindowId = getWindowIdKey(); - if (runningPhoneWindowId) { - chrome.windows.update(runningPhoneWindowId, { focused: true }, () => { - if (chrome.runtime.lastError) { - deleteWindowIdKey(); - initiateNewPhonePopup(); - } - }); - } else { - initiateNewPhonePopup(); - } + return new Promise((resolve) => { + const runningPhoneWindowId = getWindowIdKey(); + if (runningPhoneWindowId) { + chrome.windows.update(runningPhoneWindowId, { focused: true }, () => { + if (chrome.runtime.lastError) { + deleteWindowIdKey(); + initiateNewPhonePopup(resolve); + } + resolve(1); + }); + } else { + initiateNewPhonePopup(resolve); + } + }); }; -const initiateNewPhonePopup = () => { +const initiateNewPhonePopup = (callback: (v: unknown) => void) => { const cfg: chrome.windows.CreateData = { url: chrome.runtime.getURL("window/index.html"), width: 300, @@ -36,6 +39,7 @@ const initiateNewPhonePopup = () => { state: "normal", }; chrome.windows.create(cfg, (w) => { + callback(1); if (w && w.id) saveWindowIdKey(w.id); }); }; diff --git a/src/window/phone.tsx b/src/window/phone.tsx index 2d6ae9d..9379f63 100644 --- a/src/window/phone.tsx +++ b/src/window/phone.tsx @@ -15,7 +15,7 @@ import { import { useEffect, useRef, useState } from "react"; import { Delete } from "react-feather"; import { DEFAULT_COLOR_SCHEME } from "src/common/constants"; -import { SipClientStatus } from "src/common/types"; +import { Call, Message, MessageEvent, SipClientStatus } from "src/common/types"; import { SipConstants, SipUA } from "src/lib"; import IncommingCall from "./incomming-call"; import OutgoingCall from "./outgoing-call"; @@ -24,6 +24,7 @@ import { isSipClientAnswered, isSipClientIdle, isSipClientRinging, + openPhonePopup, } from "src/utils"; type PhoneProbs = { @@ -56,6 +57,28 @@ export const Phone = ({ } }, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]); + useEffect(() => { + chrome.runtime.onMessage.addListener(function (request) { + const msg = request as Message; + switch (msg.event) { + case MessageEvent.Call: + handleCallEvent(msg.data as Call); + break; + default: + break; + } + }); + }, []); + + const handleCallEvent = (call: Call) => { + if (!call.number) return; + + if (isSipClientIdle(callStatus)) { + setInputNumber(call.number); + sipUA.current?.call(call.number); + } + }; + const createSipClient = (forceOfflineMode = false) => { if (goOffline && !forceOfflineMode) { return; diff --git a/webpack.config.js b/webpack.config.js index 3ddd916..2b951b6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,6 +6,7 @@ module.exports = [ { entry: "./src/content/index.tsx", target: "web", + mode: "production", output: { path: path.join(__dirname, "dist"), filename: "js/content.js", @@ -27,7 +28,7 @@ module.exports = [ ], }, resolve: { - extensions: [".ts", ".js"], + extensions: [".ts", ".tsx",".js"], }, }, {