This commit is contained in:
Quan HL
2023-09-25 17:11:25 +07:00
parent 82439cca83
commit 93397e0c70
9 changed files with 171 additions and 62 deletions

27
.gitignore vendored Normal file
View File

@@ -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*

1
.prettierrc.json Normal file
View File

@@ -0,0 +1 @@
{}

7
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-next"
]
}

9
.vscode/settings.json vendored Normal file
View File

@@ -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"
}

View File

@@ -11,10 +11,8 @@ export interface Call {
export enum MessageEvent { export enum MessageEvent {
// Request // Request
Login,
Call, Call,
OpenPhoneWindow, OpenPhoneWindow,
Ping,
} }
export interface EmptyData {} export interface EmptyData {}

View File

@@ -1,66 +1,105 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { import { Call, CallAction, Message, MessageEvent } from "./../common/types";
ChakraProvider, import { openPhonePopup } from "./../utils";
Text,
IconButton,
Flex,
CSSReset,
} from "@chakra-ui/react";
import { Phone } from "react-feather";
export const ContentApp = () => { export const ContentApp = () => {
const [isOpen, setIsOpen] = useState(false);
const [position, setPosition] = useState({ left: 0, top: 0 });
const [selectedText, setSelectedText] = useState(""); const [selectedText, setSelectedText] = useState("");
const [counting, setCounting] = useState(0);
const [position, setPosition] = useState({ x: 0, y: 0 });
// Add event listener for text selection
useEffect(() => { useEffect(() => {
function handleMouseUp() { function mouseUpListener(e: any) {
const selectedText = window.getSelection()?.toString(); const text = window.getSelection()?.toString().trim();
if (selectedText) { if (text && text.length > 0) {
setSelectedText(selectedText); const number = validateAndFormatNumber(text);
const range = window.getSelection()?.getRangeAt(0); setSelectedText(number || "");
const rect = range?.getBoundingClientRect(); setPosition({ x: e.pageX, y: e.pageY });
setIsOpen(true);
setPosition({ left: rect?.left || 0, top: rect?.bottom || 0 });
} else { } else {
setIsOpen(false); setSelectedText("");
} }
} }
document.addEventListener("mouseup", handleMouseUp); document.addEventListener("mouseup", mouseUpListener);
return () => { 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<Call> = {
event: MessageEvent.Call,
data: {
action: CallAction.OUTBOUND,
number: selectedText || "",
},
};
chrome.runtime.sendMessage(msg);
};
if (!selectedText) {
return null;
}
return ( return (
<ChakraProvider> <div
<CSSReset /> style={{
{isOpen && ( position: "absolute",
<Flex left: `${position.x}px`,
position="fixed" top: `${position.y}px`,
left={position.left} backgroundColor: "white",
top={position.top} height: "80px",
bg="white" width: "180px",
p={4} borderRadius: "10px",
border="1px solid" boxShadow: "0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)",
borderColor="gray.200" padding: "10px",
boxShadow="md" display: "flex",
alignItems="center" alignItems: "center",
justifyContent: "space-between",
zIndex: "99999999",
fontFamily: "Arial, sans-serif",
}}
>
<div style={{ display: "flex", flexDirection: "column" }}>
<h1
style={{
fontSize: "18px",
color: "#333",
display: "inline",
fontWeight: "bold",
}}
> >
<Text>{selectedText}</Text> Number:{" "}
<IconButton </h1>
colorScheme="blue" <span style={{ fontSize: "16px", color: "#666" }}>{selectedText}</span>
ml={2} </div>
aria-label="text" <button
icon={<Phone />} style={{
/> padding: "10px",
</Flex> color: "white",
)} background: "#007BFF",
</ChakraProvider> border: "none",
borderRadius: "5px",
cursor: "pointer",
}}
onClick={onClick}
>
Call
</button>
</div>
); );
}; };

View File

@@ -1,4 +1,4 @@
import { SipConstants } from "src/lib"; import { SipConstants } from "./../lib";
import { deleteWindowIdKey, getWindowIdKey, saveWindowIdKey } from "./storage"; import { deleteWindowIdKey, getWindowIdKey, saveWindowIdKey } from "./storage";
import { PhoneNumberFormat, PhoneNumberUtil } from "google-libphonenumber"; import { PhoneNumberFormat, PhoneNumberUtil } from "google-libphonenumber";
@@ -13,20 +13,23 @@ export const formatPhoneNumber = (number: string) => {
}; };
export const openPhonePopup = () => { export const openPhonePopup = () => {
const runningPhoneWindowId = getWindowIdKey(); return new Promise((resolve) => {
if (runningPhoneWindowId) { const runningPhoneWindowId = getWindowIdKey();
chrome.windows.update(runningPhoneWindowId, { focused: true }, () => { if (runningPhoneWindowId) {
if (chrome.runtime.lastError) { chrome.windows.update(runningPhoneWindowId, { focused: true }, () => {
deleteWindowIdKey(); if (chrome.runtime.lastError) {
initiateNewPhonePopup(); deleteWindowIdKey();
} initiateNewPhonePopup(resolve);
}); }
} else { resolve(1);
initiateNewPhonePopup(); });
} } else {
initiateNewPhonePopup(resolve);
}
});
}; };
const initiateNewPhonePopup = () => { const initiateNewPhonePopup = (callback: (v: unknown) => void) => {
const cfg: chrome.windows.CreateData = { const cfg: chrome.windows.CreateData = {
url: chrome.runtime.getURL("window/index.html"), url: chrome.runtime.getURL("window/index.html"),
width: 300, width: 300,
@@ -36,6 +39,7 @@ const initiateNewPhonePopup = () => {
state: "normal", state: "normal",
}; };
chrome.windows.create(cfg, (w) => { chrome.windows.create(cfg, (w) => {
callback(1);
if (w && w.id) saveWindowIdKey(w.id); if (w && w.id) saveWindowIdKey(w.id);
}); });
}; };

View File

@@ -15,7 +15,7 @@ import {
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Delete } from "react-feather"; import { Delete } from "react-feather";
import { DEFAULT_COLOR_SCHEME } from "src/common/constants"; 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 { SipConstants, SipUA } from "src/lib";
import IncommingCall from "./incomming-call"; import IncommingCall from "./incomming-call";
import OutgoingCall from "./outgoing-call"; import OutgoingCall from "./outgoing-call";
@@ -24,6 +24,7 @@ import {
isSipClientAnswered, isSipClientAnswered,
isSipClientIdle, isSipClientIdle,
isSipClientRinging, isSipClientRinging,
openPhonePopup,
} from "src/utils"; } from "src/utils";
type PhoneProbs = { type PhoneProbs = {
@@ -56,6 +57,28 @@ export const Phone = ({
} }
}, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]); }, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]);
useEffect(() => {
chrome.runtime.onMessage.addListener(function (request) {
const msg = request as Message<any>;
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) => { const createSipClient = (forceOfflineMode = false) => {
if (goOffline && !forceOfflineMode) { if (goOffline && !forceOfflineMode) {
return; return;

View File

@@ -6,6 +6,7 @@ module.exports = [
{ {
entry: "./src/content/index.tsx", entry: "./src/content/index.tsx",
target: "web", target: "web",
mode: "production",
output: { output: {
path: path.join(__dirname, "dist"), path: path.join(__dirname, "dist"),
filename: "js/content.js", filename: "js/content.js",
@@ -27,7 +28,7 @@ module.exports = [
], ],
}, },
resolve: { resolve: {
extensions: [".ts", ".js"], extensions: [".ts", ".tsx",".js"],
}, },
}, },
{ {